From 2b03c8f72a2a39bb9159e52268f1f8777797dfa6 Mon Sep 17 00:00:00 2001 From: LoganDark Date: Mon, 7 Aug 2023 13:45:04 -0700 Subject: [PATCH 01/20] Use `const char* const*` over `const char**` (#1005) The FFI difference becomes significant in languages like Rust where you can't necessarily just cast a const pointer. --- Compiler/include/Luau/Compiler.h | 2 +- Compiler/include/luacode.h | 2 +- Compiler/src/ValueTracking.cpp | 4 ++-- Compiler/src/ValueTracking.h | 2 +- tests/Compiler.test.cpp | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Compiler/include/Luau/Compiler.h b/Compiler/include/Luau/Compiler.h index 36a21a72..9a661db2 100644 --- a/Compiler/include/Luau/Compiler.h +++ b/Compiler/include/Luau/Compiler.h @@ -39,7 +39,7 @@ struct CompileOptions const char* vectorType = nullptr; // null-terminated array of globals that are mutable; disables the import optimization for fields accessed through these - const char** mutableGlobals = nullptr; + const char* const* mutableGlobals = nullptr; }; class CompileError : public std::exception diff --git a/Compiler/include/luacode.h b/Compiler/include/luacode.h index 7c59ce0b..60cdeee7 100644 --- a/Compiler/include/luacode.h +++ b/Compiler/include/luacode.h @@ -35,7 +35,7 @@ struct lua_CompileOptions const char* vectorType; // null-terminated array of globals that are mutable; disables the import optimization for fields accessed through these - const char** mutableGlobals; + const char* const* mutableGlobals; }; // compile source to bytecode; when source compilation fails, the resulting bytecode contains the encoded error. use free() to destroy diff --git a/Compiler/src/ValueTracking.cpp b/Compiler/src/ValueTracking.cpp index 0bfaf9b3..616fca99 100644 --- a/Compiler/src/ValueTracking.cpp +++ b/Compiler/src/ValueTracking.cpp @@ -82,13 +82,13 @@ struct ValueVisitor : AstVisitor } }; -void assignMutable(DenseHashMap& globals, const AstNameTable& names, const char** mutableGlobals) +void assignMutable(DenseHashMap& globals, const AstNameTable& names, const char* const* mutableGlobals) { if (AstName name = names.get("_G"); name.value) globals[name] = Global::Mutable; if (mutableGlobals) - for (const char** ptr = mutableGlobals; *ptr; ++ptr) + for (const char* const* ptr = mutableGlobals; *ptr; ++ptr) if (AstName name = names.get(*ptr); name.value) globals[name] = Global::Mutable; } diff --git a/Compiler/src/ValueTracking.h b/Compiler/src/ValueTracking.h index fc74c84a..f8ecc6b8 100644 --- a/Compiler/src/ValueTracking.h +++ b/Compiler/src/ValueTracking.h @@ -28,7 +28,7 @@ struct Variable bool constant = false; // is the variable's value a compile-time constant? filled by constantFold }; -void assignMutable(DenseHashMap& globals, const AstNameTable& names, const char** mutableGlobals); +void assignMutable(DenseHashMap& globals, const AstNameTable& names, const char* const* mutableGlobals); void trackValues(DenseHashMap& globals, DenseHashMap& variables, AstNode* root); inline Global getGlobalState(const DenseHashMap& globals, AstName name) diff --git a/tests/Compiler.test.cpp b/tests/Compiler.test.cpp index d368af66..fa5ab7e6 100644 --- a/tests/Compiler.test.cpp +++ b/tests/Compiler.test.cpp @@ -4213,7 +4213,7 @@ RETURN R0 0 bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code); Luau::CompileOptions options; const char* mutableGlobals[] = {"Game", "Workspace", "game", "plugin", "script", "shared", "workspace", NULL}; - options.mutableGlobals = &mutableGlobals[0]; + options.mutableGlobals = mutableGlobals; Luau::compileOrThrow(bcb, source, options); CHECK_EQ("\n" + bcb.dumpFunction(0), R"( From 3f478bb439852df9fc772fe41194e1f6bd6e3fdf Mon Sep 17 00:00:00 2001 From: John Hui <11800204+j-hui@users.noreply.github.com> Date: Mon, 7 Aug 2023 16:26:16 -0700 Subject: [PATCH 02/20] Add luau-compile to .gitignore (#995) --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index af77f73c..528ab204 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,5 @@ /luau /luau-tests /luau-analyze +/luau-compile __pycache__ From bd229816c0a82a8590395416c81c333087f541fd Mon Sep 17 00:00:00 2001 From: Arseny Kapoulkine Date: Tue, 8 Aug 2023 12:15:10 -0700 Subject: [PATCH 03/20] Remove old benchmark-dev workflows Nobody is maintaining this and we haven't really used it and are unlikely to start due to a high degree of noise and lack of dedicated machines for this setup. Callgrind has worked for us well enough, with additional profiling performed locally by engineers - this is not perfect but it doesn't look like we have a path to making this better. --- .github/workflows/benchmark-dev.yml | 185 ---------------------- .github/workflows/push-results/action.yml | 63 -------- 2 files changed, 248 deletions(-) delete mode 100644 .github/workflows/benchmark-dev.yml delete mode 100644 .github/workflows/push-results/action.yml diff --git a/.github/workflows/benchmark-dev.yml b/.github/workflows/benchmark-dev.yml deleted file mode 100644 index b6115acc..00000000 --- a/.github/workflows/benchmark-dev.yml +++ /dev/null @@ -1,185 +0,0 @@ -name: benchmark-dev - -on: - push: - branches: - - master - - paths-ignore: - - "docs/**" - - "papers/**" - - "rfcs/**" - - "*.md" - -jobs: - windows: - name: windows-${{matrix.arch}} - strategy: - fail-fast: false - matrix: - os: [windows-latest] - arch: [Win32, x64] - bench: - - { - script: "run-benchmarks", - timeout: 12, - title: "Luau Benchmarks", - } - benchResultsRepo: - - { name: "luau-lang/benchmark-data", branch: "main" } - - runs-on: ${{ matrix.os }} - steps: - - name: Checkout Luau repository - uses: actions/checkout@v3 - - - name: Build Luau - shell: bash # necessary for fail-fast - run: | - mkdir build && cd build - cmake .. -DCMAKE_BUILD_TYPE=Release - cmake --build . --target Luau.Repl.CLI --config Release - cmake --build . --target Luau.Analyze.CLI --config Release - - - name: Move build files to root - run: | - move build/Release/* . - - - uses: actions/setup-python@v3 - with: - python-version: "3.9" - architecture: "x64" - - - name: Install python dependencies - run: | - python -m pip install requests - python -m pip install --user numpy scipy matplotlib ipython jupyter pandas sympy nose - - - name: Run benchmark - run: | - python bench/bench.py | tee ${{ matrix.bench.script }}-output.txt - - - name: Push benchmark results - id: pushBenchmarkAttempt1 - continue-on-error: true - uses: ./.github/workflows/push-results - with: - repository: ${{ matrix.benchResultsRepo.name }} - branch: ${{ matrix.benchResultsRepo.branch }} - token: ${{ secrets.BENCH_GITHUB_TOKEN }} - path: "./gh-pages" - bench_name: "${{ matrix.bench.title }} (Windows ${{matrix.arch}})" - bench_tool: "benchmarkluau" - bench_output_file_path: "./${{ matrix.bench.script }}-output.txt" - bench_external_data_json_path: "./gh-pages/dev/bench/data-${{ matrix.os }}.json" - - - name: Push benchmark results (Attempt 2) - id: pushBenchmarkAttempt2 - continue-on-error: true - if: steps.pushBenchmarkAttempt1.outcome == 'failure' - uses: ./.github/workflows/push-results - with: - repository: ${{ matrix.benchResultsRepo.name }} - branch: ${{ matrix.benchResultsRepo.branch }} - token: ${{ secrets.BENCH_GITHUB_TOKEN }} - path: "./gh-pages" - bench_name: "${{ matrix.bench.title }} (Windows ${{matrix.arch}})" - bench_tool: "benchmarkluau" - bench_output_file_path: "./${{ matrix.bench.script }}-output.txt" - bench_external_data_json_path: "./gh-pages/dev/bench/data-${{ matrix.os }}.json" - - - name: Push benchmark results (Attempt 3) - id: pushBenchmarkAttempt3 - continue-on-error: true - if: steps.pushBenchmarkAttempt2.outcome == 'failure' - uses: ./.github/workflows/push-results - with: - repository: ${{ matrix.benchResultsRepo.name }} - branch: ${{ matrix.benchResultsRepo.branch }} - token: ${{ secrets.BENCH_GITHUB_TOKEN }} - path: "./gh-pages" - bench_name: "${{ matrix.bench.title }} (Windows ${{matrix.arch}})" - bench_tool: "benchmarkluau" - bench_output_file_path: "./${{ matrix.bench.script }}-output.txt" - bench_external_data_json_path: "./gh-pages/dev/bench/data-${{ matrix.os }}.json" - - unix: - name: ${{matrix.os}} - strategy: - fail-fast: false - matrix: - os: [ubuntu-20.04, macos-latest] - bench: - - { - script: "run-benchmarks", - timeout: 12, - title: "Luau Benchmarks", - } - benchResultsRepo: - - { name: "luau-lang/benchmark-data", branch: "main" } - - runs-on: ${{ matrix.os }} - steps: - - name: Checkout Luau repository - uses: actions/checkout@v3 - - - name: Build Luau - run: make config=release luau luau-analyze - - - uses: actions/setup-python@v3 - with: - python-version: "3.9" - architecture: "x64" - - - name: Install python dependencies - run: | - python -m pip install requests - python -m pip install --user numpy scipy matplotlib ipython jupyter pandas sympy nose - - - name: Run benchmark - run: | - python bench/bench.py | tee ${{ matrix.bench.script }}-output.txt - - - name: Push benchmark results - id: pushBenchmarkAttempt1 - continue-on-error: true - uses: ./.github/workflows/push-results - with: - repository: ${{ matrix.benchResultsRepo.name }} - branch: ${{ matrix.benchResultsRepo.branch }} - token: ${{ secrets.BENCH_GITHUB_TOKEN }} - path: "./gh-pages" - bench_name: ${{ matrix.bench.title }} - bench_tool: "benchmarkluau" - bench_output_file_path: "./${{ matrix.bench.script }}-output.txt" - bench_external_data_json_path: "./gh-pages/dev/bench/data-${{ matrix.os }}.json" - - - name: Push benchmark results (Attempt 2) - id: pushBenchmarkAttempt2 - continue-on-error: true - if: steps.pushBenchmarkAttempt1.outcome == 'failure' - uses: ./.github/workflows/push-results - with: - repository: ${{ matrix.benchResultsRepo.name }} - branch: ${{ matrix.benchResultsRepo.branch }} - token: ${{ secrets.BENCH_GITHUB_TOKEN }} - path: "./gh-pages" - bench_name: ${{ matrix.bench.title }} - bench_tool: "benchmarkluau" - bench_output_file_path: "./${{ matrix.bench.script }}-output.txt" - bench_external_data_json_path: "./gh-pages/dev/bench/data-${{ matrix.os }}.json" - - - name: Push benchmark results (Attempt 3) - id: pushBenchmarkAttempt3 - continue-on-error: true - if: steps.pushBenchmarkAttempt2.outcome == 'failure' - uses: ./.github/workflows/push-results - with: - repository: ${{ matrix.benchResultsRepo.name }} - branch: ${{ matrix.benchResultsRepo.branch }} - token: ${{ secrets.BENCH_GITHUB_TOKEN }} - path: "./gh-pages" - bench_name: ${{ matrix.bench.title }} - bench_tool: "benchmarkluau" - bench_output_file_path: "./${{ matrix.bench.script }}-output.txt" - bench_external_data_json_path: "./gh-pages/dev/bench/data-${{ matrix.os }}.json" diff --git a/.github/workflows/push-results/action.yml b/.github/workflows/push-results/action.yml deleted file mode 100644 index b5ffebee..00000000 --- a/.github/workflows/push-results/action.yml +++ /dev/null @@ -1,63 +0,0 @@ -name: Checkout & push results -description: Checkout a given repo and push results to GitHub -inputs: - repository: - required: true - type: string - description: The benchmark results repository to check out - branch: - required: true - type: string - description: The benchmark results repository's branch to check out - token: - required: true - type: string - description: The GitHub token to use for pushing results - path: - required: true - type: string - description: The path to check out the results repository to - bench_name: - required: true - type: string - bench_tool: - required: true - type: string - bench_output_file_path: - required: true - type: string - bench_external_data_json_path: - required: true - type: string - -runs: - using: "composite" - steps: - - name: Checkout repository - uses: actions/checkout@v3 - with: - repository: ${{ inputs.repository }} - ref: ${{ inputs.branch }} - token: ${{ inputs.token }} - path: ${{ inputs.path }} - - - name: Store results - uses: Roblox/rhysd-github-action-benchmark@v-luau - with: - name: ${{ inputs.bench_name }} - tool: ${{ inputs.bench_tool }} - gh-pages-branch: ${{ inputs.branch }} - output-file-path: ${{ inputs.bench_output_file_path }} - external-data-json-path: ${{ inputs.bench_external_data_json_path }} - - - name: Push benchmark results - shell: bash - run: | - echo "Pushing benchmark results..." - cd gh-pages - git config user.name github-actions - git config user.email github@users.noreply.github.com - git add *.json - git commit -m "Add benchmarks results for ${{ github.sha }}" - git push - cd .. From d98256bb80ddef75154bdb2d31c0bab7d5aa5683 Mon Sep 17 00:00:00 2001 From: vegorov-rbx <75688451+vegorov-rbx@users.noreply.github.com> Date: Fri, 11 Aug 2023 07:42:37 -0700 Subject: [PATCH 04/20] Sync to upstream/release/590 (#1008) * Better indentation in multi-line type mismatch error messages * Error message clone can no longer cause a stack overflow (when typechecking with retainFullTypeGraphs set to false); fixes https://github.com/Roblox/luau/issues/975 * `string.format` with %s is now ~2x faster on strings smaller than 100 characters Native code generation: * All VM side exits will block return to the native execution of the current function to preserve correctness * Fixed executable page allocation on Apple platforms when using hardened runtime * Added statistics for code generation (no. of functions compiler, memory used for different areas) * Fixed issue with function entry type checks performed more that once in some functions --- Analysis/include/Luau/Clone.h | 2 + Analysis/include/Luau/Config.h | 2 +- Analysis/include/Luau/Differ.h | 14 +- Analysis/include/Luau/Error.h | 3 +- Analysis/include/Luau/Linter.h | 72 +-- Analysis/include/Luau/LinterConfig.h | 121 +++++ Analysis/include/Luau/Substitution.h | 10 - Analysis/src/Clone.cpp | 540 +++++++++++++++++++-- Analysis/src/Differ.cpp | 144 ++++-- Analysis/src/Error.cpp | 47 +- Analysis/src/Frontend.cpp | 23 +- Analysis/src/Linter.cpp | 95 +--- Analysis/src/LinterConfig.cpp | 63 +++ Analysis/src/Module.cpp | 2 +- Analysis/src/Substitution.cpp | 244 +--------- Analysis/src/TypeChecker2.cpp | 2 +- Analysis/src/TypeInfer.cpp | 17 +- CodeGen/include/Luau/CodeAllocator.h | 12 +- CodeGen/include/Luau/CodeGen.h | 15 +- CodeGen/include/Luau/ConditionX64.h | 65 +++ CodeGen/include/Luau/IrData.h | 18 +- CodeGen/src/AssemblyBuilderA64.cpp | 1 + CodeGen/src/AssemblyBuilderX64.cpp | 1 - CodeGen/src/CodeAllocator.cpp | 61 ++- CodeGen/src/CodeGen.cpp | 33 +- CodeGen/src/CodeGenA64.cpp | 30 +- CodeGen/src/CodeGenLower.h | 5 + CodeGen/src/CodeGenX64.cpp | 10 +- CodeGen/src/EmitBuiltinsX64.cpp | 3 +- CodeGen/src/EmitCommon.h | 2 +- CodeGen/src/EmitCommonX64.cpp | 4 +- CodeGen/src/EmitCommonX64.h | 2 +- CodeGen/src/IrAnalysis.cpp | 287 ++++++----- CodeGen/src/IrBuilder.cpp | 48 +- CodeGen/src/IrDump.cpp | 2 +- CodeGen/src/IrLoweringA64.cpp | 34 +- CodeGen/src/IrLoweringA64.h | 6 +- CodeGen/src/IrLoweringX64.cpp | 128 ++--- CodeGen/src/IrLoweringX64.h | 13 +- CodeGen/src/NativeState.cpp | 7 +- CodeGen/src/NativeState.h | 1 + CodeGen/src/OptimizeConstProp.cpp | 2 +- Compiler/src/Compiler.cpp | 21 +- Compiler/src/Types.cpp | 4 +- Compiler/src/Types.h | 1 + Sources.cmake | 2 + VM/src/lmathlib.cpp | 104 +--- VM/src/lstrlib.cpp | 34 +- VM/src/ltablib.cpp | 4 +- VM/src/lvmload.cpp | 5 +- bench/micro_tests/test_StringInterp.lua | 7 + tests/CodeAllocator.test.cpp | 50 ++ tests/Compiler.test.cpp | 10 - tests/Conformance.test.cpp | 7 +- tests/Differ.test.cpp | 39 ++ tests/Fixture.h | 43 +- tests/Linter.test.cpp | 2 - tests/Module.test.cpp | 61 ++- tests/ToString.test.cpp | 35 ++ tests/TypeInfer.aliases.test.cpp | 42 +- tests/TypeInfer.builtins.test.cpp | 22 +- tests/TypeInfer.classes.test.cpp | 22 +- tests/TypeInfer.functions.test.cpp | 240 ++++----- tests/TypeInfer.generics.test.cpp | 13 +- tests/TypeInfer.intersectionTypes.test.cpp | 186 +++++-- tests/TypeInfer.modules.test.cpp | 24 +- tests/TypeInfer.oop.test.cpp | 89 ++++ tests/TypeInfer.provisional.test.cpp | 14 +- tests/TypeInfer.singletons.test.cpp | 31 +- tests/TypeInfer.tables.test.cpp | 156 ++++-- tests/TypeInfer.test.cpp | 26 +- tests/TypeInfer.tryUnify.test.cpp | 14 +- tests/TypeInfer.typePacks.cpp | 11 +- tests/TypeInfer.unionTypes.test.cpp | 84 +++- tests/conformance/native.lua | 11 + tests/conformance/native_types.lua | 11 + tests/main.cpp | 6 +- 77 files changed, 2345 insertions(+), 1277 deletions(-) create mode 100644 Analysis/include/Luau/LinterConfig.h create mode 100644 Analysis/src/LinterConfig.cpp diff --git a/Analysis/include/Luau/Clone.h b/Analysis/include/Luau/Clone.h index b3cbe467..28ab9931 100644 --- a/Analysis/include/Luau/Clone.h +++ b/Analysis/include/Luau/Clone.h @@ -16,6 +16,8 @@ using SeenTypePacks = std::unordered_map; struct CloneState { + NotNull builtinTypes; + SeenTypes seenTypes; SeenTypePacks seenTypePacks; diff --git a/Analysis/include/Luau/Config.h b/Analysis/include/Luau/Config.h index 88c10554..ae865b64 100644 --- a/Analysis/include/Luau/Config.h +++ b/Analysis/include/Luau/Config.h @@ -1,7 +1,7 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #pragma once -#include "Luau/Linter.h" +#include "Luau/LinterConfig.h" #include "Luau/ParseOptions.h" #include diff --git a/Analysis/include/Luau/Differ.h b/Analysis/include/Luau/Differ.h index e9656ad4..43c44b71 100644 --- a/Analysis/include/Luau/Differ.h +++ b/Analysis/include/Luau/Differ.h @@ -128,10 +128,10 @@ struct DiffError checkValidInitialization(left, right); } - std::string toString() const; + std::string toString(bool multiLine = false) const; private: - std::string toStringALeaf(std::string rootName, const DiffPathNodeLeaf& leaf, const DiffPathNodeLeaf& otherLeaf) const; + std::string toStringALeaf(std::string rootName, const DiffPathNodeLeaf& leaf, const DiffPathNodeLeaf& otherLeaf, bool multiLine) const; void checkValidInitialization(const DiffPathNodeLeaf& left, const DiffPathNodeLeaf& right); void checkNonMissingPropertyLeavesHaveNulloptTableProperty() const; }; @@ -152,12 +152,17 @@ struct DifferEnvironment { TypeId rootLeft; TypeId rootRight; + std::optional externalSymbolLeft; + std::optional externalSymbolRight; DenseHashMap genericMatchedPairs; DenseHashMap genericTpMatchedPairs; - DifferEnvironment(TypeId rootLeft, TypeId rootRight) + DifferEnvironment( + TypeId rootLeft, TypeId rootRight, std::optional externalSymbolLeft, std::optional externalSymbolRight) : rootLeft(rootLeft) , rootRight(rootRight) + , externalSymbolLeft(externalSymbolLeft) + , externalSymbolRight(externalSymbolRight) , genericMatchedPairs(nullptr) , genericTpMatchedPairs(nullptr) { @@ -170,6 +175,8 @@ struct DifferEnvironment void popVisiting(); std::vector>::const_reverse_iterator visitingBegin() const; std::vector>::const_reverse_iterator visitingEnd() const; + std::string getDevFixFriendlyNameLeft() const; + std::string getDevFixFriendlyNameRight() const; private: // TODO: consider using DenseHashSet @@ -179,6 +186,7 @@ private: std::vector> visitingStack; }; DifferResult diff(TypeId ty1, TypeId ty2); +DifferResult diffWithSymbols(TypeId ty1, TypeId ty2, std::optional symbol1, std::optional symbol2); /** * True if ty is a "simple" type, i.e. cannot contain types. diff --git a/Analysis/include/Luau/Error.h b/Analysis/include/Luau/Error.h index 13758b37..db3e8e55 100644 --- a/Analysis/include/Luau/Error.h +++ b/Analysis/include/Luau/Error.h @@ -2,6 +2,7 @@ #pragma once #include "Luau/Location.h" +#include "Luau/NotNull.h" #include "Luau/Type.h" #include "Luau/Variant.h" @@ -432,7 +433,7 @@ std::string toString(const TypeError& error, TypeErrorToStringOptions options); bool containsParseErrorName(const TypeError& error); // Copy any types named in the error into destArena. -void copyErrors(ErrorVec& errors, struct TypeArena& destArena); +void copyErrors(ErrorVec& errors, struct TypeArena& destArena, NotNull builtinTypes); // Internal Compiler Error struct InternalErrorReporter diff --git a/Analysis/include/Luau/Linter.h b/Analysis/include/Luau/Linter.h index 6bbc3d66..303cd9e9 100644 --- a/Analysis/include/Luau/Linter.h +++ b/Analysis/include/Luau/Linter.h @@ -1,6 +1,7 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #pragma once +#include "Luau/LinterConfig.h" #include "Luau/Location.h" #include @@ -15,86 +16,15 @@ class AstStat; class AstNameTable; struct TypeChecker; struct Module; -struct HotComment; using ScopePtr = std::shared_ptr; -struct LintWarning -{ - // Make sure any new lint codes are documented here: https://luau-lang.org/lint - // Note that in Studio, the active set of lint warnings is determined by FStringStudioLuauLints - enum Code - { - Code_Unknown = 0, - - Code_UnknownGlobal = 1, // superseded by type checker - Code_DeprecatedGlobal = 2, - Code_GlobalUsedAsLocal = 3, - Code_LocalShadow = 4, // disabled in Studio - Code_SameLineStatement = 5, // disabled in Studio - Code_MultiLineStatement = 6, - Code_LocalUnused = 7, // disabled in Studio - Code_FunctionUnused = 8, // disabled in Studio - Code_ImportUnused = 9, // disabled in Studio - Code_BuiltinGlobalWrite = 10, - Code_PlaceholderRead = 11, - Code_UnreachableCode = 12, - Code_UnknownType = 13, - Code_ForRange = 14, - Code_UnbalancedAssignment = 15, - Code_ImplicitReturn = 16, // disabled in Studio, superseded by type checker in strict mode - Code_DuplicateLocal = 17, - Code_FormatString = 18, - Code_TableLiteral = 19, - Code_UninitializedLocal = 20, - Code_DuplicateFunction = 21, - Code_DeprecatedApi = 22, - Code_TableOperations = 23, - Code_DuplicateCondition = 24, - Code_MisleadingAndOr = 25, - Code_CommentDirective = 26, - Code_IntegerParsing = 27, - Code_ComparisonPrecedence = 28, - - Code__Count - }; - - Code code; - Location location; - std::string text; - - static const char* getName(Code code); - static Code parseName(const char* name); - static uint64_t parseMask(const std::vector& hotcomments); -}; - struct LintResult { std::vector errors; std::vector warnings; }; -struct LintOptions -{ - uint64_t warningMask = 0; - - void enableWarning(LintWarning::Code code) - { - warningMask |= 1ull << code; - } - void disableWarning(LintWarning::Code code) - { - warningMask &= ~(1ull << code); - } - - bool isEnabled(LintWarning::Code code) const - { - return 0 != (warningMask & (1ull << code)); - } - - void setDefaults(); -}; - std::vector lint(AstStat* root, const AstNameTable& names, const ScopePtr& env, const Module* module, const std::vector& hotcomments, const LintOptions& options); diff --git a/Analysis/include/Luau/LinterConfig.h b/Analysis/include/Luau/LinterConfig.h new file mode 100644 index 00000000..6bda930a --- /dev/null +++ b/Analysis/include/Luau/LinterConfig.h @@ -0,0 +1,121 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#pragma once + +#include "Luau/Location.h" + +#include +#include + +namespace Luau +{ + +struct HotComment; + +struct LintWarning +{ + // Make sure any new lint codes are documented here: https://luau-lang.org/lint + // Note that in Studio, the active set of lint warnings is determined by FStringStudioLuauLints + enum Code + { + Code_Unknown = 0, + + Code_UnknownGlobal = 1, // superseded by type checker + Code_DeprecatedGlobal = 2, + Code_GlobalUsedAsLocal = 3, + Code_LocalShadow = 4, // disabled in Studio + Code_SameLineStatement = 5, // disabled in Studio + Code_MultiLineStatement = 6, + Code_LocalUnused = 7, // disabled in Studio + Code_FunctionUnused = 8, // disabled in Studio + Code_ImportUnused = 9, // disabled in Studio + Code_BuiltinGlobalWrite = 10, + Code_PlaceholderRead = 11, + Code_UnreachableCode = 12, + Code_UnknownType = 13, + Code_ForRange = 14, + Code_UnbalancedAssignment = 15, + Code_ImplicitReturn = 16, // disabled in Studio, superseded by type checker in strict mode + Code_DuplicateLocal = 17, + Code_FormatString = 18, + Code_TableLiteral = 19, + Code_UninitializedLocal = 20, + Code_DuplicateFunction = 21, + Code_DeprecatedApi = 22, + Code_TableOperations = 23, + Code_DuplicateCondition = 24, + Code_MisleadingAndOr = 25, + Code_CommentDirective = 26, + Code_IntegerParsing = 27, + Code_ComparisonPrecedence = 28, + + Code__Count + }; + + Code code; + Location location; + std::string text; + + static const char* getName(Code code); + static Code parseName(const char* name); + static uint64_t parseMask(const std::vector& hotcomments); +}; + +struct LintOptions +{ + uint64_t warningMask = 0; + + void enableWarning(LintWarning::Code code) + { + warningMask |= 1ull << code; + } + void disableWarning(LintWarning::Code code) + { + warningMask &= ~(1ull << code); + } + + bool isEnabled(LintWarning::Code code) const + { + return 0 != (warningMask & (1ull << code)); + } + + void setDefaults(); +}; + +// clang-format off +static const char* kWarningNames[] = { + "Unknown", + + "UnknownGlobal", + "DeprecatedGlobal", + "GlobalUsedAsLocal", + "LocalShadow", + "SameLineStatement", + "MultiLineStatement", + "LocalUnused", + "FunctionUnused", + "ImportUnused", + "BuiltinGlobalWrite", + "PlaceholderRead", + "UnreachableCode", + "UnknownType", + "ForRange", + "UnbalancedAssignment", + "ImplicitReturn", + "DuplicateLocal", + "FormatString", + "TableLiteral", + "UninitializedLocal", + "DuplicateFunction", + "DeprecatedApi", + "TableOperations", + "DuplicateCondition", + "MisleadingAndOr", + "CommentDirective", + "IntegerParsing", + "ComparisonPrecedence", +}; +// clang-format on + +static_assert(std::size(kWarningNames) == unsigned(LintWarning::Code__Count), "did you forget to add warning to the list?"); + +} // namespace Luau diff --git a/Analysis/include/Luau/Substitution.h b/Analysis/include/Luau/Substitution.h index 398d0ab6..b18c6484 100644 --- a/Analysis/include/Luau/Substitution.h +++ b/Analysis/include/Luau/Substitution.h @@ -147,9 +147,6 @@ struct Tarjan void visitEdge(int index, int parentIndex); void visitSCC(int index); - TarjanResult loop_DEPRECATED(); - void visitSCC_DEPRECATED(int index); - // Each subclass can decide to ignore some nodes. virtual bool ignoreChildren(TypeId ty) { @@ -178,13 +175,6 @@ struct Tarjan virtual bool isDirty(TypePackId tp) = 0; virtual void foundDirty(TypeId ty) = 0; virtual void foundDirty(TypePackId tp) = 0; - - // TODO: remove with FFlagLuauTarjanSingleArr - std::vector indexToType; - std::vector indexToPack; - std::vector onStack; - std::vector lowlink; - std::vector dirty; }; // And finally substitution, which finds all the reachable dirty vertices diff --git a/Analysis/src/Clone.cpp b/Analysis/src/Clone.cpp index 9080b1fc..66bd5e8e 100644 --- a/Analysis/src/Clone.cpp +++ b/Analysis/src/Clone.cpp @@ -1,6 +1,7 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "Luau/Clone.h" +#include "Luau/NotNull.h" #include "Luau/RecursionCounter.h" #include "Luau/TxnLog.h" #include "Luau/TypePack.h" @@ -13,12 +14,424 @@ LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution) LUAU_FASTINTVARIABLE(LuauTypeCloneRecursionLimit, 300) LUAU_FASTFLAGVARIABLE(LuauCloneCyclicUnions, false) +LUAU_FASTFLAGVARIABLE(LuauStacklessTypeClone, false) +LUAU_FASTINTVARIABLE(LuauTypeCloneIterationLimit, 100'000) + namespace Luau { namespace { +using Kind = Variant; + +template +const T* get(const Kind& kind) +{ + return get_if(&kind); +} + +class TypeCloner2 +{ + NotNull arena; + NotNull builtinTypes; + + // A queue of kinds where we cloned it, but whose interior types hasn't + // been updated to point to new clones. Once all of its interior types + // has been updated, it gets removed from the queue. + std::vector queue; + + NotNull types; + NotNull packs; + + int steps = 0; + +public: + TypeCloner2(NotNull arena, NotNull builtinTypes, NotNull types, NotNull packs) + : arena(arena) + , builtinTypes(builtinTypes) + , types(types) + , packs(packs) + { + } + + TypeId clone(TypeId ty) + { + shallowClone(ty); + run(); + + if (hasExceededIterationLimit()) + { + TypeId error = builtinTypes->errorRecoveryType(); + (*types)[ty] = error; + return error; + } + + return find(ty).value_or(builtinTypes->errorRecoveryType()); + } + + TypePackId clone(TypePackId tp) + { + shallowClone(tp); + run(); + + if (hasExceededIterationLimit()) + { + TypePackId error = builtinTypes->errorRecoveryTypePack(); + (*packs)[tp] = error; + return error; + } + + return find(tp).value_or(builtinTypes->errorRecoveryTypePack()); + } + +private: + bool hasExceededIterationLimit() const + { + if (FInt::LuauTypeCloneIterationLimit == 0) + return false; + + return steps + queue.size() >= size_t(FInt::LuauTypeCloneIterationLimit); + } + + void run() + { + while (!queue.empty()) + { + ++steps; + + if (hasExceededIterationLimit()) + break; + + Kind kind = queue.back(); + queue.pop_back(); + + if (find(kind)) + continue; + + cloneChildren(kind); + } + } + + std::optional find(TypeId ty) const + { + if (auto it = types->find(ty); it != types->end()) + return it->second; + return std::nullopt; + } + + std::optional find(TypePackId tp) const + { + if (auto it = packs->find(tp); it != packs->end()) + return it->second; + return std::nullopt; + } + + std::optional find(Kind kind) const + { + if (auto ty = get(kind)) + return find(*ty); + else if (auto tp = get(kind)) + return find(*tp); + else + { + LUAU_ASSERT(!"Unknown kind?"); + return std::nullopt; + } + } + +private: + TypeId shallowClone(TypeId ty) + { + if (auto clone = find(ty)) + return *clone; + else if (ty->persistent) + return ty; + + // We want to [`Luau::follow`] but without forcing the expansion of [`LazyType`]s. + TypeId target = nullptr; + if (auto bt = get(ty)) + target = bt->boundTo; + else if (auto tt = get(ty); tt && tt->boundTo) + target = *tt->boundTo; + else + { + target = arena->addType(ty->ty); + asMutable(target)->documentationSymbol = ty->documentationSymbol; + } + + LUAU_ASSERT(target); + (*types)[ty] = target; + queue.push_back(target); + return target; + } + + TypePackId shallowClone(TypePackId tp) + { + if (auto clone = find(tp)) + return *clone; + else if (tp->persistent) + return tp; + + TypePackId target; + if (auto btp = get(tp)) + target = btp->boundTo; + else + target = arena->addTypePack(tp->ty); + + LUAU_ASSERT(target); + (*packs)[tp] = target; + queue.push_back(target); + return target; + } + + Property shallowClone(const Property& p) + { + if (FFlag::DebugLuauReadWriteProperties) + { + std::optional cloneReadTy; + if (auto ty = p.readType()) + cloneReadTy = shallowClone(*ty); + + std::optional cloneWriteTy; + if (auto ty = p.writeType()) + cloneWriteTy = shallowClone(*ty); + + std::optional cloned = Property::create(cloneReadTy, cloneWriteTy); + LUAU_ASSERT(cloned); + cloned->deprecated = p.deprecated; + cloned->deprecatedSuggestion = p.deprecatedSuggestion; + cloned->location = p.location; + cloned->tags = p.tags; + cloned->documentationSymbol = p.documentationSymbol; + return *cloned; + } + else + { + return Property{ + shallowClone(p.type()), + p.deprecated, + p.deprecatedSuggestion, + p.location, + p.tags, + p.documentationSymbol, + }; + } + } + + void cloneChildren(TypeId ty) + { + return visit( + [&](auto&& t) { + return cloneChildren(&t); + }, + asMutable(ty)->ty); + } + + void cloneChildren(TypePackId tp) + { + return visit( + [&](auto&& t) { + return cloneChildren(&t); + }, + asMutable(tp)->ty); + } + + void cloneChildren(Kind kind) + { + if (auto ty = get(kind)) + return cloneChildren(*ty); + else if (auto tp = get(kind)) + return cloneChildren(*tp); + else + LUAU_ASSERT(!"Item holds neither TypeId nor TypePackId when enqueuing its children?"); + } + + // ErrorType and ErrorTypePack is an alias to this type. + void cloneChildren(Unifiable::Error* t) + { + // noop. + } + + void cloneChildren(BoundType* t) + { + t->boundTo = shallowClone(t->boundTo); + } + + void cloneChildren(FreeType* t) + { + // TODO: clone lower and upper bounds. + // TODO: In the new solver, we should ice. + } + + void cloneChildren(GenericType* t) + { + // TOOD: clone upper bounds. + } + + void cloneChildren(PrimitiveType* t) + { + // noop. + } + + void cloneChildren(BlockedType* t) + { + // TODO: In the new solver, we should ice. + } + + void cloneChildren(PendingExpansionType* t) + { + // TODO: In the new solver, we should ice. + } + + void cloneChildren(SingletonType* t) + { + // noop. + } + + void cloneChildren(FunctionType* t) + { + for (TypeId& g : t->generics) + g = shallowClone(g); + + for (TypePackId& gp : t->genericPacks) + gp = shallowClone(gp); + + t->argTypes = shallowClone(t->argTypes); + t->retTypes = shallowClone(t->retTypes); + } + + void cloneChildren(TableType* t) + { + if (t->indexer) + { + t->indexer->indexType = shallowClone(t->indexer->indexType); + t->indexer->indexResultType = shallowClone(t->indexer->indexResultType); + } + + for (auto& [_, p] : t->props) + p = shallowClone(p); + + for (TypeId& ty : t->instantiatedTypeParams) + ty = shallowClone(ty); + + for (TypePackId& tp : t->instantiatedTypePackParams) + tp = shallowClone(tp); + } + + void cloneChildren(MetatableType* t) + { + t->table = shallowClone(t->table); + t->metatable = shallowClone(t->metatable); + } + + void cloneChildren(ClassType* t) + { + for (auto& [_, p] : t->props) + p = shallowClone(p); + + if (t->parent) + t->parent = shallowClone(*t->parent); + + if (t->metatable) + t->metatable = shallowClone(*t->metatable); + + if (t->indexer) + { + t->indexer->indexType = shallowClone(t->indexer->indexType); + t->indexer->indexResultType = shallowClone(t->indexer->indexResultType); + } + } + + void cloneChildren(AnyType* t) + { + // noop. + } + + void cloneChildren(UnionType* t) + { + for (TypeId& ty : t->options) + ty = shallowClone(ty); + } + + void cloneChildren(IntersectionType* t) + { + for (TypeId& ty : t->parts) + ty = shallowClone(ty); + } + + void cloneChildren(LazyType* t) + { + if (auto unwrapped = t->unwrapped.load()) + t->unwrapped.store(shallowClone(unwrapped)); + } + + void cloneChildren(UnknownType* t) + { + // noop. + } + + void cloneChildren(NeverType* t) + { + // noop. + } + + void cloneChildren(NegationType* t) + { + t->ty = shallowClone(t->ty); + } + + void cloneChildren(TypeFamilyInstanceType* t) + { + // TODO: In the new solver, we should ice. + } + + void cloneChildren(FreeTypePack* t) + { + // TODO: clone lower and upper bounds. + // TODO: In the new solver, we should ice. + } + + void cloneChildren(GenericTypePack* t) + { + // TOOD: clone upper bounds. + } + + void cloneChildren(BlockedTypePack* t) + { + // TODO: In the new solver, we should ice. + } + + void cloneChildren(BoundTypePack* t) + { + t->boundTo = shallowClone(t->boundTo); + } + + void cloneChildren(VariadicTypePack* t) + { + t->ty = shallowClone(t->ty); + } + + void cloneChildren(TypePack* t) + { + for (TypeId& ty : t->head) + ty = shallowClone(ty); + + if (t->tail) + t->tail = shallowClone(*t->tail); + } + + void cloneChildren(TypeFamilyInstanceTypePack* t) + { + // TODO: In the new solver, we should ice. + } +}; + +} // namespace + +namespace +{ + Property clone(const Property& prop, TypeArena& dest, CloneState& cloneState) { if (FFlag::DebugLuauReadWriteProperties) @@ -470,17 +883,25 @@ TypePackId clone(TypePackId tp, TypeArena& dest, CloneState& cloneState) if (tp->persistent) return tp; - RecursionLimiter _ra(&cloneState.recursionCount, FInt::LuauTypeCloneRecursionLimit); - - TypePackId& res = cloneState.seenTypePacks[tp]; - - if (res == nullptr) + if (FFlag::LuauStacklessTypeClone) { - TypePackCloner cloner{dest, tp, cloneState}; - Luau::visit(cloner, tp->ty); // Mutates the storage that 'res' points into. + TypeCloner2 cloner{NotNull{&dest}, cloneState.builtinTypes, NotNull{&cloneState.seenTypes}, NotNull{&cloneState.seenTypePacks}}; + return cloner.clone(tp); } + else + { + RecursionLimiter _ra(&cloneState.recursionCount, FInt::LuauTypeCloneRecursionLimit); - return res; + TypePackId& res = cloneState.seenTypePacks[tp]; + + if (res == nullptr) + { + TypePackCloner cloner{dest, tp, cloneState}; + Luau::visit(cloner, tp->ty); // Mutates the storage that 'res' points into. + } + + return res; + } } TypeId clone(TypeId typeId, TypeArena& dest, CloneState& cloneState) @@ -488,54 +909,91 @@ TypeId clone(TypeId typeId, TypeArena& dest, CloneState& cloneState) if (typeId->persistent) return typeId; - RecursionLimiter _ra(&cloneState.recursionCount, FInt::LuauTypeCloneRecursionLimit); - - TypeId& res = cloneState.seenTypes[typeId]; - - if (res == nullptr) + if (FFlag::LuauStacklessTypeClone) { - TypeCloner cloner{dest, typeId, cloneState}; - Luau::visit(cloner, typeId->ty); // Mutates the storage that 'res' points into. - - // Persistent types are not being cloned and we get the original type back which might be read-only - if (!res->persistent) - { - asMutable(res)->documentationSymbol = typeId->documentationSymbol; - } + TypeCloner2 cloner{NotNull{&dest}, cloneState.builtinTypes, NotNull{&cloneState.seenTypes}, NotNull{&cloneState.seenTypePacks}}; + return cloner.clone(typeId); } + else + { + RecursionLimiter _ra(&cloneState.recursionCount, FInt::LuauTypeCloneRecursionLimit); - return res; + TypeId& res = cloneState.seenTypes[typeId]; + + if (res == nullptr) + { + TypeCloner cloner{dest, typeId, cloneState}; + Luau::visit(cloner, typeId->ty); // Mutates the storage that 'res' points into. + + // Persistent types are not being cloned and we get the original type back which might be read-only + if (!res->persistent) + { + asMutable(res)->documentationSymbol = typeId->documentationSymbol; + } + } + + return res; + } } TypeFun clone(const TypeFun& typeFun, TypeArena& dest, CloneState& cloneState) { - TypeFun result; - - for (auto param : typeFun.typeParams) + if (FFlag::LuauStacklessTypeClone) { - TypeId ty = clone(param.ty, dest, cloneState); - std::optional defaultValue; + TypeCloner2 cloner{NotNull{&dest}, cloneState.builtinTypes, NotNull{&cloneState.seenTypes}, NotNull{&cloneState.seenTypePacks}}; - if (param.defaultValue) - defaultValue = clone(*param.defaultValue, dest, cloneState); + TypeFun copy = typeFun; - result.typeParams.push_back({ty, defaultValue}); + for (auto& param : copy.typeParams) + { + param.ty = cloner.clone(param.ty); + + if (param.defaultValue) + param.defaultValue = cloner.clone(*param.defaultValue); + } + + for (auto& param : copy.typePackParams) + { + param.tp = cloner.clone(param.tp); + + if (param.defaultValue) + param.defaultValue = cloner.clone(*param.defaultValue); + } + + copy.type = cloner.clone(copy.type); + + return copy; } - - for (auto param : typeFun.typePackParams) + else { - TypePackId tp = clone(param.tp, dest, cloneState); - std::optional defaultValue; + TypeFun result; - if (param.defaultValue) - defaultValue = clone(*param.defaultValue, dest, cloneState); + for (auto param : typeFun.typeParams) + { + TypeId ty = clone(param.ty, dest, cloneState); + std::optional defaultValue; - result.typePackParams.push_back({tp, defaultValue}); + if (param.defaultValue) + defaultValue = clone(*param.defaultValue, dest, cloneState); + + result.typeParams.push_back({ty, defaultValue}); + } + + for (auto param : typeFun.typePackParams) + { + TypePackId tp = clone(param.tp, dest, cloneState); + std::optional defaultValue; + + if (param.defaultValue) + defaultValue = clone(*param.defaultValue, dest, cloneState); + + result.typePackParams.push_back({tp, defaultValue}); + } + + result.type = clone(typeFun.type, dest, cloneState); + + return result; } - - result.type = clone(typeFun.type, dest, cloneState); - - return result; } } // namespace Luau diff --git a/Analysis/src/Differ.cpp b/Analysis/src/Differ.cpp index 20f059d3..84505071 100644 --- a/Analysis/src/Differ.cpp +++ b/Analysis/src/Differ.cpp @@ -107,15 +107,19 @@ std::string DiffPath::toString(bool prependDot) const } return pathStr; } -std::string DiffError::toStringALeaf(std::string rootName, const DiffPathNodeLeaf& leaf, const DiffPathNodeLeaf& otherLeaf) const +std::string DiffError::toStringALeaf(std::string rootName, const DiffPathNodeLeaf& leaf, const DiffPathNodeLeaf& otherLeaf, bool multiLine) const { + std::string conditionalNewline = multiLine ? "\n" : " "; + std::string conditionalIndent = multiLine ? " " : ""; std::string pathStr{rootName + diffPath.toString(true)}; switch (kind) { case DiffError::Kind::Normal: { checkNonMissingPropertyLeavesHaveNulloptTableProperty(); - return pathStr + " has type " + Luau::toString(*leaf.ty); + return pathStr + conditionalNewline + + "has type" + conditionalNewline + + conditionalIndent + Luau::toString(*leaf.ty); } case DiffError::Kind::MissingTableProperty: { @@ -123,13 +127,17 @@ std::string DiffError::toStringALeaf(std::string rootName, const DiffPathNodeLea { if (!leaf.tableProperty.has_value()) throw InternalCompilerError{"leaf.tableProperty is nullopt"}; - return pathStr + "." + *leaf.tableProperty + " has type " + Luau::toString(*leaf.ty); + return pathStr + "." + *leaf.tableProperty + conditionalNewline + + "has type" + conditionalNewline + + conditionalIndent + Luau::toString(*leaf.ty); } else if (otherLeaf.ty.has_value()) { if (!otherLeaf.tableProperty.has_value()) throw InternalCompilerError{"otherLeaf.tableProperty is nullopt"}; - return pathStr + " is missing the property " + *otherLeaf.tableProperty; + return pathStr + conditionalNewline + + "is missing the property" + conditionalNewline + + conditionalIndent + *otherLeaf.tableProperty; } throw InternalCompilerError{"Both leaf.ty and otherLeaf.ty is nullopt"}; } @@ -140,11 +148,15 @@ std::string DiffError::toStringALeaf(std::string rootName, const DiffPathNodeLea { if (!leaf.unionIndex.has_value()) throw InternalCompilerError{"leaf.unionIndex is nullopt"}; - return pathStr + " is a union containing type " + Luau::toString(*leaf.ty); + return pathStr + conditionalNewline + + "is a union containing type" + conditionalNewline + + conditionalIndent + Luau::toString(*leaf.ty); } else if (otherLeaf.ty.has_value()) { - return pathStr + " is a union missing type " + Luau::toString(*otherLeaf.ty); + return pathStr + conditionalNewline + + "is a union missing type" + conditionalNewline + + conditionalIndent + Luau::toString(*otherLeaf.ty); } throw InternalCompilerError{"Both leaf.ty and otherLeaf.ty is nullopt"}; } @@ -157,11 +169,15 @@ std::string DiffError::toStringALeaf(std::string rootName, const DiffPathNodeLea { if (!leaf.unionIndex.has_value()) throw InternalCompilerError{"leaf.unionIndex is nullopt"}; - return pathStr + " is an intersection containing type " + Luau::toString(*leaf.ty); + return pathStr + conditionalNewline + + "is an intersection containing type" + conditionalNewline + + conditionalIndent + Luau::toString(*leaf.ty); } else if (otherLeaf.ty.has_value()) { - return pathStr + " is an intersection missing type " + Luau::toString(*otherLeaf.ty); + return pathStr + conditionalNewline + + "is an intersection missing type" + conditionalNewline + + conditionalIndent + Luau::toString(*otherLeaf.ty); } throw InternalCompilerError{"Both leaf.ty and otherLeaf.ty is nullopt"}; } @@ -169,13 +185,15 @@ std::string DiffError::toStringALeaf(std::string rootName, const DiffPathNodeLea { if (!leaf.minLength.has_value()) throw InternalCompilerError{"leaf.minLength is nullopt"}; - return pathStr + " takes " + std::to_string(*leaf.minLength) + (leaf.isVariadic ? " or more" : "") + " arguments"; + return pathStr + conditionalNewline + + "takes " + std::to_string(*leaf.minLength) + (leaf.isVariadic ? " or more" : "") + " arguments"; } case DiffError::Kind::LengthMismatchInFnRets: { if (!leaf.minLength.has_value()) throw InternalCompilerError{"leaf.minLength is nullopt"}; - return pathStr + " returns " + std::to_string(*leaf.minLength) + (leaf.isVariadic ? " or more" : "") + " values"; + return pathStr + conditionalNewline + + "returns " + std::to_string(*leaf.minLength) + (leaf.isVariadic ? " or more" : "") + " values"; } default: { @@ -190,8 +208,11 @@ void DiffError::checkNonMissingPropertyLeavesHaveNulloptTableProperty() const throw InternalCompilerError{"Non-MissingProperty DiffError should have nullopt tableProperty in both leaves"}; } -std::string getDevFixFriendlyName(TypeId ty) +std::string getDevFixFriendlyName(const std::optional& maybeSymbol, TypeId ty) { + if (maybeSymbol.has_value()) + return *maybeSymbol; + if (auto table = get(ty)) { if (table->name.has_value()) @@ -206,27 +227,39 @@ std::string getDevFixFriendlyName(TypeId ty) return *metatable->syntheticName; } } - // else if (auto primitive = get(ty)) - //{ - // return ""; - //} return ""; } -std::string DiffError::toString() const +std::string DifferEnvironment::getDevFixFriendlyNameLeft() const { + return getDevFixFriendlyName(externalSymbolLeft, rootLeft); +} + +std::string DifferEnvironment::getDevFixFriendlyNameRight() const +{ + return getDevFixFriendlyName(externalSymbolRight, rootRight); +} + +std::string DiffError::toString(bool multiLine) const +{ + std::string conditionalNewline = multiLine ? "\n" : " "; + std::string conditionalIndent = multiLine ? " " : ""; switch (kind) { case DiffError::Kind::IncompatibleGeneric: { std::string diffPathStr{diffPath.toString(true)}; - return "DiffError: these two types are not equal because the left generic at " + leftRootName + diffPathStr + - " cannot be the same type parameter as the right generic at " + rightRootName + diffPathStr; + return "DiffError: these two types are not equal because the left generic at" + conditionalNewline + + conditionalIndent + leftRootName + diffPathStr + conditionalNewline + + "cannot be the same type parameter as the right generic at" + conditionalNewline + + conditionalIndent + rightRootName + diffPathStr; } default: { - return "DiffError: these two types are not equal because the left type at " + toStringALeaf(leftRootName, left, right) + - ", while the right type at " + toStringALeaf(rightRootName, right, left); + return "DiffError: these two types are not equal because the left type at" + conditionalNewline + + conditionalIndent + toStringALeaf(leftRootName, left, right, multiLine) + "," + conditionalNewline + + "while the right type at" + conditionalNewline + + conditionalIndent + toStringALeaf(rightRootName, right, left, multiLine); } } } @@ -296,8 +329,8 @@ static DifferResult diffTable(DifferEnvironment& env, TypeId left, TypeId right) DiffError::Kind::MissingTableProperty, DiffPathNodeLeaf::detailsTableProperty(value.type(), field), DiffPathNodeLeaf::nullopts(), - getDevFixFriendlyName(env.rootLeft), - getDevFixFriendlyName(env.rootRight), + env.getDevFixFriendlyNameLeft(), + env.getDevFixFriendlyNameRight(), }}; } } @@ -307,8 +340,7 @@ static DifferResult diffTable(DifferEnvironment& env, TypeId left, TypeId right) { // right has a field the left doesn't return DifferResult{DiffError{DiffError::Kind::MissingTableProperty, DiffPathNodeLeaf::nullopts(), - DiffPathNodeLeaf::detailsTableProperty(value.type(), field), getDevFixFriendlyName(env.rootLeft), - getDevFixFriendlyName(env.rootRight)}}; + DiffPathNodeLeaf::detailsTableProperty(value.type(), field), env.getDevFixFriendlyNameLeft(), env.getDevFixFriendlyNameRight()}}; } } // left and right have the same set of keys @@ -360,8 +392,8 @@ static DifferResult diffPrimitive(DifferEnvironment& env, TypeId left, TypeId ri DiffError::Kind::Normal, DiffPathNodeLeaf::detailsNormal(left), DiffPathNodeLeaf::detailsNormal(right), - getDevFixFriendlyName(env.rootLeft), - getDevFixFriendlyName(env.rootRight), + env.getDevFixFriendlyNameLeft(), + env.getDevFixFriendlyNameRight(), }}; } return DifferResult{}; @@ -380,8 +412,8 @@ static DifferResult diffSingleton(DifferEnvironment& env, TypeId left, TypeId ri DiffError::Kind::Normal, DiffPathNodeLeaf::detailsNormal(left), DiffPathNodeLeaf::detailsNormal(right), - getDevFixFriendlyName(env.rootLeft), - getDevFixFriendlyName(env.rootRight), + env.getDevFixFriendlyNameLeft(), + env.getDevFixFriendlyNameRight(), }}; } return DifferResult{}; @@ -419,8 +451,8 @@ static DifferResult diffGeneric(DifferEnvironment& env, TypeId left, TypeId righ DiffError::Kind::IncompatibleGeneric, DiffPathNodeLeaf::nullopts(), DiffPathNodeLeaf::nullopts(), - getDevFixFriendlyName(env.rootLeft), - getDevFixFriendlyName(env.rootRight), + env.getDevFixFriendlyNameLeft(), + env.getDevFixFriendlyNameRight(), }}; } @@ -432,8 +464,8 @@ static DifferResult diffGeneric(DifferEnvironment& env, TypeId left, TypeId righ DiffError::Kind::IncompatibleGeneric, DiffPathNodeLeaf::nullopts(), DiffPathNodeLeaf::nullopts(), - getDevFixFriendlyName(env.rootLeft), - getDevFixFriendlyName(env.rootRight), + env.getDevFixFriendlyNameLeft(), + env.getDevFixFriendlyNameRight(), }}; } @@ -468,8 +500,8 @@ static DifferResult diffClass(DifferEnvironment& env, TypeId left, TypeId right) DiffError::Kind::Normal, DiffPathNodeLeaf::detailsNormal(left), DiffPathNodeLeaf::detailsNormal(right), - getDevFixFriendlyName(env.rootLeft), - getDevFixFriendlyName(env.rootRight), + env.getDevFixFriendlyNameLeft(), + env.getDevFixFriendlyNameRight(), }}; } @@ -521,16 +553,16 @@ static DifferResult diffUnion(DifferEnvironment& env, TypeId left, TypeId right) DiffError::Kind::MissingUnionMember, DiffPathNodeLeaf::detailsUnionIndex(leftUnion->options[*findSeteqCexResult.mismatchIdx], *findSeteqCexResult.mismatchIdx), DiffPathNodeLeaf::nullopts(), - getDevFixFriendlyName(env.rootLeft), - getDevFixFriendlyName(env.rootRight), + env.getDevFixFriendlyNameLeft(), + env.getDevFixFriendlyNameRight(), }}; else return DifferResult{DiffError{ DiffError::Kind::MissingUnionMember, DiffPathNodeLeaf::nullopts(), DiffPathNodeLeaf::detailsUnionIndex(rightUnion->options[*findSeteqCexResult.mismatchIdx], *findSeteqCexResult.mismatchIdx), - getDevFixFriendlyName(env.rootLeft), - getDevFixFriendlyName(env.rootRight), + env.getDevFixFriendlyNameLeft(), + env.getDevFixFriendlyNameRight(), }}; } @@ -554,16 +586,16 @@ static DifferResult diffIntersection(DifferEnvironment& env, TypeId left, TypeId DiffError::Kind::MissingIntersectionMember, DiffPathNodeLeaf::detailsUnionIndex(leftIntersection->parts[*findSeteqCexResult.mismatchIdx], *findSeteqCexResult.mismatchIdx), DiffPathNodeLeaf::nullopts(), - getDevFixFriendlyName(env.rootLeft), - getDevFixFriendlyName(env.rootRight), + env.getDevFixFriendlyNameLeft(), + env.getDevFixFriendlyNameRight(), }}; else return DifferResult{DiffError{ DiffError::Kind::MissingIntersectionMember, DiffPathNodeLeaf::nullopts(), DiffPathNodeLeaf::detailsUnionIndex(rightIntersection->parts[*findSeteqCexResult.mismatchIdx], *findSeteqCexResult.mismatchIdx), - getDevFixFriendlyName(env.rootLeft), - getDevFixFriendlyName(env.rootRight), + env.getDevFixFriendlyNameLeft(), + env.getDevFixFriendlyNameRight(), }}; } @@ -583,8 +615,8 @@ static DifferResult diffUsingEnv(DifferEnvironment& env, TypeId left, TypeId rig DiffError::Kind::Normal, DiffPathNodeLeaf::detailsNormal(left), DiffPathNodeLeaf::detailsNormal(right), - getDevFixFriendlyName(env.rootLeft), - getDevFixFriendlyName(env.rootRight), + env.getDevFixFriendlyNameLeft(), + env.getDevFixFriendlyNameRight(), }}; } @@ -753,8 +785,8 @@ static DifferResult diffCanonicalTpShape(DifferEnvironment& env, DiffError::Kind possibleNonNormalErrorKind, DiffPathNodeLeaf::detailsLength(int(left.first.size()), left.second.has_value()), DiffPathNodeLeaf::detailsLength(int(right.first.size()), right.second.has_value()), - getDevFixFriendlyName(env.rootLeft), - getDevFixFriendlyName(env.rootRight), + env.getDevFixFriendlyNameLeft(), + env.getDevFixFriendlyNameRight(), }}; } @@ -769,8 +801,8 @@ static DifferResult diffHandleFlattenedTail(DifferEnvironment& env, DiffError::K DiffError::Kind::Normal, DiffPathNodeLeaf::detailsNormal(env.visitingBegin()->first), DiffPathNodeLeaf::detailsNormal(env.visitingBegin()->second), - getDevFixFriendlyName(env.rootLeft), - getDevFixFriendlyName(env.rootRight), + env.getDevFixFriendlyNameLeft(), + env.getDevFixFriendlyNameRight(), }}; } @@ -847,8 +879,8 @@ static DifferResult diffGenericTp(DifferEnvironment& env, TypePackId left, TypeP DiffError::Kind::IncompatibleGeneric, DiffPathNodeLeaf::nullopts(), DiffPathNodeLeaf::nullopts(), - getDevFixFriendlyName(env.rootLeft), - getDevFixFriendlyName(env.rootRight), + env.getDevFixFriendlyNameLeft(), + env.getDevFixFriendlyNameRight(), }}; } @@ -860,8 +892,8 @@ static DifferResult diffGenericTp(DifferEnvironment& env, TypePackId left, TypeP DiffError::Kind::IncompatibleGeneric, DiffPathNodeLeaf::nullopts(), DiffPathNodeLeaf::nullopts(), - getDevFixFriendlyName(env.rootLeft), - getDevFixFriendlyName(env.rootRight), + env.getDevFixFriendlyNameLeft(), + env.getDevFixFriendlyNameRight(), }}; } @@ -910,7 +942,13 @@ std::vector>::const_reverse_iterator DifferEnvironment DifferResult diff(TypeId ty1, TypeId ty2) { - DifferEnvironment differEnv{ty1, ty2}; + DifferEnvironment differEnv{ty1, ty2, std::nullopt, std::nullopt}; + return diffUsingEnv(differEnv, ty1, ty2); +} + +DifferResult diffWithSymbols(TypeId ty1, TypeId ty2, std::optional symbol1, std::optional symbol2) +{ + DifferEnvironment differEnv{ty1, ty2, symbol1, symbol2}; return diffUsingEnv(differEnv, ty1, ty2); } diff --git a/Analysis/src/Error.cpp b/Analysis/src/Error.cpp index 1a690a50..615ce41e 100644 --- a/Analysis/src/Error.cpp +++ b/Analysis/src/Error.cpp @@ -4,12 +4,16 @@ #include "Luau/Clone.h" #include "Luau/Common.h" #include "Luau/FileResolver.h" +#include "Luau/NotNull.h" #include "Luau/StringUtils.h" #include "Luau/ToString.h" +#include #include #include +LUAU_FASTFLAGVARIABLE(LuauIndentTypeMismatch, false) +LUAU_FASTINTVARIABLE(LuauIndentTypeMismatchMaxTypeLength, 10) static std::string wrongNumberOfArgsString( size_t expectedCount, std::optional maximumCount, size_t actualCount, const char* argPrefix = nullptr, bool isVariadic = false) @@ -66,6 +70,20 @@ struct ErrorConverter std::string result; + auto quote = [&](std::string s) { + return "'" + s + "'"; + }; + + auto constructErrorMessage = [&](std::string givenType, std::string wantedType, std::optional givenModule, + std::optional wantedModule) -> std::string { + std::string given = givenModule ? quote(givenType) + " from " + quote(*givenModule) : quote(givenType); + std::string wanted = wantedModule ? quote(wantedType) + " from " + quote(*wantedModule) : quote(wantedType); + size_t luauIndentTypeMismatchMaxTypeLength = size_t(FInt::LuauIndentTypeMismatchMaxTypeLength); + if (givenType.length() <= luauIndentTypeMismatchMaxTypeLength || wantedType.length() <= luauIndentTypeMismatchMaxTypeLength) + return "Type " + given + " could not be converted into " + wanted; + return "Type\n " + given + "\ncould not be converted into\n " + wanted; + }; + if (givenTypeName == wantedTypeName) { if (auto givenDefinitionModule = getDefinitionModuleName(tm.givenType)) @@ -76,20 +94,31 @@ struct ErrorConverter { std::string givenModuleName = fileResolver->getHumanReadableModuleName(*givenDefinitionModule); std::string wantedModuleName = fileResolver->getHumanReadableModuleName(*wantedDefinitionModule); - result = "Type '" + givenTypeName + "' from '" + givenModuleName + "' could not be converted into '" + wantedTypeName + - "' from '" + wantedModuleName + "'"; + if (FFlag::LuauIndentTypeMismatch) + result = constructErrorMessage(givenTypeName, wantedTypeName, givenModuleName, wantedModuleName); + else + result = "Type '" + givenTypeName + "' from '" + givenModuleName + "' could not be converted into '" + wantedTypeName + + "' from '" + wantedModuleName + "'"; } else { - result = "Type '" + givenTypeName + "' from '" + *givenDefinitionModule + "' could not be converted into '" + wantedTypeName + - "' from '" + *wantedDefinitionModule + "'"; + if (FFlag::LuauIndentTypeMismatch) + result = constructErrorMessage(givenTypeName, wantedTypeName, *givenDefinitionModule, *wantedDefinitionModule); + else + result = "Type '" + givenTypeName + "' from '" + *givenDefinitionModule + "' could not be converted into '" + + wantedTypeName + "' from '" + *wantedDefinitionModule + "'"; } } } } if (result.empty()) - result = "Type '" + givenTypeName + "' could not be converted into '" + wantedTypeName + "'"; + { + if (FFlag::LuauIndentTypeMismatch) + result = constructErrorMessage(givenTypeName, wantedTypeName, std::nullopt, std::nullopt); + else + result = "Type '" + givenTypeName + "' could not be converted into '" + wantedTypeName + "'"; + } if (tm.error) @@ -97,7 +126,7 @@ struct ErrorConverter result += "\ncaused by:\n "; if (!tm.reason.empty()) - result += tm.reason + " "; + result += tm.reason + (FFlag::LuauIndentTypeMismatch ? " \n" : " "); result += Luau::toString(*tm.error, TypeErrorToStringOptions{fileResolver}); } @@ -845,7 +874,7 @@ bool containsParseErrorName(const TypeError& error) } template -void copyError(T& e, TypeArena& destArena, CloneState cloneState) +void copyError(T& e, TypeArena& destArena, CloneState& cloneState) { auto clone = [&](auto&& ty) { return ::Luau::clone(ty, destArena, cloneState); @@ -998,9 +1027,9 @@ void copyError(T& e, TypeArena& destArena, CloneState cloneState) static_assert(always_false_v, "Non-exhaustive type switch"); } -void copyErrors(ErrorVec& errors, TypeArena& destArena) +void copyErrors(ErrorVec& errors, TypeArena& destArena, NotNull builtinTypes) { - CloneState cloneState; + CloneState cloneState{builtinTypes}; auto visitErrorData = [&](auto&& e) { copyError(e, destArena, cloneState); diff --git a/Analysis/src/Frontend.cpp b/Analysis/src/Frontend.cpp index 52eedece..4dd815fb 100644 --- a/Analysis/src/Frontend.cpp +++ b/Analysis/src/Frontend.cpp @@ -35,7 +35,6 @@ LUAU_FASTINTVARIABLE(LuauAutocompleteCheckTimeoutMs, 100) LUAU_FASTFLAGVARIABLE(DebugLuauDeferredConstraintResolution, false) LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson, false) LUAU_FASTFLAGVARIABLE(DebugLuauReadWriteProperties, false) -LUAU_FASTFLAGVARIABLE(LuauTypecheckCancellation, false) namespace Luau { @@ -126,7 +125,7 @@ static ParseResult parseSourceForModule(std::string_view source, Luau::SourceMod static void persistCheckedTypes(ModulePtr checkedModule, GlobalTypes& globals, ScopePtr targetScope, const std::string& packageName) { - CloneState cloneState; + CloneState cloneState{globals.builtinTypes}; std::vector typesToPersist; typesToPersist.reserve(checkedModule->declaredGlobals.size() + checkedModule->exportedTypeBindings.size()); @@ -462,7 +461,7 @@ CheckResult Frontend::check(const ModuleName& name, std::optionalcancelled) + if (item.module->cancelled) return {}; checkResult.errors.insert(checkResult.errors.end(), item.module->errors.begin(), item.module->errors.end()); @@ -635,7 +634,7 @@ std::vector Frontend::checkQueuedModules(std::optionalcancelled) + if (item.module && item.module->cancelled) cancelled = true; if (itemWithException || cancelled) @@ -677,7 +676,7 @@ std::vector Frontend::checkQueuedModules(std::optionalinterfaceTypes); - copyErrors(module->errors, module->interfaceTypes); + copyErrors(module->errors, module->interfaceTypes, builtinTypes); freeze(module->interfaceTypes); module->internalTypes.clear(); @@ -1014,7 +1011,7 @@ void Frontend::checkBuildQueueItems(std::vector& items) { checkBuildQueueItem(item); - if (FFlag::LuauTypecheckCancellation && item.module && item.module->cancelled) + if (item.module && item.module->cancelled) break; recordItemResult(item); @@ -1295,9 +1292,7 @@ ModulePtr Frontend::check(const SourceModule& sourceModule, Mode mode, std::vect typeChecker.finishTime = typeCheckLimits.finishTime; typeChecker.instantiationChildLimit = typeCheckLimits.instantiationChildLimit; typeChecker.unifierIterationLimit = typeCheckLimits.unifierIterationLimit; - - if (FFlag::LuauTypecheckCancellation) - typeChecker.cancellationToken = typeCheckLimits.cancellationToken; + typeChecker.cancellationToken = typeCheckLimits.cancellationToken; return typeChecker.check(sourceModule, mode, environmentScope); } diff --git a/Analysis/src/Linter.cpp b/Analysis/src/Linter.cpp index 4abc1aa1..e8cd7dbd 100644 --- a/Analysis/src/Linter.cpp +++ b/Analysis/src/Linter.cpp @@ -14,48 +14,9 @@ LUAU_FASTINTVARIABLE(LuauSuggestionDistance, 4) -LUAU_FASTFLAGVARIABLE(LuauLintNativeComment, false) - namespace Luau { -// clang-format off -static const char* kWarningNames[] = { - "Unknown", - - "UnknownGlobal", - "DeprecatedGlobal", - "GlobalUsedAsLocal", - "LocalShadow", - "SameLineStatement", - "MultiLineStatement", - "LocalUnused", - "FunctionUnused", - "ImportUnused", - "BuiltinGlobalWrite", - "PlaceholderRead", - "UnreachableCode", - "UnknownType", - "ForRange", - "UnbalancedAssignment", - "ImplicitReturn", - "DuplicateLocal", - "FormatString", - "TableLiteral", - "UninitializedLocal", - "DuplicateFunction", - "DeprecatedApi", - "TableOperations", - "DuplicateCondition", - "MisleadingAndOr", - "CommentDirective", - "IntegerParsing", - "ComparisonPrecedence", -}; -// clang-format on - -static_assert(std::size(kWarningNames) == unsigned(LintWarning::Code__Count), "did you forget to add warning to the list?"); - struct LintContext { struct Global @@ -2827,7 +2788,7 @@ static void lintComments(LintContext& context, const std::vector& ho "optimize directive uses unknown optimization level '%s', 0..2 expected", level); } } - else if (FFlag::LuauLintNativeComment && first == "native") + else if (first == "native") { if (space != std::string::npos) emitWarning(context, LintWarning::Code_CommentDirective, hc.location, @@ -2855,12 +2816,6 @@ static void lintComments(LintContext& context, const std::vector& ho } } -void LintOptions::setDefaults() -{ - // By default, we enable all warnings - warningMask = ~0ull; -} - std::vector lint(AstStat* root, const AstNameTable& names, const ScopePtr& env, const Module* module, const std::vector& hotcomments, const LintOptions& options) { @@ -2952,54 +2907,6 @@ std::vector lint(AstStat* root, const AstNameTable& names, const Sc return context.result; } -const char* LintWarning::getName(Code code) -{ - LUAU_ASSERT(unsigned(code) < Code__Count); - - return kWarningNames[code]; -} - -LintWarning::Code LintWarning::parseName(const char* name) -{ - for (int code = Code_Unknown; code < Code__Count; ++code) - if (strcmp(name, getName(Code(code))) == 0) - return Code(code); - - return Code_Unknown; -} - -uint64_t LintWarning::parseMask(const std::vector& hotcomments) -{ - uint64_t result = 0; - - for (const HotComment& hc : hotcomments) - { - if (!hc.header) - continue; - - if (hc.content.compare(0, 6, "nolint") != 0) - continue; - - size_t name = hc.content.find_first_not_of(" \t", 6); - - // --!nolint disables everything - if (name == std::string::npos) - return ~0ull; - - // --!nolint needs to be followed by a whitespace character - if (name == 6) - continue; - - // --!nolint name disables the specific lint - LintWarning::Code code = LintWarning::parseName(hc.content.c_str() + name); - - if (code != LintWarning::Code_Unknown) - result |= 1ull << int(code); - } - - return result; -} - std::vector getDeprecatedGlobals(const AstNameTable& names) { LintContext context; diff --git a/Analysis/src/LinterConfig.cpp b/Analysis/src/LinterConfig.cpp new file mode 100644 index 00000000..d63969d5 --- /dev/null +++ b/Analysis/src/LinterConfig.cpp @@ -0,0 +1,63 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#include "Luau/LinterConfig.h" + +#include "Luau/ParseResult.h" + +namespace Luau +{ + +void LintOptions::setDefaults() +{ + // By default, we enable all warnings + warningMask = ~0ull; +} + +const char* LintWarning::getName(Code code) +{ + LUAU_ASSERT(unsigned(code) < Code__Count); + + return kWarningNames[code]; +} + +LintWarning::Code LintWarning::parseName(const char* name) +{ + for (int code = Code_Unknown; code < Code__Count; ++code) + if (strcmp(name, getName(Code(code))) == 0) + return Code(code); + + return Code_Unknown; +} + +uint64_t LintWarning::parseMask(const std::vector& hotcomments) +{ + uint64_t result = 0; + + for (const HotComment& hc : hotcomments) + { + if (!hc.header) + continue; + + if (hc.content.compare(0, 6, "nolint") != 0) + continue; + + size_t name = hc.content.find_first_not_of(" \t", 6); + + // --!nolint disables everything + if (name == std::string::npos) + return ~0ull; + + // --!nolint needs to be followed by a whitespace character + if (name == 6) + continue; + + // --!nolint name disables the specific lint + LintWarning::Code code = LintWarning::parseName(hc.content.c_str() + name); + + if (code != LintWarning::Code_Unknown) + result |= 1ull << int(code); + } + + return result; +} + +} // namespace Luau diff --git a/Analysis/src/Module.cpp b/Analysis/src/Module.cpp index cb2114ab..580f59f3 100644 --- a/Analysis/src/Module.cpp +++ b/Analysis/src/Module.cpp @@ -199,7 +199,7 @@ void Module::clonePublicInterface(NotNull builtinTypes, InternalEr LUAU_ASSERT(interfaceTypes.types.empty()); LUAU_ASSERT(interfaceTypes.typePacks.empty()); - CloneState cloneState; + CloneState cloneState{builtinTypes}; ScopePtr moduleScope = getModuleScope(); diff --git a/Analysis/src/Substitution.cpp b/Analysis/src/Substitution.cpp index 4c6c35b0..a34a45c8 100644 --- a/Analysis/src/Substitution.cpp +++ b/Analysis/src/Substitution.cpp @@ -10,7 +10,6 @@ LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 10000) LUAU_FASTFLAG(DebugLuauReadWriteProperties) -LUAU_FASTFLAGVARIABLE(LuauTarjanSingleArr, false) namespace Luau { @@ -269,67 +268,30 @@ std::pair Tarjan::indexify(TypeId ty) { ty = log->follow(ty); - if (FFlag::LuauTarjanSingleArr) + auto [index, fresh] = typeToIndex.try_insert(ty, false); + + if (fresh) { - auto [index, fresh] = typeToIndex.try_insert(ty, false); - - if (fresh) - { - index = int(nodes.size()); - nodes.push_back({ty, nullptr, false, false, index}); - } - - return {index, fresh}; + index = int(nodes.size()); + nodes.push_back({ty, nullptr, false, false, index}); } - else - { - bool fresh = !typeToIndex.contains(ty); - int& index = typeToIndex[ty]; - if (fresh) - { - index = int(indexToType.size()); - indexToType.push_back(ty); - indexToPack.push_back(nullptr); - onStack.push_back(false); - lowlink.push_back(index); - } - return {index, fresh}; - } + return {index, fresh}; } std::pair Tarjan::indexify(TypePackId tp) { tp = log->follow(tp); - if (FFlag::LuauTarjanSingleArr) + auto [index, fresh] = packToIndex.try_insert(tp, false); + + if (fresh) { - auto [index, fresh] = packToIndex.try_insert(tp, false); - - if (fresh) - { - index = int(nodes.size()); - nodes.push_back({nullptr, tp, false, false, index}); - } - - return {index, fresh}; + index = int(nodes.size()); + nodes.push_back({nullptr, tp, false, false, index}); } - else - { - bool fresh = !packToIndex.contains(tp); - int& index = packToIndex[tp]; - - if (fresh) - { - index = int(indexToPack.size()); - indexToType.push_back(nullptr); - indexToPack.push_back(tp); - onStack.push_back(false); - lowlink.push_back(index); - } - return {index, fresh}; - } + return {index, fresh}; } void Tarjan::visitChild(TypeId ty) @@ -350,9 +312,6 @@ void Tarjan::visitChild(TypePackId tp) TarjanResult Tarjan::loop() { - if (!FFlag::LuauTarjanSingleArr) - return loop_DEPRECATED(); - // Normally Tarjan is presented recursively, but this is a hot loop, so worth optimizing while (!worklist.empty()) { @@ -476,27 +435,11 @@ TarjanResult Tarjan::visitRoot(TypePackId tp) void Tarjan::clearTarjan() { - if (FFlag::LuauTarjanSingleArr) - { - typeToIndex.clear(); - packToIndex.clear(); - nodes.clear(); + typeToIndex.clear(); + packToIndex.clear(); + nodes.clear(); - stack.clear(); - } - else - { - dirty.clear(); - - typeToIndex.clear(); - packToIndex.clear(); - indexToType.clear(); - indexToPack.clear(); - - stack.clear(); - onStack.clear(); - lowlink.clear(); - } + stack.clear(); edgesTy.clear(); edgesTp.clear(); @@ -505,32 +448,14 @@ void Tarjan::clearTarjan() bool Tarjan::getDirty(int index) { - if (FFlag::LuauTarjanSingleArr) - { - LUAU_ASSERT(size_t(index) < nodes.size()); - return nodes[index].dirty; - } - else - { - if (dirty.size() <= size_t(index)) - dirty.resize(index + 1, false); - return dirty[index]; - } + LUAU_ASSERT(size_t(index) < nodes.size()); + return nodes[index].dirty; } void Tarjan::setDirty(int index, bool d) { - if (FFlag::LuauTarjanSingleArr) - { - LUAU_ASSERT(size_t(index) < nodes.size()); - nodes[index].dirty = d; - } - else - { - if (dirty.size() <= size_t(index)) - dirty.resize(index + 1, false); - dirty[index] = d; - } + LUAU_ASSERT(size_t(index) < nodes.size()); + nodes[index].dirty = d; } void Tarjan::visitEdge(int index, int parentIndex) @@ -541,9 +466,6 @@ void Tarjan::visitEdge(int index, int parentIndex) void Tarjan::visitSCC(int index) { - if (!FFlag::LuauTarjanSingleArr) - return visitSCC_DEPRECATED(index); - bool d = getDirty(index); for (auto it = stack.rbegin(); !d && it != stack.rend(); it++) @@ -588,132 +510,6 @@ TarjanResult Tarjan::findDirty(TypePackId tp) return visitRoot(tp); } -TarjanResult Tarjan::loop_DEPRECATED() -{ - // Normally Tarjan is presented recursively, but this is a hot loop, so worth optimizing - while (!worklist.empty()) - { - auto [index, currEdge, lastEdge] = worklist.back(); - - // First visit - if (currEdge == -1) - { - ++childCount; - if (childLimit > 0 && childLimit <= childCount) - return TarjanResult::TooManyChildren; - - stack.push_back(index); - onStack[index] = true; - - currEdge = int(edgesTy.size()); - - // Fill in edge list of this vertex - if (TypeId ty = indexToType[index]) - visitChildren(ty, index); - else if (TypePackId tp = indexToPack[index]) - visitChildren(tp, index); - - lastEdge = int(edgesTy.size()); - } - - // Visit children - bool foundFresh = false; - - for (; currEdge < lastEdge; currEdge++) - { - int childIndex = -1; - bool fresh = false; - - if (auto ty = edgesTy[currEdge]) - std::tie(childIndex, fresh) = indexify(ty); - else if (auto tp = edgesTp[currEdge]) - std::tie(childIndex, fresh) = indexify(tp); - else - LUAU_ASSERT(false); - - if (fresh) - { - // Original recursion point, update the parent continuation point and start the new element - worklist.back() = {index, currEdge + 1, lastEdge}; - worklist.push_back({childIndex, -1, -1}); - - // We need to continue the top-level loop from the start with the new worklist element - foundFresh = true; - break; - } - else if (onStack[childIndex]) - { - lowlink[index] = std::min(lowlink[index], childIndex); - } - - visitEdge(childIndex, index); - } - - if (foundFresh) - continue; - - if (lowlink[index] == index) - { - visitSCC(index); - while (!stack.empty()) - { - int popped = stack.back(); - stack.pop_back(); - onStack[popped] = false; - if (popped == index) - break; - } - } - - worklist.pop_back(); - - // Original return from recursion into a child - if (!worklist.empty()) - { - auto [parentIndex, _, parentEndEdge] = worklist.back(); - - // No need to keep child edges around - edgesTy.resize(parentEndEdge); - edgesTp.resize(parentEndEdge); - - lowlink[parentIndex] = std::min(lowlink[parentIndex], lowlink[index]); - visitEdge(index, parentIndex); - } - } - - return TarjanResult::Ok; -} - - -void Tarjan::visitSCC_DEPRECATED(int index) -{ - bool d = getDirty(index); - - for (auto it = stack.rbegin(); !d && it != stack.rend(); it++) - { - if (TypeId ty = indexToType[*it]) - d = isDirty(ty); - else if (TypePackId tp = indexToPack[*it]) - d = isDirty(tp); - if (*it == index) - break; - } - - if (!d) - return; - - for (auto it = stack.rbegin(); it != stack.rend(); it++) - { - setDirty(*it, true); - if (TypeId ty = indexToType[*it]) - foundDirty(ty); - else if (TypePackId tp = indexToPack[*it]) - foundDirty(tp); - if (*it == index) - return; - } -} - std::optional Substitution::substitute(TypeId ty) { ty = log->follow(ty); diff --git a/Analysis/src/TypeChecker2.cpp b/Analysis/src/TypeChecker2.cpp index 08b1ffbc..a7653b7c 100644 --- a/Analysis/src/TypeChecker2.cpp +++ b/Analysis/src/TypeChecker2.cpp @@ -2688,7 +2688,7 @@ void check( typeChecker.visit(sourceModule.root); unfreeze(module->interfaceTypes); - copyErrors(module->errors, module->interfaceTypes); + copyErrors(module->errors, module->interfaceTypes, builtinTypes); freeze(module->interfaceTypes); } diff --git a/Analysis/src/TypeInfer.cpp b/Analysis/src/TypeInfer.cpp index d0ae4133..00cf4cd0 100644 --- a/Analysis/src/TypeInfer.cpp +++ b/Analysis/src/TypeInfer.cpp @@ -38,11 +38,9 @@ LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAGVARIABLE(LuauAllowIndexClassParameters, false) LUAU_FASTFLAGVARIABLE(LuauFixCyclicModuleExports, false) LUAU_FASTFLAG(LuauOccursIsntAlwaysFailure) -LUAU_FASTFLAGVARIABLE(LuauTypecheckTypeguards, false) LUAU_FASTFLAGVARIABLE(LuauTinyControlFlowAnalysis, false) LUAU_FASTFLAGVARIABLE(LuauAlwaysCommitInferencesOfFunctionCalls, false) LUAU_FASTFLAG(LuauParseDeclareClassIndexer) -LUAU_FASTFLAGVARIABLE(LuauIndexTableIntersectionStringExpr, false) namespace Luau { @@ -3110,22 +3108,13 @@ WithPredicate TypeChecker::checkExpr(const ScopePtr& scope, const AstExp } else if (expr.op == AstExprBinary::CompareEq || expr.op == AstExprBinary::CompareNe) { - if (!FFlag::LuauTypecheckTypeguards) - { - if (auto predicate = tryGetTypeGuardPredicate(expr)) - return {booleanType, {std::move(*predicate)}}; - } - // For these, passing expectedType is worse than simply forcing them, because their implementation // may inadvertently check if expectedTypes exist first and use it, instead of forceSingleton first. WithPredicate lhs = checkExpr(scope, *expr.left, std::nullopt, /*forceSingleton=*/true); WithPredicate rhs = checkExpr(scope, *expr.right, std::nullopt, /*forceSingleton=*/true); - if (FFlag::LuauTypecheckTypeguards) - { - if (auto predicate = tryGetTypeGuardPredicate(expr)) - return {booleanType, {std::move(*predicate)}}; - } + if (auto predicate = tryGetTypeGuardPredicate(expr)) + return {booleanType, {std::move(*predicate)}}; PredicateVec predicates; @@ -3405,7 +3394,7 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex reportError(TypeError{expr.location, UnknownProperty{exprType, value->value.data}}); return errorRecoveryType(scope); } - else if (FFlag::LuauIndexTableIntersectionStringExpr && get(exprType)) + else if (get(exprType)) { Name name = std::string(value->value.data, value->value.size); diff --git a/CodeGen/include/Luau/CodeAllocator.h b/CodeGen/include/Luau/CodeAllocator.h index e0537b64..d7e43272 100644 --- a/CodeGen/include/Luau/CodeAllocator.h +++ b/CodeGen/include/Luau/CodeAllocator.h @@ -1,6 +1,8 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #pragma once +#include "Luau/CodeGen.h" + #include #include @@ -16,6 +18,7 @@ constexpr uint32_t kCodeAlignment = 32; struct CodeAllocator { CodeAllocator(size_t blockSize, size_t maxTotalSize); + CodeAllocator(size_t blockSize, size_t maxTotalSize, AllocationCallback* allocationCallback, void* allocationCallbackContext); ~CodeAllocator(); // Places data and code into the executable page area @@ -24,7 +27,7 @@ struct CodeAllocator bool allocate( const uint8_t* data, size_t dataSize, const uint8_t* code, size_t codeSize, uint8_t*& result, size_t& resultSize, uint8_t*& resultCodeStart); - // Provided to callbacks + // Provided to unwind info callbacks void* context = nullptr; // Called when new block is created to create and setup the unwinding information for all the code in the block @@ -34,12 +37,16 @@ struct CodeAllocator // Called to destroy unwinding information returned by 'createBlockUnwindInfo' void (*destroyBlockUnwindInfo)(void* context, void* unwindData) = nullptr; +private: // Unwind information can be placed inside the block with some implementation-specific reservations at the beginning // But to simplify block space checks, we limit the max size of all that data static const size_t kMaxReservedDataSize = 256; bool allocateNewBlock(size_t& unwindInfoSize); + uint8_t* allocatePages(size_t size) const; + void freePages(uint8_t* mem, size_t size) const; + // Current block we use for allocations uint8_t* blockPos = nullptr; uint8_t* blockEnd = nullptr; @@ -50,6 +57,9 @@ struct CodeAllocator size_t blockSize = 0; size_t maxTotalSize = 0; + + AllocationCallback* allocationCallback = nullptr; + void* allocationCallbackContext = nullptr; }; } // namespace CodeGen diff --git a/CodeGen/include/Luau/CodeGen.h b/CodeGen/include/Luau/CodeGen.h index 002b4a99..c11f9628 100644 --- a/CodeGen/include/Luau/CodeGen.h +++ b/CodeGen/include/Luau/CodeGen.h @@ -18,12 +18,25 @@ enum CodeGenFlags CodeGen_OnlyNativeModules = 1 << 0, }; +struct CompilationStats +{ + size_t bytecodeSizeBytes = 0; + size_t nativeCodeSizeBytes = 0; + size_t nativeDataSizeBytes = 0; + size_t nativeMetadataSizeBytes = 0; + + uint32_t functionsCompiled = 0; +}; + +using AllocationCallback = void(void* context, void* oldPointer, size_t oldSize, void* newPointer, size_t newSize); + bool isSupported(); +void create(lua_State* L, AllocationCallback* allocationCallback, void* allocationCallbackContext); void create(lua_State* L); // Builds target function and all inner functions -void compile(lua_State* L, int idx, unsigned int flags = 0); +void compile(lua_State* L, int idx, unsigned int flags = 0, CompilationStats* stats = nullptr); using AnnotatorFn = void (*)(void* context, std::string& result, int fid, int instpos); diff --git a/CodeGen/include/Luau/ConditionX64.h b/CodeGen/include/Luau/ConditionX64.h index 78c3fda2..4432641a 100644 --- a/CodeGen/include/Luau/ConditionX64.h +++ b/CodeGen/include/Luau/ConditionX64.h @@ -1,6 +1,8 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #pragma once +#include "Luau/Common.h" + namespace Luau { namespace CodeGen @@ -43,5 +45,68 @@ enum class ConditionX64 : uint8_t Count }; +inline ConditionX64 getReverseCondition(ConditionX64 cond) +{ + switch (cond) + { + case ConditionX64::Overflow: + return ConditionX64::NoOverflow; + case ConditionX64::NoOverflow: + return ConditionX64::Overflow; + case ConditionX64::Carry: + return ConditionX64::NoCarry; + case ConditionX64::NoCarry: + return ConditionX64::Carry; + case ConditionX64::Below: + return ConditionX64::NotBelow; + case ConditionX64::BelowEqual: + return ConditionX64::NotBelowEqual; + case ConditionX64::Above: + return ConditionX64::NotAbove; + case ConditionX64::AboveEqual: + return ConditionX64::NotAboveEqual; + case ConditionX64::Equal: + return ConditionX64::NotEqual; + case ConditionX64::Less: + return ConditionX64::NotLess; + case ConditionX64::LessEqual: + return ConditionX64::NotLessEqual; + case ConditionX64::Greater: + return ConditionX64::NotGreater; + case ConditionX64::GreaterEqual: + return ConditionX64::NotGreaterEqual; + case ConditionX64::NotBelow: + return ConditionX64::Below; + case ConditionX64::NotBelowEqual: + return ConditionX64::BelowEqual; + case ConditionX64::NotAbove: + return ConditionX64::Above; + case ConditionX64::NotAboveEqual: + return ConditionX64::AboveEqual; + case ConditionX64::NotEqual: + return ConditionX64::Equal; + case ConditionX64::NotLess: + return ConditionX64::Less; + case ConditionX64::NotLessEqual: + return ConditionX64::LessEqual; + case ConditionX64::NotGreater: + return ConditionX64::Greater; + case ConditionX64::NotGreaterEqual: + return ConditionX64::GreaterEqual; + case ConditionX64::Zero: + return ConditionX64::NotZero; + case ConditionX64::NotZero: + return ConditionX64::Zero; + case ConditionX64::Parity: + return ConditionX64::NotParity; + case ConditionX64::NotParity: + return ConditionX64::Parity; + case ConditionX64::Count: + LUAU_ASSERT(!"invalid ConditionX64 value"); + } + + return ConditionX64::Count; +} + } // namespace CodeGen } // namespace Luau diff --git a/CodeGen/include/Luau/IrData.h b/CodeGen/include/Luau/IrData.h index 67909b13..1684b478 100644 --- a/CodeGen/include/Luau/IrData.h +++ b/CodeGen/include/Luau/IrData.h @@ -176,7 +176,7 @@ enum class IrCmd : uint8_t CMP_ANY, // Unconditional jump - // A: block/vmexit + // A: block/vmexit/undef JUMP, // Jump if TValue is truthy @@ -369,10 +369,8 @@ enum class IrCmd : uint8_t // Guard against tag mismatch // A, B: tag // C: block/vmexit/undef - // D: bool (finish execution in VM on failure) // In final x64 lowering, A can also be Rn - // When undef is specified instead of a block, execution is aborted on check failure; if D is true, execution is continued in VM interpreter - // instead. + // When undef is specified instead of a block, execution is aborted on check failure CHECK_TAG, // Guard against a falsy tag+value @@ -689,6 +687,10 @@ enum class IrOpKind : uint32_t VmExit, }; +// VmExit uses a special value to indicate that pcpos update should be skipped +// This is only used during type checking at function entry +constexpr uint32_t kVmExitEntryGuardPc = (1u << 28) - 1; + struct IrOp { IrOpKind kind : 4; @@ -851,6 +853,8 @@ struct IrFunction std::vector constants; std::vector bcMapping; + uint32_t entryBlock = 0; + uint32_t entryLocation = 0; // For each instruction, an operand that can be used to recompute the value std::vector valueRestoreOps; @@ -1037,5 +1041,11 @@ inline int vmUpvalueOp(IrOp op) return op.index; } +inline uint32_t vmExitOp(IrOp op) +{ + LUAU_ASSERT(op.kind == IrOpKind::VmExit); + return op.index; +} + } // namespace CodeGen } // namespace Luau diff --git a/CodeGen/src/AssemblyBuilderA64.cpp b/CodeGen/src/AssemblyBuilderA64.cpp index c62d797a..e2e713ee 100644 --- a/CodeGen/src/AssemblyBuilderA64.cpp +++ b/CodeGen/src/AssemblyBuilderA64.cpp @@ -5,6 +5,7 @@ #include "ByteUtils.h" #include +#include namespace Luau { diff --git a/CodeGen/src/AssemblyBuilderX64.cpp b/CodeGen/src/AssemblyBuilderX64.cpp index 2a8bc92e..bd2568d2 100644 --- a/CodeGen/src/AssemblyBuilderX64.cpp +++ b/CodeGen/src/AssemblyBuilderX64.cpp @@ -5,7 +5,6 @@ #include #include -#include namespace Luau { diff --git a/CodeGen/src/CodeAllocator.cpp b/CodeGen/src/CodeAllocator.cpp index 880a3244..fe45b9a4 100644 --- a/CodeGen/src/CodeAllocator.cpp +++ b/CodeGen/src/CodeAllocator.cpp @@ -33,13 +33,17 @@ static size_t alignToPageSize(size_t size) } #if defined(_WIN32) -static uint8_t* allocatePages(size_t size) +static uint8_t* allocatePagesImpl(size_t size) { - return (uint8_t*)VirtualAlloc(nullptr, alignToPageSize(size), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); + LUAU_ASSERT(size == alignToPageSize(size)); + + return (uint8_t*)VirtualAlloc(nullptr, size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); } -static void freePages(uint8_t* mem, size_t size) +static void freePagesImpl(uint8_t* mem, size_t size) { + LUAU_ASSERT(size == alignToPageSize(size)); + if (VirtualFree(mem, 0, MEM_RELEASE) == 0) LUAU_ASSERT(!"failed to deallocate block memory"); } @@ -62,14 +66,22 @@ static void flushInstructionCache(uint8_t* mem, size_t size) #endif } #else -static uint8_t* allocatePages(size_t size) +static uint8_t* allocatePagesImpl(size_t size) { - return (uint8_t*)mmap(nullptr, alignToPageSize(size), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); + LUAU_ASSERT(size == alignToPageSize(size)); + +#ifdef __APPLE__ + return (uint8_t*)mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON | MAP_JIT, -1, 0); +#else + return (uint8_t*)mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); +#endif } -static void freePages(uint8_t* mem, size_t size) +static void freePagesImpl(uint8_t* mem, size_t size) { - if (munmap(mem, alignToPageSize(size)) != 0) + LUAU_ASSERT(size == alignToPageSize(size)); + + if (munmap(mem, size) != 0) LUAU_ASSERT(!"Failed to deallocate block memory"); } @@ -94,8 +106,15 @@ namespace CodeGen { CodeAllocator::CodeAllocator(size_t blockSize, size_t maxTotalSize) - : blockSize(blockSize) - , maxTotalSize(maxTotalSize) + : CodeAllocator(blockSize, maxTotalSize, nullptr, nullptr) +{ +} + +CodeAllocator::CodeAllocator(size_t blockSize, size_t maxTotalSize, AllocationCallback* allocationCallback, void* allocationCallbackContext) + : blockSize{blockSize} + , maxTotalSize{maxTotalSize} + , allocationCallback{allocationCallback} + , allocationCallbackContext{allocationCallbackContext} { LUAU_ASSERT(blockSize > kMaxReservedDataSize); LUAU_ASSERT(maxTotalSize >= blockSize); @@ -207,5 +226,29 @@ bool CodeAllocator::allocateNewBlock(size_t& unwindInfoSize) return true; } +uint8_t* CodeAllocator::allocatePages(size_t size) const +{ + const size_t pageAlignedSize = alignToPageSize(size); + + uint8_t* const mem = allocatePagesImpl(pageAlignedSize); + if (mem == nullptr) + return nullptr; + + if (allocationCallback) + allocationCallback(allocationCallbackContext, nullptr, 0, mem, pageAlignedSize); + + return mem; +} + +void CodeAllocator::freePages(uint8_t* mem, size_t size) const +{ + const size_t pageAlignedSize = alignToPageSize(size); + + if (allocationCallback) + allocationCallback(allocationCallbackContext, mem, pageAlignedSize, nullptr, 0); + + freePagesImpl(mem, pageAlignedSize); +} + } // namespace CodeGen } // namespace Luau diff --git a/CodeGen/src/CodeGen.cpp b/CodeGen/src/CodeGen.cpp index 602130f1..10c3dc79 100644 --- a/CodeGen/src/CodeGen.cpp +++ b/CodeGen/src/CodeGen.cpp @@ -65,7 +65,7 @@ static NativeProto createNativeProto(Proto* proto, const IrBuilder& ir) int sizecode = proto->sizecode; uint32_t* instOffsets = new uint32_t[sizecode]; - uint32_t instTarget = ir.function.bcMapping[0].asmLocation; + uint32_t instTarget = ir.function.entryLocation; for (int i = 0; i < sizecode; i++) { @@ -74,6 +74,9 @@ static NativeProto createNativeProto(Proto* proto, const IrBuilder& ir) instOffsets[i] = ir.function.bcMapping[i].asmLocation - instTarget; } + // Set first instruction offset to 0 so that entering this function still executes any generated entry code. + instOffsets[0] = 0; + // entry target will be relocated when assembly is finalized return {proto, instOffsets, instTarget}; } @@ -202,11 +205,11 @@ bool isSupported() #endif } -void create(lua_State* L) +void create(lua_State* L, AllocationCallback* allocationCallback, void* allocationCallbackContext) { LUAU_ASSERT(isSupported()); - std::unique_ptr data = std::make_unique(); + std::unique_ptr data = std::make_unique(allocationCallback, allocationCallbackContext); #if defined(_WIN32) data->unwindBuilder = std::make_unique(); @@ -239,7 +242,12 @@ void create(lua_State* L) ecb->enter = onEnter; } -void compile(lua_State* L, int idx, unsigned int flags) +void create(lua_State* L) +{ + create(L, nullptr, nullptr); +} + +void compile(lua_State* L, int idx, unsigned int flags, CompilationStats* stats) { LUAU_ASSERT(lua_isLfunction(L, idx)); const TValue* func = luaA_toobject(L, idx); @@ -318,13 +326,28 @@ void compile(lua_State* L, int idx, unsigned int flags) } } - for (NativeProto result : results) + for (const NativeProto& result : results) { // the memory is now managed by VM and will be freed via onDestroyFunction result.p->execdata = result.execdata; result.p->exectarget = uintptr_t(codeStart) + result.exectarget; result.p->codeentry = &kCodeEntryInsn; } + + if (stats != nullptr) + { + for (const NativeProto& result : results) + { + stats->bytecodeSizeBytes += result.p->sizecode * sizeof(Instruction); + + // Account for the native -> bytecode instruction offsets mapping: + stats->nativeMetadataSizeBytes += result.p->sizecode * sizeof(uint32_t); + } + + stats->functionsCompiled += uint32_t(results.size()); + stats->nativeCodeSizeBytes += build.code.size(); + stats->nativeDataSizeBytes += build.data.size(); + } } void setPerfLog(void* context, PerfLogFn logFn) diff --git a/CodeGen/src/CodeGenA64.cpp b/CodeGen/src/CodeGenA64.cpp index 37af59aa..6271e376 100644 --- a/CodeGen/src/CodeGenA64.cpp +++ b/CodeGen/src/CodeGenA64.cpp @@ -24,15 +24,6 @@ struct EntryLocations Label epilogueStart; }; -static void emitClearNativeFlag(AssemblyBuilderA64& build) -{ - build.ldr(x0, mem(rState, offsetof(lua_State, ci))); - build.ldr(w1, mem(x0, offsetof(CallInfo, flags))); - build.mov(w2, ~LUA_CALLINFO_NATIVE); - build.and_(w1, w1, w2); - build.str(w1, mem(x0, offsetof(CallInfo, flags))); -} - static void emitExit(AssemblyBuilderA64& build, bool continueInVm) { build.mov(x0, continueInVm); @@ -40,14 +31,21 @@ static void emitExit(AssemblyBuilderA64& build, bool continueInVm) build.br(x1); } -static void emitUpdatePcAndContinueInVm(AssemblyBuilderA64& build) +static void emitUpdatePcForExit(AssemblyBuilderA64& build) { // x0 = pcpos * sizeof(Instruction) build.add(x0, rCode, x0); build.ldr(x1, mem(rState, offsetof(lua_State, ci))); build.str(x0, mem(x1, offsetof(CallInfo, savedpc))); +} - emitExit(build, /* continueInVm */ true); +static void emitClearNativeFlag(AssemblyBuilderA64& build) +{ + build.ldr(x0, mem(rState, offsetof(lua_State, ci))); + build.ldr(w1, mem(x0, offsetof(CallInfo, flags))); + build.mov(w2, ~LUA_CALLINFO_NATIVE); + build.and_(w1, w1, w2); + build.str(w1, mem(x0, offsetof(CallInfo, flags))); } static void emitInterrupt(AssemblyBuilderA64& build) @@ -305,6 +303,11 @@ bool initHeaderFunctions(NativeState& data) void assembleHelpers(AssemblyBuilderA64& build, ModuleHelpers& helpers) { + if (build.logText) + build.logAppend("; updatePcAndContinueInVm\n"); + build.setLabel(helpers.updatePcAndContinueInVm); + emitUpdatePcForExit(build); + if (build.logText) build.logAppend("; exitContinueVmClearNativeFlag\n"); build.setLabel(helpers.exitContinueVmClearNativeFlag); @@ -320,11 +323,6 @@ void assembleHelpers(AssemblyBuilderA64& build, ModuleHelpers& helpers) build.setLabel(helpers.exitNoContinueVm); emitExit(build, /* continueInVm */ false); - if (build.logText) - build.logAppend("; updatePcAndContinueInVm\n"); - build.setLabel(helpers.updatePcAndContinueInVm); - emitUpdatePcAndContinueInVm(build); - if (build.logText) build.logAppend("; reentry\n"); build.setLabel(helpers.reentry); diff --git a/CodeGen/src/CodeGenLower.h b/CodeGen/src/CodeGenLower.h index a7352bce..e709a89c 100644 --- a/CodeGen/src/CodeGenLower.h +++ b/CodeGen/src/CodeGenLower.h @@ -130,6 +130,11 @@ inline bool lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction& build.setLabel(block.label); + if (blockIndex == function.entryBlock) + { + function.entryLocation = build.getLabelOffset(block.label); + } + IrBlock& nextBlock = getNextBlock(function, sortedBlocks, dummy, i); for (uint32_t index = block.start; index <= block.finish; index++) diff --git a/CodeGen/src/CodeGenX64.cpp b/CodeGen/src/CodeGenX64.cpp index 1e62a4d4..ef655a24 100644 --- a/CodeGen/src/CodeGenX64.cpp +++ b/CodeGen/src/CodeGenX64.cpp @@ -180,6 +180,11 @@ bool initHeaderFunctions(NativeState& data) void assembleHelpers(X64::AssemblyBuilderX64& build, ModuleHelpers& helpers) { + if (build.logText) + build.logAppend("; updatePcAndContinueInVm\n"); + build.setLabel(helpers.updatePcAndContinueInVm); + emitUpdatePcForExit(build); + if (build.logText) build.logAppend("; exitContinueVmClearNativeFlag\n"); build.setLabel(helpers.exitContinueVmClearNativeFlag); @@ -195,11 +200,6 @@ void assembleHelpers(X64::AssemblyBuilderX64& build, ModuleHelpers& helpers) build.setLabel(helpers.exitNoContinueVm); emitExit(build, /* continueInVm */ false); - if (build.logText) - build.logAppend("; updatePcAndContinueInVm\n"); - build.setLabel(helpers.updatePcAndContinueInVm); - emitUpdatePcAndContinueInVm(build); - if (build.logText) build.logAppend("; continueCallInVm\n"); build.setLabel(helpers.continueCallInVm); diff --git a/CodeGen/src/EmitBuiltinsX64.cpp b/CodeGen/src/EmitBuiltinsX64.cpp index efc480e0..87e4e795 100644 --- a/CodeGen/src/EmitBuiltinsX64.cpp +++ b/CodeGen/src/EmitBuiltinsX64.cpp @@ -1,8 +1,9 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "EmitBuiltinsX64.h" -#include "Luau/AssemblyBuilderX64.h" #include "Luau/Bytecode.h" + +#include "Luau/AssemblyBuilderX64.h" #include "Luau/IrCallWrapperX64.h" #include "Luau/IrRegAllocX64.h" diff --git a/CodeGen/src/EmitCommon.h b/CodeGen/src/EmitCommon.h index 214cfd6d..8ac746ee 100644 --- a/CodeGen/src/EmitCommon.h +++ b/CodeGen/src/EmitCommon.h @@ -25,7 +25,7 @@ struct ModuleHelpers Label exitContinueVm; Label exitNoContinueVm; Label exitContinueVmClearNativeFlag; - Label updatePcAndContinueInVm; + Label updatePcAndContinueInVm; // no reentry Label return_; Label interrupt; diff --git a/CodeGen/src/EmitCommonX64.cpp b/CodeGen/src/EmitCommonX64.cpp index e6fae4cc..43568035 100644 --- a/CodeGen/src/EmitCommonX64.cpp +++ b/CodeGen/src/EmitCommonX64.cpp @@ -328,14 +328,12 @@ void emitFallback(IrRegAllocX64& regs, AssemblyBuilderX64& build, int offset, in emitUpdateBase(build); } -void emitUpdatePcAndContinueInVm(AssemblyBuilderX64& build) +void emitUpdatePcForExit(AssemblyBuilderX64& build) { // edx = pcpos * sizeof(Instruction) build.add(rdx, sCode); build.mov(rax, qword[rState + offsetof(lua_State, ci)]); build.mov(qword[rax + offsetof(CallInfo, savedpc)], rdx); - - emitExit(build, /* continueInVm */ true); } void emitContinueCallInVm(AssemblyBuilderX64& build) diff --git a/CodeGen/src/EmitCommonX64.h b/CodeGen/src/EmitCommonX64.h index d8c68da4..888f537f 100644 --- a/CodeGen/src/EmitCommonX64.h +++ b/CodeGen/src/EmitCommonX64.h @@ -180,7 +180,7 @@ void emitUpdateBase(AssemblyBuilderX64& build); void emitInterrupt(AssemblyBuilderX64& build); void emitFallback(IrRegAllocX64& regs, AssemblyBuilderX64& build, int offset, int pcpos); -void emitUpdatePcAndContinueInVm(AssemblyBuilderX64& build); +void emitUpdatePcForExit(AssemblyBuilderX64& build); void emitContinueCallInVm(AssemblyBuilderX64& build); void emitReturn(AssemblyBuilderX64& build, ModuleHelpers& helpers); diff --git a/CodeGen/src/IrAnalysis.cpp b/CodeGen/src/IrAnalysis.cpp index 23f2dd21..fa108af1 100644 --- a/CodeGen/src/IrAnalysis.cpp +++ b/CodeGen/src/IrAnalysis.cpp @@ -186,75 +186,12 @@ void requireVariadicSequence(RegisterSet& sourceRs, const RegisterSet& defRs, ui } } -static RegisterSet computeBlockLiveInRegSet(IrFunction& function, const IrBlock& block, RegisterSet& defRs, std::bitset<256>& capturedRegs) +template +static void visitVmRegDefsUses(T& visitor, IrFunction& function, const IrBlock& block) { - RegisterSet inRs; - - auto def = [&](IrOp op, int offset = 0) { - defRs.regs.set(vmRegOp(op) + offset, true); - }; - - auto use = [&](IrOp op, int offset = 0) { - if (!defRs.regs.test(vmRegOp(op) + offset)) - inRs.regs.set(vmRegOp(op) + offset, true); - }; - - auto maybeDef = [&](IrOp op) { - if (op.kind == IrOpKind::VmReg) - defRs.regs.set(vmRegOp(op), true); - }; - - auto maybeUse = [&](IrOp op) { - if (op.kind == IrOpKind::VmReg) - { - if (!defRs.regs.test(vmRegOp(op))) - inRs.regs.set(vmRegOp(op), true); - } - }; - - auto defVarargs = [&](uint8_t varargStart) { - defRs.varargSeq = true; - defRs.varargStart = varargStart; - }; - - auto useVarargs = [&](uint8_t varargStart) { - requireVariadicSequence(inRs, defRs, varargStart); - - // Variadic sequence has been consumed - defRs.varargSeq = false; - defRs.varargStart = 0; - }; - - auto defRange = [&](int start, int count) { - if (count == -1) - { - defVarargs(start); - } - else - { - for (int i = start; i < start + count; i++) - defRs.regs.set(i, true); - } - }; - - auto useRange = [&](int start, int count) { - if (count == -1) - { - useVarargs(start); - } - else - { - for (int i = start; i < start + count; i++) - { - if (!defRs.regs.test(i)) - inRs.regs.set(i, true); - } - } - }; - for (uint32_t instIdx = block.start; instIdx <= block.finish; instIdx++) { - const IrInst& inst = function.instructions[instIdx]; + IrInst& inst = function.instructions[instIdx]; // For correct analysis, all instruction uses must be handled before handling the definitions switch (inst.cmd) @@ -264,7 +201,7 @@ static RegisterSet computeBlockLiveInRegSet(IrFunction& function, const IrBlock& case IrCmd::LOAD_DOUBLE: case IrCmd::LOAD_INT: case IrCmd::LOAD_TVALUE: - maybeUse(inst.a); // Argument can also be a VmConst + visitor.maybeUse(inst.a); // Argument can also be a VmConst break; case IrCmd::STORE_TAG: case IrCmd::STORE_POINTER: @@ -272,63 +209,63 @@ static RegisterSet computeBlockLiveInRegSet(IrFunction& function, const IrBlock& case IrCmd::STORE_INT: case IrCmd::STORE_VECTOR: case IrCmd::STORE_TVALUE: - maybeDef(inst.a); // Argument can also be a pointer value + visitor.maybeDef(inst.a); // Argument can also be a pointer value break; case IrCmd::CMP_ANY: - use(inst.a); - use(inst.b); + visitor.use(inst.a); + visitor.use(inst.b); break; case IrCmd::JUMP_IF_TRUTHY: case IrCmd::JUMP_IF_FALSY: - use(inst.a); + visitor.use(inst.a); break; // A <- B, C case IrCmd::DO_ARITH: case IrCmd::GET_TABLE: - use(inst.b); - maybeUse(inst.c); // Argument can also be a VmConst + visitor.use(inst.b); + visitor.maybeUse(inst.c); // Argument can also be a VmConst - def(inst.a); + visitor.def(inst.a); break; case IrCmd::SET_TABLE: - use(inst.a); - use(inst.b); - maybeUse(inst.c); // Argument can also be a VmConst + visitor.use(inst.a); + visitor.use(inst.b); + visitor.maybeUse(inst.c); // Argument can also be a VmConst break; // A <- B case IrCmd::DO_LEN: - use(inst.b); + visitor.use(inst.b); - def(inst.a); + visitor.def(inst.a); break; case IrCmd::GET_IMPORT: - def(inst.a); + visitor.def(inst.a); break; case IrCmd::CONCAT: - useRange(vmRegOp(inst.a), function.uintOp(inst.b)); + visitor.useRange(vmRegOp(inst.a), function.uintOp(inst.b)); - defRange(vmRegOp(inst.a), function.uintOp(inst.b)); + visitor.defRange(vmRegOp(inst.a), function.uintOp(inst.b)); break; case IrCmd::GET_UPVALUE: - def(inst.a); + visitor.def(inst.a); break; case IrCmd::SET_UPVALUE: - use(inst.b); + visitor.use(inst.b); break; case IrCmd::PREPARE_FORN: - use(inst.a); - use(inst.b); - use(inst.c); + visitor.use(inst.a); + visitor.use(inst.b); + visitor.use(inst.c); - def(inst.a); - def(inst.b); - def(inst.c); + visitor.def(inst.a); + visitor.def(inst.b); + visitor.def(inst.c); break; case IrCmd::INTERRUPT: break; case IrCmd::BARRIER_OBJ: case IrCmd::BARRIER_TABLE_FORWARD: - use(inst.b); + visitor.use(inst.b); break; case IrCmd::CLOSE_UPVALS: // Closing an upvalue should be counted as a register use (it copies the fresh register value) @@ -336,23 +273,23 @@ static RegisterSet computeBlockLiveInRegSet(IrFunction& function, const IrBlock& // Because we don't plan to optimize captured registers atm, we skip full dataflow analysis for them right now break; case IrCmd::CAPTURE: - maybeUse(inst.a); + visitor.maybeUse(inst.a); if (function.uintOp(inst.b) == 1) - capturedRegs.set(vmRegOp(inst.a), true); + visitor.capture(vmRegOp(inst.a)); break; case IrCmd::SETLIST: - use(inst.b); - useRange(vmRegOp(inst.c), function.intOp(inst.d)); + visitor.use(inst.b); + visitor.useRange(vmRegOp(inst.c), function.intOp(inst.d)); break; case IrCmd::CALL: - use(inst.a); - useRange(vmRegOp(inst.a) + 1, function.intOp(inst.b)); + visitor.use(inst.a); + visitor.useRange(vmRegOp(inst.a) + 1, function.intOp(inst.b)); - defRange(vmRegOp(inst.a), function.intOp(inst.c)); + visitor.defRange(vmRegOp(inst.a), function.intOp(inst.c)); break; case IrCmd::RETURN: - useRange(vmRegOp(inst.a), function.intOp(inst.b)); + visitor.useRange(vmRegOp(inst.a), function.intOp(inst.b)); break; // TODO: FASTCALL is more restrictive than INVOKE_FASTCALL; we should either determine the exact semantics, or rework it @@ -364,89 +301,89 @@ static RegisterSet computeBlockLiveInRegSet(IrFunction& function, const IrBlock& { LUAU_ASSERT(inst.d.kind == IrOpKind::VmReg && vmRegOp(inst.d) == vmRegOp(inst.c) + 1); - useRange(vmRegOp(inst.c), count); + visitor.useRange(vmRegOp(inst.c), count); } else { if (count >= 1) - use(inst.c); + visitor.use(inst.c); if (count >= 2) - maybeUse(inst.d); // Argument can also be a VmConst + visitor.maybeUse(inst.d); // Argument can also be a VmConst } } else { - useVarargs(vmRegOp(inst.c)); + visitor.useVarargs(vmRegOp(inst.c)); } // Multiple return sequences (count == -1) are defined by ADJUST_STACK_TO_REG if (int count = function.intOp(inst.f); count != -1) - defRange(vmRegOp(inst.b), count); + visitor.defRange(vmRegOp(inst.b), count); break; case IrCmd::FORGLOOP: // First register is not used by instruction, we check that it's still 'nil' with CHECK_TAG - use(inst.a, 1); - use(inst.a, 2); + visitor.use(inst.a, 1); + visitor.use(inst.a, 2); - def(inst.a, 2); - defRange(vmRegOp(inst.a) + 3, function.intOp(inst.b)); + visitor.def(inst.a, 2); + visitor.defRange(vmRegOp(inst.a) + 3, function.intOp(inst.b)); break; case IrCmd::FORGLOOP_FALLBACK: - useRange(vmRegOp(inst.a), 3); + visitor.useRange(vmRegOp(inst.a), 3); - def(inst.a, 2); - defRange(vmRegOp(inst.a) + 3, uint8_t(function.intOp(inst.b))); // ignore most significant bit + visitor.def(inst.a, 2); + visitor.defRange(vmRegOp(inst.a) + 3, uint8_t(function.intOp(inst.b))); // ignore most significant bit break; case IrCmd::FORGPREP_XNEXT_FALLBACK: - use(inst.b); + visitor.use(inst.b); break; case IrCmd::FALLBACK_GETGLOBAL: - def(inst.b); + visitor.def(inst.b); break; case IrCmd::FALLBACK_SETGLOBAL: - use(inst.b); + visitor.use(inst.b); break; case IrCmd::FALLBACK_GETTABLEKS: - use(inst.c); + visitor.use(inst.c); - def(inst.b); + visitor.def(inst.b); break; case IrCmd::FALLBACK_SETTABLEKS: - use(inst.b); - use(inst.c); + visitor.use(inst.b); + visitor.use(inst.c); break; case IrCmd::FALLBACK_NAMECALL: - use(inst.c); + visitor.use(inst.c); - defRange(vmRegOp(inst.b), 2); + visitor.defRange(vmRegOp(inst.b), 2); break; case IrCmd::FALLBACK_PREPVARARGS: // No effect on explicitly referenced registers break; case IrCmd::FALLBACK_GETVARARGS: - defRange(vmRegOp(inst.b), function.intOp(inst.c)); + visitor.defRange(vmRegOp(inst.b), function.intOp(inst.c)); break; case IrCmd::FALLBACK_DUPCLOSURE: - def(inst.b); + visitor.def(inst.b); break; case IrCmd::FALLBACK_FORGPREP: - use(inst.b); + visitor.use(inst.b); - defRange(vmRegOp(inst.b), 3); + visitor.defRange(vmRegOp(inst.b), 3); break; case IrCmd::ADJUST_STACK_TO_REG: - defRange(vmRegOp(inst.a), -1); + visitor.defRange(vmRegOp(inst.a), -1); break; case IrCmd::ADJUST_STACK_TO_TOP: // While this can be considered to be a vararg consumer, it is already handled in fastcall instructions break; case IrCmd::GET_TYPEOF: - use(inst.a); + visitor.use(inst.a); break; case IrCmd::FINDUPVAL: - use(inst.a); + visitor.use(inst.a); break; default: @@ -460,8 +397,102 @@ static RegisterSet computeBlockLiveInRegSet(IrFunction& function, const IrBlock& break; } } +} - return inRs; +struct BlockVmRegLiveInComputation +{ + BlockVmRegLiveInComputation(RegisterSet& defRs, std::bitset<256>& capturedRegs) + : defRs(defRs) + , capturedRegs(capturedRegs) + { + } + + RegisterSet& defRs; + std::bitset<256>& capturedRegs; + + RegisterSet inRs; + + void def(IrOp op, int offset = 0) + { + defRs.regs.set(vmRegOp(op) + offset, true); + } + + void use(IrOp op, int offset = 0) + { + if (!defRs.regs.test(vmRegOp(op) + offset)) + inRs.regs.set(vmRegOp(op) + offset, true); + } + + void maybeDef(IrOp op) + { + if (op.kind == IrOpKind::VmReg) + defRs.regs.set(vmRegOp(op), true); + } + + void maybeUse(IrOp op) + { + if (op.kind == IrOpKind::VmReg) + { + if (!defRs.regs.test(vmRegOp(op))) + inRs.regs.set(vmRegOp(op), true); + } + } + + void defVarargs(uint8_t varargStart) + { + defRs.varargSeq = true; + defRs.varargStart = varargStart; + } + + void useVarargs(uint8_t varargStart) + { + requireVariadicSequence(inRs, defRs, varargStart); + + // Variadic sequence has been consumed + defRs.varargSeq = false; + defRs.varargStart = 0; + } + + void defRange(int start, int count) + { + if (count == -1) + { + defVarargs(start); + } + else + { + for (int i = start; i < start + count; i++) + defRs.regs.set(i, true); + } + } + + void useRange(int start, int count) + { + if (count == -1) + { + useVarargs(start); + } + else + { + for (int i = start; i < start + count; i++) + { + if (!defRs.regs.test(i)) + inRs.regs.set(i, true); + } + } + } + + void capture(int reg) + { + capturedRegs.set(reg, true); + } +}; + +static RegisterSet computeBlockLiveInRegSet(IrFunction& function, const IrBlock& block, RegisterSet& defRs, std::bitset<256>& capturedRegs) +{ + BlockVmRegLiveInComputation visitor(defRs, capturedRegs); + visitVmRegDefsUses(visitor, function, block); + return visitor.inRs; } // The algorithm used here is commonly known as backwards data-flow analysis. diff --git a/CodeGen/src/IrBuilder.cpp b/CodeGen/src/IrBuilder.cpp index 52d0a0b5..0dd6f3c6 100644 --- a/CodeGen/src/IrBuilder.cpp +++ b/CodeGen/src/IrBuilder.cpp @@ -1,7 +1,7 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "Luau/IrBuilder.h" -#include "Luau/IrAnalysis.h" +#include "Luau/IrData.h" #include "Luau/IrUtils.h" #include "IrTranslation.h" @@ -22,10 +22,14 @@ IrBuilder::IrBuilder() { } +static bool hasTypedParameters(Proto* proto) +{ + return proto->typeinfo && proto->numparams != 0; +} + static void buildArgumentTypeChecks(IrBuilder& build, Proto* proto) { - if (!proto->typeinfo || proto->numparams == 0) - return; + LUAU_ASSERT(hasTypedParameters(proto)); for (int i = 0; i < proto->numparams; ++i) { @@ -53,31 +57,31 @@ static void buildArgumentTypeChecks(IrBuilder& build, Proto* proto) switch (tag) { case LBC_TYPE_NIL: - build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TNIL), build.undef(), build.constInt(1)); + build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TNIL), build.vmExit(kVmExitEntryGuardPc)); break; case LBC_TYPE_BOOLEAN: - build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TBOOLEAN), build.undef(), build.constInt(1)); + build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TBOOLEAN), build.vmExit(kVmExitEntryGuardPc)); break; case LBC_TYPE_NUMBER: - build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TNUMBER), build.undef(), build.constInt(1)); + build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TNUMBER), build.vmExit(kVmExitEntryGuardPc)); break; case LBC_TYPE_STRING: - build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TSTRING), build.undef(), build.constInt(1)); + build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TSTRING), build.vmExit(kVmExitEntryGuardPc)); break; case LBC_TYPE_TABLE: - build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TTABLE), build.undef(), build.constInt(1)); + build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TTABLE), build.vmExit(kVmExitEntryGuardPc)); break; case LBC_TYPE_FUNCTION: - build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TFUNCTION), build.undef(), build.constInt(1)); + build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TFUNCTION), build.vmExit(kVmExitEntryGuardPc)); break; case LBC_TYPE_THREAD: - build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TTHREAD), build.undef(), build.constInt(1)); + build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TTHREAD), build.vmExit(kVmExitEntryGuardPc)); break; case LBC_TYPE_USERDATA: - build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TUSERDATA), build.undef(), build.constInt(1)); + build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TUSERDATA), build.vmExit(kVmExitEntryGuardPc)); break; case LBC_TYPE_VECTOR: - build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TVECTOR), build.undef(), build.constInt(1)); + build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TVECTOR), build.vmExit(kVmExitEntryGuardPc)); break; } @@ -103,11 +107,28 @@ void IrBuilder::buildFunctionIr(Proto* proto) function.proto = proto; function.variadic = proto->is_vararg != 0; + // Reserve entry block + bool generateTypeChecks = hasTypedParameters(proto); + IrOp entry = generateTypeChecks ? block(IrBlockKind::Internal) : IrOp{}; + // Rebuild original control flow blocks rebuildBytecodeBasicBlocks(proto); function.bcMapping.resize(proto->sizecode, {~0u, ~0u}); + if (generateTypeChecks) + { + beginBlock(entry); + buildArgumentTypeChecks(*this, proto); + inst(IrCmd::JUMP, blockAtInst(0)); + } + else + { + entry = blockAtInst(0); + } + + function.entryBlock = entry.index; + // Translate all instructions to IR inside blocks for (int i = 0; i < proto->sizecode;) { @@ -123,9 +144,6 @@ void IrBuilder::buildFunctionIr(Proto* proto) if (instIndexToBlock[i] != kNoAssociatedBlockIndex) beginBlock(blockAtInst(i)); - if (i == 0) - buildArgumentTypeChecks(*this, proto); - // We skip dead bytecode instructions when they appear after block was already terminated if (!inTerminatedBlock) { diff --git a/CodeGen/src/IrDump.cpp b/CodeGen/src/IrDump.cpp index 12b75bcc..bda36582 100644 --- a/CodeGen/src/IrDump.cpp +++ b/CodeGen/src/IrDump.cpp @@ -402,7 +402,7 @@ void toString(IrToStringContext& ctx, IrOp op) append(ctx.result, "U%d", vmUpvalueOp(op)); break; case IrOpKind::VmExit: - append(ctx.result, "exit(%d)", op.index); + append(ctx.result, "exit(%d)", vmExitOp(op)); break; } } diff --git a/CodeGen/src/IrLoweringA64.cpp b/CodeGen/src/IrLoweringA64.cpp index 16796a28..63d03135 100644 --- a/CodeGen/src/IrLoweringA64.cpp +++ b/CodeGen/src/IrLoweringA64.cpp @@ -1,10 +1,8 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "IrLoweringA64.h" -#include "Luau/CodeGen.h" #include "Luau/DenseHash.h" -#include "Luau/IrAnalysis.h" -#include "Luau/IrDump.h" +#include "Luau/IrData.h" #include "Luau/IrUtils.h" #include "EmitCommonA64.h" @@ -189,7 +187,7 @@ IrLoweringA64::IrLoweringA64(AssemblyBuilderA64& build, ModuleHelpers& helpers, }); } -void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) +void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) { valueTracker.beforeInstLowering(inst); @@ -566,7 +564,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) break; } case IrCmd::JUMP: - if (inst.a.kind == IrOpKind::VmExit) + if (inst.a.kind == IrOpKind::Undef || inst.a.kind == IrOpKind::VmExit) { Label fresh; build.b(getTargetLabel(inst.a, fresh)); @@ -1047,9 +1045,8 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) break; case IrCmd::CHECK_TAG: { - bool continueInVm = (inst.d.kind == IrOpKind::Constant && intOp(inst.d)); Label fresh; // used when guard aborts execution or jumps to a VM exit - Label& fail = continueInVm ? helpers.exitContinueVmClearNativeFlag : getTargetLabel(inst.c, fresh); + Label& fail = getTargetLabel(inst.c, fresh); // To support DebugLuauAbortingChecks, CHECK_TAG with VmReg has to be handled RegisterA64 tag = inst.a.kind == IrOpKind::VmReg ? regs.allocTemp(KindA64::w) : regOp(inst.a); @@ -1066,8 +1063,8 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) build.cmp(tag, tagOp(inst.b)); build.b(ConditionA64::NotEqual, fail); } - if (!continueInVm) - finalizeTargetLabel(inst.c, fresh); + + finalizeTargetLabel(inst.c, fresh); break; } case IrCmd::CHECK_TRUTHY: @@ -1862,7 +1859,10 @@ void IrLoweringA64::finishFunction() for (ExitHandler& handler : exitHandlers) { + LUAU_ASSERT(handler.pcpos != kVmExitEntryGuardPc); + build.setLabel(handler.self); + build.mov(x0, handler.pcpos * sizeof(Instruction)); build.b(helpers.updatePcAndContinueInVm); } @@ -1873,12 +1873,12 @@ bool IrLoweringA64::hasError() const return error || regs.error; } -bool IrLoweringA64::isFallthroughBlock(IrBlock target, IrBlock next) +bool IrLoweringA64::isFallthroughBlock(const IrBlock& target, const IrBlock& next) { return target.start == next.start; } -void IrLoweringA64::jumpOrFallthrough(IrBlock& target, IrBlock& next) +void IrLoweringA64::jumpOrFallthrough(IrBlock& target, const IrBlock& next) { if (!isFallthroughBlock(target, next)) build.b(target.label); @@ -1891,7 +1891,11 @@ Label& IrLoweringA64::getTargetLabel(IrOp op, Label& fresh) if (op.kind == IrOpKind::VmExit) { - if (uint32_t* index = exitHandlerMap.find(op.index)) + // Special exit case that doesn't have to update pcpos + if (vmExitOp(op) == kVmExitEntryGuardPc) + return helpers.exitContinueVmClearNativeFlag; + + if (uint32_t* index = exitHandlerMap.find(vmExitOp(op))) return exitHandlers[*index].self; return fresh; @@ -1906,10 +1910,10 @@ void IrLoweringA64::finalizeTargetLabel(IrOp op, Label& fresh) { emitAbort(build, fresh); } - else if (op.kind == IrOpKind::VmExit && fresh.id != 0) + else if (op.kind == IrOpKind::VmExit && fresh.id != 0 && fresh.id != helpers.exitContinueVmClearNativeFlag.id) { - exitHandlerMap[op.index] = uint32_t(exitHandlers.size()); - exitHandlers.push_back({fresh, op.index}); + exitHandlerMap[vmExitOp(op)] = uint32_t(exitHandlers.size()); + exitHandlers.push_back({fresh, vmExitOp(op)}); } } diff --git a/CodeGen/src/IrLoweringA64.h b/CodeGen/src/IrLoweringA64.h index 72b0da2f..344f18b7 100644 --- a/CodeGen/src/IrLoweringA64.h +++ b/CodeGen/src/IrLoweringA64.h @@ -25,14 +25,14 @@ struct IrLoweringA64 { IrLoweringA64(AssemblyBuilderA64& build, ModuleHelpers& helpers, IrFunction& function); - void lowerInst(IrInst& inst, uint32_t index, IrBlock& next); + void lowerInst(IrInst& inst, uint32_t index, const IrBlock& next); void finishBlock(); void finishFunction(); bool hasError() const; - bool isFallthroughBlock(IrBlock target, IrBlock next); - void jumpOrFallthrough(IrBlock& target, IrBlock& next); + bool isFallthroughBlock(const IrBlock& target, const IrBlock& next); + void jumpOrFallthrough(IrBlock& target, const IrBlock& next); Label& getTargetLabel(IrOp op, Label& fresh); void finalizeTargetLabel(IrOp op, Label& fresh); diff --git a/CodeGen/src/IrLoweringX64.cpp b/CodeGen/src/IrLoweringX64.cpp index 6fe1e771..2ea48404 100644 --- a/CodeGen/src/IrLoweringX64.cpp +++ b/CodeGen/src/IrLoweringX64.cpp @@ -1,19 +1,19 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "IrLoweringX64.h" -#include "Luau/CodeGen.h" #include "Luau/DenseHash.h" -#include "Luau/IrAnalysis.h" -#include "Luau/IrCallWrapperX64.h" -#include "Luau/IrDump.h" +#include "Luau/IrData.h" #include "Luau/IrUtils.h" +#include "Luau/IrCallWrapperX64.h" + #include "EmitBuiltinsX64.h" #include "EmitCommonX64.h" #include "EmitInstructionX64.h" #include "NativeState.h" #include "lstate.h" +#include "lgc.h" namespace Luau { @@ -59,7 +59,7 @@ void IrLoweringX64::storeDoubleAsFloat(OperandX64 dst, IrOp src) build.vmovss(dst, tmp.reg); } -void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) +void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) { regs.currInstIdx = index; @@ -565,24 +565,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) break; } case IrCmd::JUMP: - if (inst.a.kind == IrOpKind::VmExit) - { - if (uint32_t* index = exitHandlerMap.find(inst.a.index)) - { - build.jmp(exitHandlers[*index].self); - } - else - { - Label self; - build.jmp(self); - exitHandlerMap[inst.a.index] = uint32_t(exitHandlers.size()); - exitHandlers.push_back({self, inst.a.index}); - } - } - else - { - jumpOrFallthrough(blockOp(inst.a), next); - } + jumpOrAbortOnUndef(inst.a, next); break; case IrCmd::JUMP_IF_TRUTHY: jumpIfTruthy(build, vmRegOp(inst.a), labelOp(inst.b), labelOp(inst.c)); @@ -975,12 +958,9 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) callPrepareForN(regs, build, vmRegOp(inst.a), vmRegOp(inst.b), vmRegOp(inst.c)); break; case IrCmd::CHECK_TAG: - { - bool continueInVm = (inst.d.kind == IrOpKind::Constant && intOp(inst.d)); build.cmp(memRegTagOp(inst.a), tagOp(inst.b)); - jumpOrAbortOnUndef(ConditionX64::NotEqual, ConditionX64::Equal, inst.c, continueInVm); + jumpOrAbortOnUndef(ConditionX64::NotEqual, inst.c, next); break; - } case IrCmd::CHECK_TRUTHY: { // Constant tags which don't require boolean value check should've been removed in constant folding @@ -992,7 +972,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) { // Fail to fallback on 'nil' (falsy) build.cmp(memRegTagOp(inst.a), LUA_TNIL); - jumpOrAbortOnUndef(ConditionX64::Equal, ConditionX64::NotEqual, inst.c); + jumpOrAbortOnUndef(ConditionX64::Equal, inst.c, next); // Skip value test if it's not a boolean (truthy) build.cmp(memRegTagOp(inst.a), LUA_TBOOLEAN); @@ -1001,7 +981,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) // fail to fallback on 'false' boolean value (falsy) build.cmp(memRegUintOp(inst.b), 0); - jumpOrAbortOnUndef(ConditionX64::Equal, ConditionX64::NotEqual, inst.c); + jumpOrAbortOnUndef(ConditionX64::Equal, inst.c, next); if (inst.a.kind != IrOpKind::Constant) build.setLabel(skip); @@ -1009,11 +989,11 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) } case IrCmd::CHECK_READONLY: build.cmp(byte[regOp(inst.a) + offsetof(Table, readonly)], 0); - jumpOrAbortOnUndef(ConditionX64::NotEqual, ConditionX64::Equal, inst.b); + jumpOrAbortOnUndef(ConditionX64::NotEqual, inst.b, next); break; case IrCmd::CHECK_NO_METATABLE: build.cmp(qword[regOp(inst.a) + offsetof(Table, metatable)], 0); - jumpOrAbortOnUndef(ConditionX64::NotEqual, ConditionX64::Equal, inst.b); + jumpOrAbortOnUndef(ConditionX64::NotEqual, inst.b, next); break; case IrCmd::CHECK_SAFE_ENV: { @@ -1023,7 +1003,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) build.mov(tmp.reg, qword[tmp.reg + offsetof(Closure, env)]); build.cmp(byte[tmp.reg + offsetof(Table, safeenv)], 0); - jumpOrAbortOnUndef(ConditionX64::Equal, ConditionX64::NotEqual, inst.a); + jumpOrAbortOnUndef(ConditionX64::Equal, inst.a, next); break; } case IrCmd::CHECK_ARRAY_SIZE: @@ -1034,7 +1014,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) else LUAU_ASSERT(!"Unsupported instruction form"); - jumpOrAbortOnUndef(ConditionX64::BelowEqual, ConditionX64::NotBelowEqual, inst.c); + jumpOrAbortOnUndef(ConditionX64::BelowEqual, inst.c, next); break; case IrCmd::JUMP_SLOT_MATCH: case IrCmd::CHECK_SLOT_MATCH: @@ -1080,7 +1060,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) build.mov(tmp.reg, dword[regOp(inst.a) + offsetof(LuaNode, key) + kOffsetOfTKeyTagNext]); build.shr(tmp.reg, kTKeyTagBits); - jumpOrAbortOnUndef(ConditionX64::NotZero, ConditionX64::Zero, inst.b); + jumpOrAbortOnUndef(ConditionX64::NotZero, inst.b, next); break; } case IrCmd::INTERRUPT: @@ -1583,7 +1563,10 @@ void IrLoweringX64::finishFunction() for (ExitHandler& handler : exitHandlers) { + LUAU_ASSERT(handler.pcpos != kVmExitEntryGuardPc); + build.setLabel(handler.self); + build.mov(edx, handler.pcpos * sizeof(Instruction)); build.jmp(helpers.updatePcAndContinueInVm); } @@ -1598,50 +1581,81 @@ bool IrLoweringX64::hasError() const return false; } -bool IrLoweringX64::isFallthroughBlock(IrBlock target, IrBlock next) +bool IrLoweringX64::isFallthroughBlock(const IrBlock& target, const IrBlock& next) { return target.start == next.start; } -void IrLoweringX64::jumpOrFallthrough(IrBlock& target, IrBlock& next) +Label& IrLoweringX64::getTargetLabel(IrOp op, Label& fresh) +{ + if (op.kind == IrOpKind::Undef) + return fresh; + + if (op.kind == IrOpKind::VmExit) + { + // Special exit case that doesn't have to update pcpos + if (vmExitOp(op) == kVmExitEntryGuardPc) + return helpers.exitContinueVmClearNativeFlag; + + if (uint32_t* index = exitHandlerMap.find(vmExitOp(op))) + return exitHandlers[*index].self; + + return fresh; + } + + return labelOp(op); +} + +void IrLoweringX64::finalizeTargetLabel(IrOp op, Label& fresh) +{ + if (op.kind == IrOpKind::VmExit && fresh.id != 0 && fresh.id != helpers.exitContinueVmClearNativeFlag.id) + { + exitHandlerMap[vmExitOp(op)] = uint32_t(exitHandlers.size()); + exitHandlers.push_back({fresh, vmExitOp(op)}); + } +} + +void IrLoweringX64::jumpOrFallthrough(IrBlock& target, const IrBlock& next) { if (!isFallthroughBlock(target, next)) build.jmp(target.label); } -void IrLoweringX64::jumpOrAbortOnUndef(ConditionX64 cond, ConditionX64 condInverse, IrOp targetOrUndef, bool continueInVm) +void IrLoweringX64::jumpOrAbortOnUndef(ConditionX64 cond, IrOp target, const IrBlock& next) { - if (targetOrUndef.kind == IrOpKind::Undef) - { - if (continueInVm) - { - build.jcc(cond, helpers.exitContinueVmClearNativeFlag); - return; - } + Label fresh; + Label& label = getTargetLabel(target, fresh); - Label skip; - build.jcc(condInverse, skip); - build.ud2(); - build.setLabel(skip); - } - else if (targetOrUndef.kind == IrOpKind::VmExit) + if (target.kind == IrOpKind::Undef) { - if (uint32_t* index = exitHandlerMap.find(targetOrUndef.index)) + if (cond == ConditionX64::Count) { - build.jcc(cond, exitHandlers[*index].self); + build.ud2(); // Unconditional jump to abort is just an abort } else { - Label self; - build.jcc(cond, self); - exitHandlerMap[targetOrUndef.index] = uint32_t(exitHandlers.size()); - exitHandlers.push_back({self, targetOrUndef.index}); + build.jcc(getReverseCondition(cond), label); + build.ud2(); + build.setLabel(label); } } + else if (cond == ConditionX64::Count) + { + // Unconditional jump can be skipped if it's a fallthrough + if (target.kind == IrOpKind::VmExit || !isFallthroughBlock(blockOp(target), next)) + build.jmp(label); + } else { - build.jcc(cond, labelOp(targetOrUndef)); + build.jcc(cond, label); } + + finalizeTargetLabel(target, fresh); +} + +void IrLoweringX64::jumpOrAbortOnUndef(IrOp target, const IrBlock& next) +{ + jumpOrAbortOnUndef(ConditionX64::Count, target, next); } OperandX64 IrLoweringX64::memRegDoubleOp(IrOp op) diff --git a/CodeGen/src/IrLoweringX64.h b/CodeGen/src/IrLoweringX64.h index a8dab3c9..c32e3e61 100644 --- a/CodeGen/src/IrLoweringX64.h +++ b/CodeGen/src/IrLoweringX64.h @@ -27,15 +27,20 @@ struct IrLoweringX64 { IrLoweringX64(AssemblyBuilderX64& build, ModuleHelpers& helpers, IrFunction& function); - void lowerInst(IrInst& inst, uint32_t index, IrBlock& next); + void lowerInst(IrInst& inst, uint32_t index, const IrBlock& next); void finishBlock(); void finishFunction(); bool hasError() const; - bool isFallthroughBlock(IrBlock target, IrBlock next); - void jumpOrFallthrough(IrBlock& target, IrBlock& next); - void jumpOrAbortOnUndef(ConditionX64 cond, ConditionX64 condInverse, IrOp targetOrUndef, bool continueInVm = false); + bool isFallthroughBlock(const IrBlock& target, const IrBlock& next); + void jumpOrFallthrough(IrBlock& target, const IrBlock& next); + + Label& getTargetLabel(IrOp op, Label& fresh); + void finalizeTargetLabel(IrOp op, Label& fresh); + + void jumpOrAbortOnUndef(ConditionX64 cond, IrOp target, const IrBlock& next); + void jumpOrAbortOnUndef(IrOp target, const IrBlock& next); void storeDoubleAsFloat(OperandX64 dst, IrOp src); diff --git a/CodeGen/src/NativeState.cpp b/CodeGen/src/NativeState.cpp index e7a0c424..5a71345e 100644 --- a/CodeGen/src/NativeState.cpp +++ b/CodeGen/src/NativeState.cpp @@ -23,7 +23,12 @@ constexpr unsigned kBlockSize = 4 * 1024 * 1024; constexpr unsigned kMaxTotalSize = 256 * 1024 * 1024; NativeState::NativeState() - : codeAllocator(kBlockSize, kMaxTotalSize) + : NativeState(nullptr, nullptr) +{ +} + +NativeState::NativeState(AllocationCallback* allocationCallback, void* allocationCallbackContext) + : codeAllocator{kBlockSize, kMaxTotalSize, allocationCallback, allocationCallbackContext} { } diff --git a/CodeGen/src/NativeState.h b/CodeGen/src/NativeState.h index 4aa5c8a2..a9ba7cfd 100644 --- a/CodeGen/src/NativeState.h +++ b/CodeGen/src/NativeState.h @@ -112,6 +112,7 @@ using GateFn = int (*)(lua_State*, Proto*, uintptr_t, NativeContext*); struct NativeState { NativeState(); + NativeState(AllocationCallback* allocationCallback, void* allocationCallbackContext); ~NativeState(); CodeAllocator codeAllocator; diff --git a/CodeGen/src/OptimizeConstProp.cpp b/CodeGen/src/OptimizeConstProp.cpp index a5e20b16..d0ecd7dd 100644 --- a/CodeGen/src/OptimizeConstProp.cpp +++ b/CodeGen/src/OptimizeConstProp.cpp @@ -2,7 +2,7 @@ #include "Luau/OptimizeConstProp.h" #include "Luau/DenseHash.h" -#include "Luau/IrAnalysis.h" +#include "Luau/IrData.h" #include "Luau/IrBuilder.h" #include "Luau/IrUtils.h" diff --git a/Compiler/src/Compiler.cpp b/Compiler/src/Compiler.cpp index 6ae31825..226cd2ee 100644 --- a/Compiler/src/Compiler.cpp +++ b/Compiler/src/Compiler.cpp @@ -26,9 +26,6 @@ LUAU_FASTINTVARIABLE(LuauCompileInlineThreshold, 25) LUAU_FASTINTVARIABLE(LuauCompileInlineThresholdMaxBoost, 300) LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5) -LUAU_FASTFLAGVARIABLE(LuauCompileFunctionType, false) -LUAU_FASTFLAGVARIABLE(LuauCompileNativeComment, false) - LUAU_FASTFLAGVARIABLE(LuauCompileFixBuiltinArity, false) LUAU_FASTFLAGVARIABLE(LuauCompileFoldMathK, false) @@ -209,12 +206,9 @@ struct Compiler setDebugLine(func); - if (FFlag::LuauCompileFunctionType) - { - // note: we move types out of typeMap which is safe because compileFunction is only called once per function - if (std::string* funcType = typeMap.find(func)) - bytecode.setFunctionTypeInfo(std::move(*funcType)); - } + // note: we move types out of typeMap which is safe because compileFunction is only called once per function + if (std::string* funcType = typeMap.find(func)) + bytecode.setFunctionTypeInfo(std::move(*funcType)); if (func->vararg) bytecode.emitABC(LOP_PREPVARARGS, uint8_t(self + func->args.size), 0, 0); @@ -3620,9 +3614,8 @@ struct Compiler { node->body->visit(this); - if (FFlag::LuauCompileFunctionType) - for (AstLocal* arg : node->args) - hasTypes |= arg->annotation != nullptr; + for (AstLocal* arg : node->args) + hasTypes |= arg->annotation != nullptr; // this makes sure all functions that are used when compiling this one have been already added to the vector functions.push_back(node); @@ -3863,7 +3856,7 @@ void compileOrThrow(BytecodeBuilder& bytecode, const ParseResult& parseResult, c if (hc.header && hc.content.compare(0, 9, "optimize ") == 0) options.optimizationLevel = std::max(0, std::min(2, atoi(hc.content.c_str() + 9))); - if (FFlag::LuauCompileNativeComment && hc.header && hc.content == "native") + if (hc.header && hc.content == "native") { mainFlags |= LPF_NATIVE_MODULE; options.optimizationLevel = 2; // note: this might be removed in the future in favor of --!optimize @@ -3916,7 +3909,7 @@ void compileOrThrow(BytecodeBuilder& bytecode, const ParseResult& parseResult, c root->visit(&functionVisitor); // computes type information for all functions based on type annotations - if (FFlag::LuauCompileFunctionType && functionVisitor.hasTypes) + if (functionVisitor.hasTypes) buildTypeMap(compiler.typeMap, root, options.vectorType); for (AstExprFunction* expr : functions) diff --git a/Compiler/src/Types.cpp b/Compiler/src/Types.cpp index e85cc92c..8ac74d02 100644 --- a/Compiler/src/Types.cpp +++ b/Compiler/src/Types.cpp @@ -1,8 +1,8 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details -#include "Luau/BytecodeBuilder.h" - #include "Types.h" +#include "Luau/BytecodeBuilder.h" + namespace Luau { diff --git a/Compiler/src/Types.h b/Compiler/src/Types.h index cad55ab5..62f9e916 100644 --- a/Compiler/src/Types.h +++ b/Compiler/src/Types.h @@ -2,6 +2,7 @@ #pragma once #include "Luau/Ast.h" +#include "Luau/DenseHash.h" #include diff --git a/Sources.cmake b/Sources.cmake index 88f3bb01..8dcd2431 100644 --- a/Sources.cmake +++ b/Sources.cmake @@ -163,6 +163,7 @@ target_sources(Luau.Analysis PRIVATE Analysis/include/Luau/IostreamHelpers.h Analysis/include/Luau/JsonEmitter.h Analysis/include/Luau/Linter.h + Analysis/include/Luau/LinterConfig.h Analysis/include/Luau/LValue.h Analysis/include/Luau/Metamethods.h Analysis/include/Luau/Module.h @@ -221,6 +222,7 @@ target_sources(Luau.Analysis PRIVATE Analysis/src/IostreamHelpers.cpp Analysis/src/JsonEmitter.cpp Analysis/src/Linter.cpp + Analysis/src/LinterConfig.cpp Analysis/src/LValue.cpp Analysis/src/Module.cpp Analysis/src/Normalize.cpp diff --git a/VM/src/lmathlib.cpp b/VM/src/lmathlib.cpp index fe7b1a12..254fc9db 100644 --- a/VM/src/lmathlib.cpp +++ b/VM/src/lmathlib.cpp @@ -7,8 +7,6 @@ #include #include -LUAU_FASTFLAGVARIABLE(LuauFasterNoise, false) - #undef PI #define PI (3.14159265358979323846) #define RADIANS_PER_DEGREE (PI / 180.0) @@ -277,27 +275,6 @@ static int math_randomseed(lua_State* L) return 0; } -// TODO: Delete with LuauFasterNoise -static const unsigned char kPerlin[512] = {151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, - 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, - 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, - 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, - 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, - 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, - 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, - 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24, - 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180, - - 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, - 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, - 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54, 65, - 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, - 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213, - 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, - 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, - 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, - 156, 180}; - static const unsigned char kPerlinHash[257] = {151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, @@ -321,61 +298,14 @@ static float perlin_lerp(float t, float a, float b) return a + t * (b - a); } -static float grad(unsigned char hash, float x, float y, float z) -{ - LUAU_ASSERT(!FFlag::LuauFasterNoise); - unsigned char h = hash & 15; - float u = (h < 8) ? x : y; - float v = (h < 4) ? y : (h == 12 || h == 14) ? x : z; - - return (h & 1 ? -u : u) + (h & 2 ? -v : v); -} - static float perlin_grad(int hash, float x, float y, float z) { const float* g = kPerlinGrad[hash & 15]; return g[0] * x + g[1] * y + g[2] * z; } -static float perlin_dep(float x, float y, float z) -{ - LUAU_ASSERT(!FFlag::LuauFasterNoise); - float xflr = floorf(x); - float yflr = floorf(y); - float zflr = floorf(z); - - int xi = int(xflr) & 255; - int yi = int(yflr) & 255; - int zi = int(zflr) & 255; - - float xf = x - xflr; - float yf = y - yflr; - float zf = z - zflr; - - float u = perlin_fade(xf); - float v = perlin_fade(yf); - float w = perlin_fade(zf); - - const unsigned char* p = kPerlin; - - int a = p[xi] + yi; - int aa = p[a] + zi; - int ab = p[a + 1] + zi; - - int b = p[xi + 1] + yi; - int ba = p[b] + zi; - int bb = p[b + 1] + zi; - - return perlin_lerp(w, - perlin_lerp(v, perlin_lerp(u, grad(p[aa], xf, yf, zf), grad(p[ba], xf - 1, yf, zf)), - perlin_lerp(u, grad(p[ab], xf, yf - 1, zf), grad(p[bb], xf - 1, yf - 1, zf))), - perlin_lerp(v, perlin_lerp(u, grad(p[aa + 1], xf, yf, zf - 1), grad(p[ba + 1], xf - 1, yf, zf - 1)), - perlin_lerp(u, grad(p[ab + 1], xf, yf - 1, zf - 1), grad(p[bb + 1], xf - 1, yf - 1, zf - 1)))); -} - static float perlin(float x, float y, float z) { - LUAU_ASSERT(FFlag::LuauFasterNoise); float xflr = floorf(x); float yflr = floorf(y); float zflr = floorf(z); @@ -412,33 +342,19 @@ static float perlin(float x, float y, float z) static int math_noise(lua_State* L) { - if (FFlag::LuauFasterNoise) - { - int nx, ny, nz; - double x = lua_tonumberx(L, 1, &nx); - double y = lua_tonumberx(L, 2, &ny); - double z = lua_tonumberx(L, 3, &nz); + int nx, ny, nz; + double x = lua_tonumberx(L, 1, &nx); + double y = lua_tonumberx(L, 2, &ny); + double z = lua_tonumberx(L, 3, &nz); - luaL_argexpected(L, nx, 1, "number"); - luaL_argexpected(L, ny || lua_isnoneornil(L, 2), 2, "number"); - luaL_argexpected(L, nz || lua_isnoneornil(L, 3), 3, "number"); + luaL_argexpected(L, nx, 1, "number"); + luaL_argexpected(L, ny || lua_isnoneornil(L, 2), 2, "number"); + luaL_argexpected(L, nz || lua_isnoneornil(L, 3), 3, "number"); - double r = perlin((float)x, (float)y, (float)z); + double r = perlin((float)x, (float)y, (float)z); - lua_pushnumber(L, r); - return 1; - } - else - { - double x = luaL_checknumber(L, 1); - double y = luaL_optnumber(L, 2, 0.0); - double z = luaL_optnumber(L, 3, 0.0); - - double r = perlin_dep((float)x, (float)y, (float)z); - - lua_pushnumber(L, r); - return 1; - } + lua_pushnumber(L, r); + return 1; } static int math_clamp(lua_State* L) diff --git a/VM/src/lstrlib.cpp b/VM/src/lstrlib.cpp index d9ce71f9..90b30ead 100644 --- a/VM/src/lstrlib.cpp +++ b/VM/src/lstrlib.cpp @@ -9,6 +9,7 @@ #include LUAU_FASTFLAGVARIABLE(LuauFasterInterp, false) +LUAU_FASTFLAGVARIABLE(LuauFasterFormatS, false) // macro to `unsign' a character #define uchar(c) ((unsigned char)(c)) @@ -1028,18 +1029,35 @@ static int str_format(lua_State* L) { size_t l; const char* s = luaL_checklstring(L, arg, &l); - if (!strchr(form, '.') && l >= 100) + if (FFlag::LuauFasterFormatS) { - /* no precision and string is too long to be formatted; - keep original string */ - lua_pushvalue(L, arg); - luaL_addvalue(&b); - continue; // skip the `luaL_addlstring' at the end + // no precision and string is too long to be formatted, or no format necessary to begin with + if (form[2] == '\0' || (!strchr(form, '.') && l >= 100)) + { + luaL_addlstring(&b, s, l, -1); + continue; // skip the `luaL_addlstring' at the end + } + else + { + snprintf(buff, sizeof(buff), form, s); + break; + } } else { - snprintf(buff, sizeof(buff), form, s); - break; + if (!strchr(form, '.') && l >= 100) + { + /* no precision and string is too long to be formatted; + keep original string */ + lua_pushvalue(L, arg); + luaL_addvalue(&b); + continue; // skip the `luaL_addlstring' at the end + } + else + { + snprintf(buff, sizeof(buff), form, s); + break; + } } } case '*': diff --git a/VM/src/ltablib.cpp b/VM/src/ltablib.cpp index fbf03deb..cc44ee19 100644 --- a/VM/src/ltablib.cpp +++ b/VM/src/ltablib.cpp @@ -10,8 +10,6 @@ #include "ldebug.h" #include "lvm.h" -LUAU_FASTFLAGVARIABLE(LuauFasterTableConcat, false) - static int foreachi(lua_State* L) { luaL_checktype(L, 1, LUA_TTABLE); @@ -222,7 +220,7 @@ static int tmove(lua_State* L) static void addfield(lua_State* L, luaL_Buffer* b, int i) { int tt = lua_rawgeti(L, 1, i); - if (FFlag::LuauFasterTableConcat ? (tt != LUA_TSTRING && tt != LUA_TNUMBER) : !lua_isstring(L, -1)) + if (tt != LUA_TSTRING && tt != LUA_TNUMBER) luaL_error(L, "invalid value (%s) at index %d in table for 'concat'", luaL_typename(L, -1), i); luaL_addvalue(b); } diff --git a/VM/src/lvmload.cpp b/VM/src/lvmload.cpp index 7a065383..365aa5d3 100644 --- a/VM/src/lvmload.cpp +++ b/VM/src/lvmload.cpp @@ -13,8 +13,6 @@ #include -LUAU_FASTFLAGVARIABLE(LuauLoadCheckGC, false) - // TODO: RAII deallocation doesn't work for longjmp builds if a memory error happens template struct TempBuffer @@ -181,8 +179,7 @@ int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size } // we will allocate a fair amount of memory so check GC before we do - if (FFlag::LuauLoadCheckGC) - luaC_checkGC(L); + luaC_checkGC(L); // pause GC for the duration of deserialization - some objects we're creating aren't rooted // TODO: if an allocation error happens mid-load, we do not unpause GC! diff --git a/bench/micro_tests/test_StringInterp.lua b/bench/micro_tests/test_StringInterp.lua index 33d5ecea..1e7ccbc7 100644 --- a/bench/micro_tests/test_StringInterp.lua +++ b/bench/micro_tests/test_StringInterp.lua @@ -36,6 +36,13 @@ bench.runCode(function() end end, "interp: interp number") +bench.runCode(function() + local ok = "hello!" + for j=1,1e6 do + local _ = string.format("j=%s", ok) + end +end, "interp: %s format") + bench.runCode(function() local ok = "hello!" for j=1,1e6 do diff --git a/tests/CodeAllocator.test.cpp b/tests/CodeAllocator.test.cpp index b1416736..b44ca6d5 100644 --- a/tests/CodeAllocator.test.cpp +++ b/tests/CodeAllocator.test.cpp @@ -47,6 +47,56 @@ TEST_CASE("CodeAllocation") CHECK(nativeEntry == nativeData + kCodeAlignment); } +TEST_CASE("CodeAllocationCallbacks") +{ + struct AllocationData + { + size_t bytesAllocated = 0; + size_t bytesFreed = 0; + }; + + AllocationData allocationData{}; + + const auto allocationCallback = [](void* context, void* oldPointer, size_t oldSize, void* newPointer, size_t newSize) + { + AllocationData& allocationData = *static_cast(context); + if (oldPointer != nullptr) + { + CHECK(oldSize != 0); + + allocationData.bytesFreed += oldSize; + } + + if (newPointer != nullptr) + { + CHECK(newSize != 0); + + allocationData.bytesAllocated += newSize; + } + }; + + const size_t blockSize = 1024 * 1024; + const size_t maxTotalSize = 1024 * 1024; + + { + CodeAllocator allocator(blockSize, maxTotalSize, allocationCallback, &allocationData); + + uint8_t* nativeData = nullptr; + size_t sizeNativeData = 0; + uint8_t* nativeEntry = nullptr; + + std::vector code; + code.resize(128); + + REQUIRE(allocator.allocate(nullptr, 0, code.data(), code.size(), nativeData, sizeNativeData, nativeEntry)); + CHECK(allocationData.bytesAllocated == blockSize); + CHECK(allocationData.bytesFreed == 0); + } + + CHECK(allocationData.bytesAllocated == blockSize); + CHECK(allocationData.bytesFreed == blockSize); +} + TEST_CASE("CodeAllocationFailure") { size_t blockSize = 3000; diff --git a/tests/Compiler.test.cpp b/tests/Compiler.test.cpp index fa5ab7e6..5e58865b 100644 --- a/tests/Compiler.test.cpp +++ b/tests/Compiler.test.cpp @@ -7100,8 +7100,6 @@ L1: RETURN R3 1 TEST_CASE("EncodedTypeTable") { - ScopedFastFlag sff("LuauCompileFunctionType", true); - CHECK_EQ("\n" + compileTypeTable(R"( function myfunc(test: string, num: number) print(test) @@ -7153,8 +7151,6 @@ Str:test(234) TEST_CASE("HostTypesAreUserdata") { - ScopedFastFlag sff("LuauCompileFunctionType", true); - CHECK_EQ("\n" + compileTypeTable(R"( function myfunc(test: string, num: number) print(test) @@ -7181,8 +7177,6 @@ end TEST_CASE("HostTypesVector") { - ScopedFastFlag sff("LuauCompileFunctionType", true); - CHECK_EQ("\n" + compileTypeTable(R"( function myfunc(test: Instance, pos: Vector3) end @@ -7206,8 +7200,6 @@ end TEST_CASE("TypeAliasScoping") { - ScopedFastFlag sff("LuauCompileFunctionType", true); - CHECK_EQ("\n" + compileTypeTable(R"( do type Part = number @@ -7242,8 +7234,6 @@ type Instance = string TEST_CASE("TypeAliasResolve") { - ScopedFastFlag sff("LuauCompileFunctionType", true); - CHECK_EQ("\n" + compileTypeTable(R"( type Foo1 = number type Foo2 = { number } diff --git a/tests/Conformance.test.cpp b/tests/Conformance.test.cpp index f4a74b66..97d4e031 100644 --- a/tests/Conformance.test.cpp +++ b/tests/Conformance.test.cpp @@ -266,6 +266,12 @@ static void* limitedRealloc(void* ud, void* ptr, size_t osize, size_t nsize) TEST_SUITE_BEGIN("Conformance"); +TEST_CASE("CodegenSupported") +{ + if (codegen && !luau_codegen_supported()) + MESSAGE("Native code generation is not supported by the current configuration and will be disabled"); +} + TEST_CASE("Assert") { runConformance("assert.lua"); @@ -1726,7 +1732,6 @@ TEST_CASE("Native") TEST_CASE("NativeTypeAnnotations") { ScopedFastFlag bytecodeVersion4("BytecodeVersion4", true); - ScopedFastFlag luauCompileFunctionType("LuauCompileFunctionType", true); // This tests requires code to run natively, otherwise all 'is_native' checks will fail if (!codegen || !luau_codegen_supported()) diff --git a/tests/Differ.test.cpp b/tests/Differ.test.cpp index c2b09bbd..84a383c1 100644 --- a/tests/Differ.test.cpp +++ b/tests/Differ.test.cpp @@ -1528,4 +1528,43 @@ TEST_CASE_FIXTURE(DifferFixture, "equal_generictp_cyclic") compareTypesEq("foo", "almostFoo"); } +TEST_CASE_FIXTURE(DifferFixture, "symbol_forward") +{ + CheckResult result = check(R"( + local foo = 5 + local almostFoo = "five" + )"); + LUAU_REQUIRE_NO_ERRORS(result); + + INFO(Luau::toString(requireType("foo"))); + INFO(Luau::toString(requireType("almostFoo"))); + + compareTypesNe("foo", "almostFoo", + R"(DiffError: these two types are not equal because the left type at foo has type number, while the right type at almostFoo has type string)", + true); +} + +TEST_CASE_FIXTURE(DifferFixture, "newlines") +{ + CheckResult result = check(R"( + local foo = 5 + local almostFoo = "five" + )"); + LUAU_REQUIRE_NO_ERRORS(result); + + INFO(Luau::toString(requireType("foo"))); + INFO(Luau::toString(requireType("almostFoo"))); + + compareTypesNe("foo", "almostFoo", + R"(DiffError: these two types are not equal because the left type at + foo +has type + number, +while the right type at + almostFoo +has type + string)", + true, true); +} + TEST_SUITE_END(); diff --git a/tests/Fixture.h b/tests/Fixture.h index a9c5d9b0..8d997140 100644 --- a/tests/Fixture.h +++ b/tests/Fixture.h @@ -160,14 +160,37 @@ void createSomeClasses(Frontend* frontend); template struct DifferFixtureGeneric : BaseFixture { - void compareNe(TypeId left, TypeId right, const std::string& expectedMessage) + std::string normalizeWhitespace(std::string msg) + { + std::string normalizedMsg = ""; + bool wasWhitespace = true; + for (char c : msg) + { + bool isWhitespace = c == ' ' || c == '\n'; + if (wasWhitespace && isWhitespace) + continue; + normalizedMsg += isWhitespace ? ' ' : c; + wasWhitespace = isWhitespace; + } + if (wasWhitespace) + normalizedMsg.pop_back(); + return normalizedMsg; + } + + void compareNe(TypeId left, TypeId right, const std::string& expectedMessage, bool multiLine) + { + compareNe(left, std::nullopt, right, std::nullopt, expectedMessage, multiLine); + } + + void compareNe(TypeId left, std::optional symbolLeft, TypeId right, std::optional symbolRight, + const std::string& expectedMessage, bool multiLine) { std::string diffMessage; try { - DifferResult diffRes = diff(left, right); + DifferResult diffRes = diffWithSymbols(left, right, symbolLeft, symbolRight); REQUIRE_MESSAGE(diffRes.diffError.has_value(), "Differ did not report type error, even though types are unequal"); - diffMessage = diffRes.diffError->toString(); + diffMessage = diffRes.diffError->toString(multiLine); } catch (const InternalCompilerError& e) { @@ -176,9 +199,19 @@ struct DifferFixtureGeneric : BaseFixture CHECK_EQ(expectedMessage, diffMessage); } - void compareTypesNe(const std::string& leftSymbol, const std::string& rightSymbol, const std::string& expectedMessage) + void compareTypesNe(const std::string& leftSymbol, const std::string& rightSymbol, const std::string& expectedMessage, bool forwardSymbol = false, + bool multiLine = false) { - compareNe(BaseFixture::requireType(leftSymbol), BaseFixture::requireType(rightSymbol), expectedMessage); + if (forwardSymbol) + { + compareNe( + BaseFixture::requireType(leftSymbol), leftSymbol, BaseFixture::requireType(rightSymbol), rightSymbol, expectedMessage, multiLine); + } + else + { + compareNe( + BaseFixture::requireType(leftSymbol), std::nullopt, BaseFixture::requireType(rightSymbol), std::nullopt, expectedMessage, multiLine); + } } void compareEq(TypeId left, TypeId right) diff --git a/tests/Linter.test.cpp b/tests/Linter.test.cpp index e906c224..6c728da9 100644 --- a/tests/Linter.test.cpp +++ b/tests/Linter.test.cpp @@ -1657,8 +1657,6 @@ _ = (math.random() < 0.5 and false) or 42 -- currently ignored TEST_CASE_FIXTURE(Fixture, "WrongComment") { - ScopedFastFlag sff("LuauLintNativeComment", true); - LintResult result = lint(R"( --!strict --!struct diff --git a/tests/Module.test.cpp b/tests/Module.test.cpp index 5b1849a7..54a96861 100644 --- a/tests/Module.test.cpp +++ b/tests/Module.test.cpp @@ -1,5 +1,6 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "Luau/Clone.h" +#include "Luau/Common.h" #include "Luau/Module.h" #include "Luau/Scope.h" #include "Luau/RecursionCounter.h" @@ -7,11 +8,13 @@ #include "Fixture.h" +#include "ScopedFlags.h" #include "doctest.h" using namespace Luau; LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution); +LUAU_FASTFLAG(LuauStacklessTypeClone) TEST_SUITE_BEGIN("ModuleTests"); @@ -78,7 +81,7 @@ TEST_CASE_FIXTURE(Fixture, "is_within_comment_parse_result") TEST_CASE_FIXTURE(Fixture, "dont_clone_persistent_primitive") { TypeArena dest; - CloneState cloneState; + CloneState cloneState{builtinTypes}; // numberType is persistent. We leave it as-is. TypeId newNumber = clone(builtinTypes->numberType, dest, cloneState); @@ -88,7 +91,7 @@ TEST_CASE_FIXTURE(Fixture, "dont_clone_persistent_primitive") TEST_CASE_FIXTURE(Fixture, "deepClone_non_persistent_primitive") { TypeArena dest; - CloneState cloneState; + CloneState cloneState{builtinTypes}; // Create a new number type that isn't persistent unfreeze(frontend.globals.globalTypes); @@ -129,7 +132,7 @@ TEST_CASE_FIXTURE(Fixture, "deepClone_cyclic_table") TypeId ty = requireType("Cyclic"); TypeArena dest; - CloneState cloneState; + CloneState cloneState{builtinTypes}; TypeId cloneTy = clone(ty, dest, cloneState); TableType* ttv = getMutable(cloneTy); @@ -147,8 +150,8 @@ TEST_CASE_FIXTURE(Fixture, "deepClone_cyclic_table") REQUIRE(methodReturnType); CHECK_MESSAGE(methodReturnType == cloneTy, toString(methodType, {true}) << " should be pointer identical to " << toString(cloneTy, {true})); - CHECK_EQ(2, dest.typePacks.size()); // one for the function args, and another for its return type - CHECK_EQ(2, dest.types.size()); // One table and one function + CHECK_EQ(FFlag::LuauStacklessTypeClone ? 1 : 2, dest.typePacks.size()); // one for the function args, and another for its return type + CHECK_EQ(2, dest.types.size()); // One table and one function } TEST_CASE_FIXTURE(Fixture, "deepClone_cyclic_table_2") @@ -165,7 +168,7 @@ TEST_CASE_FIXTURE(Fixture, "deepClone_cyclic_table_2") TypeArena dest; - CloneState cloneState; + CloneState cloneState{builtinTypes}; TypeId cloneTy = clone(tableTy, dest, cloneState); TableType* ctt = getMutable(cloneTy); REQUIRE(ctt); @@ -209,7 +212,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "builtin_types_point_into_globalTypes_arena") TEST_CASE_FIXTURE(Fixture, "deepClone_union") { TypeArena dest; - CloneState cloneState; + CloneState cloneState{builtinTypes}; unfreeze(frontend.globals.globalTypes); TypeId oldUnion = frontend.globals.globalTypes.addType(UnionType{{builtinTypes->numberType, builtinTypes->stringType}}); @@ -224,7 +227,7 @@ TEST_CASE_FIXTURE(Fixture, "deepClone_union") TEST_CASE_FIXTURE(Fixture, "deepClone_intersection") { TypeArena dest; - CloneState cloneState; + CloneState cloneState{builtinTypes}; unfreeze(frontend.globals.globalTypes); TypeId oldIntersection = frontend.globals.globalTypes.addType(IntersectionType{{builtinTypes->numberType, builtinTypes->stringType}}); @@ -251,7 +254,7 @@ TEST_CASE_FIXTURE(Fixture, "clone_class") std::nullopt, &exampleMetaClass, {}, {}, "Test"}}; TypeArena dest; - CloneState cloneState; + CloneState cloneState{builtinTypes}; TypeId cloned = clone(&exampleClass, dest, cloneState); const ClassType* ctv = get(cloned); @@ -274,12 +277,12 @@ TEST_CASE_FIXTURE(Fixture, "clone_free_types") TypePackVar freeTp(FreeTypePack{TypeLevel{}}); TypeArena dest; - CloneState cloneState; + CloneState cloneState{builtinTypes}; TypeId clonedTy = clone(freeTy, dest, cloneState); CHECK(get(clonedTy)); - cloneState = {}; + cloneState = {builtinTypes}; TypePackId clonedTp = clone(&freeTp, dest, cloneState); CHECK(get(clonedTp)); } @@ -291,7 +294,7 @@ TEST_CASE_FIXTURE(Fixture, "clone_free_tables") ttv->state = TableState::Free; TypeArena dest; - CloneState cloneState; + CloneState cloneState{builtinTypes}; TypeId cloned = clone(&tableTy, dest, cloneState); const TableType* clonedTtv = get(cloned); @@ -332,6 +335,8 @@ TEST_CASE_FIXTURE(Fixture, "clone_recursion_limit") #else int limit = 400; #endif + + ScopedFastFlag sff{"LuauStacklessTypeClone", false}; ScopedFastInt luauTypeCloneRecursionLimit{"LuauTypeCloneRecursionLimit", limit}; TypeArena src; @@ -348,11 +353,39 @@ TEST_CASE_FIXTURE(Fixture, "clone_recursion_limit") } TypeArena dest; - CloneState cloneState; + CloneState cloneState{builtinTypes}; CHECK_THROWS_AS(clone(table, dest, cloneState), RecursionLimitException); } +TEST_CASE_FIXTURE(Fixture, "clone_iteration_limit") +{ + ScopedFastFlag sff{"LuauStacklessTypeClone", true}; + ScopedFastInt sfi{"LuauTypeCloneIterationLimit", 500}; + + TypeArena src; + + TypeId table = src.addType(TableType{}); + TypeId nested = table; + + for (int i = 0; i < 2500; i++) + { + TableType* ttv = getMutable(nested); + ttv->props["a"].setType(src.addType(TableType{})); + nested = ttv->props["a"].type(); + } + + TypeArena dest; + CloneState cloneState{builtinTypes}; + + TypeId ty = clone(table, dest, cloneState); + CHECK(get(ty)); + + // Cloning it again is an important test. + TypeId ty2 = clone(table, dest, cloneState); + CHECK(get(ty2)); +} + // Unions should never be cyclic, but we should clone them correctly even if // they are. TEST_CASE_FIXTURE(Fixture, "clone_cyclic_union") @@ -368,7 +401,7 @@ TEST_CASE_FIXTURE(Fixture, "clone_cyclic_union") uu->options.push_back(u); TypeArena dest; - CloneState cloneState; + CloneState cloneState{builtinTypes}; TypeId cloned = clone(u, dest, cloneState); REQUIRE(cloned); diff --git a/tests/ToString.test.cpp b/tests/ToString.test.cpp index d4a25f80..b0592b4c 100644 --- a/tests/ToString.test.cpp +++ b/tests/ToString.test.cpp @@ -831,4 +831,39 @@ TEST_CASE_FIXTURE(Fixture, "tostring_unsee_ttv_if_array") CHECK(toString(requireType("y")) == "({string}, {string}) -> ()"); } +TEST_CASE_FIXTURE(Fixture, "tostring_error_mismatch") +{ + ScopedFastFlag sff[] = { + {"LuauIndentTypeMismatch", true}, + }; + + ScopedFastInt sfi[] = { + {"LuauIndentTypeMismatchMaxTypeLength", 10}, + }; + + CheckResult result = check(R"( +--!strict + function f1() : {a : number, b : string, c : { d : number}} + return { a = 1, b = "a", c = {d = "a"}} + end + +)"); + std::string expected = R"(Type + '{ a: number, b: string, c: { d: string } }' +could not be converted into + '{| a: number, b: string, c: {| d: number |} |}' +caused by: + Property 'c' is not compatible. +Type + '{ d: string }' +could not be converted into + '{| d: number |}' +caused by: + Property 'd' is not compatible. +Type 'string' could not be converted into 'number' in an invariant context)"; + std::string actual = toString(result.errors[0]); + + LUAU_REQUIRE_ERROR_COUNT(1, result); + CHECK(expected == actual); +} TEST_SUITE_END(); diff --git a/tests/TypeInfer.aliases.test.cpp b/tests/TypeInfer.aliases.test.cpp index 55f4caec..bebc8942 100644 --- a/tests/TypeInfer.aliases.test.cpp +++ b/tests/TypeInfer.aliases.test.cpp @@ -187,8 +187,11 @@ TEST_CASE_FIXTURE(Fixture, "mutually_recursive_aliases") TEST_CASE_FIXTURE(Fixture, "generic_aliases") { - ScopedFastFlag sff_DebugLuauDeferredConstraintResolution{"DebugLuauDeferredConstraintResolution", true}; - + ScopedFastFlag sff[] = { + {"DebugLuauDeferredConstraintResolution", true}, + {"LuauIndentTypeMismatch", true}, + }; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( type T = { v: a } local x: T = { v = 123 } @@ -197,18 +200,22 @@ TEST_CASE_FIXTURE(Fixture, "generic_aliases") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - - const char* expectedError = "Type 'bad' could not be converted into 'T'\n" - "caused by:\n" - " Property 'v' is not compatible. Type 'string' could not be converted into 'number' in an invariant context"; - + const std::string expected = R"(Type 'bad' could not be converted into 'T' +caused by: + Property 'v' is not compatible. +Type 'string' could not be converted into 'number' in an invariant context)"; CHECK(result.errors[0].location == Location{{4, 31}, {4, 44}}); - CHECK(toString(result.errors[0]) == expectedError); + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "dependent_generic_aliases") { - ScopedFastFlag sff_DebugLuauDeferredConstraintResolution{"DebugLuauDeferredConstraintResolution", true}; + ScopedFastFlag sff[] = { + {"DebugLuauDeferredConstraintResolution", true}, + {"LuauIndentTypeMismatch", true}, + }; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( type T = { v: a } @@ -218,15 +225,16 @@ TEST_CASE_FIXTURE(Fixture, "dependent_generic_aliases") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - - std::string expectedError = "Type 'bad' could not be converted into 'U'\n" - "caused by:\n" - " Property 't' is not compatible. Type '{ v: string }' could not be converted into 'T'\n" - "caused by:\n" - " Property 'v' is not compatible. Type 'string' could not be converted into 'number' in an invariant context"; + const std::string expected = R"(Type 'bad' could not be converted into 'U' +caused by: + Property 't' is not compatible. +Type '{ v: string }' could not be converted into 'T' +caused by: + Property 'v' is not compatible. +Type 'string' could not be converted into 'number' in an invariant context)"; CHECK(result.errors[0].location == Location{{4, 31}, {4, 52}}); - CHECK(toString(result.errors[0]) == expectedError); + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "mutually_recursive_generic_aliases") @@ -261,7 +269,7 @@ TEST_CASE_FIXTURE(Fixture, "mutually_recursive_types_errors") // We had a UAF in this example caused by not cloning type function arguments ModulePtr module = frontend.moduleResolver.getModule("MainModule"); unfreeze(module->interfaceTypes); - copyErrors(module->errors, module->interfaceTypes); + copyErrors(module->errors, module->interfaceTypes, builtinTypes); freeze(module->interfaceTypes); module->internalTypes.clear(); module->astTypes.clear(); diff --git a/tests/TypeInfer.builtins.test.cpp b/tests/TypeInfer.builtins.test.cpp index 4e0b7a7e..e64bf9e9 100644 --- a/tests/TypeInfer.builtins.test.cpp +++ b/tests/TypeInfer.builtins.test.cpp @@ -132,7 +132,11 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "sort_with_predicate") TEST_CASE_FIXTURE(BuiltinsFixture, "sort_with_bad_predicate") { - ScopedFastFlag sff{"LuauAlwaysCommitInferencesOfFunctionCalls", true}; + ScopedFastFlag sff[] = { + {"LuauAlwaysCommitInferencesOfFunctionCalls", true}, + {"LuauIndentTypeMismatch", true}, + }; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( --!strict @@ -142,12 +146,20 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "sort_with_bad_predicate") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(R"(Type '(number, number) -> boolean' could not be converted into '((string, string) -> boolean)?' + const std::string expected = R"(Type + '(number, number) -> boolean' +could not be converted into + '((string, string) -> boolean)?' caused by: - None of the union options are compatible. For example: Type '(number, number) -> boolean' could not be converted into '(string, string) -> boolean' + None of the union options are compatible. For example: +Type + '(number, number) -> boolean' +could not be converted into + '(string, string) -> boolean' caused by: - Argument #1 type is not compatible. Type 'string' could not be converted into 'number')", - toString(result.errors[0])); + Argument #1 type is not compatible. +Type 'string' could not be converted into 'number')"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "strings_have_methods") diff --git a/tests/TypeInfer.classes.test.cpp b/tests/TypeInfer.classes.test.cpp index a4df1be7..7055c27e 100644 --- a/tests/TypeInfer.classes.test.cpp +++ b/tests/TypeInfer.classes.test.cpp @@ -367,8 +367,11 @@ b.X = 2 -- real Vector2.X is also read-only TEST_CASE_FIXTURE(ClassFixture, "detailed_class_unification_error") { - ScopedFastFlag sff{"LuauAlwaysCommitInferencesOfFunctionCalls", true}; - + ScopedFastFlag sff[] = { + {"LuauAlwaysCommitInferencesOfFunctionCalls", true}, + {"LuauIndentTypeMismatch", true}, + }; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( local function foo(v) return v.X :: number + string.len(v.Y) @@ -380,10 +383,11 @@ b(a) )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - - CHECK_EQ(toString(result.errors[0]), R"(Type 'Vector2' could not be converted into '{- X: number, Y: string -}' + const std::string expected = R"(Type 'Vector2' could not be converted into '{- X: number, Y: string -}' caused by: - Property 'Y' is not compatible. Type 'number' could not be converted into 'string')"); + Property 'Y' is not compatible. +Type 'number' could not be converted into 'string')"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(ClassFixture, "class_type_mismatch_with_name_conflict") @@ -453,6 +457,8 @@ TEST_CASE_FIXTURE(ClassFixture, "index_instance_property_nonstrict") TEST_CASE_FIXTURE(ClassFixture, "type_mismatch_invariance_required_for_error") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( type A = { x: ChildClass } type B = { x: BaseClass } @@ -462,9 +468,11 @@ local b: B = a )"); LUAU_REQUIRE_ERRORS(result); - CHECK_EQ(toString(result.errors[0]), R"(Type 'A' could not be converted into 'B' + const std::string expected = R"(Type 'A' could not be converted into 'B' caused by: - Property 'x' is not compatible. Type 'ChildClass' could not be converted into 'BaseClass' in an invariant context)"); + Property 'x' is not compatible. +Type 'ChildClass' could not be converted into 'BaseClass' in an invariant context)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(ClassFixture, "callable_classes") diff --git a/tests/TypeInfer.functions.test.cpp b/tests/TypeInfer.functions.test.cpp index 379ecac6..846d5b53 100644 --- a/tests/TypeInfer.functions.test.cpp +++ b/tests/TypeInfer.functions.test.cpp @@ -1095,6 +1095,9 @@ TEST_CASE_FIXTURE(Fixture, "return_type_by_overload") TEST_CASE_FIXTURE(BuiltinsFixture, "infer_anonymous_function_arguments") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + // Simple direct arg to arg propagation CheckResult result = check(R"( type Table = { x: number, y: number } @@ -1150,129 +1153,27 @@ f(function(a, b, c, ...) return a + b end) LUAU_REQUIRE_ERRORS(result); + std::string expected; if (FFlag::LuauInstantiateInSubtyping) { - CHECK_EQ(R"(Type '(number, number, a) -> number' could not be converted into '(number, number) -> number' + expected = R"(Type + '(number, number, a) -> number' +could not be converted into + '(number, number) -> number' caused by: - Argument count mismatch. Function expects 3 arguments, but only 2 are specified)", - toString(result.errors[0])); + Argument count mismatch. Function expects 3 arguments, but only 2 are specified)"; } else { - CHECK_EQ(R"(Type '(number, number, a) -> number' could not be converted into '(number, number) -> number' + expected = R"(Type + '(number, number, a) -> number' +could not be converted into + '(number, number) -> number' caused by: - Argument count mismatch. Function expects 3 arguments, but only 2 are specified)", - toString(result.errors[0])); + Argument count mismatch. Function expects 3 arguments, but only 2 are specified)"; } - // Infer from variadic packs into elements - result = check(R"( -function f(a: (...number) -> number) return a(1, 2) end -f(function(a, b) return a + b end) - )"); - - LUAU_REQUIRE_NO_ERRORS(result); - - // Infer from variadic packs into variadic packs - result = check(R"( -type Table = { x: number, y: number } -function f(a: (...Table) -> number) return a({x = 1, y = 2}, {x = 3, y = 4}) end -f(function(a, ...) local b = ... return b.z end) - )"); - - LUAU_REQUIRE_ERRORS(result); - CHECK_EQ("Key 'z' not found in table 'Table'", toString(result.errors[0])); - - // Return type inference - result = check(R"( -type Table = { x: number, y: number } -function f(a: (number) -> Table) return a(4) end -f(function(x) return x * 2 end) - )"); - - LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ("Type 'number' could not be converted into 'Table'", toString(result.errors[0])); - - // Return type doesn't inference 'nil' - result = check(R"( - function f(a: (number) -> nil) return a(4) end - f(function(x) print(x) end) - )"); - - LUAU_REQUIRE_NO_ERRORS(result); -} - -TEST_CASE_FIXTURE(BuiltinsFixture, "infer_anonymous_function_arguments") -{ - // Simple direct arg to arg propagation - CheckResult result = check(R"( -type Table = { x: number, y: number } -local function f(a: (Table) -> number) return a({x = 1, y = 2}) end -f(function(a) return a.x + a.y end) - )"); - - LUAU_REQUIRE_NO_ERRORS(result); - - // An optional function is accepted, but since we already provide a function, nil can be ignored - result = check(R"( -type Table = { x: number, y: number } -local function f(a: ((Table) -> number)?) if a then return a({x = 1, y = 2}) else return 0 end end -f(function(a) return a.x + a.y end) - )"); - - LUAU_REQUIRE_NO_ERRORS(result); - - // Make sure self calls match correct index - result = check(R"( -type Table = { x: number, y: number } -local x = {} -x.b = {x = 1, y = 2} -function x:f(a: (Table) -> number) return a(self.b) end -x:f(function(a) return a.x + a.y end) - )"); - - LUAU_REQUIRE_NO_ERRORS(result); - - // Mix inferred and explicit argument types - result = check(R"( -function f(a: (a: number, b: number, c: boolean) -> number) return a(1, 2, true) end -f(function(a: number, b, c) return c and a + b or b - a end) - )"); - - LUAU_REQUIRE_NO_ERRORS(result); - - // Anonymous function has a variadic pack - result = check(R"( -type Table = { x: number, y: number } -local function f(a: (Table) -> number) return a({x = 1, y = 2}) end -f(function(...) return select(1, ...).z end) - )"); - - LUAU_REQUIRE_ERRORS(result); - CHECK_EQ("Key 'z' not found in table 'Table'", toString(result.errors[0])); - - // Can't accept more arguments than provided - result = check(R"( -function f(a: (a: number, b: number) -> number) return a(1, 2) end -f(function(a, b, c, ...) return a + b end) - )"); - - LUAU_REQUIRE_ERRORS(result); - - if (FFlag::LuauInstantiateInSubtyping) - { - CHECK_EQ(R"(Type '(number, number, a) -> number' could not be converted into '(number, number) -> number' -caused by: - Argument count mismatch. Function expects 3 arguments, but only 2 are specified)", - toString(result.errors[0])); - } - else - { - CHECK_EQ(R"(Type '(number, number, a) -> number' could not be converted into '(number, number) -> number' -caused by: - Argument count mismatch. Function expects 3 arguments, but only 2 are specified)", - toString(result.errors[0])); - } + CHECK_EQ(expected, toString(result.errors[0])); // Infer from variadic packs into elements result = check(R"( @@ -1376,6 +1277,8 @@ end TEST_CASE_FIXTURE(Fixture, "error_detailed_function_mismatch_arg_count") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( type A = (number, number) -> string type B = (number) -> string @@ -1385,13 +1288,20 @@ local b: B = a )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), R"(Type '(number, number) -> string' could not be converted into '(number) -> string' + const std::string expected = R"(Type + '(number, number) -> string' +could not be converted into + '(number) -> string' caused by: - Argument count mismatch. Function expects 2 arguments, but only 1 is specified)"); + Argument count mismatch. Function expects 2 arguments, but only 1 is specified)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "error_detailed_function_mismatch_arg") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( type A = (number, number) -> string type B = (number, string) -> string @@ -1401,13 +1311,21 @@ local b: B = a )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), R"(Type '(number, number) -> string' could not be converted into '(number, string) -> string' + const std::string expected = R"(Type + '(number, number) -> string' +could not be converted into + '(number, string) -> string' caused by: - Argument #2 type is not compatible. Type 'string' could not be converted into 'number')"); + Argument #2 type is not compatible. +Type 'string' could not be converted into 'number')"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "error_detailed_function_mismatch_ret_count") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( type A = (number, number) -> (number) type B = (number, number) -> (number, boolean) @@ -1417,13 +1335,20 @@ local b: B = a )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), R"(Type '(number, number) -> number' could not be converted into '(number, number) -> (number, boolean)' + const std::string expected = R"(Type + '(number, number) -> number' +could not be converted into + '(number, number) -> (number, boolean)' caused by: - Function only returns 1 value, but 2 are required here)"); + Function only returns 1 value, but 2 are required here)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "error_detailed_function_mismatch_ret") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( type A = (number, number) -> string type B = (number, number) -> number @@ -1433,13 +1358,21 @@ local b: B = a )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), R"(Type '(number, number) -> string' could not be converted into '(number, number) -> number' + const std::string expected = R"(Type + '(number, number) -> string' +could not be converted into + '(number, number) -> number' caused by: - Return type is not compatible. Type 'string' could not be converted into 'number')"); + Return type is not compatible. +Type 'string' could not be converted into 'number')"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "error_detailed_function_mismatch_ret_mult") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( type A = (number, number) -> (number, string) type B = (number, number) -> (number, boolean) @@ -1449,10 +1382,14 @@ local b: B = a )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), - R"(Type '(number, number) -> (number, string)' could not be converted into '(number, number) -> (number, boolean)' + const std::string expected = R"(Type + '(number, number) -> (number, string)' +could not be converted into + '(number, number) -> (number, boolean)' caused by: - Return #2 type is not compatible. Type 'string' could not be converted into 'boolean')"); + Return #2 type is not compatible. +Type 'string' could not be converted into 'boolean')"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(BuiltinsFixture, "function_decl_quantify_right_type") @@ -1561,6 +1498,9 @@ TEST_CASE_FIXTURE(Fixture, "inferred_higher_order_functions_are_quantified_at_th TEST_CASE_FIXTURE(BuiltinsFixture, "function_decl_non_self_unsealed_overwrite") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( local t = { f = nil :: ((x: number) -> number)? } @@ -1575,11 +1515,19 @@ end )"); LUAU_REQUIRE_ERROR_COUNT(2, result); - CHECK_EQ(toString(result.errors[0]), R"(Type '(string) -> string' could not be converted into '((number) -> number)?' + CHECK_EQ(toString(result.errors[0]), R"(Type + '(string) -> string' +could not be converted into + '((number) -> number)?' caused by: - None of the union options are compatible. For example: Type '(string) -> string' could not be converted into '(number) -> number' + None of the union options are compatible. For example: +Type + '(string) -> string' +could not be converted into + '(number) -> number' caused by: - Argument #1 type is not compatible. Type 'number' could not be converted into 'string')"); + Argument #1 type is not compatible. +Type 'number' could not be converted into 'string')"); CHECK_EQ(toString(result.errors[1]), R"(Type 'string' could not be converted into 'number')"); } @@ -1595,6 +1543,9 @@ TEST_CASE_FIXTURE(Fixture, "strict_mode_ok_with_missing_arguments") TEST_CASE_FIXTURE(Fixture, "function_statement_sealed_table_assignment_through_indexer") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( local t: {[string]: () -> number} = {} @@ -1603,7 +1554,10 @@ function t:b() return 2 end -- not OK )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(R"(Type '(*error-type*) -> number' could not be converted into '() -> number' + CHECK_EQ(R"(Type + '(*error-type*) -> number' +could not be converted into + '() -> number' caused by: Argument count mismatch. Function expects 1 argument, but none are specified)", toString(result.errors[0])); @@ -1800,6 +1754,9 @@ foo(string.find("hello", "e")) TEST_CASE_FIXTURE(Fixture, "luau_subtyping_is_np_hard") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( --!strict @@ -1832,11 +1789,11 @@ z = y -- Not OK, so the line is colorable )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), - "Type '((\"blue\" | \"red\") -> (\"blue\" | \"red\") -> (\"blue\" | \"red\") -> boolean) & ((\"blue\" | \"red\") -> (\"blue\") -> (\"blue\") " - "-> false) & ((\"blue\" | \"red\") -> (\"red\") -> (\"red\") -> false) & ((\"blue\") -> (\"blue\") -> (\"blue\" | \"red\") -> false) & " - "((\"red\") -> (\"red\") -> (\"blue\" | \"red\") -> false)' could not be converted into '(\"blue\" | \"red\") -> (\"blue\" | \"red\") -> " - "(\"blue\" | \"red\") -> false'; none of the intersection parts are compatible"); + const std::string expected = R"(Type + '(("blue" | "red") -> ("blue" | "red") -> ("blue" | "red") -> boolean) & (("blue" | "red") -> ("blue") -> ("blue") -> false) & (("blue" | "red") -> ("red") -> ("red") -> false) & (("blue") -> ("blue") -> ("blue" | "red") -> false) & (("red") -> ("red") -> ("blue" | "red") -> false)' +could not be converted into + '("blue" | "red") -> ("blue" | "red") -> ("blue" | "red") -> false'; none of the intersection parts are compatible)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "function_is_supertype_of_concrete_functions") @@ -1994,7 +1951,11 @@ TEST_CASE_FIXTURE(Fixture, "function_exprs_are_generalized_at_signature_scope_no TEST_CASE_FIXTURE(BuiltinsFixture, "param_1_and_2_both_takes_the_same_generic_but_their_arguments_are_incompatible") { - ScopedFastFlag sff{"LuauAlwaysCommitInferencesOfFunctionCalls", true}; + ScopedFastFlag sff[] = { + {"LuauIndentTypeMismatch", true}, + {"LuauAlwaysCommitInferencesOfFunctionCalls", true}, + }; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( local function foo(x: a, y: a?) @@ -2038,11 +1999,12 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "param_1_and_2_both_takes_the_same_generic_bu LUAU_REQUIRE_ERROR_COUNT(2, result); - CHECK_EQ(toString(result.errors[0]), R"(Type '{ x: number }' could not be converted into 'vec2?' + const std::string expected = R"(Type '{ x: number }' could not be converted into 'vec2?' caused by: - None of the union options are compatible. For example: Table type '{ x: number }' not compatible with type 'vec2' because the former is missing field 'y')"); - - CHECK_EQ(toString(result.errors[1]), "Type 'vec2' could not be converted into 'number'"); + None of the union options are compatible. For example: +Table type '{ x: number }' not compatible with type 'vec2' because the former is missing field 'y')"; + CHECK_EQ(expected, toString(result.errors[0])); + CHECK_EQ("Type 'vec2' could not be converted into 'number'", toString(result.errors[1])); } TEST_CASE_FIXTURE(BuiltinsFixture, "param_1_and_2_both_takes_the_same_generic_but_their_arguments_are_incompatible_2") diff --git a/tests/TypeInfer.generics.test.cpp b/tests/TypeInfer.generics.test.cpp index 6b933616..f7904d45 100644 --- a/tests/TypeInfer.generics.test.cpp +++ b/tests/TypeInfer.generics.test.cpp @@ -713,6 +713,9 @@ end TEST_CASE_FIXTURE(Fixture, "generic_functions_should_be_memory_safe") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( --!strict -- At one point this produced a UAF @@ -725,12 +728,14 @@ y.a.c = y )"); LUAU_REQUIRE_ERRORS(result); - CHECK_EQ(toString(result.errors[0]), - R"(Type 'y' could not be converted into 'T' + const std::string expected = R"(Type 'y' could not be converted into 'T' caused by: - Property 'a' is not compatible. Type '{ c: T?, d: number }' could not be converted into 'U' + Property 'a' is not compatible. +Type '{ c: T?, d: number }' could not be converted into 'U' caused by: - Property 'd' is not compatible. Type 'number' could not be converted into 'string' in an invariant context)"); + Property 'd' is not compatible. +Type 'number' could not be converted into 'string' in an invariant context)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "generic_type_pack_unification1") diff --git a/tests/TypeInfer.intersectionTypes.test.cpp b/tests/TypeInfer.intersectionTypes.test.cpp index 954d9858..b390a816 100644 --- a/tests/TypeInfer.intersectionTypes.test.cpp +++ b/tests/TypeInfer.intersectionTypes.test.cpp @@ -317,6 +317,9 @@ TEST_CASE_FIXTURE(Fixture, "table_intersection_write_sealed") TEST_CASE_FIXTURE(Fixture, "table_intersection_write_sealed_indirect") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( type X = { x: (number) -> number } type Y = { y: (string) -> string } @@ -333,9 +336,13 @@ TEST_CASE_FIXTURE(Fixture, "table_intersection_write_sealed_indirect") )"); LUAU_REQUIRE_ERROR_COUNT(4, result); - CHECK_EQ(toString(result.errors[0]), R"(Type '(string, number) -> string' could not be converted into '(string) -> string' + const std::string expected = R"(Type + '(string, number) -> string' +could not be converted into + '(string) -> string' caused by: - Argument count mismatch. Function expects 2 arguments, but only 1 is specified)"); + Argument count mismatch. Function expects 2 arguments, but only 1 is specified)"; + CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(toString(result.errors[1]), "Cannot add property 'z' to table 'X & Y'"); CHECK_EQ(toString(result.errors[2]), "Type 'number' could not be converted into 'string'"); CHECK_EQ(toString(result.errors[3]), "Cannot add property 'w' to table 'X & Y'"); @@ -343,6 +350,9 @@ caused by: TEST_CASE_FIXTURE(Fixture, "table_write_sealed_indirect") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + // After normalization, previous 'table_intersection_write_sealed_indirect' is identical to this one CheckResult result = check(R"( type XY = { x: (number) -> number, y: (string) -> string } @@ -357,9 +367,14 @@ TEST_CASE_FIXTURE(Fixture, "table_write_sealed_indirect") )"); LUAU_REQUIRE_ERROR_COUNT(4, result); - CHECK_EQ(toString(result.errors[0]), R"(Type '(string, number) -> string' could not be converted into '(string) -> string' + const std::string expected = R"(Type + '(string, number) -> string' +could not be converted into + '(string) -> string' caused by: - Argument count mismatch. Function expects 2 arguments, but only 1 is specified)"); + Argument count mismatch. Function expects 2 arguments, but only 1 is specified)"; + CHECK_EQ(expected, toString(result.errors[0])); + CHECK_EQ(toString(result.errors[1]), "Cannot add property 'z' to table 'XY'"); CHECK_EQ(toString(result.errors[2]), "Type 'number' could not be converted into 'string'"); CHECK_EQ(toString(result.errors[3]), "Cannot add property 'w' to table 'XY'"); @@ -377,6 +392,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_intersection_setmetatable") TEST_CASE_FIXTURE(Fixture, "error_detailed_intersection_part") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( type X = { x: number } type Y = { y: number } @@ -386,9 +404,11 @@ local a: XYZ = 3 )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), R"(Type 'number' could not be converted into 'X & Y & Z' + const std::string expected = R"(Type 'number' could not be converted into 'X & Y & Z' caused by: - Not all intersection parts are compatible. Type 'number' could not be converted into 'X')"); + Not all intersection parts are compatible. +Type 'number' could not be converted into 'X')"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "error_detailed_intersection_all") @@ -462,6 +482,9 @@ TEST_CASE_FIXTURE(Fixture, "intersect_false_and_bool_and_false") TEST_CASE_FIXTURE(Fixture, "intersect_saturate_overloaded_functions") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( local x : ((number?) -> number?) & ((string?) -> string?) local y : (nil) -> nil = x -- OK @@ -469,12 +492,18 @@ TEST_CASE_FIXTURE(Fixture, "intersect_saturate_overloaded_functions") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), "Type '((number?) -> number?) & ((string?) -> string?)' could not be converted into '(number) -> number'; " - "none of the intersection parts are compatible"); + const std::string expected = R"(Type + '((number?) -> number?) & ((string?) -> string?)' +could not be converted into + '(number) -> number'; none of the intersection parts are compatible)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "union_saturate_overloaded_functions") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( local x : ((number) -> number) & ((string) -> string) local y : ((number | string) -> (number | string)) = x -- OK @@ -482,12 +511,18 @@ TEST_CASE_FIXTURE(Fixture, "union_saturate_overloaded_functions") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), "Type '((number) -> number) & ((string) -> string)' could not be converted into '(boolean | number) -> " - "boolean | number'; none of the intersection parts are compatible"); + const std::string expected = R"(Type + '((number) -> number) & ((string) -> string)' +could not be converted into + '(boolean | number) -> boolean | number'; none of the intersection parts are compatible)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "intersection_of_tables") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( local x : { p : number?, q : string? } & { p : number?, q : number?, r : number? } local y : { p : number?, q : nil, r : number? } = x -- OK @@ -495,12 +530,18 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), "Type '{| p: number?, q: number?, r: number? |} & {| p: number?, q: string? |}' could not be converted into " - "'{| p: nil |}'; none of the intersection parts are compatible"); + const std::string expected = R"(Type + '{| p: number?, q: number?, r: number? |} & {| p: number?, q: string? |}' +could not be converted into + '{| p: nil |}'; none of the intersection parts are compatible)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_top_properties") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( local x : { p : number?, q : any } & { p : unknown, q : string? } local y : { p : number?, q : string? } = x -- OK @@ -532,9 +573,11 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_top_properties") else { LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), - "Type '{| p: number?, q: any |} & {| p: unknown, q: string? |}' could not be converted into '{| p: string?, " - "q: number? |}'; none of the intersection parts are compatible"); + const std::string expected = R"(Type + '{| p: number?, q: any |} & {| p: unknown, q: string? |}' +could not be converted into + '{| p: string?, q: number? |}'; none of the intersection parts are compatible)"; + CHECK_EQ(expected, toString(result.errors[0])); } } @@ -551,6 +594,9 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_never_properties") TEST_CASE_FIXTURE(Fixture, "overloaded_functions_returning_intersections") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( local x : ((number?) -> ({ p : number } & { q : number })) & ((string?) -> ({ p : number } & { r : number })) local y : (nil) -> { p : number, q : number, r : number} = x -- OK @@ -558,13 +604,18 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_returning_intersections") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), - "Type '((number?) -> {| p: number |} & {| q: number |}) & ((string?) -> {| p: number |} & {| r: number |})' could not be converted into " - "'(number?) -> {| p: number, q: number, r: number |}'; none of the intersection parts are compatible"); + const std::string expected = R"(Type + '((number?) -> {| p: number |} & {| q: number |}) & ((string?) -> {| p: number |} & {| r: number |})' +could not be converted into + '(number?) -> {| p: number, q: number, r: number |}'; none of the intersection parts are compatible)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( function f() local x : ((number?) -> (a | number)) & ((string?) -> (a | string)) @@ -574,12 +625,18 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), "Type '((number?) -> a | number) & ((string?) -> a | string)' could not be converted into '(number?) -> a'; " - "none of the intersection parts are compatible"); + const std::string expected = R"(Type + '((number?) -> a | number) & ((string?) -> a | string)' +could not be converted into + '(number?) -> a'; none of the intersection parts are compatible)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generics") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( function f() local x : ((a?) -> (a | b)) & ((c?) -> (b | c)) @@ -589,12 +646,18 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generics") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), - "Type '((a?) -> a | b) & ((c?) -> b | c)' could not be converted into '(a?) -> (a & c) | b'; none of the intersection parts are compatible"); + const std::string expected = R"(Type + '((a?) -> a | b) & ((c?) -> b | c)' +could not be converted into + '(a?) -> (a & c) | b'; none of the intersection parts are compatible)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic_packs") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( function f() local x : ((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...)) @@ -604,12 +667,18 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic_packs") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), "Type '((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...))' could not be converted " - "into '(nil, b...) -> (nil, a...)'; none of the intersection parts are compatible"); + const std::string expected = R"(Type + '((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...))' +could not be converted into + '(nil, b...) -> (nil, a...)'; none of the intersection parts are compatible)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_result") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( function f() local x : ((number) -> number) & ((nil) -> unknown) @@ -619,12 +688,18 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_result") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), "Type '((nil) -> unknown) & ((number) -> number)' could not be converted into '(number?) -> number?'; none " - "of the intersection parts are compatible"); + const std::string expected = R"(Type + '((nil) -> unknown) & ((number) -> number)' +could not be converted into + '(number?) -> number?'; none of the intersection parts are compatible)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_arguments") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( function f() local x : ((number) -> number?) & ((unknown) -> string?) @@ -634,12 +709,18 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_arguments") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), "Type '((number) -> number?) & ((unknown) -> string?)' could not be converted into '(number?) -> nil'; none " - "of the intersection parts are compatible"); + const std::string expected = R"(Type + '((number) -> number?) & ((unknown) -> string?)' +could not be converted into + '(number?) -> nil'; none of the intersection parts are compatible)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_result") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( function f() local x : ((number) -> number) & ((nil) -> never) @@ -649,12 +730,18 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_result") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), "Type '((nil) -> never) & ((number) -> number)' could not be converted into '(number?) -> never'; none of " - "the intersection parts are compatible"); + const std::string expected = R"(Type + '((nil) -> never) & ((number) -> number)' +could not be converted into + '(number?) -> never'; none of the intersection parts are compatible)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_arguments") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( function f() local x : ((number) -> number?) & ((never) -> string?) @@ -664,12 +751,18 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_arguments") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), "Type '((never) -> string?) & ((number) -> number?)' could not be converted into '(number?) -> nil'; none " - "of the intersection parts are compatible"); + const std::string expected = R"(Type + '((never) -> string?) & ((number) -> number?)' +could not be converted into + '(number?) -> nil'; none of the intersection parts are compatible)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_overlapping_results_and_variadics") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( local x : ((string?) -> (string | number)) & ((number?) -> ...number) local y : ((nil) -> (number, number?)) = x -- OK @@ -677,8 +770,11 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_overlapping_results_and_ )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), "Type '((number?) -> (...number)) & ((string?) -> number | string)' could not be converted into '(number | " - "string) -> (number, number?)'; none of the intersection parts are compatible"); + const std::string expected = R"(Type + '((number?) -> (...number)) & ((string?) -> number | string)' +could not be converted into + '(number | string) -> (number, number?)'; none of the intersection parts are compatible)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_1") @@ -713,6 +809,9 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_2") TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_3") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( function f() local x : (() -> a...) & (() -> (number?,a...)) @@ -722,12 +821,18 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_3") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), - "Type '(() -> (a...)) & (() -> (number?, a...))' could not be converted into '() -> number'; none of the intersection parts are compatible"); + const std::string expected = R"(Type + '(() -> (a...)) & (() -> (number?, a...))' +could not be converted into + '() -> number'; none of the intersection parts are compatible)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_4") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( function f() local x : ((a...) -> ()) & ((number,a...) -> number) @@ -737,8 +842,11 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_4") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), "Type '((a...) -> ()) & ((number, a...) -> number)' could not be converted into '(number?) -> ()'; none of " - "the intersection parts are compatible"); + const std::string expected = R"(Type + '((a...) -> ()) & ((number, a...) -> number)' +could not be converted into + '(number?) -> ()'; none of the intersection parts are compatible)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatables") @@ -897,8 +1005,6 @@ local y = x.Bar TEST_CASE_FIXTURE(BuiltinsFixture, "index_property_table_intersection_2") { - ScopedFastFlag sff{"LuauIndexTableIntersectionStringExpr", true}; - CheckResult result = check(R"( type Foo = { Bar: string, diff --git a/tests/TypeInfer.modules.test.cpp b/tests/TypeInfer.modules.test.cpp index b75f909a..7c3d4808 100644 --- a/tests/TypeInfer.modules.test.cpp +++ b/tests/TypeInfer.modules.test.cpp @@ -389,6 +389,9 @@ type Table = typeof(tbl) TEST_CASE_FIXTURE(BuiltinsFixture, "module_type_conflict") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + fileResolver.source["game/A"] = R"( export type T = { x: number } return {} @@ -407,15 +410,19 @@ local b: B.T = a )"; CheckResult result = frontend.check("game/C"); - LUAU_REQUIRE_ERROR_COUNT(1, result); - - CHECK_EQ(toString(result.errors[0]), R"(Type 'T' from 'game/A' could not be converted into 'T' from 'game/B' + const std::string expected = R"(Type 'T' from 'game/A' could not be converted into 'T' from 'game/B' caused by: - Property 'x' is not compatible. Type 'number' could not be converted into 'string' in an invariant context)"); + Property 'x' is not compatible. +Type 'number' could not be converted into 'string' in an invariant context)"; + LUAU_REQUIRE_ERROR_COUNT(1, result); + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(BuiltinsFixture, "module_type_conflict_instantiated") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + fileResolver.source["game/A"] = R"( export type Wrap = { x: T } return {} @@ -441,11 +448,12 @@ local b: B.T = a )"; CheckResult result = frontend.check("game/D"); - LUAU_REQUIRE_ERROR_COUNT(1, result); - - CHECK_EQ(toString(result.errors[0]), R"(Type 'T' from 'game/B' could not be converted into 'T' from 'game/C' + const std::string expected = R"(Type 'T' from 'game/B' could not be converted into 'T' from 'game/C' caused by: - Property 'x' is not compatible. Type 'number' could not be converted into 'string' in an invariant context)"); + Property 'x' is not compatible. +Type 'number' could not be converted into 'string' in an invariant context)"; + LUAU_REQUIRE_ERROR_COUNT(1, result); + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(BuiltinsFixture, "constrained_anyification_clone_immutable_types") diff --git a/tests/TypeInfer.oop.test.cpp b/tests/TypeInfer.oop.test.cpp index 08c0f7ca..cc0a79f8 100644 --- a/tests/TypeInfer.oop.test.cpp +++ b/tests/TypeInfer.oop.test.cpp @@ -9,6 +9,7 @@ #include "Fixture.h" +#include "ScopedFlags.h" #include "doctest.h" using namespace Luau; @@ -404,4 +405,92 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "cycle_between_object_constructor_and_alias") CHECK_MESSAGE(get(follow(aliasType)), "Expected metatable type but got: " << toString(aliasType)); } +TEST_CASE_FIXTURE(BuiltinsFixture, "promise_type_error_too_complex") +{ + ScopedFastFlag sff{"LuauStacklessTypeClone", true}; + + frontend.options.retainFullTypeGraphs = false; + + // Used `luau-reduce` tool to extract a minimal reproduction. + // Credit: https://github.com/evaera/roblox-lua-promise/blob/v4.0.0/lib/init.lua + CheckResult result = check(R"( + --!strict + + local Promise = {} + Promise.prototype = {} + Promise.__index = Promise.prototype + + function Promise._new(traceback, callback, parent) + if parent ~= nil and not Promise.is(parent)then + end + + local self = { + _parent = parent, + } + + parent._consumers[self] = true + setmetatable(self, Promise) + self:_reject() + + return self + end + + function Promise.resolve(...) + return Promise._new(debug.traceback(nil, 2), function(resolve) + end) + end + + function Promise.reject(...) + return Promise._new(debug.traceback(nil, 2), function(_, reject) + end) + end + + function Promise._try(traceback, callback, ...) + return Promise._new(traceback, function(resolve) + end) + end + + function Promise.try(callback, ...) + return Promise._try(debug.traceback(nil, 2), callback, ...) + end + + function Promise._all(traceback, promises, amount) + if #promises == 0 or amount == 0 then + return Promise.resolve({}) + end + return Promise._new(traceback, function(resolve, reject, onCancel) + end) + end + + function Promise.all(promises) + return Promise._all(debug.traceback(nil, 2), promises) + end + + function Promise.allSettled(promises) + return Promise.resolve({}) + end + + function Promise.race(promises) + return Promise._new(debug.traceback(nil, 2), function(resolve, reject, onCancel) + end) + end + + function Promise.each(list, predicate) + return Promise._new(debug.traceback(nil, 2), function(resolve, reject, onCancel) + local predicatePromise = Promise.resolve(predicate(value, index)) + local success, result = predicatePromise:await() + end) + end + + function Promise.is(object) + end + + function Promise.prototype:_reject(...) + self:_finalize() + end + )"); + + LUAU_REQUIRE_ERRORS(result); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.provisional.test.cpp b/tests/TypeInfer.provisional.test.cpp index b3d70bd7..c33fe170 100644 --- a/tests/TypeInfer.provisional.test.cpp +++ b/tests/TypeInfer.provisional.test.cpp @@ -787,6 +787,9 @@ TEST_CASE_FIXTURE(IsSubtypeFixture, "functions_with_mismatching_arity_but_any_is TEST_CASE_FIXTURE(Fixture, "assign_table_with_refined_property_with_a_similar_type_is_illegal") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( local t: {x: number?} = {x = nil} @@ -796,11 +799,14 @@ TEST_CASE_FIXTURE(Fixture, "assign_table_with_refined_property_with_a_similar_ty )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - - CHECK_EQ(R"(Type '{| x: number? |}' could not be converted into '{| x: number |}' + const std::string expected = R"(Type + '{| x: number? |}' +could not be converted into + '{| x: number |}' caused by: - Property 'x' is not compatible. Type 'number?' could not be converted into 'number' in an invariant context)", - toString(result.errors[0])); + Property 'x' is not compatible. +Type 'number?' could not be converted into 'number' in an invariant context)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_with_a_singleton_argument") diff --git a/tests/TypeInfer.singletons.test.cpp b/tests/TypeInfer.singletons.test.cpp index 84e9fc7c..2156d1c6 100644 --- a/tests/TypeInfer.singletons.test.cpp +++ b/tests/TypeInfer.singletons.test.cpp @@ -316,6 +316,9 @@ TEST_CASE_FIXTURE(Fixture, "table_properties_type_error_escapes") TEST_CASE_FIXTURE(Fixture, "error_detailed_tagged_union_mismatch_string") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( type Cat = { tag: 'cat', catfood: string } type Dog = { tag: 'dog', dogfood: string } @@ -325,14 +328,18 @@ local a: Animal = { tag = 'cat', cafood = 'something' } )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(R"(Type 'a' could not be converted into 'Cat | Dog' + const std::string expected = R"(Type 'a' could not be converted into 'Cat | Dog' caused by: - None of the union options are compatible. For example: Table type 'a' not compatible with type 'Cat' because the former is missing field 'catfood')", - toString(result.errors[0])); + None of the union options are compatible. For example: +Table type 'a' not compatible with type 'Cat' because the former is missing field 'catfood')"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "error_detailed_tagged_union_mismatch_bool") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( type Good = { success: true, result: string } type Bad = { success: false, error: string } @@ -342,18 +349,20 @@ local a: Result = { success = false, result = 'something' } )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(R"(Type 'a' could not be converted into 'Bad | Good' + const std::string expected = R"(Type 'a' could not be converted into 'Bad | Good' caused by: - None of the union options are compatible. For example: Table type 'a' not compatible with type 'Bad' because the former is missing field 'error')", - toString(result.errors[0])); + None of the union options are compatible. For example: +Table type 'a' not compatible with type 'Bad' because the former is missing field 'error')"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "parametric_tagged_union_alias") { ScopedFastFlag sff[] = { {"DebugLuauDeferredConstraintResolution", true}, + {"LuauIndentTypeMismatch", true}, }; - + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( type Ok = {success: true, result: T} type Err = {success: false, error: T} @@ -365,10 +374,10 @@ TEST_CASE_FIXTURE(Fixture, "parametric_tagged_union_alias") LUAU_REQUIRE_ERROR_COUNT(1, result); - const std::string expectedError = "Type 'a' could not be converted into 'Err | Ok'\n" - "caused by:\n" - " None of the union options are compatible. For example: Table type 'a'" - " not compatible with type 'Err' because the former is missing field 'error'"; + const std::string expectedError = R"(Type 'a' could not be converted into 'Err | Ok' +caused by: + None of the union options are compatible. For example: +Table type 'a' not compatible with type 'Err' because the former is missing field 'error')"; CHECK(toString(result.errors[0]) == expectedError); } diff --git a/tests/TypeInfer.tables.test.cpp b/tests/TypeInfer.tables.test.cpp index 8d93561f..ca137b15 100644 --- a/tests/TypeInfer.tables.test.cpp +++ b/tests/TypeInfer.tables.test.cpp @@ -2083,6 +2083,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_should_cope_with_optional_prope TEST_CASE_FIXTURE(Fixture, "error_detailed_prop") { + ScopedFastFlag sff[] = {{"LuauIndentTypeMismatch", true}}; + ScopedFastInt sfi[] = {{"LuauIndentTypeMismatchMaxTypeLength", 10}}; CheckResult result = check(R"( type A = { x: number, y: number } type B = { x: number, y: string } @@ -2092,13 +2094,17 @@ local b: B = a )"); LUAU_REQUIRE_ERRORS(result); - CHECK_EQ(toString(result.errors[0]), R"(Type 'A' could not be converted into 'B' + const std::string expected = R"(Type 'A' could not be converted into 'B' caused by: - Property 'y' is not compatible. Type 'number' could not be converted into 'string' in an invariant context)"); + Property 'y' is not compatible. +Type 'number' could not be converted into 'string' in an invariant context)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "error_detailed_prop_nested") { + ScopedFastFlag sff[] = {{"LuauIndentTypeMismatch", true}}; + ScopedFastInt sfi[] = {{"LuauIndentTypeMismatchMaxTypeLength", 10}}; CheckResult result = check(R"( type AS = { x: number, y: number } type BS = { x: number, y: string } @@ -2111,15 +2117,21 @@ local b: B = a )"); LUAU_REQUIRE_ERRORS(result); - CHECK_EQ(toString(result.errors[0]), R"(Type 'A' could not be converted into 'B' + const std::string expected = R"(Type 'A' could not be converted into 'B' caused by: - Property 'b' is not compatible. Type 'AS' could not be converted into 'BS' + Property 'b' is not compatible. +Type 'AS' could not be converted into 'BS' caused by: - Property 'y' is not compatible. Type 'number' could not be converted into 'string' in an invariant context)"); + Property 'y' is not compatible. +Type 'number' could not be converted into 'string' in an invariant context)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(BuiltinsFixture, "error_detailed_metatable_prop") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( local a1 = setmetatable({ x = 2, y = 3 }, { __call = function(s) end }); local b1 = setmetatable({ x = 2, y = "hello" }, { __call = function(s) end }); @@ -2130,33 +2142,68 @@ local b2 = setmetatable({ x = 2, y = 4 }, { __call = function(s, t) end }); local c2: typeof(a2) = b2 )"); - LUAU_REQUIRE_ERROR_COUNT(2, result); - CHECK_EQ(toString(result.errors[0]), R"(Type 'b1' could not be converted into 'a1' + const std::string expected1 = R"(Type 'b1' could not be converted into 'a1' caused by: - Type '{ x: number, y: string }' could not be converted into '{ x: number, y: number }' + Type + '{ x: number, y: string }' +could not be converted into + '{ x: number, y: number }' caused by: - Property 'y' is not compatible. Type 'string' could not be converted into 'number' in an invariant context)"); + Property 'y' is not compatible. +Type 'string' could not be converted into 'number' in an invariant context)"; + const std::string expected2 = R"(Type 'b2' could not be converted into 'a2' +caused by: + Type + '{ __call: (a, b) -> () }' +could not be converted into + '{ __call: (a) -> () }' +caused by: + Property '__call' is not compatible. +Type + '(a, b) -> ()' +could not be converted into + '(a) -> ()'; different number of generic type parameters)"; + const std::string expected3 = R"(Type 'b2' could not be converted into 'a2' +caused by: + Type + '{ __call: (a, b) -> () }' +could not be converted into + '{ __call: (a) -> () }' +caused by: + Property '__call' is not compatible. +Type + '(a, b) -> ()' +could not be converted into + '(a) -> ()'; different number of generic type parameters)"; + LUAU_REQUIRE_ERROR_COUNT(2, result); + CHECK_EQ(expected1, toString(result.errors[0])); if (FFlag::LuauInstantiateInSubtyping) { - CHECK_EQ(toString(result.errors[1]), R"(Type 'b2' could not be converted into 'a2' -caused by: - Type '{ __call: (a, b) -> () }' could not be converted into '{ __call: (a) -> () }' -caused by: - Property '__call' is not compatible. Type '(a, b) -> ()' could not be converted into '(a) -> ()'; different number of generic type parameters)"); + CHECK_EQ(expected2, toString(result.errors[1])); } else { - CHECK_EQ(toString(result.errors[1]), R"(Type 'b2' could not be converted into 'a2' + std::string expected3 = R"(Type 'b2' could not be converted into 'a2' caused by: - Type '{ __call: (a, b) -> () }' could not be converted into '{ __call: (a) -> () }' + Type + '{ __call: (a, b) -> () }' +could not be converted into + '{ __call: (a) -> () }' caused by: - Property '__call' is not compatible. Type '(a, b) -> ()' could not be converted into '(a) -> ()'; different number of generic type parameters)"); + Property '__call' is not compatible. +Type + '(a, b) -> ()' +could not be converted into + '(a) -> ()'; different number of generic type parameters)"; + CHECK_EQ(expected3, toString(result.errors[1])); } } TEST_CASE_FIXTURE(Fixture, "error_detailed_indexer_key") { + ScopedFastFlag sff[] = {{"LuauIndentTypeMismatch", true}}; + ScopedFastInt sfi[] = {{"LuauIndentTypeMismatchMaxTypeLength", 10}}; CheckResult result = check(R"( type A = { [number]: string } type B = { [string]: string } @@ -2166,13 +2213,17 @@ TEST_CASE_FIXTURE(Fixture, "error_detailed_indexer_key") )"); LUAU_REQUIRE_ERRORS(result); - CHECK_EQ(toString(result.errors[0]), R"(Type 'A' could not be converted into 'B' + const std::string expected = R"(Type 'A' could not be converted into 'B' caused by: - Property '[indexer key]' is not compatible. Type 'number' could not be converted into 'string' in an invariant context)"); + Property '[indexer key]' is not compatible. +Type 'number' could not be converted into 'string' in an invariant context)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "error_detailed_indexer_value") { + ScopedFastFlag sff[] = {{"LuauIndentTypeMismatch", true}}; + ScopedFastInt sfi[] = {{"LuauIndentTypeMismatchMaxTypeLength", 10}}; CheckResult result = check(R"( type A = { [number]: number } type B = { [number]: string } @@ -2182,9 +2233,11 @@ TEST_CASE_FIXTURE(Fixture, "error_detailed_indexer_value") )"); LUAU_REQUIRE_ERRORS(result); - CHECK_EQ(toString(result.errors[0]), R"(Type 'A' could not be converted into 'B' + const std::string expected = R"(Type 'A' could not be converted into 'B' caused by: - Property '[indexer value]' is not compatible. Type 'number' could not be converted into 'string' in an invariant context)"); + Property '[indexer value]' is not compatible. +Type 'number' could not be converted into 'string' in an invariant context)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "explicitly_typed_table") @@ -2204,6 +2257,8 @@ a.p = { x = 9 } TEST_CASE_FIXTURE(Fixture, "explicitly_typed_table_error") { + ScopedFastFlag sff[] = {{"LuauIndentTypeMismatch", true}}; + ScopedFastInt sfi[] = {{"LuauIndentTypeMismatchMaxTypeLength", 10}}; CheckResult result = check(R"( --!strict type Super = { x : number } @@ -2218,9 +2273,11 @@ local y: number = tmp.p.y )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), R"(Type 'tmp' could not be converted into 'HasSuper' + const std::string expected = R"(Type 'tmp' could not be converted into 'HasSuper' caused by: - Property 'p' is not compatible. Table type '{ x: number, y: number }' not compatible with type 'Super' because the former has extra field 'y')"); + Property 'p' is not compatible. +Table type '{ x: number, y: number }' not compatible with type 'Super' because the former has extra field 'y')"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "explicitly_typed_table_with_indexer") @@ -3302,7 +3359,13 @@ TEST_CASE_FIXTURE(Fixture, "scalar_is_a_subtype_of_a_compatible_polymorphic_shap TEST_CASE_FIXTURE(Fixture, "scalar_is_not_a_subtype_of_a_compatible_polymorphic_shape_type") { - ScopedFastFlag sff{"LuauAlwaysCommitInferencesOfFunctionCalls", true}; + ScopedFastInt sfi[] = {{"LuauIndentTypeMismatchMaxTypeLength", 10}}; + ScopedFastFlag sff[] = { + {"LuauAlwaysCommitInferencesOfFunctionCalls", true}, + {"LuauIndentTypeMismatch", true}, + }; + + CheckResult result = check(R"( local function f(s) @@ -3316,22 +3379,32 @@ TEST_CASE_FIXTURE(Fixture, "scalar_is_not_a_subtype_of_a_compatible_polymorphic_ LUAU_REQUIRE_ERROR_COUNT(3, result); - CHECK_EQ(R"(Type 'string' could not be converted into 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' + const std::string expected1 = + R"(Type 'string' could not be converted into 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' caused by: - The former's metatable does not satisfy the requirements. Table type 'typeof(string)' not compatible with type 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' because the former is missing field 'absolutely_no_scalar_has_this_method')", - toString(result.errors[0])); + The former's metatable does not satisfy the requirements. +Table type 'typeof(string)' not compatible with type 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' because the former is missing field 'absolutely_no_scalar_has_this_method')"; + CHECK_EQ(expected1, toString(result.errors[0])); - CHECK_EQ(R"(Type '"bar"' could not be converted into 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' -caused by: - The former's metatable does not satisfy the requirements. Table type 'typeof(string)' not compatible with type 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' because the former is missing field 'absolutely_no_scalar_has_this_method')", - toString(result.errors[1])); - CHECK_EQ(R"(Type '"bar" | "baz"' could not be converted into 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' + const std::string expected2 = + R"(Type '"bar"' could not be converted into 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' caused by: - Not all union options are compatible. Type '"bar"' could not be converted into 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' + The former's metatable does not satisfy the requirements. +Table type 'typeof(string)' not compatible with type 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' because the former is missing field 'absolutely_no_scalar_has_this_method')"; + CHECK_EQ(expected2, toString(result.errors[1])); + + const std::string expected3 = R"(Type + '"bar" | "baz"' +could not be converted into + 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' caused by: - The former's metatable does not satisfy the requirements. Table type 'typeof(string)' not compatible with type 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' because the former is missing field 'absolutely_no_scalar_has_this_method')", - toString(result.errors[2])); + Not all union options are compatible. +Type '"bar"' could not be converted into 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' +caused by: + The former's metatable does not satisfy the requirements. +Table type 'typeof(string)' not compatible with type 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' because the former is missing field 'absolutely_no_scalar_has_this_method')"; + CHECK_EQ(expected3, toString(result.errors[2])); } TEST_CASE_FIXTURE(Fixture, "a_free_shape_can_turn_into_a_scalar_if_it_is_compatible") @@ -3349,6 +3422,9 @@ TEST_CASE_FIXTURE(Fixture, "a_free_shape_can_turn_into_a_scalar_if_it_is_compati TEST_CASE_FIXTURE(Fixture, "a_free_shape_cannot_turn_into_a_scalar_if_it_is_not_compatible") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( local function f(s): string local foo = s:absolutely_no_scalar_has_this_method() @@ -3356,11 +3432,13 @@ TEST_CASE_FIXTURE(Fixture, "a_free_shape_cannot_turn_into_a_scalar_if_it_is_not_ end )"); - LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(R"(Type 't1 where t1 = {+ absolutely_no_scalar_has_this_method: (t1) -> (a, b...) +}' could not be converted into 'string' + const std::string expected = + R"(Type 't1 where t1 = {+ absolutely_no_scalar_has_this_method: (t1) -> (a, b...) +}' could not be converted into 'string' caused by: - The former's metatable does not satisfy the requirements. Table type 'typeof(string)' not compatible with type 't1 where t1 = {+ absolutely_no_scalar_has_this_method: (t1) -> (a, b...) +}' because the former is missing field 'absolutely_no_scalar_has_this_method')", - toString(result.errors[0])); + The former's metatable does not satisfy the requirements. +Table type 'typeof(string)' not compatible with type 't1 where t1 = {+ absolutely_no_scalar_has_this_method: (t1) -> (a, b...) +}' because the former is missing field 'absolutely_no_scalar_has_this_method')"; + LUAU_REQUIRE_ERROR_COUNT(1, result); + CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ("(t1) -> string where t1 = {+ absolutely_no_scalar_has_this_method: (t1) -> (a, b...) +}", toString(requireType("f"))); } diff --git a/tests/TypeInfer.test.cpp b/tests/TypeInfer.test.cpp index 4f40b386..cb7429aa 100644 --- a/tests/TypeInfer.test.cpp +++ b/tests/TypeInfer.test.cpp @@ -992,6 +992,9 @@ end TEST_CASE_FIXTURE(Fixture, "cli_50041_committing_txnlog_in_apollo_client_error") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( --!strict --!nolint @@ -1028,16 +1031,23 @@ TEST_CASE_FIXTURE(Fixture, "cli_50041_committing_txnlog_in_apollo_client_error") // unsound. LUAU_REQUIRE_ERROR_COUNT(1, result); - - CHECK_EQ( - R"(Type 'Policies' from 'MainModule' could not be converted into 'Policies' from 'MainModule' + const std::string expected = R"(Type 'Policies' from 'MainModule' could not be converted into 'Policies' from 'MainModule' caused by: - Property 'getStoreFieldName' is not compatible. Type '(Policies, FieldSpecifier & {| from: number? |}) -> (a, b...)' could not be converted into '(Policies, FieldSpecifier) -> string' + Property 'getStoreFieldName' is not compatible. +Type + '(Policies, FieldSpecifier & {| from: number? |}) -> (a, b...)' +could not be converted into + '(Policies, FieldSpecifier) -> string' caused by: - Argument #2 type is not compatible. Type 'FieldSpecifier' could not be converted into 'FieldSpecifier & {| from: number? |}' + Argument #2 type is not compatible. +Type + 'FieldSpecifier' +could not be converted into + 'FieldSpecifier & {| from: number? |}' caused by: - Not all intersection parts are compatible. Table type 'FieldSpecifier' not compatible with type '{| from: number? |}' because the former has extra field 'fieldName')", - toString(result.errors[0])); + Not all intersection parts are compatible. +Table type 'FieldSpecifier' not compatible with type '{| from: number? |}' because the former has extra field 'fieldName')"; + CHECK_EQ(expected, toString(result.errors[0])); } else { @@ -1205,8 +1215,6 @@ end TEST_CASE_FIXTURE(BuiltinsFixture, "typechecking_in_type_guards") { - ScopedFastFlag sff{"LuauTypecheckTypeguards", true}; - CheckResult result = check(R"( local a = type(foo) == 'nil' local b = typeof(foo) ~= 'nil' diff --git a/tests/TypeInfer.tryUnify.test.cpp b/tests/TypeInfer.tryUnify.test.cpp index 5656e871..7910cf6d 100644 --- a/tests/TypeInfer.tryUnify.test.cpp +++ b/tests/TypeInfer.tryUnify.test.cpp @@ -345,7 +345,9 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "metatables_unify_against_shape_of_free_table { ScopedFastFlag sff[] = { {"LuauTransitiveSubtyping", true}, + {"LuauIndentTypeMismatch", true}, }; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; TableType::Props freeProps{ {"foo", {builtinTypes->numberType}}, @@ -373,11 +375,13 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "metatables_unify_against_shape_of_free_table state.log.commit(); REQUIRE_EQ(state.errors.size(), 1); - - std::string expected = "Type '{ @metatable {| __index: {| foo: string |} |}, { } }' could not be converted into '{- foo: number -}'\n" - "caused by:\n" - " Type 'number' could not be converted into 'string'"; - CHECK_EQ(toString(state.errors[0]), expected); + const std::string expected = R"(Type + '{ @metatable {| __index: {| foo: string |} |}, { } }' +could not be converted into + '{- foo: number -}' +caused by: + Type 'number' could not be converted into 'string')"; + CHECK_EQ(expected, toString(state.errors[0])); } TEST_CASE_FIXTURE(TryUnifyFixture, "fuzz_tail_unification_issue") diff --git a/tests/TypeInfer.typePacks.cpp b/tests/TypeInfer.typePacks.cpp index d2ae166b..37aaea7a 100644 --- a/tests/TypeInfer.typePacks.cpp +++ b/tests/TypeInfer.typePacks.cpp @@ -872,6 +872,9 @@ type R = { m: F } TEST_CASE_FIXTURE(Fixture, "pack_tail_unification_check") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( local a: () -> (number, ...string) local b: () -> (number, ...boolean) @@ -879,9 +882,13 @@ a = b )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), R"(Type '() -> (number, ...boolean)' could not be converted into '() -> (number, ...string)' + const std::string expected = R"(Type + '() -> (number, ...boolean)' +could not be converted into + '() -> (number, ...string)' caused by: - Type 'boolean' could not be converted into 'string')"); + Type 'boolean' could not be converted into 'string')"; + CHECK_EQ(expected, toString(result.errors[0])); } // TODO: File a Jira about this diff --git a/tests/TypeInfer.unionTypes.test.cpp b/tests/TypeInfer.unionTypes.test.cpp index 3ab7bebb..271841e9 100644 --- a/tests/TypeInfer.unionTypes.test.cpp +++ b/tests/TypeInfer.unionTypes.test.cpp @@ -459,6 +459,8 @@ local oh : boolean = t.y TEST_CASE_FIXTURE(Fixture, "error_detailed_union_part") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( type X = { x: number } type Y = { y: number } @@ -471,9 +473,11 @@ local b: { w: number } = a )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), R"(Type 'X | Y | Z' could not be converted into '{| w: number |}' + const std::string expected = R"(Type 'X | Y | Z' could not be converted into '{| w: number |}' caused by: - Not all union options are compatible. Table type 'X' not compatible with type '{| w: number |}' because the former is missing field 'w')"); + Not all union options are compatible. +Table type 'X' not compatible with type '{| w: number |}' because the former is missing field 'w')"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "error_detailed_union_all") @@ -494,6 +498,8 @@ local a: XYZ = { w = 4 } TEST_CASE_FIXTURE(Fixture, "error_detailed_optional") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( type X = { x: number } @@ -501,9 +507,11 @@ local a: X? = { w = 4 } )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), R"(Type 'a' could not be converted into 'X?' + const std::string expected = R"(Type 'a' could not be converted into 'X?' caused by: - None of the union options are compatible. For example: Table type 'a' not compatible with type 'X' because the former is missing field 'x')"); + None of the union options are compatible. For example: +Table type 'a' not compatible with type 'X' because the former is missing field 'x')"; + CHECK_EQ(expected, toString(result.errors[0])); } // We had a bug where a cyclic union caused a stack overflow. @@ -524,6 +532,9 @@ TEST_CASE_FIXTURE(Fixture, "dont_allow_cyclic_unions_to_be_inferred") TEST_CASE_FIXTURE(BuiltinsFixture, "table_union_write_indirect") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( type A = { x: number, y: (number) -> string } | { z: number, y: (number) -> string } @@ -540,8 +551,11 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_union_write_indirect") LUAU_REQUIRE_ERROR_COUNT(1, result); // NOTE: union normalization will improve this message - CHECK_EQ(toString(result.errors[0]), - R"(Type '(string) -> number' could not be converted into '((number) -> string) | ((number) -> string)'; none of the union options are compatible)"); + const std::string expected = R"(Type + '(string) -> number' +could not be converted into + '((number) -> string) | ((number) -> string)'; none of the union options are compatible)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "union_true_and_false") @@ -606,6 +620,8 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_mentioning_generics") TEST_CASE_FIXTURE(Fixture, "union_of_functions_mentioning_generic_typepacks") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( function f() local x : (number, a...) -> (number?, a...) @@ -615,12 +631,17 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_mentioning_generic_typepacks") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), "Type '(number, a...) -> (number?, a...)' could not be converted into '((number) -> number) | ((number?, " - "a...) -> (number?, a...))'; none of the union options are compatible"); + const std::string expected = R"(Type + '(number, a...) -> (number?, a...)' +could not be converted into + '((number) -> number) | ((number?, a...) -> (number?, a...))'; none of the union options are compatible)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_arities") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( local x : (number) -> number? local y : ((number?) -> number) | ((number | string) -> nil) = x -- OK @@ -628,12 +649,18 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_arities") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), "Type '(number) -> number?' could not be converted into '((number) -> nil) | ((number, string?) -> " - "number)'; none of the union options are compatible"); + const std::string expected = R"(Type + '(number) -> number?' +could not be converted into + '((number) -> nil) | ((number, string?) -> number)'; none of the union options are compatible)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_arities") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( local x : () -> (number | string) local y : (() -> number) | (() -> string) = x -- OK @@ -641,12 +668,18 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_arities") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), "Type '() -> number | string' could not be converted into '(() -> (string, string)) | (() -> number)'; none " - "of the union options are compatible"); + const std::string expected = R"(Type + '() -> number | string' +could not be converted into + '(() -> (string, string)) | (() -> number)'; none of the union options are compatible)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_variadics") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( local x : (...nil) -> (...number?) local y : ((...string?) -> (...number)) | ((...number?) -> nil) = x -- OK @@ -654,12 +687,18 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_variadics") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), "Type '(...nil) -> (...number?)' could not be converted into '((...string?) -> (...number)) | ((...string?) " - "-> nil)'; none of the union options are compatible"); + const std::string expected = R"(Type + '(...nil) -> (...number?)' +could not be converted into + '((...string?) -> (...number)) | ((...string?) -> nil)'; none of the union options are compatible)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_variadics") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( local x : (number) -> () local y : ((number?) -> ()) | ((...number) -> ()) = x -- OK @@ -667,12 +706,18 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_variadics") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), - "Type '(number) -> ()' could not be converted into '((...number?) -> ()) | ((number?) -> ())'; none of the union options are compatible"); + const std::string expected = R"(Type + '(number) -> ()' +could not be converted into + '((...number?) -> ()) | ((number?) -> ())'; none of the union options are compatible)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_variadics") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( local x : () -> (number?, ...number) local y : (() -> (...number)) | (() -> nil) = x -- OK @@ -680,8 +725,11 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_variadics )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), "Type '() -> (number?, ...number)' could not be converted into '(() -> (...number)) | (() -> number)'; none " - "of the union options are compatible"); + const std::string expected = R"(Type + '() -> (number?, ...number)' +could not be converted into + '(() -> (...number)) | (() -> number)'; none of the union options are compatible)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "less_greedy_unification_with_union_types") diff --git a/tests/conformance/native.lua b/tests/conformance/native.lua index 6c0e0e0e..ed985c49 100644 --- a/tests/conformance/native.lua +++ b/tests/conformance/native.lua @@ -14,6 +14,17 @@ assert((function(x, y) return c, b, t, t1, t2 end)(5, 10) == 50) +assert((function(x) + local oops -- split to prevent inlining + function oops() + end + + -- x is checked to be a number here; we can not execute a reentry from oops() because optimizer assumes this holds until return + local y = math.abs(x) + oops() + return y * x +end)("42") == 1764) + local function fuzzfail1(...) repeat _ = nil diff --git a/tests/conformance/native_types.lua b/tests/conformance/native_types.lua index 67779230..c375ab81 100644 --- a/tests/conformance/native_types.lua +++ b/tests/conformance/native_types.lua @@ -68,5 +68,16 @@ ecall(checkuserdata, 2) call(checkvector, vector(1, 2, 3)) ecall(checkvector, 2) +local function mutation_causes_bad_exit(a: number, count: number, sum: number) + repeat + a = 's' + sum += count + pcall(function() end) + count -= 1 + until count == 0 + return sum +end + +assert(call(mutation_causes_bad_exit, 5, 10, 0) == 55) return('OK') diff --git a/tests/main.cpp b/tests/main.cpp index cfa1f9db..9435c61a 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -161,8 +161,10 @@ struct BoostLikeReporter : doctest::IReporter } void log_message(const doctest::MessageData& md) override - { // - printf("%s(%d): ERROR: %s\n", md.m_file, md.m_line, md.m_string.c_str()); + { + const char* severity = (md.m_severity & doctest::assertType::is_warn) ? "WARNING" : "ERROR"; + + printf("%s(%d): %s: %s\n", md.m_file, md.m_line, severity, md.m_string.c_str()); } // called when a test case is skipped either because it doesn't pass the filters, has a skip decorator From a2a47104c8d8c2fa58222568c0011ba035b29c82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Bar=C4=87?= Date: Mon, 14 Aug 2023 15:10:56 +0200 Subject: [PATCH 05/20] LinterConfig.h: add missing stdint.h include (#1010) --- Analysis/include/Luau/LinterConfig.h | 1 + 1 file changed, 1 insertion(+) diff --git a/Analysis/include/Luau/LinterConfig.h b/Analysis/include/Luau/LinterConfig.h index 6bda930a..0d3b3b91 100644 --- a/Analysis/include/Luau/LinterConfig.h +++ b/Analysis/include/Luau/LinterConfig.h @@ -5,6 +5,7 @@ #include #include +#include namespace Luau { From e25b0a62754e9f223784dc3b41aae4dc0dca501f Mon Sep 17 00:00:00 2001 From: Andy Friesen Date: Fri, 18 Aug 2023 11:15:41 -0700 Subject: [PATCH 06/20] Sync to upstream/release/591 (#1012) * Fix a use-after-free bug in the new type cloning algorithm * Tighten up the type of `coroutine.wrap`. It is now `(f: (A...) -> R...) -> ((A...) -> R...)` * Break `.luaurc` out into a separate library target `Luau.Config`. This makes it easier for applications to reason about config files without also depending on the type inference engine. * Move typechecking limits into `FrontendOptions`. This allows embedders more finely-grained control over autocomplete's internal time limits. * Fix stability issue with debugger onprotectederror callback allowing break in non-yieldable contexts New solver: * Initial work toward [Local Type Inference](https://github.com/Roblox/luau/blob/0e1082108fd6fb3a32dfdf5f1766ea3fc1391328/rfcs/local-type-inference.md) * Introduce a new subtyping test. This will be much nicer than the old test because it is completely separate both from actual type inference and from error reporting. Native code generation: * Added function to compute iterated dominance frontier * Optimize barriers in SET_UPVALUE when tag is known * Cache lua_State::global in a register on A64 * Optimize constant stores in A64 lowering * Track table array size state to optimize array size checks * Add split tag/value store into a VM register * Check that spills can outlive the block only in specific conditions --------- Co-authored-by: Arseny Kapoulkine Co-authored-by: Vyacheslav Egorov --- Analysis/include/Luau/ConstraintSolver.h | 2 - Analysis/include/Luau/Frontend.h | 6 + Analysis/include/Luau/Module.h | 1 + Analysis/include/Luau/Subtyping.h | 63 ++++ Analysis/include/Luau/Type.h | 9 + Analysis/include/Luau/TypeUtils.h | 24 ++ Analysis/src/Autocomplete.cpp | 3 +- Analysis/src/Clone.cpp | 37 +- Analysis/src/ConstraintSolver.cpp | 35 -- Analysis/src/EmbeddedBuiltinDefinitions.cpp | 3 +- Analysis/src/Frontend.cpp | 119 ++++-- Analysis/src/Simplify.cpp | 12 +- Analysis/src/Subtyping.cpp | 344 +++++++++++++++++ Analysis/src/Type.cpp | 17 +- CMakeLists.txt | 7 +- CodeGen/include/Luau/IrAnalysis.h | 41 ++ CodeGen/include/Luau/IrData.h | 17 +- CodeGen/include/Luau/IrUtils.h | 1 - CodeGen/src/CodeGenA64.cpp | 5 +- CodeGen/src/CodeGenLower.h | 2 +- CodeGen/src/EmitCommonA64.h | 19 +- CodeGen/src/EmitCommonX64.h | 5 - CodeGen/src/IrAnalysis.cpp | 74 ++++ CodeGen/src/IrBuilder.cpp | 2 + CodeGen/src/IrDump.cpp | 6 +- CodeGen/src/IrLoweringA64.cpp | 155 +++++--- CodeGen/src/IrLoweringA64.h | 2 +- CodeGen/src/IrLoweringX64.cpp | 75 +++- CodeGen/src/IrLoweringX64.h | 2 +- CodeGen/src/IrRegAllocA64.cpp | 5 - CodeGen/src/IrRegAllocA64.h | 2 - CodeGen/src/IrRegAllocX64.cpp | 7 +- CodeGen/src/IrTranslation.cpp | 15 +- CodeGen/src/IrTranslation.h | 35 -- CodeGen/src/IrUtils.cpp | 14 +- CodeGen/src/IrValueLocationTracking.cpp | 1 + CodeGen/src/OptimizeConstProp.cpp | 204 +++++++--- Common/include/Luau/BytecodeUtils.h | 42 +++ Compiler/include/Luau/BytecodeBuilder.h | 1 + Compiler/src/BytecodeBuilder.cpp | 72 ++-- Compiler/src/Compiler.cpp | 15 +- Compiler/src/CostModel.cpp | 10 +- {Analysis => Config}/include/Luau/Config.h | 0 .../include/Luau/LinterConfig.h | 2 + {Analysis => Config}/src/Config.cpp | 0 {Analysis => Config}/src/LinterConfig.cpp | 0 Makefile | 20 +- Sources.cmake | 39 +- VM/src/ldo.cpp | 6 +- VM/src/lmathlib.cpp | 6 +- tests/Autocomplete.test.cpp | 2 - tests/Compiler.test.cpp | 2 - tests/Conformance.test.cpp | 94 ++++- tests/CostModel.test.cpp | 4 - tests/Error.test.cpp | 21 ++ tests/IrBuilder.test.cpp | 189 +++++++++- tests/Module.test.cpp | 38 +- tests/Subtyping.test.cpp | 352 ++++++++++++++++++ tests/TypeInfer.functions.test.cpp | 65 ++++ tests/TypeInfer.oop.test.cpp | 2 +- tests/conformance/native.lua | 18 + tests/conformance/tmerror.lua | 26 ++ tools/faillist.txt | 3 + 63 files changed, 1976 insertions(+), 424 deletions(-) create mode 100644 Analysis/include/Luau/Subtyping.h create mode 100644 Analysis/src/Subtyping.cpp create mode 100644 Common/include/Luau/BytecodeUtils.h rename {Analysis => Config}/include/Luau/Config.h (100%) rename {Analysis => Config}/include/Luau/LinterConfig.h (99%) rename {Analysis => Config}/src/Config.cpp (100%) rename {Analysis => Config}/src/LinterConfig.cpp (100%) create mode 100644 tests/Subtyping.test.cpp diff --git a/Analysis/include/Luau/ConstraintSolver.h b/Analysis/include/Luau/ConstraintSolver.h index 47effcea..76520b2c 100644 --- a/Analysis/include/Luau/ConstraintSolver.h +++ b/Analysis/include/Luau/ConstraintSolver.h @@ -279,8 +279,6 @@ private: TypeId errorRecoveryType() const; TypePackId errorRecoveryTypePack() const; - TypeId unionOfTypes(TypeId a, TypeId b, NotNull scope, bool unifyFreeTypes); - TypePackId anyifyModuleReturnTypePackGenerics(TypePackId tp); void throwTimeLimitError(); diff --git a/Analysis/include/Luau/Frontend.h b/Analysis/include/Luau/Frontend.h index 5853eb32..3404c6a2 100644 --- a/Analysis/include/Luau/Frontend.h +++ b/Analysis/include/Luau/Frontend.h @@ -100,6 +100,12 @@ struct FrontendOptions std::optional enabledLintWarnings; std::shared_ptr cancellationToken; + + // Time limit for typechecking a single module + std::optional moduleTimeLimitSec; + + // When true, some internal complexity limits will be scaled down for modules that miss the limit set by moduleTimeLimitSec + bool applyInternalLimitScaling = false; }; struct CheckResult diff --git a/Analysis/include/Luau/Module.h b/Analysis/include/Luau/Module.h index cb761714..d647750f 100644 --- a/Analysis/include/Luau/Module.h +++ b/Analysis/include/Luau/Module.h @@ -111,6 +111,7 @@ struct Module LintResult lintResult; Mode mode; SourceCode::Type type; + double checkDurationSec = 0.0; bool timeout = false; bool cancelled = false; diff --git a/Analysis/include/Luau/Subtyping.h b/Analysis/include/Luau/Subtyping.h new file mode 100644 index 00000000..9ebdfc16 --- /dev/null +++ b/Analysis/include/Luau/Subtyping.h @@ -0,0 +1,63 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#pragma once + +#include "Luau/Type.h" + +#include +#include + +namespace Luau +{ + +template +struct TryPair; + +class Normalizer; +struct NormalizedType; + +struct SubtypingGraph +{ + // Did the test succeed? + bool isSubtype = false; + bool isErrorSuppressing = false; + bool normalizationTooComplex = false; + + // If so, what constraints are implied by this relation? + // If not, what happened? + + SubtypingGraph and_(const SubtypingGraph& other); + SubtypingGraph or_(const SubtypingGraph& other); + + static SubtypingGraph and_(const std::vector& results); + static SubtypingGraph or_(const std::vector& results); +}; + +struct Subtyping +{ + NotNull builtinTypes; + NotNull normalizer; + + // TODO cache + // TODO cyclic types + // TODO recursion limits + + SubtypingGraph isSubtype(TypeId subTy, TypeId superTy); + SubtypingGraph isSubtype(TypePackId subTy, TypePackId superTy); + +private: + template + SubtypingGraph isSubtype(const TryPair& pair); + + SubtypingGraph isSubtype(TypeId subTy, const UnionType* superUnion); + SubtypingGraph isSubtype(const UnionType* subUnion, TypeId superTy); + SubtypingGraph isSubtype(TypeId subTy, const IntersectionType* superIntersection); + SubtypingGraph isSubtype(const IntersectionType* subIntersection, TypeId superTy); + SubtypingGraph isSubtype(const PrimitiveType* subPrim, const PrimitiveType* superPrim); + SubtypingGraph isSubtype(const SingletonType* subSingleton, const PrimitiveType* superPrim); + SubtypingGraph isSubtype(const SingletonType* subSingleton, const SingletonType* superSingleton); + SubtypingGraph isSubtype(const FunctionType* subFunction, const FunctionType* superFunction); + + SubtypingGraph isSubtype(const NormalizedType* subNorm, const NormalizedType* superNorm); +}; + +} // namespace Luau diff --git a/Analysis/include/Luau/Type.h b/Analysis/include/Luau/Type.h index c152fc02..cc88d54b 100644 --- a/Analysis/include/Luau/Type.h +++ b/Analysis/include/Luau/Type.h @@ -733,9 +733,17 @@ struct Type final using SeenSet = std::set>; bool areEqual(SeenSet& seen, const Type& lhs, const Type& rhs); +enum class FollowOption +{ + Normal, + DisableLazyTypeThunks, +}; + // Follow BoundTypes until we get to something real TypeId follow(TypeId t); +TypeId follow(TypeId t, FollowOption followOption); TypeId follow(TypeId t, const void* context, TypeId (*mapper)(const void*, TypeId)); +TypeId follow(TypeId t, FollowOption followOption, const void* context, TypeId (*mapper)(const void*, TypeId)); std::vector flattenIntersection(TypeId ty); @@ -818,6 +826,7 @@ public: const TypeId optionalNumberType; const TypeId optionalStringType; + const TypePackId emptyTypePack; const TypePackId anyTypePack; const TypePackId neverTypePack; const TypePackId uninhabitableTypePack; diff --git a/Analysis/include/Luau/TypeUtils.h b/Analysis/include/Luau/TypeUtils.h index 793415ee..c360e4bc 100644 --- a/Analysis/include/Luau/TypeUtils.h +++ b/Analysis/include/Luau/TypeUtils.h @@ -101,6 +101,30 @@ ErrorSuppression shouldSuppressErrors(NotNull normalizer, TypeId ty1 */ ErrorSuppression shouldSuppressErrors(NotNull normalizer, TypePackId tp1, TypePackId tp2); +// Similar to `std::optional>`, but whose `sizeof()` is the same as `std::pair` +// and cooperates with C++'s `if (auto p = ...)` syntax without the extra fatness of `std::optional`. +template +struct TryPair { + A first; + B second; + + operator bool() const + { + return bool(first) && bool(second); + } +}; + +template +TryPair get2(Ty one, Ty two) +{ + const A* a = get(one); + const B* b = get(two); + if (a && b) + return {a, b}; + else + return {nullptr, nullptr}; +} + template const T* get(std::optional ty) { diff --git a/Analysis/src/Autocomplete.cpp b/Analysis/src/Autocomplete.cpp index eaf47b77..9b6f4db7 100644 --- a/Analysis/src/Autocomplete.cpp +++ b/Analysis/src/Autocomplete.cpp @@ -13,7 +13,6 @@ #include LUAU_FASTFLAG(DebugLuauReadWriteProperties) -LUAU_FASTFLAGVARIABLE(LuauDisableCompletionOutsideQuotes, false) LUAU_FASTFLAGVARIABLE(LuauAnonymousAutofilled1, false); LUAU_FASTFLAGVARIABLE(LuauAutocompleteLastTypecheck, false) LUAU_FASTFLAGVARIABLE(LuauAutocompleteHideSelfArg, false) @@ -1345,7 +1344,7 @@ static std::optional autocompleteStringParams(const Source return std::nullopt; } - if (FFlag::LuauDisableCompletionOutsideQuotes && !nodes.back()->is()) + if (!nodes.back()->is()) { if (nodes.back()->location.end == position || nodes.back()->location.begin == position) { diff --git a/Analysis/src/Clone.cpp b/Analysis/src/Clone.cpp index 66bd5e8e..ad96527e 100644 --- a/Analysis/src/Clone.cpp +++ b/Analysis/src/Clone.cpp @@ -4,6 +4,7 @@ #include "Luau/NotNull.h" #include "Luau/RecursionCounter.h" #include "Luau/TxnLog.h" +#include "Luau/Type.h" #include "Luau/TypePack.h" #include "Luau/Unifiable.h" @@ -14,7 +15,7 @@ LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution) LUAU_FASTINTVARIABLE(LuauTypeCloneRecursionLimit, 300) LUAU_FASTFLAGVARIABLE(LuauCloneCyclicUnions, false) -LUAU_FASTFLAGVARIABLE(LuauStacklessTypeClone, false) +LUAU_FASTFLAGVARIABLE(LuauStacklessTypeClone2, false) LUAU_FASTINTVARIABLE(LuauTypeCloneIterationLimit, 100'000) namespace Luau @@ -115,6 +116,7 @@ private: std::optional find(TypeId ty) const { + ty = follow(ty, FollowOption::DisableLazyTypeThunks); if (auto it = types->find(ty); it != types->end()) return it->second; return std::nullopt; @@ -122,6 +124,7 @@ private: std::optional find(TypePackId tp) const { + tp = follow(tp); if (auto it = packs->find(tp); it != packs->end()) return it->second; return std::nullopt; @@ -143,24 +146,17 @@ private: private: TypeId shallowClone(TypeId ty) { + // We want to [`Luau::follow`] but without forcing the expansion of [`LazyType`]s. + ty = follow(ty, FollowOption::DisableLazyTypeThunks); + if (auto clone = find(ty)) return *clone; else if (ty->persistent) return ty; - // We want to [`Luau::follow`] but without forcing the expansion of [`LazyType`]s. - TypeId target = nullptr; - if (auto bt = get(ty)) - target = bt->boundTo; - else if (auto tt = get(ty); tt && tt->boundTo) - target = *tt->boundTo; - else - { - target = arena->addType(ty->ty); - asMutable(target)->documentationSymbol = ty->documentationSymbol; - } + TypeId target = arena->addType(ty->ty); + asMutable(target)->documentationSymbol = ty->documentationSymbol; - LUAU_ASSERT(target); (*types)[ty] = target; queue.push_back(target); return target; @@ -168,18 +164,15 @@ private: TypePackId shallowClone(TypePackId tp) { + tp = follow(tp); + if (auto clone = find(tp)) return *clone; else if (tp->persistent) return tp; - TypePackId target; - if (auto btp = get(tp)) - target = btp->boundTo; - else - target = arena->addTypePack(tp->ty); + TypePackId target = arena->addTypePack(tp->ty); - LUAU_ASSERT(target); (*packs)[tp] = target; queue.push_back(target); return target; @@ -883,7 +876,7 @@ TypePackId clone(TypePackId tp, TypeArena& dest, CloneState& cloneState) if (tp->persistent) return tp; - if (FFlag::LuauStacklessTypeClone) + if (FFlag::LuauStacklessTypeClone2) { TypeCloner2 cloner{NotNull{&dest}, cloneState.builtinTypes, NotNull{&cloneState.seenTypes}, NotNull{&cloneState.seenTypePacks}}; return cloner.clone(tp); @@ -909,7 +902,7 @@ TypeId clone(TypeId typeId, TypeArena& dest, CloneState& cloneState) if (typeId->persistent) return typeId; - if (FFlag::LuauStacklessTypeClone) + if (FFlag::LuauStacklessTypeClone2) { TypeCloner2 cloner{NotNull{&dest}, cloneState.builtinTypes, NotNull{&cloneState.seenTypes}, NotNull{&cloneState.seenTypePacks}}; return cloner.clone(typeId); @@ -938,7 +931,7 @@ TypeId clone(TypeId typeId, TypeArena& dest, CloneState& cloneState) TypeFun clone(const TypeFun& typeFun, TypeArena& dest, CloneState& cloneState) { - if (FFlag::LuauStacklessTypeClone) + if (FFlag::LuauStacklessTypeClone2) { TypeCloner2 cloner{NotNull{&dest}, cloneState.builtinTypes, NotNull{&cloneState.seenTypes}, NotNull{&cloneState.seenTypePacks}}; diff --git a/Analysis/src/ConstraintSolver.cpp b/Analysis/src/ConstraintSolver.cpp index e1291eeb..78dc0d54 100644 --- a/Analysis/src/ConstraintSolver.cpp +++ b/Analysis/src/ConstraintSolver.cpp @@ -2727,41 +2727,6 @@ TypePackId ConstraintSolver::errorRecoveryTypePack() const return builtinTypes->errorRecoveryTypePack(); } -TypeId ConstraintSolver::unionOfTypes(TypeId a, TypeId b, NotNull scope, bool unifyFreeTypes) -{ - a = follow(a); - b = follow(b); - - if (unifyFreeTypes && (get(a) || get(b))) - { - Unifier u{normalizer, scope, Location{}, Covariant}; - u.enableNewSolver(); - u.tryUnify(b, a); - - if (u.errors.empty()) - { - u.log.commit(); - return a; - } - else - { - return builtinTypes->errorRecoveryType(builtinTypes->anyType); - } - } - - if (*a == *b) - return a; - - std::vector types = reduceUnion({a, b}); - if (types.empty()) - return builtinTypes->neverType; - - if (types.size() == 1) - return types[0]; - - return arena->addType(UnionType{types}); -} - TypePackId ConstraintSolver::anyifyModuleReturnTypePackGenerics(TypePackId tp) { tp = follow(tp); diff --git a/Analysis/src/EmbeddedBuiltinDefinitions.cpp b/Analysis/src/EmbeddedBuiltinDefinitions.cpp index dfc6ff07..65b04d62 100644 --- a/Analysis/src/EmbeddedBuiltinDefinitions.cpp +++ b/Analysis/src/EmbeddedBuiltinDefinitions.cpp @@ -148,8 +148,7 @@ declare coroutine: { resume: (co: thread, A...) -> (boolean, R...), running: () -> thread, status: (co: thread) -> "dead" | "running" | "normal" | "suspended", - -- FIXME: This technically returns a function, but we can't represent this yet. - wrap: (f: (A...) -> R...) -> any, + wrap: (f: (A...) -> R...) -> ((A...) -> R...), yield: (A...) -> R..., isyieldable: () -> boolean, close: (co: thread) -> (boolean, any) diff --git a/Analysis/src/Frontend.cpp b/Analysis/src/Frontend.cpp index 4dd815fb..c6c360b8 100644 --- a/Analysis/src/Frontend.cpp +++ b/Analysis/src/Frontend.cpp @@ -31,10 +31,11 @@ LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauTarjanChildLimit) LUAU_FASTFLAG(LuauInferInNoCheckMode) LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3, false) -LUAU_FASTINTVARIABLE(LuauAutocompleteCheckTimeoutMs, 100) +LUAU_FASTINTVARIABLE(LuauAutocompleteCheckTimeoutMs, 100) // TODO: Remove with FFlagLuauTypecheckLimitControls LUAU_FASTFLAGVARIABLE(DebugLuauDeferredConstraintResolution, false) LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson, false) LUAU_FASTFLAGVARIABLE(DebugLuauReadWriteProperties, false) +LUAU_FASTFLAGVARIABLE(LuauTypecheckLimitControls, false) namespace Luau { @@ -873,6 +874,14 @@ void Frontend::addBuildQueueItems(std::vector& items, std::vecto } } +static void applyInternalLimitScaling(SourceNode& sourceNode, const ModulePtr module, double limit) +{ + if (module->timeout) + sourceNode.autocompleteLimitsMult = sourceNode.autocompleteLimitsMult / 2.0; + else if (module->checkDurationSec < limit / 2.0) + sourceNode.autocompleteLimitsMult = std::min(sourceNode.autocompleteLimitsMult * 2.0, 1.0); +} + void Frontend::checkBuildQueueItem(BuildQueueItem& item) { SourceNode& sourceNode = *item.sourceNode; @@ -883,43 +892,85 @@ void Frontend::checkBuildQueueItem(BuildQueueItem& item) double timestamp = getTimestamp(); const std::vector& requireCycles = item.requireCycles; - if (item.options.forAutocomplete) + TypeCheckLimits typeCheckLimits; + + if (FFlag::LuauTypecheckLimitControls) { - double autocompleteTimeLimit = FInt::LuauAutocompleteCheckTimeoutMs / 1000.0; - - // The autocomplete typecheck is always in strict mode with DM awareness - // to provide better type information for IDE features - TypeCheckLimits typeCheckLimits; - - if (autocompleteTimeLimit != 0.0) - typeCheckLimits.finishTime = TimeTrace::getClock() + autocompleteTimeLimit; + if (item.options.moduleTimeLimitSec) + typeCheckLimits.finishTime = TimeTrace::getClock() + *item.options.moduleTimeLimitSec; else typeCheckLimits.finishTime = std::nullopt; // TODO: This is a dirty ad hoc solution for autocomplete timeouts // We are trying to dynamically adjust our existing limits to lower total typechecking time under the limit // so that we'll have type information for the whole file at lower quality instead of a full abort in the middle - if (FInt::LuauTarjanChildLimit > 0) - typeCheckLimits.instantiationChildLimit = std::max(1, int(FInt::LuauTarjanChildLimit * sourceNode.autocompleteLimitsMult)); - else - typeCheckLimits.instantiationChildLimit = std::nullopt; + if (item.options.applyInternalLimitScaling) + { + if (FInt::LuauTarjanChildLimit > 0) + typeCheckLimits.instantiationChildLimit = std::max(1, int(FInt::LuauTarjanChildLimit * sourceNode.autocompleteLimitsMult)); + else + typeCheckLimits.instantiationChildLimit = std::nullopt; - if (FInt::LuauTypeInferIterationLimit > 0) - typeCheckLimits.unifierIterationLimit = std::max(1, int(FInt::LuauTypeInferIterationLimit * sourceNode.autocompleteLimitsMult)); - else - typeCheckLimits.unifierIterationLimit = std::nullopt; + if (FInt::LuauTypeInferIterationLimit > 0) + typeCheckLimits.unifierIterationLimit = std::max(1, int(FInt::LuauTypeInferIterationLimit * sourceNode.autocompleteLimitsMult)); + else + typeCheckLimits.unifierIterationLimit = std::nullopt; + } typeCheckLimits.cancellationToken = item.options.cancellationToken; + } + if (item.options.forAutocomplete) + { + double autocompleteTimeLimit = FInt::LuauAutocompleteCheckTimeoutMs / 1000.0; + + if (!FFlag::LuauTypecheckLimitControls) + { + // The autocomplete typecheck is always in strict mode with DM awareness + // to provide better type information for IDE features + TypeCheckLimits typeCheckLimits; + + if (autocompleteTimeLimit != 0.0) + typeCheckLimits.finishTime = TimeTrace::getClock() + autocompleteTimeLimit; + else + typeCheckLimits.finishTime = std::nullopt; + + // TODO: This is a dirty ad hoc solution for autocomplete timeouts + // We are trying to dynamically adjust our existing limits to lower total typechecking time under the limit + // so that we'll have type information for the whole file at lower quality instead of a full abort in the middle + if (FInt::LuauTarjanChildLimit > 0) + typeCheckLimits.instantiationChildLimit = std::max(1, int(FInt::LuauTarjanChildLimit * sourceNode.autocompleteLimitsMult)); + else + typeCheckLimits.instantiationChildLimit = std::nullopt; + + if (FInt::LuauTypeInferIterationLimit > 0) + typeCheckLimits.unifierIterationLimit = std::max(1, int(FInt::LuauTypeInferIterationLimit * sourceNode.autocompleteLimitsMult)); + else + typeCheckLimits.unifierIterationLimit = std::nullopt; + + typeCheckLimits.cancellationToken = item.options.cancellationToken; + } + + // The autocomplete typecheck is always in strict mode with DM awareness to provide better type information for IDE features ModulePtr moduleForAutocomplete = check(sourceModule, Mode::Strict, requireCycles, environmentScope, /*forAutocomplete*/ true, /*recordJsonLog*/ false, typeCheckLimits); double duration = getTimestamp() - timestamp; - if (moduleForAutocomplete->timeout) - sourceNode.autocompleteLimitsMult = sourceNode.autocompleteLimitsMult / 2.0; - else if (duration < autocompleteTimeLimit / 2.0) - sourceNode.autocompleteLimitsMult = std::min(sourceNode.autocompleteLimitsMult * 2.0, 1.0); + if (FFlag::LuauTypecheckLimitControls) + { + moduleForAutocomplete->checkDurationSec = duration; + + if (item.options.moduleTimeLimitSec && item.options.applyInternalLimitScaling) + applyInternalLimitScaling(sourceNode, moduleForAutocomplete, *item.options.moduleTimeLimitSec); + } + else + { + if (moduleForAutocomplete->timeout) + sourceNode.autocompleteLimitsMult = sourceNode.autocompleteLimitsMult / 2.0; + else if (duration < autocompleteTimeLimit / 2.0) + sourceNode.autocompleteLimitsMult = std::min(sourceNode.autocompleteLimitsMult * 2.0, 1.0); + } item.stats.timeCheck += duration; item.stats.filesStrict += 1; @@ -928,13 +979,29 @@ void Frontend::checkBuildQueueItem(BuildQueueItem& item) return; } - TypeCheckLimits typeCheckLimits; - - typeCheckLimits.cancellationToken = item.options.cancellationToken; + if (!FFlag::LuauTypecheckLimitControls) + { + typeCheckLimits.cancellationToken = item.options.cancellationToken; + } ModulePtr module = check(sourceModule, mode, requireCycles, environmentScope, /*forAutocomplete*/ false, item.recordJsonLog, typeCheckLimits); - item.stats.timeCheck += getTimestamp() - timestamp; + if (FFlag::LuauTypecheckLimitControls) + { + double duration = getTimestamp() - timestamp; + + module->checkDurationSec = duration; + + if (item.options.moduleTimeLimitSec && item.options.applyInternalLimitScaling) + applyInternalLimitScaling(sourceNode, module, *item.options.moduleTimeLimitSec); + + item.stats.timeCheck += duration; + } + else + { + item.stats.timeCheck += getTimestamp() - timestamp; + } + item.stats.filesStrict += mode == Mode::Strict; item.stats.filesNonstrict += mode == Mode::Nonstrict; diff --git a/Analysis/src/Simplify.cpp b/Analysis/src/Simplify.cpp index 15b3b2c6..6519c6ff 100644 --- a/Analysis/src/Simplify.cpp +++ b/Analysis/src/Simplify.cpp @@ -2,10 +2,12 @@ #include "Luau/Simplify.h" +#include "Luau/Normalize.h" // TypeIds #include "Luau/RecursionCounter.h" #include "Luau/ToString.h" #include "Luau/TypeArena.h" -#include "Luau/Normalize.h" // TypeIds +#include "Luau/TypeUtils.h" + #include LUAU_FASTINT(LuauTypeReductionRecursionLimit) @@ -47,14 +49,6 @@ struct TypeSimplifier TypeId simplify(TypeId ty, DenseHashSet& seen); }; -template -static std::pair get2(TID one, TID two) -{ - const A* a = get(one); - const B* b = get(two); - return a && b ? std::make_pair(a, b) : std::make_pair(nullptr, nullptr); -} - // Match the exact type false|nil static bool isFalsyType(TypeId ty) { diff --git a/Analysis/src/Subtyping.cpp b/Analysis/src/Subtyping.cpp new file mode 100644 index 00000000..596890f9 --- /dev/null +++ b/Analysis/src/Subtyping.cpp @@ -0,0 +1,344 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details + +#include "Luau/Subtyping.h" + +#include "Luau/Common.h" +#include "Luau/Normalize.h" +#include "Luau/Type.h" +#include "Luau/TypePack.h" +#include "Luau/TypeUtils.h" + +#include + +namespace Luau +{ + +SubtypingGraph SubtypingGraph::and_(const SubtypingGraph& other) +{ + return SubtypingGraph{ + isSubtype && other.isSubtype, + // `||` is intentional here, we want to preserve error-suppressing flag. + isErrorSuppressing || other.isErrorSuppressing, + normalizationTooComplex || other.normalizationTooComplex, + }; +} + +SubtypingGraph SubtypingGraph::or_(const SubtypingGraph& other) +{ + return SubtypingGraph{ + isSubtype || other.isSubtype, + isErrorSuppressing || other.isErrorSuppressing, + normalizationTooComplex || other.normalizationTooComplex, + }; +} + +SubtypingGraph SubtypingGraph::and_(const std::vector& results) +{ + SubtypingGraph acc{true, false}; + for (const SubtypingGraph& current : results) + acc = acc.and_(current); + return acc; +} + +SubtypingGraph SubtypingGraph::or_(const std::vector& results) +{ + SubtypingGraph acc{false, false}; + for (const SubtypingGraph& current : results) + acc = acc.or_(current); + return acc; +} + +SubtypingGraph Subtyping::isSubtype(TypeId subTy, TypeId superTy) +{ + subTy = follow(subTy); + superTy = follow(superTy); + + // TODO: Do we care about returning a proof that this is error-suppressing? + // e.g. given `a | error <: a | error` where both operands are pointer equal, + // then should it also carry the information that it's error-suppressing? + // If it should, then `error <: error` should also do the same. + if (subTy == superTy) + return {true}; + + + if (auto superUnion = get(superTy)) + return isSubtype(subTy, superUnion); + else if (auto subUnion = get(subTy)) + return isSubtype(subUnion, superTy); + else if (auto superIntersection = get(superTy)) + return isSubtype(subTy, superIntersection); + else if (auto subIntersection = get(subTy)) + { + SubtypingGraph result = isSubtype(subIntersection, superTy); + if (result.isSubtype || result.isErrorSuppressing || result.normalizationTooComplex) + return result; + else + return isSubtype(normalizer->normalize(subTy), normalizer->normalize(superTy)); + } + else if (get(superTy)) + return {true}; // This is always true. + else if (get(subTy)) + { + // any = unknown | error, so we rewrite this to match. + // As per TAPL: A | B <: T iff A <: T && B <: T + return isSubtype(builtinTypes->unknownType, superTy).and_(isSubtype(builtinTypes->errorType, superTy)); + } + else if (auto superUnknown = get(superTy)) + { + LUAU_ASSERT(!get(subTy)); // TODO: replace with ice. + LUAU_ASSERT(!get(subTy)); // TODO: replace with ice. + LUAU_ASSERT(!get(subTy)); // TODO: replace with ice. + + bool errorSuppressing = get(subTy); + return {!errorSuppressing, errorSuppressing}; + } + else if (get(subTy)) + return {true}; + else if (get(superTy)) + return {false, true}; + else if (get(subTy)) + return {false, true}; + else if (auto p = get2(subTy, superTy)) + return isSubtype(p); + else if (auto p = get2(subTy, superTy)) + return isSubtype(p); + else if (auto p = get2(subTy, superTy)) + return isSubtype(p); + else if (auto p = get2(subTy, superTy)) + return isSubtype(p); + + return {false}; +} + +SubtypingGraph Subtyping::isSubtype(TypePackId subTp, TypePackId superTp) +{ + subTp = follow(subTp); + superTp = follow(superTp); + + auto [subHead, subTail] = flatten(subTp); + auto [superHead, superTail] = flatten(superTp); + + const size_t headSize = std::min(subHead.size(), superHead.size()); + + std::vector results; + results.reserve(std::max(subHead.size(), superHead.size()) + 1); + + // Match head types pairwise + + for (size_t i = 0; i < headSize; ++i) + { + results.push_back(isSubtype(subHead[i], superHead[i])); + if (!results.back().isSubtype) + return {false}; + } + + // Handle mismatched head sizes + + if (subHead.size() < superHead.size()) + { + if (subTail) + { + if (auto vt = get(*subTail)) + { + for (size_t i = headSize; i < superHead.size(); ++i) + { + results.push_back(isSubtype(vt->ty, superHead[i])); + } + } + else + LUAU_ASSERT(0); // TODO + } + else + return {false}; + } + else if (subHead.size() > superHead.size()) + { + if (superTail) + { + if (auto vt = get(*superTail)) + { + for (size_t i = headSize; i < subHead.size(); ++i) + { + results.push_back(isSubtype(subHead[i], vt->ty)); + } + } + else + LUAU_ASSERT(0); // TODO + } + else + return {false}; + } + else + { + // subHead and superHead are the same size. Nothing more must be done. + } + + // Handle tails + + if (subTail && superTail) + { + if (auto p = get2(*subTail, *superTail)) + { + results.push_back(isSubtype(p.first->ty, p.second->ty)); + } + else + LUAU_ASSERT(0); // TODO + } + else if (subTail) + { + if (get(*subTail)) + { + return {false}; + } + + LUAU_ASSERT(0); // TODO + } + else if (superTail) + { + if (get(*superTail)) + { + /* + * A variadic type pack ...T can be thought of as an infinite union of finite type packs. + * () | (T) | (T, T) | (T, T, T) | ... + * + * And, per TAPL: + * T <: A | B iff T <: A or T <: B + * + * All variadic type packs are therefore supertypes of the empty type pack. + */ + } + else + LUAU_ASSERT(0); // TODO + } + + return SubtypingGraph::and_(results); +} + +template +SubtypingGraph Subtyping::isSubtype(const TryPair& pair) +{ + return isSubtype(pair.first, pair.second); +} + +/* + * This is much simpler than the Unifier implementation because we don't + * actually care about potential "cross-talk" between union parts that match the + * left side. + * + * In fact, we're very limited in what we can do: If multiple choices match, but + * all of them have non-overlapping constraints, then we're stuck with an "or" + * conjunction of constraints. Solving this in the general case is quite + * difficult. + * + * For example, we cannot dispatch anything from this constraint: + * + * {x: number, y: string} <: {x: number, y: 'a} | {x: 'b, y: string} + * + * From this constraint, we can know that either string <: 'a or number <: 'b, + * but we don't know which! + * + * However: + * + * {x: number, y: string} <: {x: number, y: 'a} | {x: number, y: string} + * + * We can dispatch this constraint because there is no 'or' conjunction. One of + * the arms requires 0 matches. + * + * {x: number, y: string, z: boolean} | {x: number, y: 'a, z: 'b} | {x: number, + * y: string, z: 'b} + * + * Here, we have two matches. One asks for string ~ 'a and boolean ~ 'b. The + * other just asks for boolean ~ 'b. We can dispatch this and only commit + * boolean ~ 'b. This constraint does not teach us anything about 'a. + */ +SubtypingGraph Subtyping::isSubtype(TypeId subTy, const UnionType* superUnion) +{ + // As per TAPL: T <: A | B iff T <: A || T <: B + std::vector subtypings; + for (TypeId ty : superUnion) + subtypings.push_back(isSubtype(subTy, ty)); + return SubtypingGraph::or_(subtypings); +} + +SubtypingGraph Subtyping::isSubtype(const UnionType* subUnion, TypeId superTy) +{ + // As per TAPL: A | B <: T iff A <: T && B <: T + std::vector subtypings; + for (TypeId ty : subUnion) + subtypings.push_back(isSubtype(ty, superTy)); + return SubtypingGraph::and_(subtypings); +} + +SubtypingGraph Subtyping::isSubtype(TypeId subTy, const IntersectionType* superIntersection) +{ + // As per TAPL: T <: A & B iff T <: A && T <: B + std::vector subtypings; + for (TypeId ty : superIntersection) + subtypings.push_back(isSubtype(subTy, ty)); + return SubtypingGraph::and_(subtypings); +} + +SubtypingGraph Subtyping::isSubtype(const IntersectionType* subIntersection, TypeId superTy) +{ + // TODO: Semantic subtyping here. + // As per TAPL: A & B <: T iff A <: T || B <: T + std::vector subtypings; + for (TypeId ty : subIntersection) + subtypings.push_back(isSubtype(ty, superTy)); + return SubtypingGraph::or_(subtypings); +} + +SubtypingGraph Subtyping::isSubtype(const PrimitiveType* subPrim, const PrimitiveType* superPrim) +{ + return {subPrim->type == superPrim->type}; +} + +SubtypingGraph Subtyping::isSubtype(const SingletonType* subSingleton, const PrimitiveType* superPrim) +{ + if (get(subSingleton) && superPrim->type == PrimitiveType::String) + return {true}; + else if (get(subSingleton) && superPrim->type == PrimitiveType::Boolean) + return {true}; + else + return {false}; +} + +SubtypingGraph Subtyping::isSubtype(const SingletonType* subSingleton, const SingletonType* superSingleton) +{ + return {*subSingleton == *superSingleton}; +} + +SubtypingGraph Subtyping::isSubtype(const FunctionType* subFunction, const FunctionType* superFunction) +{ + SubtypingGraph argResult = isSubtype(superFunction->argTypes, subFunction->argTypes); + SubtypingGraph retResult = isSubtype(subFunction->retTypes, superFunction->retTypes); + + return argResult.and_(retResult); +} + +SubtypingGraph Subtyping::isSubtype(const NormalizedType* subNorm, const NormalizedType* superNorm) +{ + if (!subNorm || !superNorm) + return {false, true, true}; + + SubtypingGraph result{true}; + result = result.and_(isSubtype(subNorm->tops, superNorm->tops)); + result = result.and_(isSubtype(subNorm->booleans, superNorm->booleans)); + // isSubtype(subNorm->classes, superNorm->classes); + // isSubtype(subNorm->classes, superNorm->tables); + result = result.and_(isSubtype(subNorm->errors, superNorm->errors)); + result = result.and_(isSubtype(subNorm->nils, superNorm->nils)); + result = result.and_(isSubtype(subNorm->numbers, superNorm->numbers)); + result.isSubtype &= Luau::isSubtype(subNorm->strings, superNorm->strings); + // isSubtype(subNorm->strings, superNorm->tables); + result = result.and_(isSubtype(subNorm->threads, superNorm->threads)); + // isSubtype(subNorm->tables, superNorm->tables); + // isSubtype(subNorm->tables, superNorm->strings); + // isSubtype(subNorm->tables, superNorm->classes); + // isSubtype(subNorm->functions, superNorm->functions); + // isSubtype(subNorm->tyvars, superNorm->tyvars); + + return result; +} + +} // namespace Luau diff --git a/Analysis/src/Type.cpp b/Analysis/src/Type.cpp index fb72bc12..2590e4dc 100644 --- a/Analysis/src/Type.cpp +++ b/Analysis/src/Type.cpp @@ -69,14 +69,24 @@ static LUAU_NOINLINE TypeId unwrapLazy(LazyType* ltv) TypeId follow(TypeId t) { - return follow(t, nullptr, [](const void*, TypeId t) -> TypeId { + return follow(t, FollowOption::Normal); +} + +TypeId follow(TypeId t, FollowOption followOption) +{ + return follow(t, followOption, nullptr, [](const void*, TypeId t) -> TypeId { return t; }); } TypeId follow(TypeId t, const void* context, TypeId (*mapper)(const void*, TypeId)) { - auto advance = [context, mapper](TypeId ty) -> std::optional { + return follow(t, FollowOption::Normal, context, mapper); +} + +TypeId follow(TypeId t, FollowOption followOption, const void* context, TypeId (*mapper)(const void*, TypeId)) +{ + auto advance = [followOption, context, mapper](TypeId ty) -> std::optional { TypeId mapped = mapper(context, ty); if (auto btv = get>(mapped)) @@ -85,7 +95,7 @@ TypeId follow(TypeId t, const void* context, TypeId (*mapper)(const void*, TypeI if (auto ttv = get(mapped)) return ttv->boundTo; - if (auto ltv = getMutable(mapped)) + if (auto ltv = getMutable(mapped); ltv && followOption != FollowOption::DisableLazyTypeThunks) return unwrapLazy(ltv); return std::nullopt; @@ -945,6 +955,7 @@ BuiltinTypes::BuiltinTypes() , truthyType(arena->addType(Type{NegationType{falsyType}, /*persistent*/ true})) , optionalNumberType(arena->addType(Type{UnionType{{numberType, nilType}}, /*persistent*/ true})) , optionalStringType(arena->addType(Type{UnionType{{stringType, nilType}}, /*persistent*/ true})) + , emptyTypePack(arena->addTypePack(TypePackVar{TypePack{{}}, /*persistent*/ true})) , anyTypePack(arena->addTypePack(TypePackVar{VariadicTypePack{anyType}, /*persistent*/ true})) , neverTypePack(arena->addTypePack(TypePackVar{VariadicTypePack{neverType}, /*persistent*/ true})) , uninhabitableTypePack(arena->addTypePack(TypePackVar{TypePack{{neverType}, neverTypePack}, /*persistent*/ true})) diff --git a/CMakeLists.txt b/CMakeLists.txt index bc66a83d..f0f0497d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,6 +26,7 @@ project(Luau LANGUAGES CXX C) add_library(Luau.Common INTERFACE) add_library(Luau.Ast STATIC) add_library(Luau.Compiler STATIC) +add_library(Luau.Config STATIC) add_library(Luau.Analysis STATIC) add_library(Luau.CodeGen STATIC) add_library(Luau.VM STATIC) @@ -71,9 +72,13 @@ target_compile_features(Luau.Compiler PUBLIC cxx_std_17) target_include_directories(Luau.Compiler PUBLIC Compiler/include) target_link_libraries(Luau.Compiler PUBLIC Luau.Ast) +target_compile_features(Luau.Config PUBLIC cxx_std_17) +target_include_directories(Luau.Config PUBLIC Config/include) +target_link_libraries(Luau.Config PUBLIC Luau.Ast) + target_compile_features(Luau.Analysis PUBLIC cxx_std_17) target_include_directories(Luau.Analysis PUBLIC Analysis/include) -target_link_libraries(Luau.Analysis PUBLIC Luau.Ast) +target_link_libraries(Luau.Analysis PUBLIC Luau.Ast Luau.Config) target_compile_features(Luau.CodeGen PRIVATE cxx_std_17) target_include_directories(Luau.CodeGen PUBLIC CodeGen/include) diff --git a/CodeGen/include/Luau/IrAnalysis.h b/CodeGen/include/Luau/IrAnalysis.h index ca1eba62..5fcaf46b 100644 --- a/CodeGen/include/Luau/IrAnalysis.h +++ b/CodeGen/include/Luau/IrAnalysis.h @@ -4,6 +4,7 @@ #include "Luau/Common.h" #include +#include #include #include @@ -96,6 +97,46 @@ struct CfgInfo void computeCfgImmediateDominators(IrFunction& function); void computeCfgDominanceTreeChildren(IrFunction& function); +struct IdfContext +{ + struct BlockAndOrdering + { + uint32_t blockIdx; + BlockOrdering ordering; + + bool operator<(const BlockAndOrdering& rhs) const + { + if (ordering.depth != rhs.ordering.depth) + return ordering.depth < rhs.ordering.depth; + + return ordering.preOrder < rhs.ordering.preOrder; + } + }; + + // Using priority queue to work on nodes in the order from the bottom of the dominator tree to the top + // If the depth of keys is equal, DFS order is used to provide strong ordering + std::priority_queue queue; + std::vector worklist; + + struct IdfVisitMarks + { + bool seenInQueue = false; + bool seenInWorklist = false; + }; + + std::vector visits; + + std::vector idf; +}; + +// Compute iterated dominance frontier (IDF or DF+) for a variable, given the set of blocks where that variable is defined +// Providing a set of blocks where the variable is a live-in at the entry helps produce a pruned SSA form (inserted phi nodes will not be dead) +// +// 'Iterated' comes from the definition where we recompute the IDFn+1 = DF(S) while adding IDFn to S until a fixed point is reached +// Iterated dominance frontier has been shown to be equal to the set of nodes where phi instructions have to be inserted +void computeIteratedDominanceFrontierForDefs( + IdfContext& ctx, const IrFunction& function, const std::vector& defBlocks, const std::vector& liveInBlocks); + // Function used to update all CFG data void computeCfgInfo(IrFunction& function); diff --git a/CodeGen/include/Luau/IrData.h b/CodeGen/include/Luau/IrData.h index 1684b478..5ac5b2ac 100644 --- a/CodeGen/include/Luau/IrData.h +++ b/CodeGen/include/Luau/IrData.h @@ -53,12 +53,9 @@ enum class IrCmd : uint8_t // Load a TValue from memory // A: Rn or Kn or pointer (TValue) + // B: int (optional 'A' pointer offset) LOAD_TVALUE, - // Load a TValue from table node value - // A: pointer (LuaNode) - LOAD_NODE_VALUE_TV, // TODO: we should find a way to generalize LOAD_TVALUE - // Load current environment table LOAD_ENV, @@ -113,12 +110,15 @@ enum class IrCmd : uint8_t // Store a TValue into memory // A: Rn or pointer (TValue) // B: TValue + // C: int (optional 'A' pointer offset) STORE_TVALUE, - // Store a TValue into table node value - // A: pointer (LuaNode) - // B: TValue - STORE_NODE_VALUE_TV, // TODO: we should find a way to generalize STORE_TVALUE + // Store a pair of tag and value into memory + // A: Rn or pointer (TValue) + // B: tag (must be a constant) + // C: int/double/pointer + // D: int (optional 'A' pointer offset) + STORE_SPLIT_TVALUE, // Add/Sub two integers together // A, B: int @@ -356,6 +356,7 @@ enum class IrCmd : uint8_t // Store TValue from stack slot into a function upvalue // A: UPn // B: Rn + // C: tag/undef (tag of the value that was written) SET_UPVALUE, // Convert TValues into numbers for a numerical for loop diff --git a/CodeGen/include/Luau/IrUtils.h b/CodeGen/include/Luau/IrUtils.h index fe38cb90..9c077914 100644 --- a/CodeGen/include/Luau/IrUtils.h +++ b/CodeGen/include/Luau/IrUtils.h @@ -145,7 +145,6 @@ inline bool hasResult(IrCmd cmd) case IrCmd::LOAD_DOUBLE: case IrCmd::LOAD_INT: case IrCmd::LOAD_TVALUE: - case IrCmd::LOAD_NODE_VALUE_TV: case IrCmd::LOAD_ENV: case IrCmd::GET_ARR_ADDR: case IrCmd::GET_SLOT_NODE_ADDR: diff --git a/CodeGen/src/CodeGenA64.cpp b/CodeGen/src/CodeGenA64.cpp index 6271e376..2e268d26 100644 --- a/CodeGen/src/CodeGenA64.cpp +++ b/CodeGen/src/CodeGenA64.cpp @@ -225,6 +225,7 @@ static EntryLocations buildEntryFunction(AssemblyBuilderA64& build, UnwindBuilde build.stp(x19, x20, mem(sp, 16)); build.stp(x21, x22, mem(sp, 32)); build.stp(x23, x24, mem(sp, 48)); + build.str(x25, mem(sp, 64)); build.mov(x29, sp); // this is only necessary if we maintain frame pointers, which we do in the JIT for now @@ -235,6 +236,7 @@ static EntryLocations buildEntryFunction(AssemblyBuilderA64& build, UnwindBuilde // Setup native execution environment build.mov(rState, x0); build.mov(rNativeContext, x3); + build.ldr(rGlobalState, mem(x0, offsetof(lua_State, global))); build.ldr(rBase, mem(x0, offsetof(lua_State, base))); // L->base @@ -252,6 +254,7 @@ static EntryLocations buildEntryFunction(AssemblyBuilderA64& build, UnwindBuilde locations.epilogueStart = build.setLabel(); // Cleanup and exit + build.ldr(x25, mem(sp, 64)); build.ldp(x23, x24, mem(sp, 48)); build.ldp(x21, x22, mem(sp, 32)); build.ldp(x19, x20, mem(sp, 16)); @@ -262,7 +265,7 @@ static EntryLocations buildEntryFunction(AssemblyBuilderA64& build, UnwindBuilde // Our entry function is special, it spans the whole remaining code area unwind.startFunction(); - unwind.prologueA64(prologueSize, kStackSize, {x29, x30, x19, x20, x21, x22, x23, x24}); + unwind.prologueA64(prologueSize, kStackSize, {x29, x30, x19, x20, x21, x22, x23, x24, x25}); unwind.finishFunction(build.getLabelOffset(locations.start), kFullBlockFuncton); return locations; diff --git a/CodeGen/src/CodeGenLower.h b/CodeGen/src/CodeGenLower.h index e709a89c..171a8c0e 100644 --- a/CodeGen/src/CodeGenLower.h +++ b/CodeGen/src/CodeGenLower.h @@ -194,7 +194,7 @@ inline bool lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction& } } - lowering.finishBlock(); + lowering.finishBlock(block, nextBlock); if (options.includeIr) build.logAppend("#\n"); diff --git a/CodeGen/src/EmitCommonA64.h b/CodeGen/src/EmitCommonA64.h index 9e89b1c0..894570d9 100644 --- a/CodeGen/src/EmitCommonA64.h +++ b/CodeGen/src/EmitCommonA64.h @@ -31,23 +31,24 @@ namespace A64 // 1. Constant registers (only loaded during codegen entry) constexpr RegisterA64 rState = x19; // lua_State* L constexpr RegisterA64 rNativeContext = x20; // NativeContext* context +constexpr RegisterA64 rGlobalState = x21; // global_State* L->global // 2. Frame registers (reloaded when call frame changes; rBase is also reloaded after all calls that may reallocate stack) -constexpr RegisterA64 rConstants = x21; // TValue* k -constexpr RegisterA64 rClosure = x22; // Closure* cl -constexpr RegisterA64 rCode = x23; // Instruction* code -constexpr RegisterA64 rBase = x24; // StkId base +constexpr RegisterA64 rConstants = x22; // TValue* k +constexpr RegisterA64 rClosure = x23; // Closure* cl +constexpr RegisterA64 rCode = x24; // Instruction* code +constexpr RegisterA64 rBase = x25; // StkId base // Native code is as stackless as the interpreter, so we can place some data on the stack once and have it accessible at any point // See CodeGenA64.cpp for layout -constexpr unsigned kStashSlots = 8; // stashed non-volatile registers +constexpr unsigned kStashSlots = 9; // stashed non-volatile registers +constexpr unsigned kTempSlots = 1; // 8 bytes of temporary space, such luxury! constexpr unsigned kSpillSlots = 22; // slots for spilling temporary registers -constexpr unsigned kTempSlots = 2; // 16 bytes of temporary space, such luxury! -constexpr unsigned kStackSize = (kStashSlots + kSpillSlots + kTempSlots) * 8; +constexpr unsigned kStackSize = (kStashSlots + kTempSlots + kSpillSlots) * 8; -constexpr AddressA64 sSpillArea = mem(sp, kStashSlots * 8); -constexpr AddressA64 sTemporary = mem(sp, (kStashSlots + kSpillSlots) * 8); +constexpr AddressA64 sSpillArea = mem(sp, (kStashSlots + kTempSlots) * 8); +constexpr AddressA64 sTemporary = mem(sp, kStashSlots * 8); inline void emitUpdateBase(AssemblyBuilderA64& build) { diff --git a/CodeGen/src/EmitCommonX64.h b/CodeGen/src/EmitCommonX64.h index 888f537f..782f2084 100644 --- a/CodeGen/src/EmitCommonX64.h +++ b/CodeGen/src/EmitCommonX64.h @@ -114,11 +114,6 @@ inline OperandX64 luauNodeKeyTag(RegisterX64 node) return dword[node + offsetof(LuaNode, key) + kOffsetOfTKeyTagNext]; } -inline OperandX64 luauNodeValue(RegisterX64 node) -{ - return xmmword[node + offsetof(LuaNode, val)]; -} - inline void setLuauReg(AssemblyBuilderX64& build, RegisterX64 tmp, int ri, OperandX64 op) { LUAU_ASSERT(op.cat == CategoryX64::mem); diff --git a/CodeGen/src/IrAnalysis.cpp b/CodeGen/src/IrAnalysis.cpp index fa108af1..b29927bb 100644 --- a/CodeGen/src/IrAnalysis.cpp +++ b/CodeGen/src/IrAnalysis.cpp @@ -209,6 +209,7 @@ static void visitVmRegDefsUses(T& visitor, IrFunction& function, const IrBlock& case IrCmd::STORE_INT: case IrCmd::STORE_VECTOR: case IrCmd::STORE_TVALUE: + case IrCmd::STORE_SPLIT_TVALUE: visitor.maybeDef(inst.a); // Argument can also be a pointer value break; case IrCmd::CMP_ANY: @@ -897,6 +898,79 @@ void computeCfgDominanceTreeChildren(IrFunction& function) computeBlockOrdering(function, info.domOrdering, /* preOrder */ nullptr, /* postOrder */ nullptr); } +// This algorithm is based on 'A Linear Time Algorithm for Placing Phi-Nodes' [Vugranam C.Sreedhar] +// It uses the optimized form from LLVM that relies an implicit DJ-graph (join edges are edges of the CFG that are not part of the dominance tree) +void computeIteratedDominanceFrontierForDefs( + IdfContext& ctx, const IrFunction& function, const std::vector& defBlocks, const std::vector& liveInBlocks) +{ + LUAU_ASSERT(!function.cfg.domOrdering.empty()); + + LUAU_ASSERT(ctx.queue.empty()); + LUAU_ASSERT(ctx.worklist.empty()); + + ctx.idf.clear(); + + ctx.visits.clear(); + ctx.visits.resize(function.blocks.size()); + + for (uint32_t defBlock : defBlocks) + { + const BlockOrdering& ordering = function.cfg.domOrdering[defBlock]; + ctx.queue.push({defBlock, ordering}); + } + + while (!ctx.queue.empty()) + { + IdfContext::BlockAndOrdering root = ctx.queue.top(); + ctx.queue.pop(); + + LUAU_ASSERT(ctx.worklist.empty()); + ctx.worklist.push_back(root.blockIdx); + ctx.visits[root.blockIdx].seenInWorklist = true; + + while (!ctx.worklist.empty()) + { + uint32_t blockIdx = ctx.worklist.back(); + ctx.worklist.pop_back(); + + // Check if successor node is the node where dominance of the current root ends, making it a part of dominance frontier set + for (uint32_t succIdx : successors(function.cfg, blockIdx)) + { + const BlockOrdering& succOrdering = function.cfg.domOrdering[succIdx]; + + // Nodes in the DF of root always have a level that is less than or equal to the level of root + if (succOrdering.depth > root.ordering.depth) + continue; + + if (ctx.visits[succIdx].seenInQueue) + continue; + + ctx.visits[succIdx].seenInQueue = true; + + // Skip successor block if it doesn't have our variable as a live in there + if (std::find(liveInBlocks.begin(), liveInBlocks.end(), succIdx) == liveInBlocks.end()) + continue; + + ctx.idf.push_back(succIdx); + + // If block doesn't have its own definition of the variable, add it to the queue + if (std::find(defBlocks.begin(), defBlocks.end(), succIdx) == defBlocks.end()) + ctx.queue.push({succIdx, succOrdering}); + } + + // Add dominance tree children that haven't been processed yet to the worklist + for (uint32_t domChildIdx : domChildren(function.cfg, blockIdx)) + { + if (ctx.visits[domChildIdx].seenInWorklist) + continue; + + ctx.visits[domChildIdx].seenInWorklist = true; + ctx.worklist.push_back(domChildIdx); + } + } + } +} + void computeCfgInfo(IrFunction& function) { computeCfgBlockEdges(function); diff --git a/CodeGen/src/IrBuilder.cpp b/CodeGen/src/IrBuilder.cpp index 0dd6f3c6..d34dfb57 100644 --- a/CodeGen/src/IrBuilder.cpp +++ b/CodeGen/src/IrBuilder.cpp @@ -1,6 +1,8 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "Luau/IrBuilder.h" +#include "Luau/Bytecode.h" +#include "Luau/BytecodeUtils.h" #include "Luau/IrData.h" #include "Luau/IrUtils.h" diff --git a/CodeGen/src/IrDump.cpp b/CodeGen/src/IrDump.cpp index bda36582..50d5012e 100644 --- a/CodeGen/src/IrDump.cpp +++ b/CodeGen/src/IrDump.cpp @@ -89,8 +89,6 @@ const char* getCmdName(IrCmd cmd) return "LOAD_INT"; case IrCmd::LOAD_TVALUE: return "LOAD_TVALUE"; - case IrCmd::LOAD_NODE_VALUE_TV: - return "LOAD_NODE_VALUE_TV"; case IrCmd::LOAD_ENV: return "LOAD_ENV"; case IrCmd::GET_ARR_ADDR: @@ -113,8 +111,8 @@ const char* getCmdName(IrCmd cmd) return "STORE_VECTOR"; case IrCmd::STORE_TVALUE: return "STORE_TVALUE"; - case IrCmd::STORE_NODE_VALUE_TV: - return "STORE_NODE_VALUE_TV"; + case IrCmd::STORE_SPLIT_TVALUE: + return "STORE_SPLIT_TVALUE"; case IrCmd::ADD_INT: return "ADD_INT"; case IrCmd::SUB_INT: diff --git a/CodeGen/src/IrLoweringA64.cpp b/CodeGen/src/IrLoweringA64.cpp index 63d03135..03006e30 100644 --- a/CodeGen/src/IrLoweringA64.cpp +++ b/CodeGen/src/IrLoweringA64.cpp @@ -122,6 +122,7 @@ static void emitFallback(AssemblyBuilderA64& build, int offset, int pcpos) static void emitInvokeLibm1P(AssemblyBuilderA64& build, size_t func, int arg) { + LUAU_ASSERT(kTempSlots >= 1); build.ldr(d0, mem(rBase, arg * sizeof(TValue) + offsetof(TValue, value.n))); build.add(x0, sp, sTemporary.data); // sp-relative offset build.ldr(x1, mem(rNativeContext, uint32_t(func))); @@ -224,16 +225,12 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) case IrCmd::LOAD_TVALUE: { inst.regA64 = regs.allocReg(KindA64::q, index); - AddressA64 addr = tempAddr(inst.a, 0); + + int addrOffset = inst.b.kind != IrOpKind::None ? intOp(inst.b) : 0; + AddressA64 addr = tempAddr(inst.a, addrOffset); build.ldr(inst.regA64, addr); break; } - case IrCmd::LOAD_NODE_VALUE_TV: - { - inst.regA64 = regs.allocReg(KindA64::q, index); - build.ldr(inst.regA64, mem(regOp(inst.a), offsetof(LuaNode, val))); - break; - } case IrCmd::LOAD_ENV: inst.regA64 = regs.allocReg(KindA64::x, index); build.ldr(inst.regA64, mem(rClosure, offsetof(Closure, env))); @@ -322,10 +319,17 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) } case IrCmd::STORE_TAG: { - RegisterA64 temp = regs.allocTemp(KindA64::w); AddressA64 addr = tempAddr(inst.a, offsetof(TValue, tt)); - build.mov(temp, tagOp(inst.b)); - build.str(temp, addr); + if (tagOp(inst.b) == 0) + { + build.str(wzr, addr); + } + else + { + RegisterA64 temp = regs.allocTemp(KindA64::w); + build.mov(temp, tagOp(inst.b)); + build.str(temp, addr); + } break; } case IrCmd::STORE_POINTER: @@ -343,9 +347,16 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) } case IrCmd::STORE_INT: { - RegisterA64 temp = tempInt(inst.b); AddressA64 addr = tempAddr(inst.a, offsetof(TValue, value)); - build.str(temp, addr); + if (inst.b.kind == IrOpKind::Constant && intOp(inst.b) == 0) + { + build.str(wzr, addr); + } + else + { + RegisterA64 temp = tempInt(inst.b); + build.str(temp, addr); + } break; } case IrCmd::STORE_VECTOR: @@ -368,13 +379,48 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) } case IrCmd::STORE_TVALUE: { - AddressA64 addr = tempAddr(inst.a, 0); + int addrOffset = inst.c.kind != IrOpKind::None ? intOp(inst.c) : 0; + AddressA64 addr = tempAddr(inst.a, addrOffset); build.str(regOp(inst.b), addr); break; } - case IrCmd::STORE_NODE_VALUE_TV: - build.str(regOp(inst.b), mem(regOp(inst.a), offsetof(LuaNode, val))); + case IrCmd::STORE_SPLIT_TVALUE: + { + int addrOffset = inst.d.kind != IrOpKind::None ? intOp(inst.d) : 0; + + RegisterA64 tempt = regs.allocTemp(KindA64::w); + AddressA64 addrt = tempAddr(inst.a, offsetof(TValue, tt) + addrOffset); + build.mov(tempt, tagOp(inst.b)); + build.str(tempt, addrt); + + AddressA64 addr = tempAddr(inst.a, offsetof(TValue, value) + addrOffset); + + if (tagOp(inst.b) == LUA_TBOOLEAN) + { + if (inst.c.kind == IrOpKind::Constant) + { + // note: we reuse tag temp register as value for true booleans, and use built-in zero register for false values + LUAU_ASSERT(LUA_TBOOLEAN == 1); + build.str(intOp(inst.c) ? tempt : wzr, addr); + } + else + build.str(regOp(inst.c), addr); + } + else if (tagOp(inst.b) == LUA_TNUMBER) + { + RegisterA64 temp = tempDouble(inst.c); + build.str(temp, addr); + } + else if (isGCO(tagOp(inst.b))) + { + build.str(regOp(inst.c), addr); + } + else + { + LUAU_ASSERT(!"Unsupported instruction form"); + } break; + } case IrCmd::ADD_INT: inst.regA64 = regs.allocReuse(KindA64::w, index, {inst.a, inst.b}); if (inst.b.kind == IrOpKind::Constant && unsigned(intOp(inst.b)) <= AssemblyBuilderA64::kMaxImmediate) @@ -705,15 +751,14 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) build.ldr(x1, mem(rNativeContext, offsetof(NativeContext, luaH_getn))); build.blr(x1); inst.regA64 = regs.allocReg(KindA64::d, index); - build.scvtf(inst.regA64, x0); + build.scvtf(inst.regA64, w0); break; } case IrCmd::STRING_LEN: { - RegisterA64 reg = regOp(inst.a); inst.regA64 = regs.allocReg(KindA64::w, index); - build.ldr(inst.regA64, mem(reg, offsetof(TString, len))); + build.ldr(inst.regA64, mem(regOp(inst.a), offsetof(TString, len))); break; } case IrCmd::NEW_TABLE: @@ -774,8 +819,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) regs.spill(build, index, {temp1}); build.mov(x0, temp1); build.mov(w1, intOp(inst.b)); - build.ldr(x2, mem(rState, offsetof(lua_State, global))); - build.ldr(x2, mem(x2, offsetof(global_State, tmname) + intOp(inst.b) * sizeof(TString*))); + build.ldr(x2, mem(rGlobalState, offsetof(global_State, tmname) + intOp(inst.b) * sizeof(TString*))); build.ldr(x3, mem(rNativeContext, offsetof(NativeContext, luaT_gettm))); build.blr(x3); @@ -972,8 +1016,8 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) case IrCmd::CONCAT: regs.spill(build, index); build.mov(x0, rState); - build.mov(x1, uintOp(inst.b)); - build.mov(x2, vmRegOp(inst.a) + uintOp(inst.b) - 1); + build.mov(w1, uintOp(inst.b)); + build.mov(w2, vmRegOp(inst.a) + uintOp(inst.b) - 1); build.ldr(x3, mem(rNativeContext, offsetof(NativeContext, luaV_concat))); build.blr(x3); @@ -1016,21 +1060,24 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) build.ldr(temp3, mem(rBase, vmRegOp(inst.b) * sizeof(TValue))); build.str(temp3, temp2); - Label skip; - checkObjectBarrierConditions(build, temp1, temp2, vmRegOp(inst.b), /* ratag */ -1, skip); + if (inst.c.kind == IrOpKind::Undef || isGCO(tagOp(inst.c))) + { + Label skip; + checkObjectBarrierConditions(build, temp1, temp2, vmRegOp(inst.b), inst.c.kind == IrOpKind::Undef ? -1 : tagOp(inst.c), skip); - size_t spills = regs.spill(build, index, {temp1}); + size_t spills = regs.spill(build, index, {temp1}); - build.mov(x1, temp1); - build.mov(x0, rState); - build.ldr(x2, mem(rBase, vmRegOp(inst.b) * sizeof(TValue) + offsetof(TValue, value))); - build.ldr(x3, mem(rNativeContext, offsetof(NativeContext, luaC_barrierf))); - build.blr(x3); + build.mov(x1, temp1); + build.mov(x0, rState); + build.ldr(x2, mem(rBase, vmRegOp(inst.b) * sizeof(TValue) + offsetof(TValue, value))); + build.ldr(x3, mem(rNativeContext, offsetof(NativeContext, luaC_barrierf))); + build.blr(x3); - regs.restore(build, spills); // need to restore before skip so that registers are in a consistent state + regs.restore(build, spills); // need to restore before skip so that registers are in a consistent state - // note: no emitUpdateBase necessary because luaC_ barriers do not reallocate stack - build.setLabel(skip); + // note: no emitUpdateBase necessary because luaC_ barriers do not reallocate stack + build.setLabel(skip); + } break; } case IrCmd::PREPARE_FORN: @@ -1213,8 +1260,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) Label self; - build.ldr(x0, mem(rState, offsetof(lua_State, global))); - build.ldr(x0, mem(x0, offsetof(global_State, cb.interrupt))); + build.ldr(x0, mem(rGlobalState, offsetof(global_State, cb.interrupt))); build.cbnz(x0, self); Label next = build.setLabel(); @@ -1227,11 +1273,9 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) RegisterA64 temp1 = regs.allocTemp(KindA64::x); RegisterA64 temp2 = regs.allocTemp(KindA64::x); + LUAU_ASSERT(offsetof(global_State, totalbytes) == offsetof(global_State, GCthreshold) + 8); Label skip; - build.ldr(temp1, mem(rState, offsetof(lua_State, global))); - // TODO: totalbytes and GCthreshold loads can be fused with ldp - build.ldr(temp2, mem(temp1, offsetof(global_State, totalbytes))); - build.ldr(temp1, mem(temp1, offsetof(global_State, GCthreshold))); + build.ldp(temp1, temp2, mem(rGlobalState, offsetof(global_State, GCthreshold))); build.cmp(temp1, temp2); build.b(ConditionA64::UnsignedGreater, skip); @@ -1239,8 +1283,8 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) build.mov(x0, rState); build.mov(w1, 1); - build.ldr(x1, mem(rNativeContext, offsetof(NativeContext, luaC_step))); - build.blr(x1); + build.ldr(x2, mem(rNativeContext, offsetof(NativeContext, luaC_step))); + build.blr(x2); emitUpdateBase(build); @@ -1450,9 +1494,9 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) // clear extra variables since we might have more than two if (intOp(inst.b) > 2) { - build.mov(w0, LUA_TNIL); + LUAU_ASSERT(LUA_TNIL == 0); for (int i = 2; i < intOp(inst.b); ++i) - build.str(w0, mem(rBase, (vmRegOp(inst.a) + 3 + i) * sizeof(TValue) + offsetof(TValue, tt))); + build.str(wzr, mem(rBase, (vmRegOp(inst.a) + 3 + i) * sizeof(TValue) + offsetof(TValue, tt))); } // we use full iter fallback for now; in the future it could be worthwhile to accelerate array iteration here build.mov(x0, rState); @@ -1561,7 +1605,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) { emitAddOffset(build, x1, rCode, uintOp(inst.a) * sizeof(Instruction)); build.mov(x2, rBase); - build.mov(x3, vmRegOp(inst.b)); + build.mov(w3, vmRegOp(inst.b)); build.ldr(x4, mem(rNativeContext, offsetof(NativeContext, executeGETVARARGSMultRet))); build.blr(x4); @@ -1570,10 +1614,12 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) else { build.mov(x1, rBase); - build.mov(x2, vmRegOp(inst.b)); - build.mov(x3, intOp(inst.c)); + build.mov(w2, vmRegOp(inst.b)); + build.mov(w3, intOp(inst.c)); build.ldr(x4, mem(rNativeContext, offsetof(NativeContext, executeGETVARARGSConst))); build.blr(x4); + + // note: no emitUpdateBase necessary because executeGETVARARGSConst does not reallocate stack } break; case IrCmd::NEWCLOSURE: @@ -1790,13 +1836,12 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) { inst.regA64 = regs.allocReg(KindA64::x, index); - build.ldr(inst.regA64, mem(rState, offsetof(lua_State, global))); LUAU_ASSERT(sizeof(TString*) == 8); if (inst.a.kind == IrOpKind::Inst) - build.add(inst.regA64, inst.regA64, zextReg(regOp(inst.a)), 3); + build.add(inst.regA64, rGlobalState, zextReg(regOp(inst.a)), 3); else if (inst.a.kind == IrOpKind::Constant) - build.add(inst.regA64, inst.regA64, uint16_t(tagOp(inst.a)) * 8); + build.add(inst.regA64, rGlobalState, uint16_t(tagOp(inst.a)) * 8); else LUAU_ASSERT(!"Unsupported instruction form"); @@ -1836,9 +1881,17 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) regs.freeTempRegs(); } -void IrLoweringA64::finishBlock() +void IrLoweringA64::finishBlock(const IrBlock& curr, const IrBlock& next) { - regs.assertNoSpills(); + if (!regs.spills.empty()) + { + // If we have spills remaining, we have to immediately lower the successor block + for (uint32_t predIdx : predecessors(function.cfg, function.getBlockIndex(next))) + LUAU_ASSERT(predIdx == function.getBlockIndex(curr)); + + // And the next block cannot be a join block in cfg + LUAU_ASSERT(next.useCount == 1); + } } void IrLoweringA64::finishFunction() diff --git a/CodeGen/src/IrLoweringA64.h b/CodeGen/src/IrLoweringA64.h index 344f18b7..5134ceda 100644 --- a/CodeGen/src/IrLoweringA64.h +++ b/CodeGen/src/IrLoweringA64.h @@ -26,7 +26,7 @@ struct IrLoweringA64 IrLoweringA64(AssemblyBuilderA64& build, ModuleHelpers& helpers, IrFunction& function); void lowerInst(IrInst& inst, uint32_t index, const IrBlock& next); - void finishBlock(); + void finishBlock(const IrBlock& curr, const IrBlock& next); void finishFunction(); bool hasError() const; diff --git a/CodeGen/src/IrLoweringX64.cpp b/CodeGen/src/IrLoweringX64.cpp index 2ea48404..ad18b849 100644 --- a/CodeGen/src/IrLoweringX64.cpp +++ b/CodeGen/src/IrLoweringX64.cpp @@ -111,22 +111,21 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) build.mov(inst.regX64, luauRegValueInt(vmRegOp(inst.a))); break; case IrCmd::LOAD_TVALUE: + { inst.regX64 = regs.allocReg(SizeX64::xmmword, index); + int addrOffset = inst.b.kind != IrOpKind::None ? intOp(inst.b) : 0; + if (inst.a.kind == IrOpKind::VmReg) build.vmovups(inst.regX64, luauReg(vmRegOp(inst.a))); else if (inst.a.kind == IrOpKind::VmConst) build.vmovups(inst.regX64, luauConstant(vmConstOp(inst.a))); else if (inst.a.kind == IrOpKind::Inst) - build.vmovups(inst.regX64, xmmword[regOp(inst.a)]); + build.vmovups(inst.regX64, xmmword[regOp(inst.a) + addrOffset]); else LUAU_ASSERT(!"Unsupported instruction form"); break; - case IrCmd::LOAD_NODE_VALUE_TV: - inst.regX64 = regs.allocReg(SizeX64::xmmword, index); - - build.vmovups(inst.regX64, luauNodeValue(regOp(inst.a))); - break; + } case IrCmd::LOAD_ENV: inst.regX64 = regs.allocReg(SizeX64::qword, index); @@ -252,16 +251,59 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) storeDoubleAsFloat(luauRegValueVector(vmRegOp(inst.a), 2), inst.d); break; case IrCmd::STORE_TVALUE: + { + int addrOffset = inst.c.kind != IrOpKind::None ? intOp(inst.c) : 0; + if (inst.a.kind == IrOpKind::VmReg) build.vmovups(luauReg(vmRegOp(inst.a)), regOp(inst.b)); else if (inst.a.kind == IrOpKind::Inst) - build.vmovups(xmmword[regOp(inst.a)], regOp(inst.b)); + build.vmovups(xmmword[regOp(inst.a) + addrOffset], regOp(inst.b)); else LUAU_ASSERT(!"Unsupported instruction form"); break; - case IrCmd::STORE_NODE_VALUE_TV: - build.vmovups(luauNodeValue(regOp(inst.a)), regOp(inst.b)); + } + case IrCmd::STORE_SPLIT_TVALUE: + { + int addrOffset = inst.d.kind != IrOpKind::None ? intOp(inst.d) : 0; + + OperandX64 tagLhs = inst.a.kind == IrOpKind::Inst ? dword[regOp(inst.a) + offsetof(TValue, tt) + addrOffset] : luauRegTag(vmRegOp(inst.a)); + build.mov(tagLhs, tagOp(inst.b)); + + if (tagOp(inst.b) == LUA_TBOOLEAN) + { + OperandX64 valueLhs = + inst.a.kind == IrOpKind::Inst ? dword[regOp(inst.a) + offsetof(TValue, value) + addrOffset] : luauRegValueInt(vmRegOp(inst.a)); + build.mov(valueLhs, inst.c.kind == IrOpKind::Constant ? OperandX64(intOp(inst.c)) : regOp(inst.c)); + } + else if (tagOp(inst.b) == LUA_TNUMBER) + { + OperandX64 valueLhs = + inst.a.kind == IrOpKind::Inst ? qword[regOp(inst.a) + offsetof(TValue, value) + addrOffset] : luauRegValue(vmRegOp(inst.a)); + + if (inst.c.kind == IrOpKind::Constant) + { + ScopedRegX64 tmp{regs, SizeX64::xmmword}; + + build.vmovsd(tmp.reg, build.f64(doubleOp(inst.c))); + build.vmovsd(valueLhs, tmp.reg); + } + else + { + build.vmovsd(valueLhs, regOp(inst.c)); + } + } + else if (isGCO(tagOp(inst.b))) + { + OperandX64 valueLhs = + inst.a.kind == IrOpKind::Inst ? qword[regOp(inst.a) + offsetof(TValue, value) + addrOffset] : luauRegValue(vmRegOp(inst.a)); + build.mov(valueLhs, regOp(inst.c)); + } + else + { + LUAU_ASSERT(!"Unsupported instruction form"); + } break; + } case IrCmd::ADD_INT: { inst.regX64 = regs.allocRegOrReuse(SizeX64::dword, index, {inst.a}); @@ -951,7 +993,8 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) tmp1.free(); - callBarrierObject(regs, build, tmp2.release(), {}, vmRegOp(inst.b), /* ratag */ -1); + if (inst.c.kind == IrOpKind::Undef || isGCO(tagOp(inst.c))) + callBarrierObject(regs, build, tmp2.release(), {}, vmRegOp(inst.b), inst.c.kind == IrOpKind::Undef ? -1 : tagOp(inst.c)); break; } case IrCmd::PREPARE_FORN: @@ -1540,9 +1583,17 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) regs.freeLastUseRegs(inst, index); } -void IrLoweringX64::finishBlock() +void IrLoweringX64::finishBlock(const IrBlock& curr, const IrBlock& next) { - regs.assertNoSpills(); + if (!regs.spills.empty()) + { + // If we have spills remaining, we have to immediately lower the successor block + for (uint32_t predIdx : predecessors(function.cfg, function.getBlockIndex(next))) + LUAU_ASSERT(predIdx == function.getBlockIndex(curr)); + + // And the next block cannot be a join block in cfg + LUAU_ASSERT(next.useCount == 1); + } } void IrLoweringX64::finishFunction() diff --git a/CodeGen/src/IrLoweringX64.h b/CodeGen/src/IrLoweringX64.h index c32e3e61..a32e034d 100644 --- a/CodeGen/src/IrLoweringX64.h +++ b/CodeGen/src/IrLoweringX64.h @@ -28,7 +28,7 @@ struct IrLoweringX64 IrLoweringX64(AssemblyBuilderX64& build, ModuleHelpers& helpers, IrFunction& function); void lowerInst(IrInst& inst, uint32_t index, const IrBlock& next); - void finishBlock(); + void finishBlock(const IrBlock& curr, const IrBlock& next); void finishFunction(); bool hasError() const; diff --git a/CodeGen/src/IrRegAllocA64.cpp b/CodeGen/src/IrRegAllocA64.cpp index 02d7df98..5afcc8dd 100644 --- a/CodeGen/src/IrRegAllocA64.cpp +++ b/CodeGen/src/IrRegAllocA64.cpp @@ -411,11 +411,6 @@ void IrRegAllocA64::restoreReg(AssemblyBuilderA64& build, IrInst& inst) LUAU_ASSERT(!"Expected to find a spill record"); } -void IrRegAllocA64::assertNoSpills() const -{ - LUAU_ASSERT(spills.empty()); -} - IrRegAllocA64::Set& IrRegAllocA64::getSet(KindA64 kind) { switch (kind) diff --git a/CodeGen/src/IrRegAllocA64.h b/CodeGen/src/IrRegAllocA64.h index 854a9f10..ae3110d7 100644 --- a/CodeGen/src/IrRegAllocA64.h +++ b/CodeGen/src/IrRegAllocA64.h @@ -43,8 +43,6 @@ struct IrRegAllocA64 // Restores register for a single instruction; may not assign the previously used register! void restoreReg(AssemblyBuilderA64& build, IrInst& inst); - void assertNoSpills() const; - struct Set { // which registers are in the set that the allocator manages (initialized at construction) diff --git a/CodeGen/src/IrRegAllocX64.cpp b/CodeGen/src/IrRegAllocX64.cpp index b81aec8c..607c975f 100644 --- a/CodeGen/src/IrRegAllocX64.cpp +++ b/CodeGen/src/IrRegAllocX64.cpp @@ -54,7 +54,12 @@ RegisterX64 IrRegAllocX64::allocReg(SizeX64 size, uint32_t instIdx) // Out of registers, spill the value with the furthest next use const std::array& regInstUsers = size == SizeX64::xmmword ? xmmInstUsers : gprInstUsers; if (uint32_t furthestUseTarget = findInstructionWithFurthestNextUse(regInstUsers); furthestUseTarget != kInvalidInstIdx) - return takeReg(function.instructions[furthestUseTarget].regX64, instIdx); + { + RegisterX64 reg = function.instructions[furthestUseTarget].regX64; + reg.size = size; // Adjust size to the requested + + return takeReg(reg, instIdx); + } LUAU_ASSERT(!"Out of registers to allocate"); return noreg; diff --git a/CodeGen/src/IrTranslation.cpp b/CodeGen/src/IrTranslation.cpp index 363a1cdb..38922131 100644 --- a/CodeGen/src/IrTranslation.cpp +++ b/CodeGen/src/IrTranslation.cpp @@ -2,6 +2,7 @@ #include "IrTranslation.h" #include "Luau/Bytecode.h" +#include "Luau/BytecodeUtils.h" #include "Luau/IrBuilder.h" #include "Luau/IrUtils.h" @@ -526,7 +527,7 @@ void translateInstSetUpval(IrBuilder& build, const Instruction* pc, int pcpos) int ra = LUAU_INSN_A(*pc); int up = LUAU_INSN_B(*pc); - build.inst(IrCmd::SET_UPVALUE, build.vmUpvalue(up), build.vmReg(ra)); + build.inst(IrCmd::SET_UPVALUE, build.vmUpvalue(up), build.vmReg(ra), build.undef()); } void translateInstCloseUpvals(IrBuilder& build, const Instruction* pc) @@ -988,7 +989,7 @@ void translateInstGetTableKS(IrBuilder& build, const Instruction* pc, int pcpos) build.inst(IrCmd::CHECK_SLOT_MATCH, addrSlotEl, build.vmConst(aux), fallback); - IrOp tvn = build.inst(IrCmd::LOAD_NODE_VALUE_TV, addrSlotEl); + IrOp tvn = build.inst(IrCmd::LOAD_TVALUE, addrSlotEl, build.constInt(offsetof(LuaNode, val))); build.inst(IrCmd::STORE_TVALUE, build.vmReg(ra), tvn); IrOp next = build.blockAtInst(pcpos + 2); @@ -1017,7 +1018,7 @@ void translateInstSetTableKS(IrBuilder& build, const Instruction* pc, int pcpos) build.inst(IrCmd::CHECK_READONLY, vb, fallback); IrOp tva = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(ra)); - build.inst(IrCmd::STORE_NODE_VALUE_TV, addrSlotEl, tva); + build.inst(IrCmd::STORE_TVALUE, addrSlotEl, tva, build.constInt(offsetof(LuaNode, val))); build.inst(IrCmd::BARRIER_TABLE_FORWARD, vb, build.vmReg(ra), build.undef()); @@ -1040,7 +1041,7 @@ void translateInstGetGlobal(IrBuilder& build, const Instruction* pc, int pcpos) build.inst(IrCmd::CHECK_SLOT_MATCH, addrSlotEl, build.vmConst(aux), fallback); - IrOp tvn = build.inst(IrCmd::LOAD_NODE_VALUE_TV, addrSlotEl); + IrOp tvn = build.inst(IrCmd::LOAD_TVALUE, addrSlotEl, build.constInt(offsetof(LuaNode, val))); build.inst(IrCmd::STORE_TVALUE, build.vmReg(ra), tvn); IrOp next = build.blockAtInst(pcpos + 2); @@ -1064,7 +1065,7 @@ void translateInstSetGlobal(IrBuilder& build, const Instruction* pc, int pcpos) build.inst(IrCmd::CHECK_READONLY, env, fallback); IrOp tva = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(ra)); - build.inst(IrCmd::STORE_NODE_VALUE_TV, addrSlotEl, tva); + build.inst(IrCmd::STORE_TVALUE, addrSlotEl, tva, build.constInt(offsetof(LuaNode, val))); build.inst(IrCmd::BARRIER_TABLE_FORWARD, env, build.vmReg(ra), build.undef()); @@ -1136,7 +1137,7 @@ void translateInstNamecall(IrBuilder& build, const Instruction* pc, int pcpos) build.inst(IrCmd::STORE_POINTER, build.vmReg(ra + 1), table); build.inst(IrCmd::STORE_TAG, build.vmReg(ra + 1), build.constTag(LUA_TTABLE)); - IrOp nodeEl = build.inst(IrCmd::LOAD_NODE_VALUE_TV, addrNodeEl); + IrOp nodeEl = build.inst(IrCmd::LOAD_TVALUE, addrNodeEl, build.constInt(offsetof(LuaNode, val))); build.inst(IrCmd::STORE_TVALUE, build.vmReg(ra), nodeEl); build.inst(IrCmd::JUMP, next); @@ -1158,7 +1159,7 @@ void translateInstNamecall(IrBuilder& build, const Instruction* pc, int pcpos) build.inst(IrCmd::STORE_POINTER, build.vmReg(ra + 1), table2); build.inst(IrCmd::STORE_TAG, build.vmReg(ra + 1), build.constTag(LUA_TTABLE)); - IrOp indexNodeEl = build.inst(IrCmd::LOAD_NODE_VALUE_TV, addrIndexNodeEl); + IrOp indexNodeEl = build.inst(IrCmd::LOAD_TVALUE, addrIndexNodeEl, build.constInt(offsetof(LuaNode, val))); build.inst(IrCmd::STORE_TVALUE, build.vmReg(ra), indexNodeEl); build.inst(IrCmd::JUMP, next); diff --git a/CodeGen/src/IrTranslation.h b/CodeGen/src/IrTranslation.h index aff18b30..3736b44d 100644 --- a/CodeGen/src/IrTranslation.h +++ b/CodeGen/src/IrTranslation.h @@ -1,8 +1,6 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #pragma once -#include "Luau/Bytecode.h" - #include #include "ltm.h" @@ -67,38 +65,5 @@ void translateInstAndX(IrBuilder& build, const Instruction* pc, int pcpos, IrOp void translateInstOrX(IrBuilder& build, const Instruction* pc, int pcpos, IrOp c); void translateInstNewClosure(IrBuilder& build, const Instruction* pc, int pcpos); -inline int getOpLength(LuauOpcode op) -{ - switch (op) - { - case LOP_GETGLOBAL: - case LOP_SETGLOBAL: - case LOP_GETIMPORT: - case LOP_GETTABLEKS: - case LOP_SETTABLEKS: - case LOP_NAMECALL: - case LOP_JUMPIFEQ: - case LOP_JUMPIFLE: - case LOP_JUMPIFLT: - case LOP_JUMPIFNOTEQ: - case LOP_JUMPIFNOTLE: - case LOP_JUMPIFNOTLT: - case LOP_NEWTABLE: - case LOP_SETLIST: - case LOP_FORGLOOP: - case LOP_LOADKX: - case LOP_FASTCALL2: - case LOP_FASTCALL2K: - case LOP_JUMPXEQKNIL: - case LOP_JUMPXEQKB: - case LOP_JUMPXEQKN: - case LOP_JUMPXEQKS: - return 2; - - default: - return 1; - } -} - } // namespace CodeGen } // namespace Luau diff --git a/CodeGen/src/IrUtils.cpp b/CodeGen/src/IrUtils.cpp index c2d3e1a8..e5a55f11 100644 --- a/CodeGen/src/IrUtils.cpp +++ b/CodeGen/src/IrUtils.cpp @@ -32,7 +32,6 @@ IrValueKind getCmdValueKind(IrCmd cmd) case IrCmd::LOAD_INT: return IrValueKind::Int; case IrCmd::LOAD_TVALUE: - case IrCmd::LOAD_NODE_VALUE_TV: return IrValueKind::Tvalue; case IrCmd::LOAD_ENV: case IrCmd::GET_ARR_ADDR: @@ -46,7 +45,7 @@ IrValueKind getCmdValueKind(IrCmd cmd) case IrCmd::STORE_INT: case IrCmd::STORE_VECTOR: case IrCmd::STORE_TVALUE: - case IrCmd::STORE_NODE_VALUE_TV: + case IrCmd::STORE_SPLIT_TVALUE: return IrValueKind::None; case IrCmd::ADD_INT: case IrCmd::SUB_INT: @@ -216,6 +215,8 @@ void removeUse(IrFunction& function, IrOp op) bool isGCO(uint8_t tag) { + LUAU_ASSERT(tag < LUA_T_COUNT); + // mirrors iscollectable(o) from VM/lobject.h return tag >= LUA_TSTRING; } @@ -388,6 +389,7 @@ void applySubstitutions(IrFunction& function, IrInst& inst) bool compare(double a, double b, IrCondition cond) { + // Note: redundant bool() casts work around invalid MSVC optimization that merges cases in this switch, violating IEEE754 comparison semantics switch (cond) { case IrCondition::Equal: @@ -397,19 +399,19 @@ bool compare(double a, double b, IrCondition cond) case IrCondition::Less: return a < b; case IrCondition::NotLess: - return !(a < b); + return !bool(a < b); case IrCondition::LessEqual: return a <= b; case IrCondition::NotLessEqual: - return !(a <= b); + return !bool(a <= b); case IrCondition::Greater: return a > b; case IrCondition::NotGreater: - return !(a > b); + return !bool(a > b); case IrCondition::GreaterEqual: return a >= b; case IrCondition::NotGreaterEqual: - return !(a >= b); + return !bool(a >= b); default: LUAU_ASSERT(!"Unsupported condition"); } diff --git a/CodeGen/src/IrValueLocationTracking.cpp b/CodeGen/src/IrValueLocationTracking.cpp index 4536630b..e781bda3 100644 --- a/CodeGen/src/IrValueLocationTracking.cpp +++ b/CodeGen/src/IrValueLocationTracking.cpp @@ -28,6 +28,7 @@ void IrValueLocationTracking::beforeInstLowering(IrInst& inst) case IrCmd::STORE_INT: case IrCmd::STORE_VECTOR: case IrCmd::STORE_TVALUE: + case IrCmd::STORE_SPLIT_TVALUE: invalidateRestoreOp(inst.a); break; case IrCmd::ADJUST_STACK_TO_REG: diff --git a/CodeGen/src/OptimizeConstProp.cpp b/CodeGen/src/OptimizeConstProp.cpp index d0ecd7dd..9ef57afa 100644 --- a/CodeGen/src/OptimizeConstProp.cpp +++ b/CodeGen/src/OptimizeConstProp.cpp @@ -9,6 +9,7 @@ #include "lua.h" #include +#include #include LUAU_FASTINTVARIABLE(LuauCodeGenMinLinearBlockPath, 3) @@ -31,6 +32,7 @@ struct RegisterInfo bool knownNotReadonly = false; bool knownNoMetatable = false; + int knownTableArraySize = -1; }; // Load instructions are linked to target register to carry knowledge about the target @@ -95,6 +97,7 @@ struct ConstPropState info->value = value; info->knownNotReadonly = false; info->knownNoMetatable = false; + info->knownTableArraySize = -1; info->version++; } } @@ -112,6 +115,7 @@ struct ConstPropState reg.value = {}; reg.knownNotReadonly = false; reg.knownNoMetatable = false; + reg.knownTableArraySize = -1; } reg.version++; @@ -176,6 +180,7 @@ struct ConstPropState { reg.knownNotReadonly = false; reg.knownNoMetatable = false; + reg.knownTableArraySize = -1; } void invalidateUserCall() @@ -236,28 +241,66 @@ struct ConstPropState return IrInst{loadCmd, op}; } + uint32_t* getPreviousInstIndex(const IrInst& inst) + { + LUAU_ASSERT(useValueNumbering); + + if (uint32_t* prevIdx = valueMap.find(inst)) + { + // Previous load might have been removed as unused + if (function.instructions[*prevIdx].useCount != 0) + return prevIdx; + } + + return nullptr; + } + + uint32_t* getPreviousVersionedLoadIndex(IrCmd cmd, IrOp vmReg) + { + LUAU_ASSERT(vmReg.kind == IrOpKind::VmReg); + return getPreviousInstIndex(versionedVmRegLoad(cmd, vmReg)); + } + + std::pair getPreviousVersionedLoadForTag(uint8_t tag, IrOp vmReg) + { + if (useValueNumbering && !function.cfg.captured.regs.test(vmRegOp(vmReg))) + { + if (tag == LUA_TBOOLEAN) + { + if (uint32_t* prevIdx = getPreviousVersionedLoadIndex(IrCmd::LOAD_INT, vmReg)) + return std::make_pair(IrCmd::LOAD_INT, *prevIdx); + } + else if (tag == LUA_TNUMBER) + { + if (uint32_t* prevIdx = getPreviousVersionedLoadIndex(IrCmd::LOAD_DOUBLE, vmReg)) + return std::make_pair(IrCmd::LOAD_DOUBLE, *prevIdx); + } + else if (isGCO(tag)) + { + if (uint32_t* prevIdx = getPreviousVersionedLoadIndex(IrCmd::LOAD_POINTER, vmReg)) + return std::make_pair(IrCmd::LOAD_POINTER, *prevIdx); + } + } + + return std::make_pair(IrCmd::NOP, kInvalidInstIdx); + } + // Find existing value of the instruction that is exactly the same, or record current on for future lookups void substituteOrRecord(IrInst& inst, uint32_t instIdx) { if (!useValueNumbering) return; - if (uint32_t* prevIdx = valueMap.find(inst)) + if (uint32_t* prevIdx = getPreviousInstIndex(inst)) { - const IrInst& prev = function.instructions[*prevIdx]; - - // Previous load might have been removed as unused - if (prev.useCount != 0) - { - substitute(function, inst, IrOp{IrOpKind::Inst, *prevIdx}); - return; - } + substitute(function, inst, IrOp{IrOpKind::Inst, *prevIdx}); + return; } valueMap[inst] = instIdx; } - // Vm register load can be replaced by a previous load of the same version of the register + // VM register load can be replaced by a previous load of the same version of the register // If there is no previous load, we record the current one for future lookups void substituteOrRecordVmRegLoad(IrInst& loadInst) { @@ -274,22 +317,16 @@ struct ConstPropState IrInst versionedLoad = versionedVmRegLoad(loadInst.cmd, loadInst.a); // Check if there is a value that already has this version of the register - if (uint32_t* prevIdx = valueMap.find(versionedLoad)) + if (uint32_t* prevIdx = getPreviousInstIndex(versionedLoad)) { - const IrInst& prev = function.instructions[*prevIdx]; + // Previous value might not be linked to a register yet + // For example, it could be a NEW_TABLE stored into a register and we might need to track guards made with this value + if (!instLink.contains(*prevIdx)) + createRegLink(*prevIdx, loadInst.a); - // Previous load might have been removed as unused - if (prev.useCount != 0) - { - // Previous value might not be linked to a register yet - // For example, it could be a NEW_TABLE stored into a register and we might need to track guards made with this value - if (!instLink.contains(*prevIdx)) - createRegLink(*prevIdx, loadInst.a); - - // Substitute load instructon with the previous value - substitute(function, loadInst, IrOp{IrOpKind::Inst, *prevIdx}); - return; - } + // Substitute load instructon with the previous value + substitute(function, loadInst, IrOp{IrOpKind::Inst, *prevIdx}); + return; } uint32_t instIdx = function.getInstIndex(loadInst); @@ -403,10 +440,8 @@ static void handleBuiltinEffects(ConstPropState& state, LuauBuiltinFunction bfid case LBF_MATH_CLAMP: case LBF_MATH_SIGN: case LBF_MATH_ROUND: - case LBF_RAWSET: case LBF_RAWGET: case LBF_RAWEQUAL: - case LBF_TABLE_INSERT: case LBF_TABLE_UNPACK: case LBF_VECTOR: case LBF_BIT32_COUNTLZ: @@ -418,6 +453,12 @@ static void handleBuiltinEffects(ConstPropState& state, LuauBuiltinFunction bfid case LBF_TONUMBER: case LBF_TOSTRING: break; + case LBF_TABLE_INSERT: + state.invalidateHeap(); + return; // table.insert does not modify result registers. + case LBF_RAWSET: + state.invalidateHeap(); + break; case LBF_SETMETATABLE: state.invalidateHeap(); // TODO: only knownNoMetatable is affected and we might know which one break; @@ -470,21 +511,18 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& if (inst.a.kind == IrOpKind::VmReg) { const IrOp source = inst.a; - uint32_t activeLoadDoubleValue = kInvalidInstIdx; + + IrCmd activeLoadCmd = IrCmd::NOP; + uint32_t activeLoadValue = kInvalidInstIdx; if (inst.b.kind == IrOpKind::Constant) { uint8_t value = function.tagOp(inst.b); // STORE_TAG usually follows a store of the value, but it also bumps the version of the whole register - // To be able to propagate STORE_DOUBLE into LOAD_DOUBLE, we find active LOAD_DOUBLE value and recreate it with updated version + // To be able to propagate STORE_*** into LOAD_***, we find active LOAD_*** value and recreate it with updated version // Register in this optimization cannot be captured to avoid complications in lowering (IrValueLocationTracking doesn't model it) - // If stored tag is not a number, we can skip the lookup as there won't be future loads of this register as a number - if (value == LUA_TNUMBER && !function.cfg.captured.regs.test(vmRegOp(source))) - { - if (uint32_t* prevIdx = state.valueMap.find(state.versionedVmRegLoad(IrCmd::LOAD_DOUBLE, source))) - activeLoadDoubleValue = *prevIdx; - } + std::tie(activeLoadCmd, activeLoadValue) = state.getPreviousVersionedLoadForTag(value, source); if (state.tryGetTag(source) == value) { @@ -503,9 +541,9 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& state.invalidateTag(source); } - // Future LOAD_DOUBLE instructions can re-use previous register version load - if (activeLoadDoubleValue != kInvalidInstIdx) - state.valueMap[state.versionedVmRegLoad(IrCmd::LOAD_DOUBLE, source)] = activeLoadDoubleValue; + // Future LOAD_*** instructions can re-use previous register version load + if (activeLoadValue != kInvalidInstIdx) + state.valueMap[state.versionedVmRegLoad(activeLoadCmd, source)] = activeLoadValue; } break; case IrCmd::STORE_POINTER: @@ -520,6 +558,7 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& { info->knownNotReadonly = true; info->knownNoMetatable = true; + info->knownTableArraySize = function.uintOp(instOp->a); } } } @@ -562,17 +601,62 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& state.invalidateValue(inst.a); break; case IrCmd::STORE_TVALUE: + if (inst.a.kind == IrOpKind::VmReg || inst.a.kind == IrOpKind::Inst) + { + if (inst.a.kind == IrOpKind::VmReg) + state.invalidate(inst.a); + + uint8_t tag = state.tryGetTag(inst.b); + IrOp value = state.tryGetValue(inst.b); + + if (inst.a.kind == IrOpKind::VmReg) + { + if (tag != 0xff) + state.saveTag(inst.a, tag); + + if (value.kind != IrOpKind::None) + state.saveValue(inst.a, value); + } + + IrCmd activeLoadCmd = IrCmd::NOP; + uint32_t activeLoadValue = kInvalidInstIdx; + + if (tag != 0xff) + { + // If we know the tag, try to extract the value from a register used by LOAD_TVALUE + if (IrInst* arg = function.asInstOp(inst.b); arg && arg->cmd == IrCmd::LOAD_TVALUE && arg->a.kind == IrOpKind::VmReg) + { + std::tie(activeLoadCmd, activeLoadValue) = state.getPreviousVersionedLoadForTag(tag, arg->a); + + if (activeLoadValue != kInvalidInstIdx) + value = IrOp{IrOpKind::Inst, activeLoadValue}; + } + } + + // If we have constant tag and value, replace TValue store with tag/value pair store + if (tag != 0xff && value.kind != IrOpKind::None && (tag == LUA_TBOOLEAN || tag == LUA_TNUMBER || isGCO(tag))) + { + replace(function, block, index, {IrCmd::STORE_SPLIT_TVALUE, inst.a, build.constTag(tag), value, inst.c}); + + // Value can be propagated to future loads of the same register + if (inst.a.kind == IrOpKind::VmReg && activeLoadValue != kInvalidInstIdx) + state.valueMap[state.versionedVmRegLoad(activeLoadCmd, inst.a)] = activeLoadValue; + } + else if (inst.a.kind == IrOpKind::VmReg) + { + state.forwardVmRegStoreToLoad(inst, IrCmd::LOAD_TVALUE); + } + } + break; + case IrCmd::STORE_SPLIT_TVALUE: if (inst.a.kind == IrOpKind::VmReg) { state.invalidate(inst.a); - if (uint8_t tag = state.tryGetTag(inst.b); tag != 0xff) - state.saveTag(inst.a, tag); + state.saveTag(inst.a, function.tagOp(inst.b)); - if (IrOp value = state.tryGetValue(inst.b); value.kind != IrOpKind::None) - state.saveValue(inst.a, value); - - state.forwardVmRegStoreToLoad(inst, IrCmd::LOAD_TVALUE); + if (inst.c.kind == IrOpKind::Constant) + state.saveValue(inst.a, inst.c); } break; case IrCmd::JUMP_IF_TRUTHY: @@ -666,6 +750,15 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& case IrCmd::GET_UPVALUE: state.invalidate(inst.a); break; + case IrCmd::SET_UPVALUE: + if (inst.b.kind == IrOpKind::VmReg) + { + if (uint8_t tag = state.tryGetTag(inst.b); tag != 0xff) + { + replace(function, inst.c, build.constTag(tag)); + } + } + break; case IrCmd::CHECK_TAG: { uint8_t b = function.tagOp(inst.b); @@ -768,8 +861,6 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& // These instructions don't have an effect on register/memory state we are tracking case IrCmd::NOP: - case IrCmd::LOAD_NODE_VALUE_TV: - case IrCmd::STORE_NODE_VALUE_TV: case IrCmd::LOAD_ENV: case IrCmd::GET_ARR_ADDR: case IrCmd::GET_SLOT_NODE_ADDR: @@ -824,12 +915,33 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& state.substituteOrRecord(inst, index); break; case IrCmd::CHECK_ARRAY_SIZE: + { + std::optional arrayIndex = function.asIntOp(inst.b.kind == IrOpKind::Constant ? inst.b : state.tryGetValue(inst.b)); + + if (RegisterInfo* info = state.tryGetRegisterInfo(inst.a); info && arrayIndex) + { + if (info->knownTableArraySize >= 0) + { + if (unsigned(*arrayIndex) < unsigned(info->knownTableArraySize)) + { + if (FFlag::DebugLuauAbortingChecks) + replace(function, inst.c, build.undef()); + else + kill(function, inst); + } + else + { + replace(function, block, index, {IrCmd::JUMP, inst.c}); + } + } + } + break; + } case IrCmd::CHECK_SLOT_MATCH: case IrCmd::CHECK_NODE_NO_NEXT: case IrCmd::BARRIER_TABLE_BACK: case IrCmd::RETURN: case IrCmd::COVERAGE: - case IrCmd::SET_UPVALUE: case IrCmd::SET_SAVEDPC: // TODO: we may be able to remove some updates to PC case IrCmd::CLOSE_UPVALS: // Doesn't change memory that we track case IrCmd::CAPTURE: diff --git a/Common/include/Luau/BytecodeUtils.h b/Common/include/Luau/BytecodeUtils.h new file mode 100644 index 00000000..957c804c --- /dev/null +++ b/Common/include/Luau/BytecodeUtils.h @@ -0,0 +1,42 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#pragma once + +#include "Luau/Bytecode.h" + +namespace Luau +{ + +inline int getOpLength(LuauOpcode op) +{ + switch (op) + { + case LOP_GETGLOBAL: + case LOP_SETGLOBAL: + case LOP_GETIMPORT: + case LOP_GETTABLEKS: + case LOP_SETTABLEKS: + case LOP_NAMECALL: + case LOP_JUMPIFEQ: + case LOP_JUMPIFLE: + case LOP_JUMPIFLT: + case LOP_JUMPIFNOTEQ: + case LOP_JUMPIFNOTLE: + case LOP_JUMPIFNOTLT: + case LOP_NEWTABLE: + case LOP_SETLIST: + case LOP_FORGLOOP: + case LOP_LOADKX: + case LOP_FASTCALL2: + case LOP_FASTCALL2K: + case LOP_JUMPXEQKNIL: + case LOP_JUMPXEQKB: + case LOP_JUMPXEQKN: + case LOP_JUMPXEQKS: + return 2; + + default: + return 1; + } +} + +} // namespace Luau diff --git a/Compiler/include/Luau/BytecodeBuilder.h b/Compiler/include/Luau/BytecodeBuilder.h index 3044e448..48b89404 100644 --- a/Compiler/include/Luau/BytecodeBuilder.h +++ b/Compiler/include/Luau/BytecodeBuilder.h @@ -16,6 +16,7 @@ public: virtual ~BytecodeEncoder() {} virtual uint8_t encodeOp(uint8_t op) = 0; + virtual void encode(uint32_t* data, size_t count) = 0; }; class BytecodeBuilder diff --git a/Compiler/src/BytecodeBuilder.cpp b/Compiler/src/BytecodeBuilder.cpp index eeb9c10e..96754569 100644 --- a/Compiler/src/BytecodeBuilder.cpp +++ b/Compiler/src/BytecodeBuilder.cpp @@ -1,12 +1,14 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "Luau/BytecodeBuilder.h" +#include "Luau/BytecodeUtils.h" #include "Luau/StringUtils.h" #include #include LUAU_FASTFLAGVARIABLE(BytecodeVersion4, false) +LUAU_FASTFLAGVARIABLE(BytecodeEnc, false) namespace Luau { @@ -55,39 +57,6 @@ static void writeVarInt(std::string& ss, unsigned int value) } while (value); } -static int getOpLength(LuauOpcode op) -{ - switch (op) - { - case LOP_GETGLOBAL: - case LOP_SETGLOBAL: - case LOP_GETIMPORT: - case LOP_GETTABLEKS: - case LOP_SETTABLEKS: - case LOP_NAMECALL: - case LOP_JUMPIFEQ: - case LOP_JUMPIFLE: - case LOP_JUMPIFLT: - case LOP_JUMPIFNOTEQ: - case LOP_JUMPIFNOTLE: - case LOP_JUMPIFNOTLT: - case LOP_NEWTABLE: - case LOP_SETLIST: - case LOP_FORGLOOP: - case LOP_LOADKX: - case LOP_FASTCALL2: - case LOP_FASTCALL2K: - case LOP_JUMPXEQKNIL: - case LOP_JUMPXEQKB: - case LOP_JUMPXEQKN: - case LOP_JUMPXEQKS: - return 2; - - default: - return 1; - } -} - inline bool isJumpD(LuauOpcode op) { switch (op) @@ -262,17 +231,20 @@ void BytecodeBuilder::endFunction(uint8_t maxstacksize, uint8_t numupvalues, uin validate(); #endif + // this call is indirect to make sure we only gain link time dependency on dumpCurrentFunction when needed + if (dumpFunctionPtr) + func.dump = (this->*dumpFunctionPtr)(func.dumpinstoffs); + // very approximate: 4 bytes per instruction for code, 1 byte for debug line, and 1-2 bytes for aux data like constants plus overhead func.data.reserve(32 + insns.size() * 7); + if (FFlag::BytecodeEnc && encoder) + encoder->encode(insns.data(), insns.size()); + writeFunction(func.data, currentFunction, flags); currentFunction = ~0u; - // this call is indirect to make sure we only gain link time dependency on dumpCurrentFunction when needed - if (dumpFunctionPtr) - func.dump = (this->*dumpFunctionPtr)(func.dumpinstoffs); - insns.clear(); lines.clear(); constants.clear(); @@ -653,20 +625,28 @@ void BytecodeBuilder::writeFunction(std::string& ss, uint32_t id, uint8_t flags) // instructions writeVarInt(ss, uint32_t(insns.size())); - for (size_t i = 0; i < insns.size();) + if (encoder && !FFlag::BytecodeEnc) { - uint8_t op = LUAU_INSN_OP(insns[i]); - LUAU_ASSERT(op < LOP__COUNT); + for (size_t i = 0; i < insns.size();) + { + uint8_t op = LUAU_INSN_OP(insns[i]); + LUAU_ASSERT(op < LOP__COUNT); - int oplen = getOpLength(LuauOpcode(op)); - uint8_t openc = encoder ? encoder->encodeOp(op) : op; + int oplen = getOpLength(LuauOpcode(op)); + uint8_t openc = encoder->encodeOp(op); - writeInt(ss, openc | (insns[i] & ~0xff)); + writeInt(ss, openc | (insns[i] & ~0xff)); - for (int j = 1; j < oplen; ++j) - writeInt(ss, insns[i + j]); + for (int j = 1; j < oplen; ++j) + writeInt(ss, insns[i + j]); - i += oplen; + i += oplen; + } + } + else + { + for (uint32_t insn : insns) + writeInt(ss, insn); } // constants diff --git a/Compiler/src/Compiler.cpp b/Compiler/src/Compiler.cpp index 226cd2ee..b673ffc2 100644 --- a/Compiler/src/Compiler.cpp +++ b/Compiler/src/Compiler.cpp @@ -26,8 +26,6 @@ LUAU_FASTINTVARIABLE(LuauCompileInlineThreshold, 25) LUAU_FASTINTVARIABLE(LuauCompileInlineThresholdMaxBoost, 300) LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5) -LUAU_FASTFLAGVARIABLE(LuauCompileFixBuiltinArity, false) - LUAU_FASTFLAGVARIABLE(LuauCompileFoldMathK, false) namespace Luau @@ -793,15 +791,10 @@ struct Compiler return compileExprFastcallN(expr, target, targetCount, targetTop, multRet, regs, bfid); else if (options.optimizationLevel >= 2) { - if (FFlag::LuauCompileFixBuiltinArity) - { - // when a builtin is none-safe with matching arity, even if the last expression returns 0 or >1 arguments, - // we can rely on the behavior of the function being the same (none-safe means nil and none are interchangeable) - BuiltinInfo info = getBuiltinInfo(bfid); - if (int(expr->args.size) == info.params && (info.flags & BuiltinInfo::Flag_NoneSafe) != 0) - return compileExprFastcallN(expr, target, targetCount, targetTop, multRet, regs, bfid); - } - else if (int(expr->args.size) == getBuiltinInfo(bfid).params) + // when a builtin is none-safe with matching arity, even if the last expression returns 0 or >1 arguments, + // we can rely on the behavior of the function being the same (none-safe means nil and none are interchangeable) + BuiltinInfo info = getBuiltinInfo(bfid); + if (int(expr->args.size) == info.params && (info.flags & BuiltinInfo::Flag_NoneSafe) != 0) return compileExprFastcallN(expr, target, targetCount, targetTop, multRet, regs, bfid); } } diff --git a/Compiler/src/CostModel.cpp b/Compiler/src/CostModel.cpp index 2f7af6ea..04adf3e3 100644 --- a/Compiler/src/CostModel.cpp +++ b/Compiler/src/CostModel.cpp @@ -6,8 +6,6 @@ #include -LUAU_FASTFLAGVARIABLE(LuauAssignmentHasCost, false) - namespace Luau { namespace Compile @@ -308,10 +306,7 @@ struct CostVisitor : AstVisitor { // unconditional 'else' may require a jump after the 'if' body // note: this ignores cases when 'then' always terminates and also assumes comparison requires an extra instruction which may be false - if (!FFlag::LuauAssignmentHasCost) - result += 2; - else - result += 1 + (node->elsebody && !node->elsebody->is()); + result += 1 + (node->elsebody && !node->elsebody->is()); return true; } @@ -337,9 +332,6 @@ struct CostVisitor : AstVisitor for (size_t i = 0; i < node->vars.size; ++i) assign(node->vars.data[i]); - if (!FFlag::LuauAssignmentHasCost) - return true; - for (size_t i = 0; i < node->vars.size || i < node->values.size; ++i) { Cost ac; diff --git a/Analysis/include/Luau/Config.h b/Config/include/Luau/Config.h similarity index 100% rename from Analysis/include/Luau/Config.h rename to Config/include/Luau/Config.h diff --git a/Analysis/include/Luau/LinterConfig.h b/Config/include/Luau/LinterConfig.h similarity index 99% rename from Analysis/include/Luau/LinterConfig.h rename to Config/include/Luau/LinterConfig.h index 0d3b3b91..6b24db17 100644 --- a/Analysis/include/Luau/LinterConfig.h +++ b/Config/include/Luau/LinterConfig.h @@ -7,6 +7,8 @@ #include #include +#include + namespace Luau { diff --git a/Analysis/src/Config.cpp b/Config/src/Config.cpp similarity index 100% rename from Analysis/src/Config.cpp rename to Config/src/Config.cpp diff --git a/Analysis/src/LinterConfig.cpp b/Config/src/LinterConfig.cpp similarity index 100% rename from Analysis/src/LinterConfig.cpp rename to Config/src/LinterConfig.cpp diff --git a/Makefile b/Makefile index 17bae919..d1c2ac90 100644 --- a/Makefile +++ b/Makefile @@ -16,6 +16,10 @@ COMPILER_SOURCES=$(wildcard Compiler/src/*.cpp) COMPILER_OBJECTS=$(COMPILER_SOURCES:%=$(BUILD)/%.o) COMPILER_TARGET=$(BUILD)/libluaucompiler.a +CONFIG_SOURCES=$(wildcard Config/src/*.cpp) +CONFIG_OBJECTS=$(CONFIG_SOURCES:%=$(BUILD)/%.o) +CONFIG_TARGET=$(BUILD)/libluauconfig.a + ANALYSIS_SOURCES=$(wildcard Analysis/src/*.cpp) ANALYSIS_OBJECTS=$(ANALYSIS_SOURCES:%=$(BUILD)/%.o) ANALYSIS_TARGET=$(BUILD)/libluauanalysis.a @@ -59,7 +63,7 @@ ifneq ($(opt),) TESTS_ARGS+=-O$(opt) endif -OBJECTS=$(AST_OBJECTS) $(COMPILER_OBJECTS) $(ANALYSIS_OBJECTS) $(CODEGEN_OBJECTS) $(VM_OBJECTS) $(ISOCLINE_OBJECTS) $(TESTS_OBJECTS) $(REPL_CLI_OBJECTS) $(ANALYZE_CLI_OBJECTS) $(COMPILE_CLI_OBJECTS) $(FUZZ_OBJECTS) +OBJECTS=$(AST_OBJECTS) $(COMPILER_OBJECTS) $(CONFIG_OBJECTS) $(ANALYSIS_OBJECTS) $(CODEGEN_OBJECTS) $(VM_OBJECTS) $(ISOCLINE_OBJECTS) $(TESTS_OBJECTS) $(REPL_CLI_OBJECTS) $(ANALYZE_CLI_OBJECTS) $(COMPILE_CLI_OBJECTS) $(FUZZ_OBJECTS) EXECUTABLE_ALIASES = luau luau-analyze luau-compile luau-tests # common flags @@ -129,13 +133,14 @@ endif # target-specific flags $(AST_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include $(COMPILER_OBJECTS): CXXFLAGS+=-std=c++17 -ICompiler/include -ICommon/include -IAst/include -$(ANALYSIS_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -IAnalysis/include +$(CONFIG_OBJECTS): CXXFLAGS+=-std=c++17 -IConfig/include -ICommon/include -IAst/include +$(ANALYSIS_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -IAnalysis/include -IConfig/include $(CODEGEN_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -ICodeGen/include -IVM/include -IVM/src # Code generation needs VM internals $(VM_OBJECTS): CXXFLAGS+=-std=c++11 -ICommon/include -IVM/include $(ISOCLINE_OBJECTS): CXXFLAGS+=-Wno-unused-function -Iextern/isocline/include -$(TESTS_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IAnalysis/include -ICodeGen/include -IVM/include -ICLI -Iextern -DDOCTEST_CONFIG_DOUBLE_STRINGIFY +$(TESTS_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IConfig/include -IAnalysis/include -ICodeGen/include -IVM/include -ICLI -Iextern -DDOCTEST_CONFIG_DOUBLE_STRINGIFY $(REPL_CLI_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IVM/include -ICodeGen/include -Iextern -Iextern/isocline/include -$(ANALYZE_CLI_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -IAnalysis/include -Iextern +$(ANALYZE_CLI_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -IAnalysis/include -IConfig/include -Iextern $(COMPILE_CLI_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IVM/include -ICodeGen/include $(FUZZ_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IAnalysis/include -IVM/include -ICodeGen/include @@ -205,9 +210,9 @@ luau-tests: $(TESTS_TARGET) ln -fs $^ $@ # executable targets -$(TESTS_TARGET): $(TESTS_OBJECTS) $(ANALYSIS_TARGET) $(COMPILER_TARGET) $(AST_TARGET) $(CODEGEN_TARGET) $(VM_TARGET) $(ISOCLINE_TARGET) +$(TESTS_TARGET): $(TESTS_OBJECTS) $(ANALYSIS_TARGET) $(COMPILER_TARGET) $(CONFIG_TARGET) $(AST_TARGET) $(CODEGEN_TARGET) $(VM_TARGET) $(ISOCLINE_TARGET) $(REPL_CLI_TARGET): $(REPL_CLI_OBJECTS) $(COMPILER_TARGET) $(AST_TARGET) $(CODEGEN_TARGET) $(VM_TARGET) $(ISOCLINE_TARGET) -$(ANALYZE_CLI_TARGET): $(ANALYZE_CLI_OBJECTS) $(ANALYSIS_TARGET) $(AST_TARGET) +$(ANALYZE_CLI_TARGET): $(ANALYZE_CLI_OBJECTS) $(ANALYSIS_TARGET) $(AST_TARGET) $(CONFIG_TARGET) $(COMPILE_CLI_TARGET): $(COMPILE_CLI_OBJECTS) $(COMPILER_TARGET) $(AST_TARGET) $(CODEGEN_TARGET) $(VM_TARGET) $(TESTS_TARGET) $(REPL_CLI_TARGET) $(ANALYZE_CLI_TARGET) $(COMPILE_CLI_TARGET): @@ -223,12 +228,13 @@ fuzz-prototest: $(BUILD)/fuzz/prototest.cpp.o $(BUILD)/fuzz/protoprint.cpp.o $(B # static library targets $(AST_TARGET): $(AST_OBJECTS) $(COMPILER_TARGET): $(COMPILER_OBJECTS) +$(CONFIG_TARGET): $(CONFIG_OBJECTS) $(ANALYSIS_TARGET): $(ANALYSIS_OBJECTS) $(CODEGEN_TARGET): $(CODEGEN_OBJECTS) $(VM_TARGET): $(VM_OBJECTS) $(ISOCLINE_TARGET): $(ISOCLINE_OBJECTS) -$(AST_TARGET) $(COMPILER_TARGET) $(ANALYSIS_TARGET) $(CODEGEN_TARGET) $(VM_TARGET) $(ISOCLINE_TARGET): +$(AST_TARGET) $(COMPILER_TARGET) $(CONFIG_TARGET) $(ANALYSIS_TARGET) $(CODEGEN_TARGET) $(VM_TARGET) $(ISOCLINE_TARGET): ar rcs $@ $^ # object file targets diff --git a/Sources.cmake b/Sources.cmake index 8dcd2431..4776b6b8 100644 --- a/Sources.cmake +++ b/Sources.cmake @@ -4,6 +4,7 @@ if(NOT ${CMAKE_VERSION} VERSION_LESS "3.19") target_sources(Luau.Common PRIVATE Common/include/Luau/Common.h Common/include/Luau/Bytecode.h + Common/include/Luau/BytecodeUtils.h Common/include/Luau/DenseHash.h Common/include/Luau/ExperimentalFlags.h ) @@ -55,6 +56,15 @@ target_sources(Luau.Compiler PRIVATE Compiler/src/ValueTracking.h ) +# Luau.Config Sources +target_sources(Luau.Config PRIVATE + Config/include/Luau/Config.h + Config/include/Luau/LinterConfig.h + + Config/src/Config.cpp + Config/src/LinterConfig.cpp +) + # Luau.CodeGen Sources target_sources(Luau.CodeGen PRIVATE CodeGen/include/Luau/AddressA64.h @@ -145,7 +155,6 @@ target_sources(Luau.Analysis PRIVATE Analysis/include/Luau/BuiltinDefinitions.h Analysis/include/Luau/Cancellation.h Analysis/include/Luau/Clone.h - Analysis/include/Luau/Config.h Analysis/include/Luau/Constraint.h Analysis/include/Luau/ConstraintGraphBuilder.h Analysis/include/Luau/ConstraintSolver.h @@ -163,7 +172,6 @@ target_sources(Luau.Analysis PRIVATE Analysis/include/Luau/IostreamHelpers.h Analysis/include/Luau/JsonEmitter.h Analysis/include/Luau/Linter.h - Analysis/include/Luau/LinterConfig.h Analysis/include/Luau/LValue.h Analysis/include/Luau/Metamethods.h Analysis/include/Luau/Module.h @@ -177,6 +185,7 @@ target_sources(Luau.Analysis PRIVATE Analysis/include/Luau/Scope.h Analysis/include/Luau/Simplify.h Analysis/include/Luau/Substitution.h + Analysis/include/Luau/Subtyping.h Analysis/include/Luau/Symbol.h Analysis/include/Luau/ToDot.h Analysis/include/Luau/TopoSortStatements.h @@ -207,7 +216,6 @@ target_sources(Luau.Analysis PRIVATE Analysis/src/Autocomplete.cpp Analysis/src/BuiltinDefinitions.cpp Analysis/src/Clone.cpp - Analysis/src/Config.cpp Analysis/src/Constraint.cpp Analysis/src/ConstraintGraphBuilder.cpp Analysis/src/ConstraintSolver.cpp @@ -222,7 +230,6 @@ target_sources(Luau.Analysis PRIVATE Analysis/src/IostreamHelpers.cpp Analysis/src/JsonEmitter.cpp Analysis/src/Linter.cpp - Analysis/src/LinterConfig.cpp Analysis/src/LValue.cpp Analysis/src/Module.cpp Analysis/src/Normalize.cpp @@ -232,6 +239,7 @@ target_sources(Luau.Analysis PRIVATE Analysis/src/Scope.cpp Analysis/src/Simplify.cpp Analysis/src/Substitution.cpp + Analysis/src/Subtyping.cpp Analysis/src/Symbol.cpp Analysis/src/ToDot.cpp Analysis/src/TopoSortStatements.cpp @@ -350,33 +358,32 @@ endif() if(TARGET Luau.UnitTest) # Luau.UnitTest Sources target_sources(Luau.UnitTest PRIVATE - tests/AstQueryDsl.cpp - tests/AstQueryDsl.h - tests/ClassFixture.cpp - tests/ClassFixture.h - tests/ConstraintGraphBuilderFixture.cpp - tests/ConstraintGraphBuilderFixture.h - tests/Fixture.cpp - tests/Fixture.h - tests/IostreamOptional.h - tests/ScopedFlags.h tests/AssemblyBuilderA64.test.cpp tests/AssemblyBuilderX64.test.cpp tests/AstJsonEncoder.test.cpp tests/AstQuery.test.cpp + tests/AstQueryDsl.cpp + tests/AstQueryDsl.h tests/AstVisitor.test.cpp tests/Autocomplete.test.cpp tests/BuiltinDefinitions.test.cpp + tests/ClassFixture.cpp + tests/ClassFixture.h tests/CodeAllocator.test.cpp tests/Compiler.test.cpp tests/Config.test.cpp + tests/ConstraintGraphBuilderFixture.cpp + tests/ConstraintGraphBuilderFixture.h tests/ConstraintSolver.test.cpp tests/CostModel.test.cpp tests/DataFlowGraph.test.cpp tests/DenseHash.test.cpp - tests/Differ.test.cpp + tests/Differ.test.cpp tests/Error.test.cpp + tests/Fixture.cpp + tests/Fixture.h tests/Frontend.test.cpp + tests/IostreamOptional.h tests/IrBuilder.test.cpp tests/IrCallWrapperX64.test.cpp tests/IrRegAllocX64.test.cpp @@ -391,8 +398,10 @@ if(TARGET Luau.UnitTest) tests/Parser.test.cpp tests/RequireTracer.test.cpp tests/RuntimeLimits.test.cpp + tests/ScopedFlags.h tests/Simplify.test.cpp tests/StringUtils.test.cpp + tests/Subtyping.test.cpp tests/Symbol.test.cpp tests/ToDot.test.cpp tests/TopoSort.test.cpp diff --git a/VM/src/ldo.cpp b/VM/src/ldo.cpp index e5fde4d4..69d3c128 100644 --- a/VM/src/ldo.cpp +++ b/VM/src/ldo.cpp @@ -17,6 +17,8 @@ #include +LUAU_FASTFLAGVARIABLE(LuauPCallDebuggerFix, false) + /* ** {====================================================== ** Error-recovery functions @@ -576,11 +578,13 @@ int luaD_pcall(lua_State* L, Pfunc func, void* u, ptrdiff_t old_top, ptrdiff_t e if (!oldactive) L->isactive = false; + bool yieldable = L->nCcalls <= L->baseCcalls; // Inlined logic from 'lua_isyieldable' to avoid potential for an out of line call. + // restore nCcalls before calling the debugprotectederror callback which may rely on the proper value to have been restored. L->nCcalls = oldnCcalls; // an error occurred, check if we have a protected error callback - if (L->global->cb.debugprotectederror) + if ((!FFlag::LuauPCallDebuggerFix || yieldable) && L->global->cb.debugprotectederror) { L->global->cb.debugprotectederror(L); diff --git a/VM/src/lmathlib.cpp b/VM/src/lmathlib.cpp index 254fc9db..8a140780 100644 --- a/VM/src/lmathlib.cpp +++ b/VM/src/lmathlib.cpp @@ -288,17 +288,17 @@ static const unsigned char kPerlinHash[257] = {151, 160, 137, 91, 90, 15, 131, 1 const float kPerlinGrad[16][3] = {{1, 1, 0}, {-1, 1, 0}, {1, -1, 0}, {-1, -1, 0}, {1, 0, 1}, {-1, 0, 1}, {1, 0, -1}, {-1, 0, -1}, {0, 1, 1}, {0, -1, 1}, {0, 1, -1}, {0, -1, -1}, {1, 1, 0}, {0, -1, 1}, {-1, 1, 0}, {0, -1, -1}}; -static float perlin_fade(float t) +inline float perlin_fade(float t) { return t * t * t * (t * (t * 6 - 15) + 10); } -static float perlin_lerp(float t, float a, float b) +inline float perlin_lerp(float t, float a, float b) { return a + t * (b - a); } -static float perlin_grad(int hash, float x, float y, float z) +inline float perlin_grad(int hash, float x, float y, float z) { const float* g = kPerlinGrad[hash & 15]; return g[0] * x + g[1] * y + g[2] * z; diff --git a/tests/Autocomplete.test.cpp b/tests/Autocomplete.test.cpp index daa1f81a..b8171a75 100644 --- a/tests/Autocomplete.test.cpp +++ b/tests/Autocomplete.test.cpp @@ -3556,8 +3556,6 @@ TEST_CASE_FIXTURE(ACFixture, "frontend_use_correct_global_scope") TEST_CASE_FIXTURE(ACFixture, "string_completion_outside_quotes") { - ScopedFastFlag flag{"LuauDisableCompletionOutsideQuotes", true}; - loadDefinition(R"( declare function require(path: string): any )"); diff --git a/tests/Compiler.test.cpp b/tests/Compiler.test.cpp index 5e58865b..078b8af6 100644 --- a/tests/Compiler.test.cpp +++ b/tests/Compiler.test.cpp @@ -6978,8 +6978,6 @@ L3: RETURN R0 0 TEST_CASE("BuiltinArity") { - ScopedFastFlag sff("LuauCompileFixBuiltinArity", true); - // by default we can't assume that we know parameter/result count for builtins as they can be overridden at runtime CHECK_EQ("\n" + compileFunction(R"( return math.abs(unknown()) diff --git a/tests/Conformance.test.cpp b/tests/Conformance.test.cpp index 97d4e031..a06138fc 100644 --- a/tests/Conformance.test.cpp +++ b/tests/Conformance.test.cpp @@ -8,7 +8,6 @@ #include "Luau/DenseHash.h" #include "Luau/ModuleResolver.h" #include "Luau/TypeInfer.h" -#include "Luau/StringUtils.h" #include "Luau/BytecodeBuilder.h" #include "Luau/Frontend.h" @@ -24,6 +23,8 @@ extern bool verbose; extern bool codegen; extern int optimizationLevel; +LUAU_FASTFLAG(LuauPCallDebuggerFix); + static lua_CompileOptions defaultOptions() { lua_CompileOptions copts = {}; @@ -279,8 +280,6 @@ TEST_CASE("Assert") TEST_CASE("Basic") { - ScopedFastFlag sff("LuauCompileFixBuiltinArity", true); - runConformance("basic.lua"); } @@ -334,8 +333,6 @@ TEST_CASE("Clear") TEST_CASE("Strings") { - ScopedFastFlag sff("LuauCompileFixBuiltinArity", true); - runConformance("strings.lua"); } @@ -1217,15 +1214,90 @@ TEST_CASE("IfElseExpression") runConformance("ifelseexpr.lua"); } +// Optionally returns debug info for the first Luau stack frame that is encountered on the callstack. +static std::optional getFirstLuauFrameDebugInfo(lua_State* L) +{ + static std::string_view kLua = "Lua"; + lua_Debug ar; + for (int i = 0; lua_getinfo(L, i, "sl", &ar); i++) + { + if (kLua == ar.what) + return ar; + } + return std::nullopt; +} + TEST_CASE("TagMethodError") { - runConformance("tmerror.lua", [](lua_State* L) { - auto* cb = lua_callbacks(L); + static std::vector expectedHits; - cb->debugprotectederror = [](lua_State* L) { - CHECK(lua_isyieldable(L)); - }; - }); + // Loop over two modes: + // when doLuaBreak is false the test only verifies that callbacks occur on the expected lines in the Luau source + // when doLuaBreak is true the test additionally calls lua_break to ensure breaking the debugger doesn't cause the VM to crash + for (bool doLuaBreak : {false, true}) + { + std::optional sff; + if (doLuaBreak) + { + // If doLuaBreak is true then LuauPCallDebuggerFix must be enabled to avoid crashing the tests. + sff = {"LuauPCallDebuggerFix", true}; + } + + if (FFlag::LuauPCallDebuggerFix) + { + expectedHits = {22, 32}; + } + else + { + expectedHits = { + 9, + 17, + 17, + 22, + 27, + 27, + 32, + 37, + }; + } + + static int index; + static bool luaBreak; + index = 0; + luaBreak = doLuaBreak; + + // 'yieldCallback' doesn't do anything, but providing the callback to runConformance + // ensures that the call to lua_break doesn't cause an error to be generated because + // runConformance doesn't expect the VM to be in the state LUA_BREAK. + auto yieldCallback = [](lua_State* L) {}; + + runConformance( + "tmerror.lua", + [](lua_State* L) { + auto* cb = lua_callbacks(L); + + cb->debugprotectederror = [](lua_State* L) { + std::optional ar = getFirstLuauFrameDebugInfo(L); + + CHECK(lua_isyieldable(L)); + REQUIRE(ar.has_value()); + REQUIRE(index < int(std::size(expectedHits))); + CHECK(ar->currentline == expectedHits[index++]); + + if (luaBreak) + { + // Cause luau execution to break when 'error' is called via 'pcall' + // This call to lua_break is a regression test for an issue where debugprotectederror + // was called on a thread that couldn't be yielded even though lua_isyieldable was true. + lua_break(L); + } + }; + }, + yieldCallback); + + // Make sure the number of break points hit was the expected number + CHECK(index == std::size(expectedHits)); + } } TEST_CASE("Coverage") diff --git a/tests/CostModel.test.cpp b/tests/CostModel.test.cpp index 206b83a8..29fffb4f 100644 --- a/tests/CostModel.test.cpp +++ b/tests/CostModel.test.cpp @@ -133,8 +133,6 @@ end TEST_CASE("ControlFlow") { - ScopedFastFlag sff("LuauAssignmentHasCost", true); - uint64_t model = modelFunction(R"( function test(a) while a < 0 do @@ -244,8 +242,6 @@ end TEST_CASE("MultipleAssignments") { - ScopedFastFlag sff("LuauAssignmentHasCost", true); - uint64_t model = modelFunction(R"( function test(a) local x = 0 diff --git a/tests/Error.test.cpp b/tests/Error.test.cpp index 5ba5c112..0a71794f 100644 --- a/tests/Error.test.cpp +++ b/tests/Error.test.cpp @@ -1,6 +1,7 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "Luau/Error.h" +#include "Fixture.h" #include "doctest.h" using namespace Luau; @@ -13,4 +14,24 @@ TEST_CASE("TypeError_code_should_return_nonzero_code") CHECK_GE(e.code(), 1000); } +TEST_CASE_FIXTURE(BuiltinsFixture, "metatable_names_show_instead_of_tables") +{ + frontend.options.retainFullTypeGraphs = false; + ScopedFastFlag sff{"LuauStacklessTypeClone2", true}; + CheckResult result = check(R"( +--!strict +local Account = {} +Account.__index = Account +function Account.deposit(self: Account, x: number) + self.balance += x +end +type Account = typeof(setmetatable({} :: { balance: number }, Account)) +local x: Account = 5 +)"); + + LUAU_REQUIRE_ERROR_COUNT(1, result); + + CHECK_EQ("Type 'number' could not be converted into 'Account'", toString(result.errors[0])); +} + TEST_SUITE_END(); diff --git a/tests/IrBuilder.test.cpp b/tests/IrBuilder.test.cpp index 57103352..dff13342 100644 --- a/tests/IrBuilder.test.cpp +++ b/tests/IrBuilder.test.cpp @@ -883,8 +883,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "PropagateThroughTvalue") bb_0: STORE_TAG R0, tnumber STORE_DOUBLE R0, 0.5 - %2 = LOAD_TVALUE R0 - STORE_TVALUE R1, %2 + STORE_SPLIT_TVALUE R1, tnumber, 0.5 STORE_TAG R3, tnumber STORE_DOUBLE R3, 0.5 RETURN 0u @@ -991,6 +990,52 @@ bb_fallback_1: )"); } +TEST_CASE_FIXTURE(IrBuilderFixture, "RememberNewTableState") +{ + IrOp block = build.block(IrBlockKind::Internal); + IrOp fallback = build.block(IrBlockKind::Fallback); + + build.beginBlock(block); + + IrOp newtable = build.inst(IrCmd::NEW_TABLE, build.constUint(16), build.constUint(32)); + build.inst(IrCmd::STORE_POINTER, build.vmReg(0), newtable); + + IrOp table = build.inst(IrCmd::LOAD_POINTER, build.vmReg(0)); + + build.inst(IrCmd::CHECK_NO_METATABLE, table, fallback); + build.inst(IrCmd::CHECK_READONLY, table, fallback); + build.inst(IrCmd::CHECK_ARRAY_SIZE, table, build.constInt(14), fallback); + + build.inst(IrCmd::SET_TABLE, build.vmReg(1), build.vmReg(0), build.constUint(13)); // Invalidate table knowledge + + build.inst(IrCmd::CHECK_NO_METATABLE, table, fallback); + build.inst(IrCmd::CHECK_READONLY, table, fallback); + build.inst(IrCmd::CHECK_ARRAY_SIZE, table, build.constInt(14), fallback); + + build.inst(IrCmd::RETURN, build.constUint(0)); + + build.beginBlock(fallback); + build.inst(IrCmd::RETURN, build.constUint(1)); + + updateUseCounts(build.function); + constPropInBlockChains(build, true); + + CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( +bb_0: + %0 = NEW_TABLE 16u, 32u + STORE_POINTER R0, %0 + SET_TABLE R1, R0, 13u + CHECK_NO_METATABLE %0, bb_fallback_1 + CHECK_READONLY %0, bb_fallback_1 + CHECK_ARRAY_SIZE %0, 14i, bb_fallback_1 + RETURN 0u + +bb_fallback_1: + RETURN 1u + +)"); +} + TEST_CASE_FIXTURE(IrBuilderFixture, "SkipUselessBarriers") { IrOp block = build.block(IrBlockKind::Internal); @@ -1586,7 +1631,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "InvalidateReglinkVersion") build.inst(IrCmd::STORE_TAG, build.vmReg(2), build.constTag(tstring)); IrOp tv2 = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(2)); build.inst(IrCmd::STORE_TVALUE, build.vmReg(1), tv2); - IrOp ft = build.inst(IrCmd::NEW_TABLE); + IrOp ft = build.inst(IrCmd::NEW_TABLE, build.constUint(0), build.constUint(0)); build.inst(IrCmd::STORE_POINTER, build.vmReg(2), ft); build.inst(IrCmd::STORE_TAG, build.vmReg(2), build.constTag(ttable)); IrOp tv1 = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(1)); @@ -1606,7 +1651,7 @@ bb_0: STORE_TAG R2, tstring %1 = LOAD_TVALUE R2 STORE_TVALUE R1, %1 - %3 = NEW_TABLE + %3 = NEW_TABLE 0u, 0u STORE_POINTER R2, %3 STORE_TAG R2, ttable STORE_TVALUE R0, %1 @@ -1811,8 +1856,8 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "PartialStoreInvalidation") build.inst(IrCmd::STORE_TVALUE, build.vmReg(1), build.inst(IrCmd::LOAD_TVALUE, build.vmReg(0))); build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(0.5)); build.inst(IrCmd::STORE_TVALUE, build.vmReg(1), build.inst(IrCmd::LOAD_TVALUE, build.vmReg(0))); // Should be reloaded - build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tboolean)); - build.inst(IrCmd::STORE_TVALUE, build.vmReg(1), build.inst(IrCmd::LOAD_TVALUE, build.vmReg(0))); // Should be reloaded + build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber)); + build.inst(IrCmd::STORE_TVALUE, build.vmReg(1), build.inst(IrCmd::LOAD_TVALUE, build.vmReg(0))); build.inst(IrCmd::RETURN, build.constUint(0)); @@ -1826,9 +1871,8 @@ bb_0: STORE_DOUBLE R0, 0.5 %3 = LOAD_TVALUE R0 STORE_TVALUE R1, %3 - STORE_TAG R0, tboolean - %6 = LOAD_TVALUE R0 - STORE_TVALUE R1, %6 + STORE_TAG R0, tnumber + STORE_SPLIT_TVALUE R1, tnumber, 0.5 RETURN 0u )"); @@ -2239,6 +2283,32 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "DominanceVerification3") CHECK(build.function.cfg.idoms == std::vector{~0u, 0, 0, 0, 2, 0, 4, 0}); } +// 'Static Single Assignment Book' Figure 4.1 +TEST_CASE_FIXTURE(IrBuilderFixture, "DominanceVerification4") +{ + defineCfgTree({{1}, {2, 10}, {3, 7}, {4}, {5}, {4, 6}, {1}, {8}, {5, 9}, {7}, {}}); + + IdfContext ctx; + + computeIteratedDominanceFrontierForDefs(ctx, build.function, {0, 2, 3, 6}, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}); + CHECK(ctx.idf == std::vector{1, 4, 5}); +} + +// 'Static Single Assignment Book' Figure 4.5 +TEST_CASE_FIXTURE(IrBuilderFixture, "DominanceVerification4") +{ + defineCfgTree({{1}, {2}, {3, 7}, {4, 5}, {6}, {6}, {8}, {8}, {9}, {10, 11}, {11}, {9, 12}, {2}}); + + IdfContext ctx; + + computeIteratedDominanceFrontierForDefs(ctx, build.function, {4, 5, 7, 12}, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}); + CHECK(ctx.idf == std::vector{2, 6, 8}); + + // Pruned form, when variable is only live-in in limited set of blocks + computeIteratedDominanceFrontierForDefs(ctx, build.function, {4, 5, 7, 12}, {6, 8, 9}); + CHECK(ctx.idf == std::vector{6, 8}); +} + TEST_SUITE_END(); TEST_SUITE_BEGIN("ValueNumbering"); @@ -2484,4 +2554,105 @@ bb_0: )"); } +TEST_CASE_FIXTURE(IrBuilderFixture, "TValueLoadToSplitStore") +{ + IrOp entry = build.block(IrBlockKind::Internal); + IrOp fallback = build.block(IrBlockKind::Fallback); + + build.beginBlock(entry); + IrOp op1 = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(0)); + IrOp op1v2 = build.inst(IrCmd::ADD_NUM, op1, build.constDouble(4.0)); + build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), op1v2); + build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(tnumber)); + + // Check that this TValue store will be replaced by a split store + IrOp tv = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(1)); + build.inst(IrCmd::STORE_TVALUE, build.vmReg(2), tv); + + // Check that tag and value can be extracted from R2 now (removing the fallback) + IrOp tag2 = build.inst(IrCmd::LOAD_TAG, build.vmReg(2)); + build.inst(IrCmd::CHECK_TAG, tag2, build.constTag(tnumber), fallback); + IrOp op2 = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(2)); + build.inst(IrCmd::STORE_DOUBLE, build.vmReg(3), op2); + build.inst(IrCmd::STORE_TAG, build.vmReg(3), build.constTag(tnumber)); + + build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(1)); + + build.beginBlock(fallback); + build.inst(IrCmd::RETURN, build.vmReg(2), build.constInt(1)); + + updateUseCounts(build.function); + constPropInBlockChains(build, true); + + CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( +bb_0: + %0 = LOAD_DOUBLE R0 + %1 = ADD_NUM %0, 4 + STORE_DOUBLE R1, %1 + STORE_TAG R1, tnumber + STORE_SPLIT_TVALUE R2, tnumber, %1 + STORE_DOUBLE R3, %1 + STORE_TAG R3, tnumber + RETURN R1, 1i + +)"); +} + +TEST_CASE_FIXTURE(IrBuilderFixture, "TagStoreUpdatesValueVersion") +{ + IrOp entry = build.block(IrBlockKind::Internal); + + build.beginBlock(entry); + + IrOp op1 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(0)); + build.inst(IrCmd::STORE_POINTER, build.vmReg(1), op1); + build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(tstring)); + + IrOp str = build.inst(IrCmd::LOAD_POINTER, build.vmReg(1)); + build.inst(IrCmd::STORE_POINTER, build.vmReg(2), str); + build.inst(IrCmd::STORE_TAG, build.vmReg(2), build.constTag(tstring)); + + build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(1)); + + updateUseCounts(build.function); + constPropInBlockChains(build, true); + + CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( +bb_0: + %0 = LOAD_POINTER R0 + STORE_POINTER R1, %0 + STORE_TAG R1, tstring + STORE_POINTER R2, %0 + STORE_TAG R2, tstring + RETURN R1, 1i + +)"); +} + +TEST_CASE_FIXTURE(IrBuilderFixture, "TagStoreUpdatesSetUpval") +{ + IrOp entry = build.block(IrBlockKind::Internal); + + build.beginBlock(entry); + + build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber)); + build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(0.5)); + + build.inst(IrCmd::SET_UPVALUE, build.vmUpvalue(0), build.vmReg(0), build.undef()); + + build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(0)); + + updateUseCounts(build.function); + constPropInBlockChains(build, true); + + CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( +bb_0: + STORE_TAG R0, tnumber + STORE_DOUBLE R0, 0.5 + SET_UPVALUE U0, R0, tnumber + RETURN R0, 0i + +)"); +} + TEST_SUITE_END(); diff --git a/tests/Module.test.cpp b/tests/Module.test.cpp index 54a96861..4f489065 100644 --- a/tests/Module.test.cpp +++ b/tests/Module.test.cpp @@ -14,7 +14,7 @@ using namespace Luau; LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution); -LUAU_FASTFLAG(LuauStacklessTypeClone) +LUAU_FASTFLAG(LuauStacklessTypeClone2) TEST_SUITE_BEGIN("ModuleTests"); @@ -150,8 +150,8 @@ TEST_CASE_FIXTURE(Fixture, "deepClone_cyclic_table") REQUIRE(methodReturnType); CHECK_MESSAGE(methodReturnType == cloneTy, toString(methodType, {true}) << " should be pointer identical to " << toString(cloneTy, {true})); - CHECK_EQ(FFlag::LuauStacklessTypeClone ? 1 : 2, dest.typePacks.size()); // one for the function args, and another for its return type - CHECK_EQ(2, dest.types.size()); // One table and one function + CHECK_EQ(2, dest.typePacks.size()); // one for the function args, and another for its return type + CHECK_EQ(2, dest.types.size()); // One table and one function } TEST_CASE_FIXTURE(Fixture, "deepClone_cyclic_table_2") @@ -336,7 +336,7 @@ TEST_CASE_FIXTURE(Fixture, "clone_recursion_limit") int limit = 400; #endif - ScopedFastFlag sff{"LuauStacklessTypeClone", false}; + ScopedFastFlag sff{"LuauStacklessTypeClone2", false}; ScopedFastInt luauTypeCloneRecursionLimit{"LuauTypeCloneRecursionLimit", limit}; TypeArena src; @@ -360,7 +360,7 @@ TEST_CASE_FIXTURE(Fixture, "clone_recursion_limit") TEST_CASE_FIXTURE(Fixture, "clone_iteration_limit") { - ScopedFastFlag sff{"LuauStacklessTypeClone", true}; + ScopedFastFlag sff{"LuauStacklessTypeClone2", true}; ScopedFastInt sfi{"LuauTypeCloneIterationLimit", 500}; TypeArena src; @@ -501,4 +501,32 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "do_not_clone_types_of_reexported_values") CHECK(tableA->props["a"].type() == tableB->props["b"].type()); } +TEST_CASE_FIXTURE(BuiltinsFixture, "clone_table_bound_to_table_bound_to_table") +{ + TypeArena arena; + + TypeId a = arena.addType(TableType{TableState::Free, TypeLevel{}}); + getMutable(a)->name = "a"; + + TypeId b = arena.addType(TableType{TableState::Free, TypeLevel{}}); + getMutable(b)->name = "b"; + + TypeId c = arena.addType(TableType{TableState::Free, TypeLevel{}}); + getMutable(c)->name = "c"; + + getMutable(a)->boundTo = b; + getMutable(b)->boundTo = c; + + TypeArena dest; + CloneState state{builtinTypes}; + TypeId res = clone(a, dest, state); + + REQUIRE(dest.types.size() == 1); + + auto tableA = get(res); + REQUIRE_MESSAGE(tableA, "Expected table, got " << res); + REQUIRE(tableA->name == "c"); + REQUIRE(!tableA->boundTo); +} + TEST_SUITE_END(); diff --git a/tests/Subtyping.test.cpp b/tests/Subtyping.test.cpp new file mode 100644 index 00000000..f7f24d0f --- /dev/null +++ b/tests/Subtyping.test.cpp @@ -0,0 +1,352 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details + +#include "doctest.h" +#include "Fixture.h" + +#include "Luau/Subtyping.h" + +using namespace Luau; + +struct SubtypeFixture : Fixture +{ + TypeArena arena; + InternalErrorReporter ice; + UnifierSharedState sharedState{&ice}; + Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}}; + Subtyping subtyping{builtinTypes, NotNull{&normalizer}}; + + TypePackId pack(std::initializer_list tys) + { + return arena.addTypePack(tys); + } + + TypePackId pack(std::initializer_list tys, TypePackVariant tail) + { + return arena.addTypePack(tys, arena.addTypePack(std::move(tail))); + } + + TypeId fn(std::initializer_list args, std::initializer_list rets) + { + return arena.addType(FunctionType{pack(args), pack(rets)}); + } + + TypeId fn(std::initializer_list argHead, TypePackVariant argTail, std::initializer_list rets) + { + return arena.addType(FunctionType{pack(argHead, std::move(argTail)), pack(rets)}); + } + + TypeId fn(std::initializer_list args, std::initializer_list retHead, TypePackVariant retTail) + { + return arena.addType(FunctionType{pack(args), pack(retHead, std::move(retTail))}); + } + + TypeId fn(std::initializer_list argHead, TypePackVariant argTail, std::initializer_list retHead, TypePackVariant retTail) + { + return arena.addType(FunctionType{pack(argHead, std::move(argTail)), pack(retHead, std::move(retTail))}); + } + + SubtypingGraph isSubtype(TypeId subTy, TypeId superTy) + { + return subtyping.isSubtype(subTy, superTy); + } + + TypeId helloType = arena.addType(SingletonType{StringSingleton{"hello"}}); + TypeId helloType2 = arena.addType(SingletonType{StringSingleton{"hello"}}); + TypeId worldType = arena.addType(SingletonType{StringSingleton{"world"}}); + + TypeId helloOrWorldType = arena.addType(UnionType{{helloType, worldType}}); + TypeId trueOrFalseType = arena.addType(UnionType{{builtinTypes->trueType, builtinTypes->falseType}}); + + TypeId helloAndWorldType = arena.addType(IntersectionType{{helloType, worldType}}); + TypeId booleanAndTrueType = arena.addType(IntersectionType{{builtinTypes->booleanType, builtinTypes->trueType}}); + + // (number) -> string + const TypeId numberToStringType = fn( + {builtinTypes->numberType}, + {builtinTypes->stringType} + ); + + // (unknown) -> string + const TypeId unknownToStringType = fn( + {builtinTypes->unknownType}, + {builtinTypes->stringType} + ); + + // (number) -> unknown + const TypeId numberToUnknownType = fn( + {builtinTypes->numberType}, + {builtinTypes->unknownType} + ); + + // (number) -> (string, string) + const TypeId numberToTwoStringsType = fn( + {builtinTypes->numberType}, + {builtinTypes->stringType, builtinTypes->stringType} + ); + + // (number) -> (string, unknown) + const TypeId numberToStringAndUnknownType = fn( + {builtinTypes->numberType}, + {builtinTypes->stringType, builtinTypes->unknownType} + ); + + // (number, number) -> string + const TypeId numberNumberToStringType = fn( + {builtinTypes->numberType, builtinTypes->numberType}, + {builtinTypes->stringType} + ); + + // (unknown, number) -> string + const TypeId unknownNumberToStringType = fn( + {builtinTypes->unknownType, builtinTypes->numberType}, + {builtinTypes->stringType} + ); + + // (number, string) -> string + const TypeId numberAndStringToStringType = fn( + {builtinTypes->numberType, builtinTypes->stringType}, + {builtinTypes->stringType} + ); + + // (number, ...string) -> string + const TypeId numberAndStringsToStringType = fn( + {builtinTypes->numberType}, VariadicTypePack{builtinTypes->stringType}, + {builtinTypes->stringType} + ); + + // (number, ...string?) -> string + const TypeId numberAndOptionalStringsToStringType = fn( + {builtinTypes->numberType}, VariadicTypePack{builtinTypes->optionalStringType}, + {builtinTypes->stringType} + ); + +}; + +#define CHECK_IS_SUBTYPE(left, right) \ + do \ + { \ + const auto& leftTy = (left); \ + const auto& rightTy = (right); \ + SubtypingGraph result = isSubtype(leftTy, rightTy); \ + CHECK_MESSAGE(result.isSubtype, "Expected " << leftTy << " <: " << rightTy); \ + } while (0) + +#define CHECK_IS_NOT_SUBTYPE(left, right) \ + do \ + { \ + const auto& leftTy = (left); \ + const auto& rightTy = (right); \ + SubtypingGraph result = isSubtype(leftTy, rightTy); \ + CHECK_MESSAGE(!result.isSubtype, "Expected " << leftTy << " numberType, builtinTypes->anyType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "any anyType, builtinTypes->unknownType); + CHECK(!result.isSubtype); + CHECK(result.isErrorSuppressing); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "number? <: unknown") +{ + CHECK_IS_SUBTYPE(builtinTypes->optionalNumberType, builtinTypes->unknownType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "number <: unknown") +{ + CHECK_IS_SUBTYPE(builtinTypes->numberType, builtinTypes->unknownType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "number <: number") +{ + CHECK_IS_SUBTYPE(builtinTypes->numberType, builtinTypes->numberType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "number numberType, builtinTypes->stringType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "number <: number?") +{ + CHECK_IS_SUBTYPE(builtinTypes->numberType, builtinTypes->optionalNumberType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "\"hello\" <: string") +{ + CHECK_IS_SUBTYPE(helloType, builtinTypes->stringType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "string stringType, helloType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "\"hello\" <: \"hello\"") +{ + CHECK_IS_SUBTYPE(helloType, helloType2); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "true <: boolean") +{ + CHECK_IS_SUBTYPE(builtinTypes->trueType, builtinTypes->booleanType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "true <: true | false") +{ + CHECK_IS_SUBTYPE(builtinTypes->trueType, trueOrFalseType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "true | false trueType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "true | false <: boolean") +{ + CHECK_IS_SUBTYPE(trueOrFalseType, builtinTypes->booleanType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "true | false <: true | false") +{ + CHECK_IS_SUBTYPE(trueOrFalseType, trueOrFalseType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "\"hello\" | \"world\" <: number") +{ + CHECK_IS_NOT_SUBTYPE(helloOrWorldType, builtinTypes->numberType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "true <: boolean & true") +{ + CHECK_IS_SUBTYPE(builtinTypes->trueType, booleanAndTrueType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "boolean & true <: true") +{ + CHECK_IS_SUBTYPE(booleanAndTrueType, builtinTypes->trueType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "boolean & true <: boolean & true") +{ + CHECK_IS_SUBTYPE(booleanAndTrueType, booleanAndTrueType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "\"hello\" & \"world\" <: number") +{ + CHECK_IS_SUBTYPE(helloAndWorldType, builtinTypes->numberType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "false falseType, booleanAndTrueType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "(unknown) -> string <: (number) -> string") +{ + CHECK_IS_SUBTYPE(unknownToStringType, numberToStringType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "(number) -> string string") +{ + CHECK_IS_NOT_SUBTYPE(numberToStringType, unknownToStringType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "(number, number) -> string string") +{ + CHECK_IS_NOT_SUBTYPE(numberNumberToStringType, numberToStringType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "(number) -> string string") +{ + CHECK_IS_NOT_SUBTYPE(numberToStringType, numberNumberToStringType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "(number, number) -> string string") +{ + CHECK_IS_NOT_SUBTYPE(numberNumberToStringType, unknownNumberToStringType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "(unknown, number) -> string <: (number, number) -> string") +{ + CHECK_IS_SUBTYPE(unknownNumberToStringType, numberNumberToStringType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "(number) -> (string, unknown) (string, string)") +{ + CHECK_IS_NOT_SUBTYPE(numberToStringAndUnknownType, numberToTwoStringsType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "(number) -> (string, string) <: (number) -> (string, unknown)") +{ + CHECK_IS_SUBTYPE(numberToTwoStringsType, numberToStringAndUnknownType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "(number) -> (string, string) string") +{ + CHECK_IS_NOT_SUBTYPE(numberToTwoStringsType, numberToStringType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "(number) -> string (string, string)") +{ + CHECK_IS_NOT_SUBTYPE(numberToStringType, numberToTwoStringsType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "(number, ...string) -> string <: (number) -> string") +{ + CHECK_IS_SUBTYPE(numberAndStringsToStringType, numberToStringType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "(number) -> string string") +{ + CHECK_IS_NOT_SUBTYPE(numberToStringType, numberAndStringsToStringType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "(number, ...string?) -> string <: (number, ...string) -> string") +{ + CHECK_IS_SUBTYPE(numberAndOptionalStringsToStringType, numberAndStringsToStringType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "(number, ...string) -> string string") +{ + CHECK_IS_NOT_SUBTYPE(numberAndStringsToStringType, numberAndOptionalStringsToStringType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "(number, ...string) -> string <: (number, string) -> string") +{ + CHECK_IS_SUBTYPE(numberAndStringsToStringType, numberAndStringToStringType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "(number, string) -> string string") +{ + CHECK_IS_NOT_SUBTYPE(numberAndStringToStringType, numberAndStringsToStringType); +} + +TEST_SUITE_END(); diff --git a/tests/TypeInfer.functions.test.cpp b/tests/TypeInfer.functions.test.cpp index 846d5b53..1c46b91a 100644 --- a/tests/TypeInfer.functions.test.cpp +++ b/tests/TypeInfer.functions.test.cpp @@ -1212,6 +1212,71 @@ f(function(x) return x * 2 end) LUAU_REQUIRE_NO_ERRORS(result); } +TEST_CASE_FIXTURE(BuiltinsFixture, "infer_generic_function_function_argument") +{ + CheckResult result = check(R"( +local function sum(x: a, y: a, f: (a, a) -> a) return f(x, y) end +return sum(2, 3, function(a, b) return a + b end) + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + + result = check(R"( +local function map(arr: {a}, f: (a) -> b) local r = {} for i,v in ipairs(arr) do table.insert(r, f(v)) end return r end +local a = {1, 2, 3} +local r = map(a, function(a) return a + a > 100 end) + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + REQUIRE_EQ("{boolean}", toString(requireType("r"))); + + check(R"( +local function foldl(arr: {a}, init: b, f: (b, a) -> b) local r = init for i,v in ipairs(arr) do r = f(r, v) end return r end +local a = {1, 2, 3} +local r = foldl(a, {s=0,c=0}, function(a, b) return {s = a.s + b, c = a.c + 1} end) + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + REQUIRE_EQ("{ c: number, s: number }", toString(requireType("r"))); +} + +TEST_CASE_FIXTURE(Fixture, "infer_generic_function_function_argument_overloaded") +{ + CheckResult result = check(R"( +local function g1(a: T, f: (T) -> T) return f(a) end +local function g2(a: T, b: T, f: (T, T) -> T) return f(a, b) end + +local g12: typeof(g1) & typeof(g2) + +g12(1, function(x) return x + x end) +g12(1, 2, function(x, y) return x + y end) + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + + result = check(R"( +local function g1(a: T, f: (T) -> T) return f(a) end +local function g2(a: T, b: T, f: (T, T) -> T) return f(a, b) end + +local g12: typeof(g1) & typeof(g2) + +g12({x=1}, function(x) return {x=-x.x} end) +g12({x=1}, {x=2}, function(x, y) return {x=x.x + y.x} end) + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "infer_generic_lib_function_function_argument") +{ + CheckResult result = check(R"( +local a = {{x=4}, {x=7}, {x=1}} +table.sort(a, function(x, y) return x.x < y.x end) + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + TEST_CASE_FIXTURE(Fixture, "variadic_any_is_compatible_with_a_generic_TypePack") { CheckResult result = check(R"( diff --git a/tests/TypeInfer.oop.test.cpp b/tests/TypeInfer.oop.test.cpp index cc0a79f8..c589f134 100644 --- a/tests/TypeInfer.oop.test.cpp +++ b/tests/TypeInfer.oop.test.cpp @@ -407,7 +407,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "cycle_between_object_constructor_and_alias") TEST_CASE_FIXTURE(BuiltinsFixture, "promise_type_error_too_complex") { - ScopedFastFlag sff{"LuauStacklessTypeClone", true}; + ScopedFastFlag sff{"LuauStacklessTypeClone2", true}; frontend.options.retainFullTypeGraphs = false; diff --git a/tests/conformance/native.lua b/tests/conformance/native.lua index ed985c49..9b5bb884 100644 --- a/tests/conformance/native.lua +++ b/tests/conformance/native.lua @@ -112,4 +112,22 @@ end assert(pcall(fuzzfail10) == false) +local function fuzzfail11(x, ...) + return bit32.arshift(bit32.bnot(x),(...)) +end + +assert(fuzzfail11(0xffff0000, 8) == 0xff) + +local function fuzzfail12() + _,_,_,_,_,_,_,_ = not _, not _, not _, not _, not _, not _, not _, not _ +end + +assert(pcall(fuzzfail12) == true) + +local function fuzzfail13() + _,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_ = not _, not _, not _, not _, not _, not _, not _, not _, not _, not _, not _, not _, not _, not _, not _, not _ +end + +assert(pcall(fuzzfail13) == true) + return('OK') diff --git a/tests/conformance/tmerror.lua b/tests/conformance/tmerror.lua index 1ad4dd16..fef077ea 100644 --- a/tests/conformance/tmerror.lua +++ b/tests/conformance/tmerror.lua @@ -12,4 +12,30 @@ pcall(function() testtable.missingmethod() end) +-- +local testtable2 = {} +setmetatable(testtable2, { __index = function() pcall(function() error("Error") end) end }) + +local m2 = testtable2.missingmethod + +pcall(function() + testtable2.missingmethod() +end) + +-- +local testtable3 = {} +setmetatable(testtable3, { __index = function() pcall(error, "Error") end }) + +local m3 = testtable3.missingmethod + +pcall(function() + testtable3.missingmethod() +end) + +-- +local testtable4 = {} +setmetatable(testtable4, { __index = function() pcall(error) end }) + +local m4 = testtable4.missingmember + return('OK') diff --git a/tools/faillist.txt b/tools/faillist.txt index 390b8b62..f179a63f 100644 --- a/tools/faillist.txt +++ b/tools/faillist.txt @@ -254,6 +254,9 @@ TypeInferFunctions.higher_order_function_2 TypeInferFunctions.higher_order_function_4 TypeInferFunctions.improved_function_arg_mismatch_errors TypeInferFunctions.infer_anonymous_function_arguments +TypeInferFunctions.infer_generic_function_function_argument +TypeInferFunctions.infer_generic_function_function_argument_overloaded +TypeInferFunctions.infer_generic_lib_function_function_argument TypeInferFunctions.infer_anonymous_function_arguments_outside_call TypeInferFunctions.infer_that_function_does_not_return_a_table TypeInferFunctions.luau_subtyping_is_np_hard From d5e37ab367a5c20b6a38f4b089d028f880b335cf Mon Sep 17 00:00:00 2001 From: Alan Jeffrey <403333+asajeffrey@users.noreply.github.com> Date: Mon, 21 Aug 2023 14:21:10 -0500 Subject: [PATCH 07/20] Responding to referee comments on the HATRA 23 paper (#1014) --- papers/hatra23/hatra23.pdf | Bin 399315 -> 415900 bytes papers/hatra23/hatra23.tex | 52 ++++++++++++++++++++++++++++++++----- 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/papers/hatra23/hatra23.pdf b/papers/hatra23/hatra23.pdf index 60201e651322b49b8097d3f533d5da0869124dd4..32e83b9ba8770868167b4160dfaa159a54bd6a6c 100644 GIT binary patch delta 138659 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

^B7>}MP7CduOTDq7mA$g9+5UA; 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 z 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 Zt;`BiR4QyiJ|TW#J`5%%C2eJl{{c4iZJ7W7 delta 122313 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

B^%9{MiqEwizUU77Y1l*3v0 z6j=^a3rcc3jKt~3PN$J$x1g&cLO16tNg9WK-7}MM**B|5l444YQ!wJkUfaSoZ@dr{ z_^1mDOUg)bt2)SN$jZGEB9Xn1G+t?WV&`Keq(<>+pU7&(yXG?^Czb=`Mz};Wm9uYu z)u-Alf#+p5_!)Y@`})h(79ReT*Z0q{G7)rC(AQp%t}`^+H{|ulUI-G$a!RmVcc;KS z??K++(a7g;B;~hP%*sf!rL&!K<>r8#xUZ1)O4Pa^a<}oob>tM|V_K z-^vMT9BFKuKACx>s?AzvE8A9clcBSr$$7Q<<{nfL#bd}n zH}g~MV#?v#U@>>{<%`>9+7YEhm*iO+ykeAz}xo8!kG7+RuaO~p_H`!Hx%xhz- za*$4jo9&QFQ=rfA(1!uwo$1EzV1h+V{+#Wx8xGdi zKtJZcmSd6rzKRVOp?vJ(?|c7RU=r5VC*%;e-XfK;HuWN98Cp8Ev?Iy*!G??GZ1v>{ zUaqoK8=`6?$ddz98Pf7lnCoV;A47tfkmdHEC?R#H6v6EZNk2zUIrKq)t$>6yg@2gN zljLn(8tcQ<>6x2I9nM+<1(yp1x4dfLDU&CAN?PDt^=u0@0gHU?tIFYPfojmW`tC^( zr8oRjm-pbZL(3IP_Hr1l#1n|c7_XM2bF*6p3O?rIzG8{51G196;X^y=lQ3UWk^ZeU zh(CJ&9>9j;?eO^UlUII!APe3EnI3SKoEe?})Wx4nlB*bM2#>9%_i*tNJ;w}=x?KPn z?-9kq-!z(pS%_YvXQS$#+H~^Ek98QYg!s~rivLA?Z|I^3W{7mB)ijwrv2TS9{r67w zQ3`4VZ)2^DPb4dg>(1^_=KEYmru;0C!`EG)=*#Qk!MjxRnJ1`!BZ6Wg{Cy)Ajl<>c zqV=W+xWoJ2yollXo<3dpZaib>RY@)XIh+a7q~6OT;snS+w z{YQzf{G+*)dk`~5f1arDT;N?f%qkt&kDytlKom*_p2L_k4SGx-lu-58z<``Ox%yF8w9%-lQ9BGf_-38a_^@>|XAkx%fRYOxd|bRR~?yw?Xl7+NIB ziK`1eBu8)bW8b&8_(Y*MW>-OV0{J2D*vxP^$$RO|l3 z)wU4j0c3Pd>#QwQyVS4a0B2fwieO^*JTUSh)L-#`7=`)P8mJuM=x7b)ACQB`@6t)Y zTT!$R5W%3q2#X(IVP-!{tqulan0Co@OglgLV^vd5Kc!7hn`!bsfo{>_3T7+9aSwy! zfw*^t6UPB9Z>m*v+vAb*G)pIrh-=tI%WEGSH#S;rR?f_V=Xy8*iCHD>xS#^2*HmVE zdiqL#I!p)k1Z$>{mh-Nct~aI)#QVqoUjNvH0k_pt9y7;_ktS^;Hi=-5RH(QSV4*d= zFGGn`u3o{9N@PO6X3xO#)FN=J?;|@N!(-mGdB36sy&yEA@km;t6HhK9)De}{o@mzN zcLrv3Yzpl(sjQ|S`ktMz7!#}N+a>~`{Dbm;78=@5^#^bB7u7@+^S`g@9+g?M1w@8J zfT9&IX=^$Ff08$Z3~lml)JW6MsU$tse4T_LR}mVxit9*VOcRX%Kg)uRaJzsvdZRv z<2+A!#dZlM9PQBxh_3AW}A<|g><0mUBk{rfbeyo|wuN=x5mBmfj zf}B|qa?MdiWMJ*EWY}eh=XC);WYCyv-C<&|;5_-a!yjF9t4jMB<$!_|8v8xhZ+dvk zC-A`0y(O3Y`|t9c{jUvB3|GIMqKnUeP3SPDwQG`LJUIO;E`>r>F#~bB2y1xPtGg_H zqBx0=F!)*wO#@xtbGy+Iy$|iI*QFZ*st@9;J`&jwo|pMseHfpXq5^GW=>aNww%WWK zGCLO0NFvhe(2%{a5&S%xz0N>o% zq#}cN1-R&`Q31ag-VGNHDZOnXjhNx?gXObJh*Zs$TlsoqY(!1mUEh1xhJ8;|yWXG7w-SL)h)Hh{d@bE%k=sn{m4A*o1T za^LU0xM1h>40Gt14F7?zx<1xr5CV_g+xctDRt8=m2iev=5vk5PLa>q%PA|&WoIt;x=C!6h7M@K{4vCF9jt^A{UT-=9t2-^9(Gg%65RfR!+Mzdju`s>Vc ze_Y7y2lF`Jicjf2MCZnatz;TeRuM2G&A?>EkE6kx?+V=gA=-?2tq{lo+M z$Oj;;i}B*4gzTb17=9~%UdBoji{v2p*~B;I2iv;(`bzaLWu$7BF|OjPx3fAVtWEu{ zWt(7oR)I#M0R82`E&*k|X)suy#fPS6H-bJEJHd*U<}&4E|Gta{D!*Nkz5=N$^vxU% zd+}|zTXLbGLXm98wLG64c|w&sq1lwQm&VA7pp=0LO7T++E`Wu97WUb6OYb+0IBX`2 zfsaM9gdX>U?amLZvL1VJ*Se(lwnw3mAp}j*vP6OC#r^M>>5Qc)X4kC@x3;1=n3T-t zw0kZb3hCw6YE59E=m_NuYE(ah*I$PNkrJ>r*^nKYeV8uXS?R4{teO{;@57`XbRJqlrj4`QJ-7UXY z`Av~h?`n=xwBkKYB1{HX05 z)ycG969<3KL-d0Gp45dFYk?{?Yhdzp+N~KN>&g*-0lbM#U`>$spB79Xg>`|Tt0VCN zLHAYyF5VrcDS6@E_>mxsnPT$c?lT=brM)(>aPBcbEDh~9*>k9+>?$efpcrID0{E6J$(mj4a*j$K7AF)aTeB#I%p8jmg9$a8f zxHhMM;jv8D%G&;rhj_5taiybl9>4a&e88!1mBOuFjC&Tm=mmAcawG*!qst|_HD03XI#i+o4_4Wu^uIrb+oVfE}t=U_A|Ja@f&GX5V z6qwrg=!8ntPxak#sr3Bxd;zDGzN^Q?Sx|x@VnU1m89|qw2c8_r8Cw zDKuvi3zKUvYPc0A%-S*5*)yXmoq1tLue}(AV)NTMWFJz0@fT`E3OOWLjf?u~CS%=y zR_(JqR*Eih@yWnPz#3L=LuGk_J`gmb+gsznoaRUS$4jDP$iZm>ollg=VI;WZbSpyH zc-p`B--_hUM--oZB*1>2szb=&$vZQ&?l1ptGMQFuBqsx9uvJFCWQ=O|&7#d*oZP#7 z0(VjWIQ?X?$xtnoOJkXG^shsY>Q(d6M zEd#ISVFu!Co{|hU>WU7qK}ZH^@BOh*_or01w$9K4Gk9Uru$(75KcOW3Z0OwiC&{?G zjvBS)VvD16oTX6&lU1!M7I%4$Mv|I|fcb^p!i-l=Lfw97=r5pT0A(L7WQs?B0*vT= z1DSHn0^H;+o<(rrC^zhRL_n3h6$05#JY>#;Ut)V24x;4?A^lVXjv{X)87bHB32&Ud z1qyX;YDchW24c%)$ybM9Kz!Q9Z50#b6Qz3PQ0{rrTDf!rgFTP zYUcK?^3?^)DFLi~ymC*y{;w5(aH7B9QfYvV&1komK&oW#OCE;;wD1;}{qa&=!}FJl z9CJI1WdaUWGqLzRGJvIPr!4WgvRP22U-dz+$k)yCEVs})Rif>BQ4~%9wS6K zGR5&;>Y*WO3Lv@&F2XG!x4E7|@>PmpP=K!^NDCIPx%B~mdhT@776xU1@wHmQop3W| zjromvhC$LP&t@KS^wXM<_y^;kz(Io9O9DwRloVQQUg0#B`trO(=4%!9qL3u}i@RI>(Xm>Ic)7tW;eS7l@E z%M63d6#o<1=b=<86~h${gcW{jJp)GHPtE*{y+j2DTeKlZI?ACNg`y@4M!MWUBM$ZD z<GaUMP8o?~8oTfYE}e>K4pd1xMI(u~;}sN?fF80=}NRK9uM(kww6W6a^2@-YN^ zUN`t$IvP6nkOdN3#q#!bK z{^G&7Cmd2;zqU<9FKQW`(R)Q77DvE=?dRS=`o4eFAi(Z-Qj_5xe?$%FS8(o}k*gmy zjqVQDsbR*nTo1?3m3?(AYM|w!_Mp@G)i`;>z9Rq#&Oj-d*H6xyTL5P9p5#}$Ii~(* zA^`e0Gt%91FSyTv8i z5geyJ>ODPQ%4+$Ke@kcRd-;#Ko?K&h)mpPv;svm^K8`Be0aWMtWBr2XE~Q7HpfQwuFusOrH(a0Z9ivi1nDvGE&N<%^p&e<>X9p_|(c3EHj+ZG@26 zY042RhU!bTnyF6z@{IJ?V)0UI;Bw`0vcWIRdf2}%(yg0u)_3u+LRH?TDP3V)Z>yMVp ziTVC%d4gBjf0h|C0t(f0h2fem!$}&#ci$;cajm!MM{F;d%&b4hn;K%7oe46|su~(7 zm=3IQM&j-Hf!C?Pb4Qu=6VvK0ILqYr`h#Xu2GvJf$7H-YXZxqaq$^(#!d&0Mn|i-% zHh%>Vp=WTTvjQ!)9*dPJe;86NS6YmXVke4X0%MDBfQ%r) z3g%LpGyF=at_eU{P_4o|vVcqCyukmEd$};1>Bab=P$gygf}?ljquj(R#1Q5q9hO)& z$xQVX(JNMPC-0V8#L!0fwVbPeJMBU+@nKa*yGl?_!v576ZtStub|u2D-5P=LWdsY$ zjmalDe;YkRkQ> z6c>5Z2k#(Y0~) zf931sbBjnJ75#I6DnY!teZz`o zH$k@t(cur;w~z{TP_vR@^(={L&98&7Z1;FB`vdC@sx6PHb4;a83jODWsPEyMjBtDP zOqJg|zd}W?5u-#+E)C~>3WN_UHPyG+f6mnA&jmjo5#4OsRAE_EJm>R1<51-==}`I! z2UoLACn4aqqj&$!Ldiu9UQ9Q{)BWHh?x4wZ6IuQ{bdKQcmifraWcIzYE0MZKQQi*7 zVRbNzJD^}PKRJg%*i9m<v_By^thPvWxFBrC z;d=8rKj6_2!b4DmV?f;++P-@a7BewZ{^K~+Ch7u}aYNKc87PCF>(z-EL=g=|Vrwx` zF+oa@@e|4I-m+6y(Ns_gJ5U*Re*mc6V>30sjn1mQrSPdLf_q->d+9*|U50ILi`6H# z(u?ne@;67RbDSO|0^4t z0Fm1_L|wU>BQH=3X^oCxfx*HsT6M@kL^VkTWF-XA6K9I_ZN>G#$St zD^k-H+2Fbo1j(B$tu64Tlkz`~e4~+Nh!GRa0kyY5TD#7WK~-Q!n)io1461}m%+zq4 zl&OQ@#6C=5CJT&$cK%Trf6))>M2FZDlaGm7nC770{khjdo8Lv>u-lStM;N8^gQYxQ zK?0iDgiCntWt-;j4ueuvnuY{QEdFRFA8`~yL9_haEK!2>*wQGIn~!R-V|g4n&W7)S z$7Ze5A=UmE#-}piq+sA#9uG3GEm}9mYYTO^dbWU9agp7F3R>r(f7bJUusv6MHG0aF z4>-yWjQS#*Uo!=CPaovQ3PL##gz4&A5c%V|_oxSP^5_?fx@$bXav&}i>6zG-_5Awc zyA%K$$GUDTw}I^VQuU^3Gg$rl-Ei)Yy&RJJPCn!|4@P9V9~o*bV?aOF{e&rz*bv3phVpRS84d2dYShnZ70+1nJg|? zvDOA=QUG_N%-=Bv**K*scJd+z956C2kDsRZADBul?%*XL7Mn^*O6Z#gg&?R+bKEXW zqB0VmqB#H5L2)zGG}p?rPMiS{p#@~*)vFFRNg)`Obz3-Te~S>WQ6k1=L~$-`67GRU zuF9h5Gf+$=A)%u{AD$P3>*RGvf!+rUqtK}PlfbDtpZGfVOVcAbkEgj1{(t4-@Z)XW zV^K9ETO&#h(HROS*RIq&vC32)=3RH-yO9B|L3%h1+< z;PrkFjfZ;}e?A>{gE8f|xMCv)k|gT{io0!BN=qTxMD(GYTDR8#}>59%;?y@)KLzTn@2 zas9Xoe|z;4xAamt1&(z*|H5sw3LBxR)XUFqo|$C7VdXKk2c7Q=Nr=<9+H~*lv{b;g zr8my~(GzVJl4?q5lP}U#C=Q;cnAQPU`BJr!fB)OX_cr|y?fg%KL@w~_(-!Y7eWE(1tw8Mt3}a+iw4PyTJP(n}5l^&HnT{#6v_J z{Z~pj6Ka-8@wMy5>#7XpSyX5grlYVLf3?_oRMqt!RtZ;TpDfm$8I@mKOsLQY7gq=L zu~|#oGi$SwY$aDJo&TBY2Mt>9D*~_fqrDai?OkMeuLs6j{a7q^HZVj#vXpAWEfD@4 zZfvuOrl`DU2mzN`eIDvDjk^!ZYzo6hh&LCKIk-HEV$IoGP+LgaV%eQ^7BAxIe?Nn? z7r&{*uB5}52nQ*kxy!Jciw1PWoEXxo!P?0TzSYd&#F_4B3}7NpG*L1m8HtzVe--L$ zx{WyWGvwJPwm64_|22oBW3!@f&SY z8u9u$lNyI-=4Vi_zmrKQxk&j2f1h5SlZ0OBN{^H%I8uhl&akVA{!TS%N&A~dPj{b6 zUX_y{IZw6Y(lRbQ^gMkAnnRN{x<`BnbJo?)d2H1|_Ze8S4Fo~O@xecBdG!MmHM{Vf zh(cUF2>`aK58y5kX{Mb4Xc8Fak=}4pi7o3k&y8njrT*GB=?qne0T%J2e?@4S1C9)f zTl@zhJaR8uDrvLf8hop$!G}L)19va)Q=w&X6>sJVb3CgcMMWj931UBI6w9jE5lhPIDCZ+$GN_;#AZH0LwPqIpUgq z<8`T|oq-q2q7PNnT$?xAzpDq14)N-caxeAxDEOkF9T#KSOVr6>BeCFRcK8>TKef(?=na zTA_!DlT(R_0})^^>};V`zW!g$9lHP!P}h7=1#-KToKak{@9bK#F&BQeo)-;H+|?%{ zYnP|c7V(rYR}3yxq0gZJJFV#7quTuXZkR4r#CxyZ_EV`iA%i?rIiYODOuLoLflPU3 z^;g-LH$BB7f0EFHGXq|I*vS(RA!uocOPy{br~XIyVC{SU37AmN{4A7V&YnPUT2b)Q z>54eOs{btH)@hz8+R_f{@KTm@8>=_>MV0GNEk?P6b6!v*4T|@Bzg6Jh5$hhoMWs}M zEggiuU4y9aqWQ_A3>8j6FU(1tqWN{rG+S}AY&3bafA%^BT=Plcc1ounv{YSlPW?&i zMmx!tNcKQv&Mx3mje;tZhv8j2#Gl>!)6lScx$HBB+cuC0LQxYNhpt%ecLwpLIfi_0 zS)u7P)!lvo`Z#j*!*2?lC!q`Jw=L`KV=JI7jb&EXDv^IS?^5;d+^lAB@7VZ0;2RfB zi77ddVy?htry;j3&nv zUrOC@@ev;^gsU?=uDVl1eoNpUi>=ftlf@N4SLofjfRbX{i&FoP$57R!ch`;v`$j~P zwa*A(Hd<~1@OIZXWdWsv9!9Fs;&Op8)Lt&DD0az58gYL?B%^ch13F}v>R z8<_T_Kw5XmpTLa!ia0Ke@%t)q02h9&!CE~nukUL%czvbrm17N^nw=Sh9zO}KB%c&H zoY{gEy5F;!xGGVt##`UZ0GO10F!u)StgbM1^*XHk9x8z4_}Kvg{EiD{b$I0X9+!hI zf3YcKOSaDH+GH4oZuQ+1m4jdlrXVv-@`i7M{y4jBW>~Khl_?Hre$6wVG8Cl zm4J!j%>S*lt<%+v*@5`Iw$Ya?_n1rcux_-1uhu5e>eegG5IHcPy@}O6_+VLxKDVl( zT^v>rh2(3ukHYEtW!#M-fzw7??}T(Oe<=cKB|Ox#WIKK6Ljl=L_-3T@Ln4Q!)uzS$ zZ+tgYB;_p>9+jPy$w*zBEEzjcts>9CYcZ@s`eU?0t7JrKs4twqpWQxctpv-g3)a9lx*TU|f#|bI=Wt^jQHT~5#U>CMfKf)*f6fLD zvW7neypMeSLA7a*XsG|d1Xwa|oymrd6wla~hLU8fI}_(MVQ&H;KuWR80TLaSwdECPY6T*?ZWh25o1EgpS zpFePIfo*KF!UApHb^?g0au+van+E`JA)Q;0fpkQHaE(xpF;X*8W2Zy-`Z@>t1*2Tr z6p2C;O@DvuI&w^Ou7v0~Gu{&ML-4*h&_$S4lN8P$Z=l$tyY6+=;J?MMf1OXO@4?=! zZ;}$Gf@o3NXb%K5?kOk+39q2rgxalHTu{k5unrD$D;Ie^eu?A7XYCGZCecSB4v-^gAZOnT^qsY#iU%`b4fv=i0pDqp z9v-II&hv_pUmOSZJJ?dnfBMn~t%f2vE;n%-8L5T$Ob|F&iv3H8Jn#)E&g+MACCGtrhe%p5wtE_GwF?|eMjA|pStLai zr@Xl*7MKY=Kk%naJH@O$^0GG>`S+e81j+<#OQ*?oF|D&76xTGbe{a~*xHT9)Q}t@e z-}JG32VXMx&Vk_9Asj4cWcLfN@M1t>I;FMCZuGX08HvKX37A8 z1HWn9XbQeP7iPy0#KcZpMJG*izW^YSE-lv4yF})Py8MELGzTX^=q7>=I{wEZ3eA>? z^|FzPMpqT1!p2~ve>yY=R)Hf;P$>RwQlecrV&1(nEb8LmmrZnO^Ij+%VBNERfnl*< z&Z%N)%yWz6YyEFrUgP23XY5T9mb{n)%?V06NO$W`izK9k+q-`0i@Gw|b^2Rtug4Z+ zmHXMIy3w1EWREYlubqMyG(uo}Ysf10*!pzRXoE{BHSqvZ>1 zQC~mCer-ozo@}gK%g1iuq<|xt*hsE9SU#?r-*6Gk`kI^b4*Z+bZW0jy&xBvu^D8cc5mSnExl_I4D2ASu5d|Y4Q+m%-VIt$x-P`hw>b>2?dhb zBe%X^Or@U~e?%d&I1wGjKk=Ig#u)eKXf1&CSp^_}{7$We@B0bm6WxS7B7e-KI=sm7MclXfy}g#zYso70^} z%l=MmPW<{6Nm2u7J~^pdhgHfXhiSQAB#~wM_;@}1Ur!Ddl(dK ztdFyZap*qTk*&Mpo-NH&bQR{o>dY1h);n2ySdc$Fh!Ctl0ZUkO46;d2{^NejNx< zo1DMS6ep7KjMWPh?ts5`G6~pWrANbdf9-CwCB)-Re#^63)(ds!kQpfcFM0?BxCAtc z=pL&1c63m=L?oBnb91dk z?5n^4yyuPluc>XGqOm?)+*wFh6(f0BRrwgC+}D_MZG#Y~3KjzvMIk$$G%xM7>%2>F zvUdH zlLhjE`GaESQnFK1%*wl~f5C(BB#WdNv~QEB`17fYI7E-4jF;U(c8GA0zArpS!hhW~ zub#P!9HaynMiCti+ol6>Q1OxM^nEzXes7qUo4Q1Rf}Ea0&*j3<7)H@H6gur`x}Gj~ zy1doQ--@_H-TFB^Kju3hbB+}u3&(=#2IxW1$^T*gqbj45oy7V^e?x1qqDf5c7w%;- zwx*aMMnGMB;fzs#v}~%CG=meM;%Iq^d$;`P$g70T;OMn4yjCC!C-$K%6f-2J9B)Z@ zYVRY=(dqztfI=ZUec4SM0uOGC8@&1SwxxC$T_?wg9`b~|{CaG;wzSzC zA)T3%qq#RmYec=+HhMV2QC&L7=`^;DBCB?O8i(|>aWs*5))d=9CfjIYRv#P{F?t*d zx#IHfQP84?e@2Z>GYBK0o^1l}1#*&dENp$2-#yQ3v&!U9D_{)oqm9&IODPGZv`RrL zNR!O7%CdWohKRZJS1J^pFs~G)&A!qB4GmSIo74g=i<;y5y{HCfMkY`A0`_x;s2N8U ziUH?>cie1T0gMcKAgMV)m;%Pr>BB5#nN|xX9W(pKe{2*!?UXp)#xY^9SA(I#Vwot; zrN>w#_Y-;@WesHw5MblKp*POXk<^x)aIL6iM1|J?xO?DQ)%NmSUr#SLD@ud@3(iMb zoy`hk9A=#&mAjY2EC{Qx(|uw!)b}<#x~jJJv%NiJ>BX4pKY5`f3_wfx7>488wv#lJ z@1C*ee;GZ4@ly+S0Ys(5>+>BXw-md!HVh6m7tU^!M8w}*bDl&2>-um0i^+XY zm@6>=b)RhaHu)rvtDB^5oHVPG99#W7oQ(i^Ti&?0g&4|@g2FImk=>jhChT4NKIDaB zhCHk(>=Xm-NNyI87 zh+l^+O2iZ`qSTUU(7%uuP5Bi+nj#^-*eKPEiiq`Ewb6zk>qy4m2cW>nnxVX`Sv98? z{xC!?>el%AqaHW?#QEZtNy%XdY6wMnf1Y76^BOU+Fqx)yf{M8h|L2+?{1;6kk{p!y zz`f9+T?~);3wK-VWB*T%(bUPhR9!>c_VllSrZN^y@TV%nq~9*M;>k5coG*0Y+3hJC zuQEtkdA_=)l(n=7mF7~^ZLGmP?(osS@T&xdKH~|B8p8)}WA~jYf3FhNhnBs}e|Y}V z{^X*Ck*&cIPePI}VDQAd6_w@0fUt!Z2syzddKc4-CQz%Y&Ozw8rSK4D7swc zl`0>UPncM;96Cr&*uvqE#+!pIdm~zS%bca4KBjX#7cvlgfwERP;>igFfF5tQDYu7Q zhPFenK8TPn3*NH^gwvd}inGTce~@|&RLzvwMWoBO2SU;7vOHL0d&fsvsQ-71NR|~E zEO#^p_og(?cyZVSf90#hT=o*%($7(B8=Douou7RL91s=W*vjxk{_W+Xa{CZ-PtTZp z7atdW@;9M=;jokOYO;Xr=UMMk{UIKN9igWCj`+afgI+hsr8)P&+f3#Qe@@4VBE~Wa zW0`9zcloV&-QPP)OJ$WEdBc1T?!0N*D231yLPU%&`>0yCr2%Eq@b75F_Td`v+qMIe z(*p5HEpgj!1RFNb7lCa}BiVrFqe=lr_nmc@un~0_<5)MKkbnSmQ ze^-F_x74C71;hwEN}B~7D#zb*e_%*T*Q!C02!+PV7a3A~J7b8MwMZkS1xAXSUSz~V ztxYD-XybP+$qYeuSR5jqM)Goiy^yjNo==;a$Ypp5*D+(+=<7R_e?OjFW|FRL?5a1Q zc88$ZuJsGwZ7=&4C;~tNg&bW}O04z`8gxoFlzd;&Xqeq{NgBvD#J?o3g2PK~l$X5Q zm}CfIo_g0-Tr8)nzjD++=R5sW_@(Zn=LBU0!EK^in5|^O407(>@LYw`o}$V?N?1ch z@Ait#_b5U>n!H0?fA00Cw;b~-PC{=VrCi8O1R(^10@kQEj9{QFeauTCMPZ_TmVtUQ zfvDx6=DWvE8FUG-RfLZp3R^_{-5Uv+F(b;(L#B=jqu1!+4zmt>8~kebt9v8b28bDN ziq}8kW7}m{q({HGSyrpO#ruTFCAv5fl+XFig;bNGU&m9me+*=^E?XuR>n83PzEk6- z>qM5d0b@}dyY*-^{`bf?8^L16s zu-=5yoG7|em2qTLu%K)KCTU*!iI%(kDmHEo)kGX-47Ww_9@~|aJ!@T<`xj|lMd`aN z7}z`0J&#A4f4bw|$?8FokG?sqZxSBy53i;)blKxBjQ@k^fuLL)1NA?BW#P%WP}aL$ zF}{}=MMOn{^`*K)@3y_QQvN2b3*Cmj6bRR;A<1UfCR@n;(xP=;q!oMR$Hfm{uMV^a$1*omQCRPL#)t|uu?0Fo zHaGFoI6v~X#1LMh=BFH?TFtJ684k<8$yS1awZUUF?QD?phtCKiHtT36t*B18l(-qk zG$Q|Qe-#o0I_Wc$rJFk;WF5N&fF0l&f;gpe#PJ;nfGl!U8DPg?DObMT9Q#fh&D*@s z6Lp2Lo?xWyU4z=POMGa{d?VPYF%d4f8NTQL5!e?({;_pR-_S)nZ1EW;y43yd!_nBU zPZ+ziO!#XYsi`OqE_fHJX%c9@UPpTT@@Q>9f6dqEun_EA2ivlBPeKabQ zcRBaiid0^0Uu>tq$oXiM37$DP+njnX{px;P`*Jo+cpdb)-;YbyhC_ z8D2sn;b>1Y0tQ8@UQ3|SLq8jc9jp(wf7R}ps^co>AMg?x{y*zLv|la7%#BokAXtxl)vckC)!b{hehunVcuydJN{Q#DBy(1?c%FRMe=#4y z3OVJ{sg34t&q7|ffxyjLDtih4c8&9W?-UXmWz`V33T;#ibbxGD+7^3)OThWb4|DLo zUR=HDH4Gg!rQdPg5?aEcny4Q%1ec7<@YW+}$Q%R)RxDM*ri_d);3G!xECd4UiJhYC zZONE%75K%DJNQDYYCe_TOl+kXf1CL5BrDv>KI;xkdFm?(s|7pHS_`Mg@%Pe!N6mNC zC@Rb)1abK7O8QlUUBUSZo8z5kN+{tV;@%u{ZPV3E7Dy-m0nQNQ`2JiEd6MZN=b`hG zfsWb={pZ^RZGL-E7}H@|1`Eq50NY0MWPAvUDcV;r=wd4@6$f-oL@Mwje_^KT%jECB zbf$oN<=21G2QxsMm?2=$VJuyBiVAa*TZ_3ZR1@$ut&6J;Z)1|IELcBTox%rs`S%xK z!vd65j8@8$KAek(b7VZVnQd_vVn35f1@x2;PCFKQ@C&?ZFw*A9JyQixhP3&oRYA&E z&y?LB1x9B0e49V5gzC9Tf4r@+*j-C1hBti`$}!6OcUwScW7TdDU??ae1FUADT1#oS zyka&}4jc0V#rfXU6(%2PuwEnS@|plexPK&OFOAJh{tsA2weLQM+VcWzG9+5}DA z+x<#AsxArH1+&qVpC^KTz{8;(|Fzs``D%3x zZMUkxEJV_E)+78UN)mB7(4J9IY#Z4Sq;0l+EcEpMig@$je(%#r_(Orex;8uxuW&j$ZOthqM1eWKSDVe{LEF-(^7**{1K6?hVZK z@ov)|H#+umD%-@;M^GIuD8D}7!dacn08s*`6Pg)dXhJY85Ccv6KeG`X{bFXCcxJ1nt?@`Db=E^~X{j4^r#E#al9;vA0kJyB( zo0do4?!XZ!K}66a`#C^PGZuW@B8=)mA@t>l+Swvs<75Z$9)3L zgEFiG6FD20-QEp(L}%(b773*ExYe#a+{aoJcA5~+f7>paN-q^mnT(Jhq4TEMkJpd? zJ?Isl1h3cg`)NI<4BfQtQKyaRX><|~)gbx*9*C|Ra}Bquzd`TP)>+gq#$k#9x?=&H$}W0@htj!Q;d!^p*+4%B zw;r}we^DB`L`EWlq;rib+P6RAm5|*jTbG1UJ?5~8@6f_O225M>o6~f}{pbV<>l#jd+Mn&`& zRKe6P$o*RpkMs(pc}O|W&0UO>);))70q#x!f0>OF(1_9RRox9U^Mke%DX2gFVN1oq zw;BExV5W3|w@{60rvsITP@c#8?LCxjYEqOUh`W@@ADbPxVZ@3e`?4N zl?z%r38`7>kk%bv1I{%9qkj*LR+34z5p6>us}dq%XzBb?#iOJ^pTp)evKO(4WX1vp z)PO4fB9L)osTqGBwM<$Sbn3psfnu%4vxyuUbh$0c{HJhEsj+b@vyg{R&~MQ}i+)hdw5UyXhVGyy=*oe`<^I zn+w~bv%D_h1f7gY5&9a&%=_c?0oya(gcVCUeGTA>gj|k=ZY~6)pFs_)e?0=lM59(x zT=I-bu|3(%@WzV)xfriO5_?SG>6sps1+sRoHkICE+eIa9+_6Fb(%HDP0Qd&vbvhb} zvGLqta6n|r6?jo!BjKLpe~>^Msl5Ds-8|Y^8c`X}i?EoViSwt6 ziz;7)yiDJvCn(wOjY5PzGog{QE70ZZ8sie3(C5o)%#e9DeBUIQh3) z+?a}u#kILzqtE}73MUeN>^*aC}@qm29-0I~Hje}~yw4-@sa7EqeBdD@P0R`D+-vb|Y3WC)hL4BIvRH9VjVRvOI6 zQw}6bS zeYR@EqkK^@7iDu9e7l&eHY&tPeRQCLvZ|!qmZn*LDh;CYe;odjMeUp7B0@ftz}P_W zxKR$HdgJOelt`5lc9K_+?V||X#KIu7i~6}SzE1B21i{?;@eSbR%9#P<Vk|MGAGuU*9ZLfGL%xye>r;KR`J@dO^N zvgXc~BT9!;ntBUnx4FluE#5*LHc`H^vC%sy67M1ye^3)tMtSrUFbKb(0S@(DX*zUs zq>{{N5AN7l@}}s*)uZ|{2&!3Rt1w)nOLI&O8|MD?xDHFmlR*1CUJ{KP4UrWH7hHe_ zz7RI^I1+meIp4b z7x`Bce`*^-g}Ri5MQH}&U~`*=_xFV!h&WYIFL%}?QNjWWFiuz?g(wVXw_uB$poJ&K z$sxHNt=b+A>S>4D0&0?Gq<`T1v0|kbx+vZy#pQ^{=GPUHq0=HC$|#4f2rir?1aB?YnlG>fPrgY4w=7F(zzEY zbPo1AZ+)j10JOC?C@O3PMeUzTt9K~rA5#DoVm!1DAi@8QQR4&PdigIgPNJS(3-{u5 z!=1a)Y_#991NOmTpJddb`=-Auh>l2N4fHOtBHhEDkVXFK#PhbbOx+IKVZJXc2bEn- zf2K8K0fS)4w`J0=6I~p8?RWY&TV{?Sum6`cALW=A!4C|v-JIC(Nws7Yv4oKOjy?VJ zJ)B3sLnzXRak`nvl`>WU$lSrVVA&g*8=SudNy)RCm;_f;6N0x#vHfL5DFnjD7v`h! z0jpgxX4|XH1D1mS^9&eS7ofxfGu^Qae@LUrk-Uf=0>9i#7Gi*7_ou_SqbRqBd?LlQ z_J!5q2CUW11aKWVW{QTN$_@(k?V3`u%b4Zmj=&SLV3{h#9AwRJZVV&_^ge$5^oF}Z zWS#Jq3f;ZJq9jo}NIR)luWU;FWj$?oON^ws@&c=s&-RW=;WVr|{R7ti*i`wHf4|+N zKd2PPLEFuTDyM6nwYRaj#Vm?vbBm@wdUh_u#NI{1Uan<55wyT-MQT4jloU26ZR5$;f$?borM}Z!QkBaiYlVFgZ`qnceC4DdfA3ZAc!z!a@6UB zm(?%qa$nd5*pih!;^Pkr1oJ8@e`1IF-~!!VeR|h(CWu zP`p87|C{t=fQwgQ;?*tZQk{(dI1idOh5PpGbPjK%@BD3J()fAI_0W;F;k z5T1}B))43iaKr0Cz0^^NK~yR<3LO*mSw;=%Y@SIROJQ|pH}hgK)khj;z!bT?Mkn0L z2k0?Db#a{ZijgZ=yuxKhwHGQ0m`x6@V()qAdRt4uKxTk{gl8>i22)h(viSwms(q%` z`Ic`iIvZ?PT4qP86}+>%f5xPl2}!D`JQyF%E{hN@{h1vSE_7fMtwtjynoU&`WPY-| z$&%9E;5O>z-%0{@HQMFD>nZXz=VSB}^D3vyOeuOJB2MX`@B$;qTdM_T-EzW5Ojt3{ zd9Pb)G%;iWmRnLPNXTW2vk`CK?c{2UWs7zF8`H!sa+hIR@-UQ=f1uWn(XeQAW%;@o2qZ}|P_!wd(!P8Y_v80}~nV%2p2 zX9tIuoQgkaLnM0c<%FSfe~&L8Mhzsyo9=ljN=(UxEa;iJ&6i((D(x%Rj+S!YNF++> zO69@yM#P)!^(V1|e^TH^C==2p&`OZa)~)apr^_fosrkXE#!7jf2`>%?KF4>aZPRzl zhLUC;vaI3uc-I(-H!%CdHvy1AIB})Wh`5avm`qCEP#@f)gt`~+Wm;c+2x<=%!-Emd z?-)5CK$9a93A(j`iD0fBi5rQx8$uo7k0-h_z8h!KRacNC25NAid^Y#?-x06$SVB- z>3;@ldR&GOrufyZ-M5SX?&uLxMlW*z9^Yl0L#E^fpy!q2R$5QrWm}H?43eUtjP`D9 z@}(W?Tm}N-e=KoXGY1BQv{NO<@;kGyTFD&ZLc({eJhHzjhH{$pMF-Ll<@WTpMb&&{5^aQT;Y_~OGjnZ3(z278{*gF<}ilvgG}ZbJlZ*@jwf z@y)_uCJ*E`U}eX7%=(@s04vQX-xQk`S8?&f?WhPqe|&Ye8K$CcrIs1v9$x{_vjs-a zC!AgIdqa(gea_Fg~ z)W-AChq&o$)Vnp_YE;2y6^--HHc<%h@Pd9wf0Di9N%Z7vbWK^Hua~nauU{F9*b0RE zSk~l8zLBI(Zv37;c)m|cQsPaep6+UgywU*sIZuK57~0=WJs*R8A1$WxqUa9PS(!)R zhYuBEyDN`@{}gWak`I6}izrIMS^0#_w8~!RBq$$OEGn{HDlNgfyW6r^iWnLSh32n= ze?@y;cng%k>mb}`OrLC+r+^ym6_6@TbW>aOc2M(=6kP5Q|BaT{ztbQP*}|PO=oTLY z4zt&j=VOseWnLakfR?&!*fBBO<2Z#|FrDNHE2)h)9+=FQ=#!sx<654`Tr-r$;@-2TKbzQnYBpkf8@p(S>YP-fNe|EnkTK$cz6LX`%sek-!YQb zqsi(!f*QR@dOzuf5i5G+Kwyn*UgM2g72ry3zpihr@B=^e@ArhGJy8v*=N<1&K{5Hd zK!aUgMVM8i1|nx>(;dEBw3?(V$S6ku0fjE0l49{ z5py<}HR?XXiCT?*m625lKl~F8f2+LD(t>tDJoJO{-E`&^4Pqf$W}`FT`)V4j*U=bfqS<*-IGlf3OXeHs48R z?Na|-SCHg-3Ha*Oi#wRmfWa9ed+|(ow2trXfF|WZx;ka5SDjjz*Oq5m4oKL;`7qIg zNPA>PvKCw2PxrU4<_cn^yTuY~Ot@D6$MY{?N0o{+2}k_TZ()5^r5i>93rd(GS#I^n z7GQU?(V+D>WD}%R%uC18e+XwPv_3m*G2dJ-oM65UVAfOWg^@FIQg=9U09&{}q4)+6o$r7H*A5jIpDE*!;Q=liWtEwq*Oaa`w04<@i@4lJ;L)JU?e+r`o>z2#5ZQHhO z+qP}nwyk&Bwr$%s?&;igI=#;?m|td9rShc4aK12*N%2TAi+yH~(SX7F)ZNlRc!0{; zN(glq!6>PKjIq~skZ*VF7FM~R>az5!;V`r-P@g1(NM645_&_XasjzRu5qPqEj?M7p zbL&Pv4xPkGJ?ewcf9Hy7weo>2DMV{CwBJ)#I}JX{kI$m*_WA*AHYQ?=Qd z|4up~;w#-~LqW%g$amBf)BnmIVq&2~eba_0ydmuX;vcFt3Ga~(!(}%s5)=R*L7u`> zV10)ag*eXQsGjrUD$DZlILChjo|2F<$@lBFO1|wJGn{0&e-Ro<6ZM4M98qW~AS${( z&Sd9zFoq86-&Lanh}INzmK6{a>v*O!0Lu*79XPQ{Y+qyXt#CZ6w4R$-<)>Wv9LZHO zquz8_1I(WDbQrZsC|~!6CASpmxg=GOyeK9qQ5f_hVzK#z@ljRBok^00+~=A`2}yBT zMbt#Y_bl-)e;K#0iovWv;?0Uv4^EpC0uMI#k1!Ld)}m&%tV#^dg}~fqoR8*Ku#a9r ze_YQi84$7PJ{@`3ig9kjk9X585=f5$3~}GwtTtJt3?Lf@;by;F^G2Z9t&JeFez1b=xjfe|_pGRv49*T}64x$46tzgSx_{oE82h8*_7S-qCO%_!P_XJ~ zzka4K)xa)*hK5P+;c_kdQ3?C;d^H%}!30}wQ|>N;PW}mO9HvlDM7}(76iWSHxfPZO z%~2$1@{Lcbb0^J>1lyImA-FnUksQ^?>Na85yz6ky9u6WaykYHZ{x!N?6V84@mAH;Rt!ppuW{^6ixVw-8pZ^Dv%c0s zULuPeHD8-PMo)15e;66hm#`5tYmSDkA?N!v^!o1SCsFqj@t8<34%;=7&9vZB<)Oh0 ze|LAafC`?Y{YlFSGn@JK}he0d|qBb(F$vI!esja`*{F z`^-*LeKN-$g43BA{}@<(&OqFph!OT4e>swsl*Z-4ZQNCriBy=0kY-@94&&hLsX5ru z0ES^N2;Y~E))H39dn>Vo%3w~yi7WbV+4@w^Y@e$aZ0QHd)IRkf#78I%@MT3fjlwq5NRa^(cF12w0|l% zfSHB99MQUE8+|L|nIniQs{#N)K)%20S2Fr{we3PI?`SA&tJRK(RWiss@JXvRE`+Qf z-G7)qk<^Q?2Obj_=7Qx!o7`4}9@;|9Ie!4uj#@9yF6_~FIa^fLjQ2l)>?m)_Rw!^d zHd)BSMa`Fhk9^EXPQ2X*sqyS8bdXgDGVR>P$<1VxF)?`k4WTH^C|+uJ5QXoEKw%pL zsYT6dk0C)+gEJ&!G*cKzq$5mZ?39ao z(d5?*Z!c4h`%Jgl3sv=c#1@GuKw@V)In$Zi;IS8*>hz9q$2D= zy_okt`2mEb*zN_#>=;1Xf;Y5Bj)D?Er#K|Eq0HSsCJ*JBZ@CKh2Ati#--&IZQ4M?K z?V6mpsdawEo|_Hg=n-VFQDCNbA?uYK%%xZPh;7H$r?#Fgs#EZ$v43TZnGXF%WQ&Od zbpl4`tR-8ALvZbCk8wf*x1(%>+M~XsuBK>ms21$LG!!;>*`n}{g+-_`|ICpf-61@6 z6Q!;$ZI=h*`tmpXgZMIX4?kOa^le$0^q5@kGW(Ip6$e4Dg!tlug996J7{|bh@x{5_ zkp+77&dwkPL(-`Sw12iO>)4DLaIS5CXUHW9q+ zG+A1KOuhg8_NT`{LA-}hp^Ih>T$_fJ(3#LC)j6em*S0<+g#SlgvERj#_#0BjsDTF? z$xmUbtMu<7*u0?1DMcqG5d>j1QvVjM5!wZTUmK7&a@+b}nt!c|lO$HY!!40i9iD~r zXQ+0gL}f;$wvRKn#qt<%t&WEedx;2#X8pQXQh*EU*yzB$%}>)$0(;5~Or@jrdw|eu zQ8>}gbChDxU);+)T;vM#He6T>?E5s)3EIf`k8oii!1xC9%zQ>s!7XW8tC@6WsgRH;A#5t5$E(3LDT{VOJd|`FPd_X-|$Y^e_ zN7&j8KS}qwKavZA?xzO#k;WX{N45@=2f%>Bo?FNX%706M&@dZZ7lp2QoeU5C_RSTM z|L+VG;F0+Vi33&453lB4N&F;36uM!Db;j+Qx`HHB01Cu>=i1QKQ2Yt^d7Dv!se8p4MQgxBwY1M0e2zbT4qF-*GTs;+u z6mQHvrhl2jjYT1zVaX*R(nEmEU75ZT=D+fmHbS5s@ixI~%UvZvrXYFx{)?1jtw?bz z_2M4v1!Ki%h2@o%gI~OwAiw!yKLh3~Mm~p)K|W9S(l8a|a_hM8VAe*=GHh#cj7jm9 zf$|HnkiI_2;gM!MIUCq;e=IF#*<5zPf$c7vm4CDG0j4)_$KMvO*XN{*UVt>&f252t zK)>+HGswe)7%*axk3`NeqsBAYyLqDfoh-$o6>t-`wwg*FXPdthLY_u4oP5fFOg>*a zSyGN6UsP7nVko!uytj!wWE9QCfWvjSPuo~X6x?4V&zS+zZmZrWi4DS99lfesxqIf%u$l5;7&gGBf8pBPrRUpS36oC3PNHu#B zY!F!O(oG|5-XKE!^IKfPcUou{(GxWxL%%OjXvG?)4eD=t8J%f4u4q6OaLcgpJ0LA) z26>*tIgEwc@h0N`A0P7VpU0Qn_tMe@I)6c_?+6Gs7V-~$UCCgiR5JePavtcPiqQ*q zv;%VN>4nMpDs3&KcC{@CYrdhYvMRa2`1-@ykJS2ZW9f(CSvra;F9HEb32jhtTd&$f z{rdDXk?Iq3J^MP9`k6({2_H*N>uV95r=6Qd@FRE%Du{c`3|EulRsO2VotuN3$bVR; zrXRG*&*dk9&}oB1S;051;D2MWhK{|{OroIP%}dN=#X_%~NO*G&Pes7&{=Rt82g8bRaBoy9 z2&%(j60ZVk5q8^P5_1RGu_#^I{1@;l)aosz=Ja}z5PaOn+PWtsSP=jjzZ6KGPo<01ww9{*r>FGdvx zxS0HNTFo>Jb{*w3CGau^$T71*?hOa988lN73_X!CPa7Y^VX=R3Y;#tE5q~a$!l$#j zVwe+bKD=_*`6hSvk^%s_kH1TND$L!oMi`QN#Z)%q+!@WArrb2pBLt1KMbyZdp|nna zXOLP(gYrG6o^uteqbE8M^v*Vbaa{asl^Ah=#*1a3^n|`3=#9rhm7eXA@l|AGAIp7X z$v|PoSLIe#oiDT~;QhSK)PHC9`Idmc+WJF@**0j#-)5%#g{mb4Z!>E##oI2GUwO%M>5h0DNqR=l4lYUmW2(S$NT=eScUFngOC$ z#p_>QvPKg3QBM)G0cqdm3Q~UAazKBle7dDTvdJ|@$u&y8N@1=p!hcKx*NEM-*+ao( zcpNLm#(qi7Tx3;92^q!G>zs(9a`w!~S{Y5;y zIi<}UiGi_Yq}ijE!#%jqLR=M76s#t2^yX5day!7iCSLh}P#o%--3>3!TvsNmdW`1? z=pF-s42aE*?R>aFXn%IaT1rmdM0wC<6{T`Sy~+%_U&Oe?Py8W?nMcv3N;A;od(0)wgDzqO&0!4c}aycHKe zSy4v;*e%vUeKyTAi18(Z5}9c+h7ZDMFeU#@%3^Dg9a`T0v432Eid3{5G_!B8j2$F@ z=$|Dx<=X-mC2aMrM?HoX2JP@m2H|>`g4msZlfW|*U#3Ef--+esGC9D&x($!K7G2cr zax+XCf?HMNs3iVz|EsDTqRc@ihaU-BuK83+{<>6wzlD4!+rIa8`;#_A8UiGtxZYHu z;aFtfSf*)^W`7eYpb+0W0d?FYG$MQ#`hn-^1)j;6oVIdtdGq4t6rbat;&Mu1L6&o# zP&Sz5#y$BlPdmt18?DY@*b_~M2%d@fOqNSuJ9kC0LnD)176+#539(y968-P(-VV}=q1(>v74><$0;i#8}gUn)nKH6sQ zvTLAw7vy8n6Q`o?#+=Tp1&J;l=qJzN_ZHYiA7>Z(8wO-pVz_mZkw4Q3-qz6P45mGh zI05ecex$Fqa#056g26JpSps*?G6TO0{-NkG6n`zCtu(w;PraVL#pAm%-}^bEIlfs8 z1kyitEER3wctBfVZat-q%cf@ZDj&3KEG(6?jURNQAY9=JFN5s zfX|>AFH)vQn^3(r8^mn=ve%MlG4R)WUbZ`aI@;O=C>v-Uy1>s}`F7*WnR9@Jf*Nsu z#eFeoF||Ipio)4gLvD53D=(Lr5+O0qIFg)5N)?NYYPM=Oq<@XBOa{jXp5;ICpD3v$tv?lPQCfq!@v zqYU3FX>q$bKRCm%l9E?T5G)}FTZm1K`9`3ZX+e@uw)(z149d(!6Ju13t)gBLr%#q3 zhZkx;!+PBSlQzAPLNQNfj+~eJOHMzTewN(VU2Q_JuJpw9&QpzQe{m%LyUMXuS+dOi@{ zxe+yY`Y0Go72CcHN*X|4#}TJe87-ncy3pvCqF!A%673wmX7g2|HM^C@Sc23 zcD-i^O{xB1f{W&OK7WLjF8w|&FA<%Oo;-Y18^13qeeDl6=<#%@B;Fx1%4>fRzVRz} zir@Y(uCdeU+z?`J34}5k`orkzHtxme5F7dHZX`HZwkVH>;HXZr$SnU}MY= z;>}ERun)d9H7yo^SK~s@@v1#lTsNamp58so-h3Dq~*;>o;%J)hb>1r}{S(#*SYdO~U z!{lQXtY|3d&wnCAV5$4X;Z1zJ87Gh4;-yhI6#?_|Nu?^I=T%SlL+pvfY5 zBzPS2R5Lfu1SeO08d>f^trX)pf_UA*&%kIrla#O{0-)v)cyls&p%$u4>t(^(x&y#u*L(Ke=drsQZ zoj7MUq@Q4l$iEl84jgnR7?#j1S;ocHu=nr(wXNp@RO6#;Pncxv8p6S&J8U)^kaeR{ zqWEJjWq+k2!NUR&>J5p}KRX{RjQGVb(IzH4@#@NKl^UI zUCoE+y~uN^+U#JinTIg4U*k1l6`(( z#UM_9mY7SrYMfYc?0RxMO=R^{;BI#)>)QUDXMbb>uGhx20Xzt`sC<@$r!TveQFdTA zR?xBE=SOM<_H=@>AY)$0#hju~XI|xZgof@t_C3TnQyB~Rig!k_8|QXI#N8$8e3BlA zQH9y<&)(DN3U} zTI5$IWJyJA8FM>1eqB@oZli=4V!Vv4SACLOw@aXX>UXE@VL;=yFCfgrLVC+ejB?23 zAB@X4<8Iyl()F69ulRMhNceR8(#qf~L4U*Lul{gHHj5LfyyBq#z$AxWJf}dGGIO7!EW~!b?CSvTr%eF#~tcv?|P2nOQCKvtHwxL zrnqwrnG+@#`0F$%W4&~iSI6!*%bZh;O>Fs!V0&FgRp4RSmRnq8vhPc?eSdBaaegEA zA1qFgP?dNZ%3R+xS3Xo!KMuJa)xUZ$y(eb~p)@KXqRk4@^wn)`8=p`GBX2+|f!OxF zXoK6~Q4u2#8|HA5te}{%iKcz2(U46%6(F|yZrRRos9nC1c4+S_$Uqs3TR1xU&Rm48dllqG~INxClhhAMB%MLf>gC11&CjP$9mTjbs=hLGt@ zugUycx{ucX{jZkRL$1jLG&y8x~}Dg@5(de?Of}$wK6_ zRf`$Ck^+|r2#0vOM>LA6EEmzG)FrWg0CSq|Z-jXt*OPFGej#~18`?6{(3{6A3(?=^ zk=JdfxpN9ZD%W}1N?tG!Tg+U>Ik+|~)sTn5*% zEmnM0cXV64YQvMBfqzKd|KF@BzL5MsC)C%_>epJfB4B+pogD}48?t_y$G9uYV3A82 zVdb%m{*o0I!+*mXqRTg7*KH-f>QZb-&R@nlZREI-7KNP>$aFrOIZ@YKnF*|OBTHC~ z>|Ked{?KLt$}Nn?iD0_wrV%2C;&m;iF^m4QC9P}F34c(hSbvE0%9N=ppPT2^L6n%&EUun-R=%W zFzl2C1Hj_L%zt(v&6HC#QiCrg|4_FMp*j$WYFP-|{K$t08KRY*}g+M(}DcF4)@ zuPiD1)H3XDng)>MR*EdaWRS&23V%6OXfl51vp+L5X z#!;fiT(M7|^=!)`k7QzsQr-T!jU+nq02w~j0okssNTlJCZ)xS}BTXYrUfmXUuUx*X72Ft!~QB z4BbfD8Wqkhji-efQtBBk+V46&OIiE&FdPRCZ}|@g@uP871&m^utFZ})S8_+|$&3h= zVt`BsvaHM%|rCyj`L_ktOqehw0qvMK&%W9o#2LP?u zBmfKYv9P#@-9li)37;gwT)1#hd)dm0ohXN$&u>{IVwH1Gn@}4pV|}|ASDv(bIMJ91 zXn|?xvLAZn!GX(eZbERxOtJ7&>5CZ%*?<0Z8si4{c!!O?6#pZlx*HLw8g*tA-tOrW zq|XkqDEdA-QynZ1E9`ku*?Bj*_R8Y7xf469-@rBaV5*`#s{me&edz z+1<`l+CG4VwVH)UH#4W-3B>#~j%Ex=P9U;wT!S0GYoHmWfj7xq+I`xz_6KxJ>J#^NEu`LEa-l8aY55mcyf(BHD^Lmqf)kzU@<|)v z@eN03-4arZSP2SZ6|owI()_B1L&dBn$H~-v79|*z3NmuX6ToF=9K@+ONA-G9ap9M^I7bK60P4cQ?+%`xl{_u!c%s-q| zyy7#LZueVOmrpZEy~Y{o0R@wQvZzYR8PM7~;DEHlPq41or)2)&t6}W5HKJe=ozou9mPiI zCV2ueUjA=IL~LvktG*NSfa`^hp*-$^k*9bzDuwmpXKyOf zTWobm`-K^7WV8I?7u(ULdQPqnh=K`U7ncOEngT!>gx^)o(>RXUU<^QMiG?&qR%Wf` z_$xL)-OyW%05hS7itd(pl3Z(B*O-^_sd-$iuSY9-z4iPH%!{O1-XMvD5q+Zv6v93*VJDJ2qQ-${^-AdtKYvfl=KyI+`4Lbf znLk1uS2g08!e_@O>oq+#^A-@n2Wb2TG=iqwP8N(4h@k<=Y9tNfMeBviy=NI{>Kb?N zq9qhbjHumy@1Kc+<%E9kZjk`WRzPfA4tJW!KwBEzrEx8WCWXrSd&0T{!rOxaxE_qnP^F{DA>gnw;SRuHYkxOsv*sI<8RYo|YqShs zqt+`~D!B3g*~x>3elNeaG{T+gn)ucOquEnxDH^8}ua1R10g6ilt0094R9$(LbgeOY zY8KZWFbt5I%cM+mx*iqW^K(0Y<$tl9l0mpYE z=ReTFvov=*#!3eDmjz=Sdf6CB;mw#<^ai&Uq<`e-f6bK7pq`eAoFRb9cwtB!?L)uhbLJfJl)_-OT35nXwpC|knZM|T;VDfGLdY9 z_baLjUh%oz%qd%jq!b~r)#C#qQrO(n(<-sohy~m;OGZA1XfTpd`bG;Wl4RHMjuqXxaw6`)6;V}I*l!OxtV)B!#XD*y~u#oC% zHh0z%OnZ#RZ4nQ;GP?ojPuLSO?QTGSWq;h14h9xaLhyEskJ%8uS)A!+Hd+Kld3hP3 z{lhx5KrqC>mqWuNZPh3$JcVxZr^u|b!+}RflfowCzPcUzxR#m$*`DTX+Y)9gua_si zt#xJu^?LrmZV*qV)jUPItpbRQ!t3iuXW5hra2%Ko&5D9^qvIOozxen9Xd=Z)DSy|V zTsO5g@0+xZp6Y{@{ARr-CpA0!m-06rFKY>nJAOtgm``zzZ@Q=X0H38;j})C+&|URm zfTURWF}z)(oOTlX0f%{5-C-un0D^JLveI6b6QmAVpPx?p^YbK=#hQ*>NGM4Y&1$DI z6}ULjc?Rldo>r)PGBz(+!&Kbd%zx3-K|1cG%{}I9ZDDeM0X01xDXrjvMVIG8a0Qk< zmc<)GoRYScyYS3_sHc>@O)MkkXcZ(F?irbX-sRw#i*9DpxKV*>83wg&01M8KE~Lf4 zcROBqb&3gQv$`*O1IG$i@)yP)DTJb51k|XuBj=3j=0Gf^J+Vh$?70pUX7yBEMuRrbRo9YGqN$NlR;H07FlpjnAjf zH}RCv2uCY*uTi@G?bp1nVyFgLDf_n!@)muOetlPl2rlZ&797A8K()P^D=d93av<>Z zu1eQTR?QVA#(={leEWwAa(~5O-6Kg-4Z-bn5kq2pFfNU#wtQuS8FC@u``(&r&}}e- ztOG)n?#dh2FKkhotBiV_jX8&$+T0Fz1$q~^xNU2ABy6W`9m4Ey`y z#Kd%H7T1rBnC>;P(+rE~#vt~VD;a3T(aHwjZICzEe$sK%qFm;i#D9Rneq@F`b|~pP zq}?X-0kAVARMZ%Sn+UsXA?e?a7EE)K6Dkzu9LcNUz@t5Mk_q4sUBYAtiy)JK4Jf!D z_%w4sl&j>ZZSS6by+89PJK#N;hZ?4-=jHl3a*Ej*RZ?tgh59y11oD3722t11u}D<1G;50i>UF11?cMn{wzDM=K|C~=Qp05z<^ zaw*~v5a<0W{K_o;(DMVoM}i#B$}e@NUK^ftk+w@c(Bns2wx2WxN$L8X@1#)2)2>+7 zeC*LD3VgY0CsmDlj_TAD4h~RaKFX*+6V$wmO|Hxh z0%sZH4KXXyC6I}T=|?X)w-{<^ywc4bxsU;hYrKJJp}#++%mO8#(m)q4#?SNQoBq{n zaU@vxAb&iuTQq(A%^r!Jsiz$&CKr5?Q#R%H273;TLSA#(a#YnrS3>S`Njyq7Px2hq z=PHrbodUYB!HGPW1%KWeM6*uu2o?T^_Z~O^)B>0wHrm&h6Vj zu5y7KV@Xj^(+L%uI5F<=SCEC;WaaNf&$2#Xr};o8i51JX9XZoXX77 zX%TG4A?1dKs)a*c406!haAZBO ztcblnuu##~x6EW=c9{LZE#zMOKk+925G(i7w_1W@4w8bCyx?9~2DGq&_2%mL1 z`lp!63InOF4wK&N`%sZmrsGuqmo9ZAYS>n>1R0oCM_ny_G$&==zjAS@J0JJU`hU;5 zaoZtrG`+|lzd{4WxS`L&G0&y{WMEU!SnBsu>?R2>R>r^tzOQCWhx@}SyeFl~%~gIZ z4<;=-4veZ=B2zw%X;qcN<}R93ZgG1cTQ7*En7Pu!>*3AqtkaU%CPl7#W?7JQgL!k0 zd@!ef&>*U=913OZYPzvn#>%qMVSgZf*(zuJA~&6z$+lcyK$)jA-BU4V3xd~q4NctT z&j$kRi8YMx8_|jWY$zAtyOV&S&t9=oVH@%ZHwUNzt2}Ofn8ry>RcrFj<86b|kYNUr z>UL^ES)nsAt-ox>)nEQLv9<7^R5+LN47cY%3DQrekxfme*MHF<^yb0u zDHJ@qVNfl4{;p>q%9YpP_OF7@RcKVIazH<;POf94X0w5_j+S&g(BICj{Y{?h7^xF? z8l-A%{CF~}j;0=$1gOc3gy?4WL=pb$)4_#_)14>!Lf94}TUf75k~b@2&rZob{C!LH z=CrC90LjNDCBgo-1m6q)#D8|&((r{79Lq+$ix zz{8pc(pC0)fgnR8&-ENtN_ds?BxRg##BuNCw(vb^u}@I15~nSohzXkZvF$oXmLU#4}HlMKd$?ACP79oO1pBdfq(L|NeQK6J`VGs z17@0!68{!aPYh;ltF6WYCSEW}2%K}&GZu?!FA16x0GQu*FYL?>S2$nH1!oEH`&SKT zX=DKfzp?xA_^yOQiRcGx`g}dQt&_BY8Dh;ntIKFXxMmVR@{rvG?Mpk%@aq$R_V2p& z7kFKMS5|s{1cBzZ2!GP**WnyAj9|C)1{*00X->-f_!GKO!hoH~Ig$-Ia5Zme=@1=`J6_+g^%Ep!5Cbpg=|95r53=P7=uD-huGM_PKG) z6pUeH4Naq-`o!#*db`{=`Kn*qQ#V{dc9loxZN#>3L*G={V_Mqn$E4^2|8*{#`xDtANB79&aYdj^~ZuiA*pRRf@7#*1!c z5df&CrFS;6`+tIBi8V$NVQ^a{yshg1G|@EYNgEv&&3tATtKvH`FTcH{QiWJc{r)9y zy$0~@vnDaHY)_g4o-6QpjkTy0@)aLfq-i`)K{7 zn;M>mjeovnMyqre+P`iGaQRS}KIO@U+TYVm7sJqj9G<+%XnzfHRrX7x^aPZW}Y}huuxL2p0$As6amx32YJ1F-|Jn1UC8HYB5Km`dX=Q7M4zx zd#(kY6uy=u-17qrg5`zHT*P~t7TSt+x7Auccz;Z`>*+(50e!oDVMnd zj-h$a8z&@PuQDNkE={RGm%%E||JMqXA`OPuYzH_P^!tx!CU1<7vJ&Y{ zGIXOo*Dp<`_$~7(Nw?-&7}ENOmY+RB>PlcW{tOs>qlCZ3lD&y7LK6yEac06P@9B<8 z#wmJSp77p1N8Yl7jQU_fk7rCrR5AH#!GHLs+SFXJv;qqDXgbep%gRRMU)mj*W4qg= zLuZ!o!9cheLgC#xio<^lw|XzWmbzzqV$G6b!+vvs-JoKeY#iDRm7E75z-5PBXTfaH zsY+xadmLn_r|gjE#5^}~aca4|yu)WrX_tJeT~r7nJ$BZH-#%;P>{x&6>jM2-k)-O*ucz^G zKYqZsng7wjt&6Y5wgE3+8^21#8;=f_OJU}5RQV9M6Ym~lSkhK=yzjAZRqM{}s~H~b zB6r|IYq+$My%T$aM58X7reCSJ{C~}}U+w=jPXW&PH6fq%j@^EX1pCC5*=j}E^p`dY z!3ClgL9M)YMhe#gJ2cC`-05^0g`+#BiobH>pxf~O_ULzkTW@r*hB70abzHxB)v6T) zz}NXprBXe!r)Ge}7F+vfXLIp~%-#;Azj!6d;L?zt2Q5DnP?)07pD!@L8h`Xh5WBx> zu!)GK&=#n$q-2@S5!1$2&FAbp4tZR}*Yq8+`Ee#8dg1Vw^Rqs9PhLeMXX*dDc_h}~ zZ5qkbNRyVSe^iB&3Lv)ox7ORRiN`eV$>Vj1)q?`IJG@x3dE!N2+s;zpGR4s0-Sw-E z#RIEZJ&RDaDZh&L(I4@kJtO;y&&rY93~L13INowZ%%yIk zUEv%4KgDK<;>iA(3!b1AjHiQzLFnY#OmQh#*|)}q|w^-UXR z&qm(ux)ex@rH%9nn)#xnO0%7Z<=)$;l5|#*!0mgQZ@mHv`~f#AUW%7hVoszJhoj-b z@Ty(8hZ;_&^C=HQJqCPZV{dquj=k{9VUJTXgCjL68^qFbkxp|Zc)zHSFn=E}T5!4q znIb#M-6^llDCrIztbgiN%+Uyfy9p=8w;!oiY=l`z<*>I9s*8yV>~L0s2LdouKW1rz zdCWCQM>k&o4=aHX^B+68NZO!IrhuKmaHUqQX8(CRf$Jz#HAHy5xvp9`RIB2ciR}_^ z9;?LzK@xrwrU*KCF4@&iO~s~uVYDAI@1^H7K_Y0Jyk!Lan$|+CThhi5e7vqrI}@8`MJudNu4q;d~$IwYd|4MZIUhcLPP|TceYEY z_6HBZjN>R7HI7BhVV&2%FpG+H zpQem|AL4;w6!oAeX_^yK!uE^bYHvPm_Tt1{Kx8tbqJM%KW(x}!6U)aW2?}TOqBtvL zozQunL_1X>MrfGW8iQAwT=0{ANVy<7H@M%39d2G*31=#= zQ<$S~in_%+0y^n70y9Llqou}!l281NVf9-zP(xFg1uQZ&*^U;Y2)$}l-%a5SWPVCA zyE!o&{(q?VCU4EB9}4N@Utx}G9BPc6XVI*I)E&pny=HFVOSgh&yAy0Hq8FoxYv#_M zBr=E8kg#iLqieSqpWQCLFzlMMLF>1N$hB7a2Sqq?hLiP-OANfxhRrNDSn+HI}$10g_4Rn3d6c zd`XmjR(s)C8lheYtE#d@}&U`%2f%JPR z8h~^o=coOzPNhwgqsGF0UmCx)-}6{zC-m2oYe1if9YuehTPQ1;xu1qZm+?WK6G|fg zGZxsupgT#0+avWxuB0cDQsKlS7z(2fMt@9I&XWpyb^*L%suGwQg^cNI7gmQB{}5BA zOr@q6qDr}u22nQhEFO?h72+UqiC#{=OMmw{P5_4MCns>CUoWS1+4IvGst&ZLfwOU} z@@4BbRs?$>zBYj6WgczUy5(hY+ANd)b1EQ|uu!fe{X90L#LZ%(>rDIMWJ3ml34b}M zk8Pjo4iHVfyVg#N5iJKz!B#%y1O6!ambe}1aW@-;TZM7E0$kr5aR#Cm{>JOEF;W7M zK;5?RGYEc`1N;-!G#W$yIa{JCzi zxy&qGn~9l#y{3d9CW?DE5Z56A6o2UnmIf`m-JeNS(Wf!yI4)o|E4E;WO1?YQL{8nv z87vMQj_-#QXOAuBSVA{slZJ@>gn#p2)wFom zISpb!sAtk|I8k+{Bz~86T7Tm*)+aKOZZxpZf(CC+D&7{Lcqj>Ub<~yW>`%>Tj|za4 z)31_+z@0`vaXb{&zfE#$lBG%UL+QBAfDd`NSOh=0lrwT!T}nUaIx_#SjF|b;?H4<1 zwAvcBOJnN9XIAi0H)QU?seg&A{>=VG{2sWVWoNQYq(}L7aE_E`HKQ@dV82Ma5#40_ zVO@c_Yv}62`4AvD+`Z^27nJ6!7?h@xTN~%)NxHk5bH>qKdI;w_OSqI}ZdRRBC=XNg z)OYZIJOp&Pl{h=7p>+Q;#F7IX;(bL*80;EmAkk8?bkwySHh8SQqV zj!;MXNYAK_E)W^O!f~;*JXZ4u{PFU}+Y1C6X(0 zKdA|zZrBIij!fr4q6i?Z9bvdhvlO*yy?iuHqJRmutUbYPqJJkq4U$V>02~M=#U4Hm zxLBV~iC`Lj`n9|(L_n5l>f)fC2zpyE{9C*N(FZ#1xbZ1YjylfT%Mnm&%C&V4TwhXl zG)-fK@2A2~XV{+QIkAf;1AL8!rb;67;*7cESmJ=YgReE4O&B8Tp@dDvOl$bBPT{rdK; z!b;J@&+hEE8Ah>}mJ=ezt`OAHjs;I*LPahU{(J5Y#(xr{vRg(Z5DdKUINi*}@cJBa z(zX)e6E2_DK6a&SKIf!iW8&Z;ZHh!OhCB`5re zfO+ejm|+=$q!EVQvv3JuwgO@pM-)Ck2ZO^x^+h!eOWgS0puYTagb?F*Akp0hnM0Ph z();%+pntcZ^gSLC`|?piDNV9vT58ML0iCv*x3{{`I~c58j7j|AL<|M^swA*!VR__O zmv9%S$HicAfLFCa(T5zEod*mR#>Xp27ouk$)L#I02)1hgRF+*U{i#x=HaX-2zB4QI zS$(J_!}q!5A9fhU0PQTH?E5%g)5$VQgor4o zqkKSH%VKS;qP2Tlzmoyc{09wyWv=s-ZSI^uqosuyo3N+}5_mECGiU2+ z*k9A>vTh6q=Gk^XknTt>kYd`ZCBe}7w#Zyf7}sNWtD?w*jiw=-Is4<*w<57AIt0=sdOu(a*?%nC z)eKO?9C*>! zQZ6)U-i65Iz(v0-B}pCjH%-E0&ar43(oH#+Mw-l5vj*ShO`E%y;j6Llspx5C;xu7y za7{qR8>$|C9=3z3#PvMeJBZ_8y?>F)_)2ycx)Q_7yCjoa1_+hOLXavu-;eWq_k5iYe9+jlZ@x=%j~@g~|Y35F0@qwek#CGaVi~&G=L=3kdu2{sgD*k?w@vXnY+C z!U=2l0rC{3yN)78j}!AHKFlZ(nU8RP6~q;{v~&I_Qe_u1uVIfmhdF4txPQOP#{mFa zQd<9qtaoY>hUtQA+qP}nwr$&X_tUm*+qP}nwrz91I9F%h`3Y4K6`8RsbL~D?Vh6_M-DGMH#XbDvA0MX&Y#>|0RKZBdI(zwP2!G-g%~}op-1EB; zIJ!w_Paw~P6MSm^YV`S4zf~B=@RZ~Xyi*8l5So75`oKVCIrU|8mesb8vNK$?fT*yQ zfLu2W)`Ome-$aV>lUQ6n^f-SpE2#FS4`xsiL-s0pjVaZi@sLOmkeoMRwcwBLvCP%2 ze^-Qb9=b(0>b>~Rgnw_%iYl#gDu-mByu^LUUdER-<8ZRJF0Y9Xl{PeC=5qd$9BGi0j6I6m_rjT?!?5rlN~IM?avw| zwTAg~P@}R7-+yn>O`_;=%Rij%C66E?na~)z@k3J)H0`_Fs3*-jsl+kCmc2<`X*_)f zz%Q7colPVn)$wuOD%wDE6Ab*cPP2EBc?D9D_j`)?r>A8Kn>`@RnmlCSMO@TF$R6Wg z4jJ9KX$*!qwrh#?M%FktsRgdtV2*6b3qO)FDT85sFMo>6!T92R54?ls&k^CC+n;N; zU7cASsYQi03byq7M&P!Q(Afa&XK}x|cC59qZ8UXDVJ8nnL4mo=k7}PG3_ypLEj2Lu zf&h+o_vIMXCj6oi&v8J_y%U3=My}#SHImvT@!H$Y{nQdKOjfLv>UTHPq5ZfVJp9v1 zdWF4G2!G);w-8)0ZWpb&;1h}}Q*;gwjga!)y+qsz>SKNma=US>+_=R3R5;SP7iLXx zf&fO+z=q8_nZMHDy|6aiZDZ~4`Lbmv?jPjn?=WLS8tUj=I8Nt`yMn+eDB_`2w%04! zB~@HSpwmsx#Gc6>07ePv;g^kRh!J!C75jD{x_@E5cJS4*JB<_K082o$zxf+C&dd6} z;0g%>V93r6vm}7kLxi)oO11RR7W!6@&ng;Cv1`Q_e4t@yYPBwW8#vZa6#vfFHDI9g z+?@H|%&7_oREK>hzFP#cD;=G8XMNk}rfN&&JGFj@OoRUrW)(SVWlw%Tm$v&kKwc|} zOV;o!lk9)W`fXM7Q}gSQ@7>>1{7E;RYL)CSGLY$<#3dJaptgOfXLtQn{VA&uE^dh=8;K7WCvwv>dO_ro3}*B!&X92pK&%*Jlpru?)Ht? zp=rM%?_(+RH%Gw6SAf5oS#7&bZbv$NZKfO}xY>xX`)$CiByF>SF!@h~q5aEbJJuxevt=c?}#7J8;|FDm`DH#377R!Qr?8gh!t$%+2cfr1E(kDSRu>#LYOMDrtoT|5Er`b81 z%qv=@sdUYgfjtVy35PFAo~2U)Fa_+kzYH5PMR#Sy7ksJ;vvplz#Idb{JvEx*?{hqj zphb~f@*d~_CF(a|4ukkP7Wp`6-Fu6=P@>3e<$&gbM_3Q(%K$oK^^r-*f$ea3DDHnu zcnAThF-ErTp#k^J)TLvJPw%Ge!K)y^Z;vi?YlIY+mP3M#R*UC+CE3a#wn9HIC`Bxt zlgJiXcs<--<$N{1Hp_DvUG<`;td6q5Vn*bLI`42Kc?^r|UGU>*ZF>Za)Kp10;tbuq zY^W_S(*}3^Y7m_O0VBXn7r0rPe@cJZKNe2tBEym*PcVg|FMz#{z=^ z@`8g^3jV3NWf#<{TWm?g{0y2BYCOjI(Wp7j$eReB;#hHGo*rBAX>`wq>4;8}PxnT7 zR9%J-4xW-`5aBjw!BM;7V&2m8v%=VD(hZNdV;xWGoOmWD6jJ-9b0Bn=4(DA$Xq%G7 zrJY^^4UUd1<}?W5S$UU8xm|zzkaWaA#$k=q^COO|*80LaJK^j7bY%<}iYb2TCn;2Qirj~=XR{j`5L6S22#N|L;Z-hwnJ1KEdLPhl&wCO4jqRJFB zL@jC=>3c=%aqaO;;=vh+h|;3pqQ>QhQBaG0gBq_#LgtK~bQNm^R_TAuGjhe#lFgqq z#Hfq=bH3n6_tX0Xrf1py$ATRPD}txUl{^GtLX`%h+I6gD0_jP)FclrW zf?jkv%9-ck=4Nvh-GzVUydc&K6ifaptt;)=mE~y+cIbO##i5`#cr>m1KBf5lN^buI z8qwv9w_Kf~8Jk$C832EhG$=AHv}D8v52c3LIo^eU{!`eqTe|fL0iqwvU;yCzu@weM)a<0tTZRq~j0?=- zY5a#3c}t{}Ubd?7vo^W<2Cn^>O3O+%7A4&*mmQY3g}e~3hSz`Y653WpKhK&s1g)?S zoxe7KH3VIqG}=kJUQ3Td2?hgSWGQ9g3MN@cZE0E3xXtuom}|Tbnh_E7p!uOB`4J>d zh)DB?hQnc}sG+1vPBIk}6M`pQZO2jW_XP8#MZ17e!iYw~OAAVAT;-q*EQ@x84(T-% zm#D3w&D~e4vBrNxHneGB)VtC1upE`}jThf}j;1ZFPV*fEks!YMp)*zflP~OpdM4q3Q#T8LIqF#vblkZZ@&SR?5myqLv0>Xy@+v5yU8!qr zuna4raf~D0_!XRJxUVqh`O*n1(B>n@>VvK-uY>ho-d=w}wA|YQ!dX8oX*^HpykV0x z(#o~w!Q?A0_qO7KQ1RYb@)!wh=xW+Ym_8g)0}$gk#iju`1Mu48%I5yH5WvpOmZAd; z&{K(b`5Q6UeuuS+n59zCZL7fM02h3-l?`V4Kx8nmjeU}80u=(1ryxz3#f z-+n^wGu4059P70GB&1utkKDy*fQ<^hg4(s&`>O{*l&VXg{-T0fc=gjzpDtwg;WH!k zE#=|C)H7sQ4?Z_25|LMeDyVrOlfwZR{1P8*`*as$ss?r#Cc0)el!>_(l!50E#aMT^ zmj2Et6CNovDn*2ma@5qYl9HU2Va%_>j*+8Q#3O%SG89%JGOaOQJO<0+(A;Pv(r{F( z$5wBBXfcC^8{niq0ECF+ut2l@?Y^Cp`Rx+Gth^^FzfAT8`bjFUq#J24gA0pRrw?x_ z5#Bz8VnpMBIW#UziInaToTbqU$lep5q*IJ1j%|L2(9$V2K^BI@mLavY;Gi}~dv=1! zkcNLFSbLvwC`MDJHhcCxMwaMlyrkvZL}HQXZ-Uo$7M8?$S~Il)+Fg27WY!yzryS{I z12kKYo=|1eRdm~H$0Cl2`9>?*8#|Xt4af3VyYj|&f@9wPd&okpjdPEa#7Vy+;gROZ zo=XbG0Xn~*QiD>_a=?vAGR+85=DZE~Pzt zdtF~Io4kf}UVj;Vl|#;1!F78EjOej06$nnZD{&2|^a zXGcN^p*B@Z9MBrp2`bArHHb7Ld>lq?-;=iWaNsGt%>2~6KyWz9fPGYJOq^N-?TLT% zJ@<>5`FhQ7T-h97@~uLuzpDFHwDVArh)mV9?1%lq{v!dvq})9Wqf_ExLh|JC@=)Ud z5LT#gv7X$;c^gYQA((v#1$GDKqWc^lHaS4dg%RbvX;e)A?LDV{fc#<^`g{nq=yf~P z;c|8q9V6jSG)1lk%2op6h6p#)(Dr`^ZZzl#cOg7S0#oedeP8Vz>VbH6w^$usS069b zYWzXUY(^5-%NIksq}Q^E^lCMs#ho(gp>9X4f@eGosxiL-j@NOWF8-X-c63kz zuhY%zVq(tYAY3Pi{{B~FA9nw)XinFS@KSoTKDu&SV0aQXXC`Vd84&ki&Q0u}W z(g$kbvr%}^sn;IL<*Z@%>9v1n-W?k_e8nEL2(IS9*jJQJFE6Dg;H~2&n$$ASO*ZvW zo86Kb*#w^!sM?2;W@hY-st~Va^>JjYO&T*NP#!rSLD!7rj9T3&wN-czPT~DQlT5!y z>}(q+13yEjn>DP3#n8LD{iH+LQTMzd9>EbvE?O_~$s81mp#4}ftPX#U8*v)*s?rH# z3HpG19Kr}J18Gaz{_?-4{nf@p#1MVHmer%LkCCcS6;2X`t5@&KFQW0~W9nKs49F!A zYVYwA>Gy)}K{i48JoNN-XU^4^&@|Cj~e0pBiJj3gkgHmA)qjws%GY>>VnEX(D5EXFJ##TO{%pP zMUPP4y$&VIk~!hCOU}MB4p*XhnjzwrXz&;kRc-p{kz}b z1RC=O9Df3Zz-HSNM>Dn%^Xr9ELIl~m5a)L|*c>WyX!G&ol8?z|B*fPJtv}`4y!Co^ zfkdiziP|Kr zEMR2PLq>D>g++fq?#5%hHIN;qw>AJ#JvZLngmJ3d6gHx=i}UX|fPZ=jO2TDKNORnK z<

#>WI|gw^-Dr4On705VFtnjh#lGFK$f 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%0Ax{NtwAHYN==;yBYMIrbR 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 CFrame) & ((CFrame, Vector3) -> Vector3) +\end{verbatim} +In order to typecheck this program, we check that that type is a subtype of: +\begin{verbatim} + (CFrame, Vector3 | CFrame) -> (Vector3 | CFrame) +\end{verbatim} +In the previous, syntax-driven, implementation of subtyping, this subtype check would fail, resulting in a false positive. +We have now released an implementation of semantic subtyping, which does not suffer from this defect. See our technical blog for more details~\cite{Jef22:SemanticSubtyping}. Rather than the gradual typing approach @@ -94,8 +110,30 @@ an error-suppressing type, and any failures of subtyping involving error-suppressing types are not reported. Users can explicitly suppress type errors by declaring variables with type $\ANY$, and since an expression with a type error has an error-suppressing type we -avoid cascading errors. Error suppression is in production Luau, and is -mechanically verified~\cite{BJ23:agda-typeck}. +avoid cascading errors. + +We do this by defining a \emph{infallible} typing judgment $\Gamma \vdash M : T$ +such that for any $\Gamma$ and $M$, there is a $T$ such that $\Gamma \vdash M : T$. +For example the rule for addition (ignoring overloads for simplicity) is: +\[ + \frac{\Gamma \vdash M : T \quad \Gamma \vdash M : U}{\Gamma \vdash M+N : \NUMBER} +\] +We define which judgments produce warnings, for example that rule produces a warning +when +\begin{itemize} + \item either $T \not<: \NUMBER$ and $T$ is not error-suppressing, + \item or $U \not<: \NUMBER$ and $U$ is not error-suppressing. +\end{itemize} +To retain type soundness (in the absence of user-supplied error-suppressing types) +we show that +if $\Gamma \vdash M : T$ and $T$ is error-suppressing, then either +\begin{itemize} + \item $\Gamma$ or $M$ contains an error-suppressing type, or + \item $\Gamma \vdash M : T$ produces a warning. +\end{itemize} +From this it is straightforward to show the usual ``well typed +programs don't go wrong'' type soundness result for programs without explicit +error-suppressing types~\cite{BJ23:agda-typeck}. \section{Further work} From 5097c60f107659fbd4908401a4dac997838f0322 Mon Sep 17 00:00:00 2001 From: Alan Jeffrey <403333+asajeffrey@users.noreply.github.com> Date: Mon, 21 Aug 2023 15:25:25 -0500 Subject: [PATCH 08/20] Fix HATRA 23 citations (#1015) --- papers/hatra23/bibliography.bib | 2 +- papers/hatra23/hatra23.pdf | Bin 415900 -> 415897 bytes papers/hatra23/hatra23.tex | 4 ++-- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/papers/hatra23/bibliography.bib b/papers/hatra23/bibliography.bib index 067bf967..3fbae85e 100644 --- a/papers/hatra23/bibliography.bib +++ b/papers/hatra23/bibliography.bib @@ -1,6 +1,6 @@ @InProceedings{BFJ21:GoalsLuau, author = {L. Brown and A. Friesen and A. S. A. Jeffrey}, - title = {Goals of the Luau Type System}, + title = {Position Paper: Goals of the Luau Type System}, booktitle = {Proc. Human Aspects of Types and Reasoning Assistants}, year = {2021}, url = {https://asaj.org/papers/hatra21.pdf}, diff --git a/papers/hatra23/hatra23.pdf b/papers/hatra23/hatra23.pdf index 32e83b9ba8770868167b4160dfaa159a54bd6a6c..b750ecaee636b26091494a76fd6c879fbe84e97a 100644 GIT binary patch delta 12247 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 R4=3+`FGr%Kl~9yK`hRlxY+(QZ delta 12225 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%0D8xMQbeyzb(8W*obfkP^z5zXj>&D0Rc*L^ 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 D$?ucC diff --git a/papers/hatra23/hatra23.tex b/papers/hatra23/hatra23.tex index 72e1f181..1d73ee53 100644 --- a/papers/hatra23/hatra23.tex +++ b/papers/hatra23/hatra23.tex @@ -49,7 +49,7 @@ error reporting). Roblox has hundreds of millions of users, and millions of creators, ranging from children learning to program for the first time to professional development studios. -In HATRA 2021, we presented \emph{The Goals Of The Luau Type +In HATRA 2021, we presented a position paper on the \emph{Goals Of The Luau Type System}~\cite{BFJ21:GoalsLuau}, describing the human factors issues with designing a type system for a language with a heterogeneous developer community. The design flows from the needs of the different @@ -103,7 +103,7 @@ We have now released an implementation of semantic subtyping, which does not suf See our technical blog for more details~\cite{Jef22:SemanticSubtyping}. Rather than the gradual typing approach -of~\cite{ST07:GradualTyping}, which uses \emph{consistent +of Siek and Taha~\cite{ST07:GradualTyping}, which uses \emph{consistent subtyping} where $\ANY \lesssim T \lesssim \ANY$ for any type $T$, we adopt an approach based on \emph{error suppression}, where $\ANY$ is an error-suppressing type, and any failures of subtyping involving From 535f85ebd334e10edaeb8988913532f260a42115 Mon Sep 17 00:00:00 2001 From: Arseny Kapoulkine Date: Wed, 23 Aug 2023 10:55:14 -0700 Subject: [PATCH 09/20] Update performance.md (#1016) Document tostring/tonumber and math.pi/huge optimizations --- docs/_pages/performance.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/_pages/performance.md b/docs/_pages/performance.md index d4dc0e8e..c2b4ced3 100644 --- a/docs/_pages/performance.md +++ b/docs/_pages/performance.md @@ -88,7 +88,7 @@ For this mechanism to work, function call must be "obvious" to the compiler - it The mechanism works by directly invoking a highly specialized and optimized implementation of a builtin function from the interpreter core loop without setting up a stack frame and omitting other work; additionally, some fastcall specializations are partial in that they don't support all types of arguments, for example all `math` library builtins are only specialized for numeric arguments, so calling `math.abs` with a string argument will fall back to the slower implementation that will do string->number coercion. -As a result, builtin calls are very fast in Luau - they are still slightly slower than core instructions such as arithmetic operations, but only slightly so. The set of fastcall builtins is slowly expanding over time and as of this writing contains `assert`, `type`, `typeof`, `rawget`/`rawset`/`rawequal`, `getmetatable`/`setmetatable`, all functions from `math` and `bit32`, and some functions from `string` and `table` library. +As a result, builtin calls are very fast in Luau - they are still slightly slower than core instructions such as arithmetic operations, but only slightly so. The set of fastcall builtins is slowly expanding over time and as of this writing contains `assert`, `type`, `typeof`, `rawget`/`rawset`/`rawequal`, `getmetatable`/`setmetatable`, `tonumber`/`tostring`, all functions from `math` (except `noise` and `random`/`randomseed`) and `bit32`, and some functions from `string` and `table` library. Some builtin functions have partial specializations that reduce the cost of the common case further. Notably: @@ -98,7 +98,7 @@ Some builtin functions have partial specializations that reduce the cost of the Some functions from `math` library like `math.floor` can additionally take advantage of advanced SIMD instruction sets like SSE4.1 when available. -In addition to runtime optimizations for builtin calls, many builtin calls can also be constant-folded by the bytecode compiler when using aggressive optimizations (level 2); this currently applies to most builtin calls with constant arguments and a single return value. For builtin calls that can not be constant folded, compiler assumes knowledge of argument/return count (level 2) to produce more efficient bytecode instructions. +In addition to runtime optimizations for builtin calls, many builtin calls, as well as constants like `math.pi`/`math.huge`, can also be constant-folded by the bytecode compiler when using aggressive optimizations (level 2); this currently applies to most builtin calls with constant arguments and a single return value. For builtin calls that can not be constant folded, compiler assumes knowledge of argument/return count (level 2) to produce more efficient bytecode instructions. ## Optimized table iteration From c3fc0d7bc8986d4f5b87688e7f537ca164aad16d Mon Sep 17 00:00:00 2001 From: Andy Friesen Date: Wed, 23 Aug 2023 15:52:59 -0700 Subject: [PATCH 10/20] RFC for local type inference. (#1007) [Rendered](https://github.com/Roblox/luau/blob/0e1082108fd6fb3a32dfdf5f1766ea3fc1391328/rfcs/local-type-inference.md) --- rfcs/local-type-inference.md | 180 +++++++++++++++++++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 rfcs/local-type-inference.md diff --git a/rfcs/local-type-inference.md b/rfcs/local-type-inference.md new file mode 100644 index 00000000..88cfe3cd --- /dev/null +++ b/rfcs/local-type-inference.md @@ -0,0 +1,180 @@ +# Local Type Inference + +## Summary + +We are going to supplant the current type solver with one based on Benjamin Pierce's Local Type Inference algorithm: + +https://www.cis.upenn.edu/~bcpierce/papers/lti-toplas.pdf + +## Motivation + +Luau's type inference algorithm is used for much more than typechecking scripts. It is also the backbone of an autocomplete algorithm which has to work even for people who don't know what types or type systems are. + +We originally implemented nonstrict mode by making some tactical adjustments to the type inference algorithm. This was great for reducing false positives in untyped code, but carried with it the drawback that the inference result was usually not good enough for the autocomplete system. In order to offer a high quality experience, we've found ourselves to run type inference on nonstrict scripts twice: once for error feedback, and once again to populate the autocomplete database. + +Separately, we would also like more accurate type inference in general. Our current type solver jumps to conclusions a little bit too quickly. For example, it cannot infer an accurate type for an ordinary search function: + +```lua +function index_of(tbl, el) + for i = 0, #tbl do + if tbl[i] == el then + return i + end + end + return nil +end +``` + +Our solver sees two `return` statements and assumes that, because the first statement yields a `number`, so too must the second. + +To fix this, we are going to move to an architecture where type inference and type checking are two separate steps. Whatever mode the user is programming with, we will run an accurate type inference pass over their code and then run one of two typechecking passes over it. + +## Notation + +We'll use the standard notation `A <: B` to indicate that `A` is a subtype of `B`. + +## Design + +At a very high level, local type inference is built around the idea that we track the lower bounds and their upper bounds. The lower bounds of a binding is the set of values that it might conceivably receive. If a binding receives a value outside of its upper bounds, the program will fail. + +At the implementation level, we reencode free types as the space between these bounds. + +Upper bounds arise only from type annotations and certain builtin operations whereas lower bounds arise from assignments, return statements, and uses. + +Free types all start out with bounds `never <: 't <: unknown`. Intuitively, we say that `'t` represents some set of values whose domain is at least `never` and at most `unknown`. This naturally could be any value at all. + +When dispatching a constraint `T <: 't`, we replace the lower bounds of `'t` by the union of its old lower bounds and `T`. When dispatching a constraint `'t <: T`, we replace the upper bounds by its upper bound intersected with `T`. In other words, lower bounds grow from nothing as we see the value used whereas the upper bound initially encompasses everything and shrinks as we constrain it. + +### Constraint Generation Rules + +A return statement expands the lower bounds of the enclosing function's return type. + +```lua +function f(): R + local x: X + return x + -- X <: R +end +``` + +An assignment adds to the lower bounds of the assignee. + +```lua +local a: A +local b: B +a = b +-- B <: A +``` + +A function call adds to the upper bounds of the function being called. + +Equivalently, passing a value to a function adds to the upper bounds of that value and to the lower bounds of its return value. + +```lua +local g +local h: H +local j = g(h) +-- G <: (H) -> I... +-- I... <: J +``` + +Property access is a constraint on a value's upper bounds. +```lua +local a: A +a.b = 2 +-- A <: {b: number} + +a[1] = 3 +-- A <: {number} +``` + +### Generalization + +Generalization is the process by which we infer that a function argument is generic. Broadly speaking, we solve constraints that arise from the function interior, we scan the signature of the function for types that are unconstrained, and we replace those types with generics. This much is all unchanged from the old solver. + +Unlike with the old solver, we never bind free types when dispatching a subtype constraint under local type inference. We only bind free types during generalization. + +If a type only appears in covariant positions in the function's signature, we can replace it by its lower bound. If it only appears in contravariant positions, we replace it by its upper bound. If it appears in both, we'll need to implement bounded generics to get it right. This is beyond the scope of this RFC. + +If a free type has neither upper nor lower bounds, we replace it with a generic. + +Some simple examples: + +```lua +function print_number(n: number) print(n) end + +function f(n) + print_number(n) +end +``` + +We arrive at the solution `never <: 'n <: number`. When we generalize, we can replace `'n` by its upper bound, namely `number`. We infer `f : (number) -> ()`. + +Next example: + +```lua +function index_of(tbl, el) -- index_of : ('a, 'b) -> 'r + for i = 0, #tbl do -- i : number + if tbl[i] == el then -- 'a <: {'c} + return i -- number <: 'r + end + end + return nil -- nil <: 'r +end +``` + +When typechecking this function, we have two constraints on `'r`, the return type. We can combine these constraints by taking the union of the lower bounds, leading us to `number | nil <: 'r <: unknown`. The type `'r` only appears in the return type of the function. The return type of this function is `number | nil`. + +At runtime, Luau allows any two values to be compared. Comparisons of values of mismatched types always return `false`. We therefore cannot produce any interesting constraints about `'b` or `'c`. + +We end up with these bounds: + +``` +never <: 'a <: {'c} +never <: 'b <: unknown +never <: 'c <: unknown +number | nil <: 'r <: unknown +``` + +`'a` appears in the argument position, so we replace it with its upper bound `{'c}`. `'b` and `'c` have no constraints at all so they are replaced by generics `B` and `C`. `'r` appears only in the return position and so is replaced by its lower bound `number | nil`. + +The final inferred type of `index_of` is `({C}, B) -> number | nil`. + +## Drawbacks + +This algorithm requires that we create a lot of union and intersection types. We need to be able to consistently pare down degenerate unions like `number | number`. + +Local type inference is also more permissive than what we have been doing up until now. For instance, the following is perfectly fine: + +```lua +local x = nil +if something then + x = 41 +else + x = "fourty one" +end +``` + +We'll infer `x : number | string | nil`. If the user wishes to constrain a value more tightly, they will have to write an annotation. + +## Alternatives + +### What TypeScript does + +TypeScript very clearly makes it work in what we would call a strict mode context, but we need more in order to offer a high quality nonstrict mode. For instance, TypeScript's autocomplete is completely helpless in the face of this code fragment: + +```ts +let x = null; +x = {a: "a", b: "pickles"}; +x. +``` + +TypeScript will complain that the assignment to `x` is illegal because `x` has type `null`. It will further offer no autocomplete suggestions at all when the user types the final `.`. + +It's not viable for us to require users to write type annotations. Many of our users do not yet know what types are but we are nevertheless committed to providing them a tool that is helpful to them. + +### Success Typing + +Success typing is the algorithm used by the Dialyzer inference engine for Erlang. Instead of attempting to prove that values always flow in sensible ways, it tries to prove that values _could_ flow in sensible ways. + +Success typing is quite nice in that it's very forgiving and can draw surprisingly useful information from untyped code, but that forgiving nature works against us in the case of strict mode. From ce9414cb9819b947a4f0060574879a650963e1fd Mon Sep 17 00:00:00 2001 From: vegorov-rbx <75688451+vegorov-rbx@users.noreply.github.com> Date: Fri, 25 Aug 2023 10:23:55 -0700 Subject: [PATCH 11/20] Sync to upstream/release/592 (#1018) * AST queries at position where function name is will now return AstExprLocal * Lexer performance has been slightly improved * Fixed incorrect string singleton autocomplete suggestions (fixes #858) * Improved parsing error messages * Fixed crash on null pointer access in unification (fixes #1017) * Native code support is enabled by default and `native=1` (make)/`LUAU_NATIVE` (CMake)/`-DLUA_CUSTOM_EXECUTION` configuration is no longer required New typechecker: * New subtyping check can now handle generic functions and tables (including those that contain cycles) Native code generation: * Loops with non-numeric parameters are now handled by VM to streamline native code * Array size check can be optimized away in SETLIST * On failure, CodeGen::compile returns a reason * Fixed clobbering of non-volatile xmm registers on Windows --- Analysis/include/Luau/Subtyping.h | 75 +++- Analysis/include/Luau/TypeUtils.h | 2 +- Analysis/src/AstQuery.cpp | 35 ++ Analysis/src/Autocomplete.cpp | 71 ++-- Analysis/src/Subtyping.cpp | 467 ++++++++++++++++----- Analysis/src/TypeInfer.cpp | 14 +- Analysis/src/Unifier.cpp | 4 + Ast/include/Luau/Ast.h | 9 +- Ast/include/Luau/Lexer.h | 2 + Ast/src/Ast.cpp | 3 +- Ast/src/Lexer.cpp | 36 +- Ast/src/Parser.cpp | 35 +- CLI/Repl.cpp | 13 +- CMakeLists.txt | 9 +- CodeGen/include/Luau/CodeGen.h | 12 +- CodeGen/include/Luau/IrData.h | 11 +- CodeGen/include/Luau/IrRegAllocX64.h | 1 + CodeGen/include/Luau/UnwindBuilder.h | 4 +- CodeGen/include/Luau/UnwindBuilderDwarf2.h | 3 +- CodeGen/include/Luau/UnwindBuilderWin.h | 3 +- CodeGen/src/CodeGen.cpp | 39 +- CodeGen/src/CodeGenX64.cpp | 66 ++- CodeGen/src/EmitCommonX64.cpp | 10 - CodeGen/src/EmitCommonX64.h | 58 ++- CodeGen/src/EmitInstructionX64.cpp | 31 +- CodeGen/src/EmitInstructionX64.h | 2 +- CodeGen/src/IrAnalysis.cpp | 9 - CodeGen/src/IrBuilder.cpp | 2 +- CodeGen/src/IrCallWrapperX64.cpp | 2 +- CodeGen/src/IrDump.cpp | 2 - CodeGen/src/IrLoweringA64.cpp | 10 - CodeGen/src/IrLoweringX64.cpp | 11 +- CodeGen/src/IrRegAllocX64.cpp | 3 +- CodeGen/src/IrTranslation.cpp | 21 +- CodeGen/src/IrUtils.cpp | 1 - CodeGen/src/IrValueLocationTracking.cpp | 5 - CodeGen/src/NativeState.cpp | 1 - CodeGen/src/NativeState.h | 1 - CodeGen/src/OptimizeConstProp.cpp | 13 +- CodeGen/src/UnwindBuilderDwarf2.cpp | 11 +- CodeGen/src/UnwindBuilderWin.cpp | 56 ++- Common/include/Luau/DenseHash.h | 7 + Compiler/src/Builtins.cpp | 7 +- Compiler/src/Compiler.cpp | 7 +- Makefile | 11 +- VM/include/luaconf.h | 5 - VM/src/laux.cpp | 19 +- VM/src/lbuiltins.cpp | 8 +- VM/src/lstrlib.cpp | 48 +-- VM/src/lvmexecute.cpp | 4 +- fuzz/proto.cpp | 1 + tests/AstQuery.test.cpp | 30 ++ tests/Autocomplete.test.cpp | 83 +++- tests/CodeAllocator.test.cpp | 134 +++++- tests/Compiler.test.cpp | 116 ++++- tests/Frontend.test.cpp | 2 - tests/Parser.test.cpp | 114 ++++- tests/Subtyping.test.cpp | 350 ++++++++++++++- tests/TypeInfer.intersectionTypes.test.cpp | 50 +++ 59 files changed, 1688 insertions(+), 471 deletions(-) diff --git a/Analysis/include/Luau/Subtyping.h b/Analysis/include/Luau/Subtyping.h index 9ebdfc16..8864ef81 100644 --- a/Analysis/include/Luau/Subtyping.h +++ b/Analysis/include/Luau/Subtyping.h @@ -2,6 +2,7 @@ #pragma once #include "Luau/Type.h" +#include "Luau/UnifierSharedState.h" #include #include @@ -11,11 +12,12 @@ namespace Luau template struct TryPair; +struct InternalErrorReporter; class Normalizer; struct NormalizedType; -struct SubtypingGraph +struct SubtypingResult { // Did the test succeed? bool isSubtype = false; @@ -25,39 +27,78 @@ struct SubtypingGraph // If so, what constraints are implied by this relation? // If not, what happened? - SubtypingGraph and_(const SubtypingGraph& other); - SubtypingGraph or_(const SubtypingGraph& other); + void andAlso(const SubtypingResult& other); + void orElse(const SubtypingResult& other); - static SubtypingGraph and_(const std::vector& results); - static SubtypingGraph or_(const std::vector& results); + static SubtypingResult all(const std::vector& results); + static SubtypingResult any(const std::vector& results); }; struct Subtyping { NotNull builtinTypes; + NotNull arena; NotNull normalizer; + NotNull iceReporter; + + enum class Variance + { + Covariant, + Contravariant + }; + + Variance variance = Variance::Covariant; + + struct GenericBounds + { + DenseHashSet lowerBound{nullptr}; + DenseHashSet upperBound{nullptr}; + }; + + /* + * When we encounter a generic over the course of a subtyping test, we need + * to tentatively map that generic onto a type on the other side. + */ + DenseHashMap mappedGenerics{nullptr}; + DenseHashMap mappedGenericPacks{nullptr}; + + using SeenSet = std::unordered_set, TypeIdPairHash>; + + SeenSet seenTypes; // TODO cache // TODO cyclic types // TODO recursion limits - SubtypingGraph isSubtype(TypeId subTy, TypeId superTy); - SubtypingGraph isSubtype(TypePackId subTy, TypePackId superTy); + SubtypingResult isSubtype(TypeId subTy, TypeId superTy); + SubtypingResult isSubtype(TypePackId subTy, TypePackId superTy); private: + SubtypingResult isSubtype_(TypeId subTy, TypeId superTy); + SubtypingResult isSubtype_(TypePackId subTy, TypePackId superTy); + template - SubtypingGraph isSubtype(const TryPair& pair); + SubtypingResult isSubtype_(const TryPair& pair); - SubtypingGraph isSubtype(TypeId subTy, const UnionType* superUnion); - SubtypingGraph isSubtype(const UnionType* subUnion, TypeId superTy); - SubtypingGraph isSubtype(TypeId subTy, const IntersectionType* superIntersection); - SubtypingGraph isSubtype(const IntersectionType* subIntersection, TypeId superTy); - SubtypingGraph isSubtype(const PrimitiveType* subPrim, const PrimitiveType* superPrim); - SubtypingGraph isSubtype(const SingletonType* subSingleton, const PrimitiveType* superPrim); - SubtypingGraph isSubtype(const SingletonType* subSingleton, const SingletonType* superSingleton); - SubtypingGraph isSubtype(const FunctionType* subFunction, const FunctionType* superFunction); + SubtypingResult isSubtype_(TypeId subTy, const UnionType* superUnion); + SubtypingResult isSubtype_(const UnionType* subUnion, TypeId superTy); + SubtypingResult isSubtype_(TypeId subTy, const IntersectionType* superIntersection); + SubtypingResult isSubtype_(const IntersectionType* subIntersection, TypeId superTy); + SubtypingResult isSubtype_(const PrimitiveType* subPrim, const PrimitiveType* superPrim); + SubtypingResult isSubtype_(const SingletonType* subSingleton, const PrimitiveType* superPrim); + SubtypingResult isSubtype_(const SingletonType* subSingleton, const SingletonType* superSingleton); + SubtypingResult isSubtype_(const TableType* subTable, const TableType* superTable); + SubtypingResult isSubtype_(const FunctionType* subFunction, const FunctionType* superFunction); + SubtypingResult isSubtype_(const NormalizedType* subNorm, const NormalizedType* superNorm); - SubtypingGraph isSubtype(const NormalizedType* subNorm, const NormalizedType* superNorm); + bool bindGeneric(TypeId subTp, TypeId superTp); + bool bindGeneric(TypePackId subTp, TypePackId superTp); + + template + TypeId makeAggregateType(const Container& container, TypeId orElse); + + [[noreturn]] + void unexpected(TypePackId tp); }; } // namespace Luau diff --git a/Analysis/include/Luau/TypeUtils.h b/Analysis/include/Luau/TypeUtils.h index c360e4bc..9699c4ae 100644 --- a/Analysis/include/Luau/TypeUtils.h +++ b/Analysis/include/Luau/TypeUtils.h @@ -108,7 +108,7 @@ struct TryPair { A first; B second; - operator bool() const + explicit operator bool() const { return bool(first) && bool(second); } diff --git a/Analysis/src/AstQuery.cpp b/Analysis/src/AstQuery.cpp index 6a6f10e8..a71dd592 100644 --- a/Analysis/src/AstQuery.cpp +++ b/Analysis/src/AstQuery.cpp @@ -12,6 +12,7 @@ #include LUAU_FASTFLAG(DebugLuauReadWriteProperties) +LUAU_FASTFLAGVARIABLE(FixFindBindingAtFunctionName, false); namespace Luau { @@ -148,6 +149,23 @@ struct FindNode : public AstVisitor return false; } + bool visit(AstStatFunction* node) override + { + if (FFlag::FixFindBindingAtFunctionName) + { + visit(static_cast(node)); + if (node->name->location.contains(pos)) + node->name->visit(this); + else if (node->func->location.contains(pos)) + node->func->visit(this); + return false; + } + else + { + return AstVisitor::visit(node); + } + } + bool visit(AstStatBlock* block) override { visit(static_cast(block)); @@ -188,6 +206,23 @@ struct FindFullAncestry final : public AstVisitor return false; } + bool visit(AstStatFunction* node) override + { + if (FFlag::FixFindBindingAtFunctionName) + { + visit(static_cast(node)); + if (node->name->location.contains(pos)) + node->name->visit(this); + else if (node->func->location.contains(pos)) + node->func->visit(this); + return false; + } + else + { + return AstVisitor::visit(node); + } + } + bool visit(AstNode* node) override { if (node->location.contains(pos)) diff --git a/Analysis/src/Autocomplete.cpp b/Analysis/src/Autocomplete.cpp index 9b6f4db7..471fd006 100644 --- a/Analysis/src/Autocomplete.cpp +++ b/Analysis/src/Autocomplete.cpp @@ -15,7 +15,7 @@ LUAU_FASTFLAG(DebugLuauReadWriteProperties) LUAU_FASTFLAGVARIABLE(LuauAnonymousAutofilled1, false); LUAU_FASTFLAGVARIABLE(LuauAutocompleteLastTypecheck, false) -LUAU_FASTFLAGVARIABLE(LuauAutocompleteHideSelfArg, false) +LUAU_FASTFLAGVARIABLE(LuauAutocompleteStringLiteralBounds, false); static const std::unordered_set kStatementStartingKeywords = { "while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"}; @@ -283,38 +283,20 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, NotNul ParenthesesRecommendation parens = indexType == PropIndexType::Key ? ParenthesesRecommendation::None : getParenRecommendation(type, nodes, typeCorrect); - if (FFlag::LuauAutocompleteHideSelfArg) - { - result[name] = AutocompleteEntry{ - AutocompleteEntryKind::Property, - type, - prop.deprecated, - isWrongIndexer(type), - typeCorrect, - containingClass, - &prop, - prop.documentationSymbol, - {}, - parens, - {}, - indexType == PropIndexType::Colon - }; - } - else - { - result[name] = AutocompleteEntry{ - AutocompleteEntryKind::Property, - type, - prop.deprecated, - isWrongIndexer(type), - typeCorrect, - containingClass, - &prop, - prop.documentationSymbol, - {}, - parens - }; - } + result[name] = AutocompleteEntry{ + AutocompleteEntryKind::Property, + type, + prop.deprecated, + isWrongIndexer(type), + typeCorrect, + containingClass, + &prop, + prop.documentationSymbol, + {}, + parens, + {}, + indexType == PropIndexType::Colon + }; } } }; @@ -484,8 +466,19 @@ AutocompleteEntryMap autocompleteModuleTypes(const Module& module, Position posi return result; } -static void autocompleteStringSingleton(TypeId ty, bool addQuotes, AutocompleteEntryMap& result) +static void autocompleteStringSingleton(TypeId ty, bool addQuotes, AstNode* node, Position position, AutocompleteEntryMap& result) { + if (FFlag::LuauAutocompleteStringLiteralBounds) + { + if (position == node->location.begin || position == node->location.end) + { + if (auto str = node->as(); str && str->quoteStyle == AstExprConstantString::Quoted) + return; + else if (node->is()) + return; + } + } + auto formatKey = [addQuotes](const std::string& key) { if (addQuotes) return "\"" + escape(key) + "\""; @@ -1238,7 +1231,7 @@ static AutocompleteContext autocompleteExpression(const SourceModule& sourceModu result["function"] = {AutocompleteEntryKind::Keyword, std::nullopt, false, false, correctForFunction}; if (auto ty = findExpectedTypeAt(module, node, position)) - autocompleteStringSingleton(*ty, true, result); + autocompleteStringSingleton(*ty, true, node, position, result); } return AutocompleteContext::Expression; @@ -1719,7 +1712,7 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M auto result = autocompleteProps(*module, typeArena, builtinTypes, *it, PropIndexType::Key, ancestry); if (auto nodeIt = module->astExpectedTypes.find(node->asExpr())) - autocompleteStringSingleton(*nodeIt, !node->is(), result); + autocompleteStringSingleton(*nodeIt, !node->is(), node, position, result); if (!key) { @@ -1731,7 +1724,7 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M // suggest those too. if (auto ttv = get(follow(*it)); ttv && ttv->indexer) { - autocompleteStringSingleton(ttv->indexer->indexType, false, result); + autocompleteStringSingleton(ttv->indexer->indexType, false, node, position, result); } } @@ -1768,7 +1761,7 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M AutocompleteEntryMap result; if (auto it = module->astExpectedTypes.find(node->asExpr())) - autocompleteStringSingleton(*it, false, result); + autocompleteStringSingleton(*it, false, node, position, result); if (ancestry.size() >= 2) { @@ -1782,7 +1775,7 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M if (binExpr->op == AstExprBinary::CompareEq || binExpr->op == AstExprBinary::CompareNe) { if (auto it = module->astTypes.find(node == binExpr->left ? binExpr->right : binExpr->left)) - autocompleteStringSingleton(*it, false, result); + autocompleteStringSingleton(*it, false, node, position, result); } } } diff --git a/Analysis/src/Subtyping.cpp b/Analysis/src/Subtyping.cpp index 596890f9..b55686b9 100644 --- a/Analysis/src/Subtyping.cpp +++ b/Analysis/src/Subtyping.cpp @@ -3,8 +3,12 @@ #include "Luau/Subtyping.h" #include "Luau/Common.h" +#include "Luau/Error.h" #include "Luau/Normalize.h" +#include "Luau/StringUtils.h" +#include "Luau/ToString.h" #include "Luau/Type.h" +#include "Luau/TypeArena.h" #include "Luau/TypePack.h" #include "Luau/TypeUtils.h" @@ -13,42 +17,109 @@ namespace Luau { -SubtypingGraph SubtypingGraph::and_(const SubtypingGraph& other) +struct VarianceFlipper { - return SubtypingGraph{ - isSubtype && other.isSubtype, - // `||` is intentional here, we want to preserve error-suppressing flag. - isErrorSuppressing || other.isErrorSuppressing, - normalizationTooComplex || other.normalizationTooComplex, - }; + Subtyping::Variance* variance; + Subtyping::Variance oldValue; + + VarianceFlipper(Subtyping::Variance* v) + : variance(v) + , oldValue(*v) + { + switch (oldValue) + { + case Subtyping::Variance::Covariant: + *variance = Subtyping::Variance::Contravariant; + break; + case Subtyping::Variance::Contravariant: + *variance = Subtyping::Variance::Covariant; + break; + } + } + + ~VarianceFlipper() + { + *variance = oldValue; + } +}; + +void SubtypingResult::andAlso(const SubtypingResult& other) +{ + isSubtype &= other.isSubtype; + // `|=` is intentional here, we want to preserve error related flags. + isErrorSuppressing |= other.isErrorSuppressing; + normalizationTooComplex |= other.normalizationTooComplex; } -SubtypingGraph SubtypingGraph::or_(const SubtypingGraph& other) +void SubtypingResult::orElse(const SubtypingResult& other) { - return SubtypingGraph{ - isSubtype || other.isSubtype, - isErrorSuppressing || other.isErrorSuppressing, - normalizationTooComplex || other.normalizationTooComplex, - }; + isSubtype |= other.isSubtype; + isErrorSuppressing |= other.isErrorSuppressing; + normalizationTooComplex |= other.normalizationTooComplex; } -SubtypingGraph SubtypingGraph::and_(const std::vector& results) +SubtypingResult SubtypingResult::all(const std::vector& results) { - SubtypingGraph acc{true, false}; - for (const SubtypingGraph& current : results) - acc = acc.and_(current); + SubtypingResult acc{true, false}; + for (const SubtypingResult& current : results) + acc.andAlso(current); return acc; } -SubtypingGraph SubtypingGraph::or_(const std::vector& results) +SubtypingResult SubtypingResult::any(const std::vector& results) { - SubtypingGraph acc{false, false}; - for (const SubtypingGraph& current : results) - acc = acc.or_(current); + SubtypingResult acc{false, false}; + for (const SubtypingResult& current : results) + acc.orElse(current); return acc; } -SubtypingGraph Subtyping::isSubtype(TypeId subTy, TypeId superTy) +SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy) +{ + mappedGenerics.clear(); + mappedGenericPacks.clear(); + + SubtypingResult result = isSubtype_(subTy, superTy); + + for (const auto& [subTy, bounds]: mappedGenerics) + { + const auto& lb = bounds.lowerBound; + const auto& ub = bounds.upperBound; + + TypeId lowerBound = makeAggregateType(lb, builtinTypes->neverType); + TypeId upperBound = makeAggregateType(ub, builtinTypes->unknownType); + + result.andAlso(isSubtype_(lowerBound, upperBound)); + } + + return result; +} + +SubtypingResult Subtyping::isSubtype(TypePackId subTp, TypePackId superTp) +{ + return isSubtype_(subTp, superTp); +} + +namespace +{ +struct SeenSetPopper +{ + Subtyping::SeenSet* seenTypes; + std::pair pair; + + SeenSetPopper(Subtyping::SeenSet* seenTypes, std::pair pair) + : seenTypes(seenTypes) + , pair(pair) + {} + + ~SeenSetPopper() + { + seenTypes->erase(pair); + } +}; +} + +SubtypingResult Subtyping::isSubtype_(TypeId subTy, TypeId superTy) { subTy = follow(subTy); superTy = follow(superTy); @@ -60,20 +131,25 @@ SubtypingGraph Subtyping::isSubtype(TypeId subTy, TypeId superTy) if (subTy == superTy) return {true}; + std::pair typePair{subTy, superTy}; + if (!seenTypes.insert(typePair).second) + return {true}; + + SeenSetPopper ssp{&seenTypes, typePair}; if (auto superUnion = get(superTy)) - return isSubtype(subTy, superUnion); + return isSubtype_(subTy, superUnion); else if (auto subUnion = get(subTy)) - return isSubtype(subUnion, superTy); + return isSubtype_(subUnion, superTy); else if (auto superIntersection = get(superTy)) - return isSubtype(subTy, superIntersection); + return isSubtype_(subTy, superIntersection); else if (auto subIntersection = get(subTy)) { - SubtypingGraph result = isSubtype(subIntersection, superTy); + SubtypingResult result = isSubtype_(subIntersection, superTy); if (result.isSubtype || result.isErrorSuppressing || result.normalizationTooComplex) return result; else - return isSubtype(normalizer->normalize(subTy), normalizer->normalize(superTy)); + return isSubtype_(normalizer->normalize(subTy), normalizer->normalize(superTy)); } else if (get(superTy)) return {true}; // This is always true. @@ -81,9 +157,11 @@ SubtypingGraph Subtyping::isSubtype(TypeId subTy, TypeId superTy) { // any = unknown | error, so we rewrite this to match. // As per TAPL: A | B <: T iff A <: T && B <: T - return isSubtype(builtinTypes->unknownType, superTy).and_(isSubtype(builtinTypes->errorType, superTy)); + SubtypingResult result = isSubtype_(builtinTypes->unknownType, superTy); + result.andAlso(isSubtype_(builtinTypes->errorType, superTy)); + return result; } - else if (auto superUnknown = get(superTy)) + else if (get(superTy)) { LUAU_ASSERT(!get(subTy)); // TODO: replace with ice. LUAU_ASSERT(!get(subTy)); // TODO: replace with ice. @@ -98,19 +176,31 @@ SubtypingGraph Subtyping::isSubtype(TypeId subTy, TypeId superTy) return {false, true}; else if (get(subTy)) return {false, true}; + else if (auto subGeneric = get(subTy); subGeneric && variance == Variance::Covariant) + { + bool ok = bindGeneric(subTy, superTy); + return {ok}; + } + else if (auto superGeneric = get(superTy); superGeneric && variance == Variance::Contravariant) + { + bool ok = bindGeneric(subTy, superTy); + return {ok}; + } else if (auto p = get2(subTy, superTy)) - return isSubtype(p); + return isSubtype_(p); else if (auto p = get2(subTy, superTy)) - return isSubtype(p); + return isSubtype_(p); else if (auto p = get2(subTy, superTy)) - return isSubtype(p); + return isSubtype_(p); else if (auto p = get2(subTy, superTy)) - return isSubtype(p); + return isSubtype_(p); + else if (auto p = get2(subTy, superTy)) + return isSubtype_(p); return {false}; } -SubtypingGraph Subtyping::isSubtype(TypePackId subTp, TypePackId superTp) +SubtypingResult Subtyping::isSubtype_(TypePackId subTp, TypePackId superTp) { subTp = follow(subTp); superTp = follow(superTp); @@ -120,14 +210,17 @@ SubtypingGraph Subtyping::isSubtype(TypePackId subTp, TypePackId superTp) const size_t headSize = std::min(subHead.size(), superHead.size()); - std::vector results; + std::vector results; results.reserve(std::max(subHead.size(), superHead.size()) + 1); + if (subTp == superTp) + return {true}; + // Match head types pairwise for (size_t i = 0; i < headSize; ++i) { - results.push_back(isSubtype(subHead[i], superHead[i])); + results.push_back(isSubtype_(subHead[i], superHead[i])); if (!results.back().isSubtype) return {false}; } @@ -141,12 +234,40 @@ SubtypingGraph Subtyping::isSubtype(TypePackId subTp, TypePackId superTp) if (auto vt = get(*subTail)) { for (size_t i = headSize; i < superHead.size(); ++i) + results.push_back(isSubtype_(vt->ty, superHead[i])); + } + else if (auto gt = get(*subTail)) + { + if (variance == Variance::Covariant) { - results.push_back(isSubtype(vt->ty, superHead[i])); + // For any non-generic type T: + // + // (X) -> () <: (T) -> () + + // Possible optimization: If headSize == 0 then we can just use subTp as-is. + std::vector headSlice(begin(superHead), end(superHead) + headSize); + TypePackId superTailPack = arena->addTypePack(std::move(headSlice), superTail); + + if (TypePackId* other = mappedGenericPacks.find(*subTail)) + results.push_back(isSubtype_(*other, superTailPack)); + else + mappedGenericPacks.try_insert(*subTail, superTailPack); + + // FIXME? Not a fan of the early return here. It makes the + // control flow harder to reason about. + return SubtypingResult::all(results); + } + else + { + // For any non-generic type T: + // + // (T) -> () (X) -> () + // + return {false}; } } else - LUAU_ASSERT(0); // TODO + unexpected(*subTail); } else return {false}; @@ -158,20 +279,43 @@ SubtypingGraph Subtyping::isSubtype(TypePackId subTp, TypePackId superTp) if (auto vt = get(*superTail)) { for (size_t i = headSize; i < subHead.size(); ++i) + results.push_back(isSubtype_(subHead[i], vt->ty)); + } + else if (auto gt = get(*superTail)) + { + if (variance == Variance::Contravariant) { - results.push_back(isSubtype(subHead[i], vt->ty)); + // For any non-generic type T: + // + // (X...) -> () <: (T) -> () + + // Possible optimization: If headSize == 0 then we can just use subTp as-is. + std::vector headSlice(begin(subHead), end(subHead) + headSize); + TypePackId subTailPack = arena->addTypePack(std::move(headSlice), subTail); + + if (TypePackId* other = mappedGenericPacks.find(*superTail)) + results.push_back(isSubtype_(*other, subTailPack)); + else + mappedGenericPacks.try_insert(*superTail, subTailPack); + + // FIXME? Not a fan of the early return here. It makes the + // control flow harder to reason about. + return SubtypingResult::all(results); + } + else + { + // For any non-generic type T: + // + // () -> T () -> X... + return {false}; } } else - LUAU_ASSERT(0); // TODO + unexpected(*superTail); } else return {false}; } - else - { - // subHead and superHead are the same size. Nothing more must be done. - } // Handle tails @@ -179,10 +323,43 @@ SubtypingGraph Subtyping::isSubtype(TypePackId subTp, TypePackId superTp) { if (auto p = get2(*subTail, *superTail)) { - results.push_back(isSubtype(p.first->ty, p.second->ty)); + results.push_back(isSubtype_(p.first->ty, p.second->ty)); + } + else if (auto p = get2(*subTail, *superTail)) + { + bool ok = bindGeneric(*subTail, *superTail); + results.push_back({ok}); + } + else if (get2(*subTail, *superTail)) + { + if (variance == Variance::Contravariant) + { + // (A...) -> number <: (...number) -> number + bool ok = bindGeneric(*subTail, *superTail); + results.push_back({ok}); + } + else + { + // (number) -> ...number (number) -> A... + results.push_back({false}); + } + } + else if (get2(*subTail, *superTail)) + { + if (variance == Variance::Contravariant) + { + // (...number) -> number (A...) -> number + results.push_back({false}); + } + else + { + // () -> A... <: () -> ...number + bool ok = bindGeneric(*subTail, *superTail); + results.push_back({ok}); + } } else - LUAU_ASSERT(0); // TODO + iceReporter->ice(format("Subtyping::isSubtype got unexpected type packs %s and %s", toString(*subTail).c_str(), toString(*superTail).c_str())); } else if (subTail) { @@ -190,8 +367,13 @@ SubtypingGraph Subtyping::isSubtype(TypePackId subTp, TypePackId superTp) { return {false}; } - - LUAU_ASSERT(0); // TODO + else if (get(*subTail)) + { + bool ok = bindGeneric(*subTail, builtinTypes->emptyTypePack); + return {ok}; + } + else + unexpected(*subTail); } else if (superTail) { @@ -207,17 +389,27 @@ SubtypingGraph Subtyping::isSubtype(TypePackId subTp, TypePackId superTp) * All variadic type packs are therefore supertypes of the empty type pack. */ } + else if (get(*superTail)) + { + if (variance == Variance::Contravariant) + { + bool ok = bindGeneric(builtinTypes->emptyTypePack, *superTail); + results.push_back({ok}); + } + else + results.push_back({false}); + } else LUAU_ASSERT(0); // TODO } - return SubtypingGraph::and_(results); + return SubtypingResult::all(results); } template -SubtypingGraph Subtyping::isSubtype(const TryPair& pair) +SubtypingResult Subtyping::isSubtype_(const TryPair& pair) { - return isSubtype(pair.first, pair.second); + return isSubtype_(pair.first, pair.second); } /* @@ -251,49 +443,49 @@ SubtypingGraph Subtyping::isSubtype(const TryPair& * other just asks for boolean ~ 'b. We can dispatch this and only commit * boolean ~ 'b. This constraint does not teach us anything about 'a. */ -SubtypingGraph Subtyping::isSubtype(TypeId subTy, const UnionType* superUnion) +SubtypingResult Subtyping::isSubtype_(TypeId subTy, const UnionType* superUnion) { // As per TAPL: T <: A | B iff T <: A || T <: B - std::vector subtypings; + std::vector subtypings; for (TypeId ty : superUnion) - subtypings.push_back(isSubtype(subTy, ty)); - return SubtypingGraph::or_(subtypings); + subtypings.push_back(isSubtype_(subTy, ty)); + return SubtypingResult::any(subtypings); } -SubtypingGraph Subtyping::isSubtype(const UnionType* subUnion, TypeId superTy) +SubtypingResult Subtyping::isSubtype_(const UnionType* subUnion, TypeId superTy) { // As per TAPL: A | B <: T iff A <: T && B <: T - std::vector subtypings; + std::vector subtypings; for (TypeId ty : subUnion) - subtypings.push_back(isSubtype(ty, superTy)); - return SubtypingGraph::and_(subtypings); + subtypings.push_back(isSubtype_(ty, superTy)); + return SubtypingResult::all(subtypings); } -SubtypingGraph Subtyping::isSubtype(TypeId subTy, const IntersectionType* superIntersection) +SubtypingResult Subtyping::isSubtype_(TypeId subTy, const IntersectionType* superIntersection) { // As per TAPL: T <: A & B iff T <: A && T <: B - std::vector subtypings; + std::vector subtypings; for (TypeId ty : superIntersection) - subtypings.push_back(isSubtype(subTy, ty)); - return SubtypingGraph::and_(subtypings); + subtypings.push_back(isSubtype_(subTy, ty)); + return SubtypingResult::all(subtypings); } -SubtypingGraph Subtyping::isSubtype(const IntersectionType* subIntersection, TypeId superTy) +SubtypingResult Subtyping::isSubtype_(const IntersectionType* subIntersection, TypeId superTy) { // TODO: Semantic subtyping here. // As per TAPL: A & B <: T iff A <: T || B <: T - std::vector subtypings; + std::vector subtypings; for (TypeId ty : subIntersection) - subtypings.push_back(isSubtype(ty, superTy)); - return SubtypingGraph::or_(subtypings); + subtypings.push_back(isSubtype_(ty, superTy)); + return SubtypingResult::any(subtypings); } -SubtypingGraph Subtyping::isSubtype(const PrimitiveType* subPrim, const PrimitiveType* superPrim) +SubtypingResult Subtyping::isSubtype_(const PrimitiveType* subPrim, const PrimitiveType* superPrim) { return {subPrim->type == superPrim->type}; } -SubtypingGraph Subtyping::isSubtype(const SingletonType* subSingleton, const PrimitiveType* superPrim) +SubtypingResult Subtyping::isSubtype_(const SingletonType* subSingleton, const PrimitiveType* superPrim) { if (get(subSingleton) && superPrim->type == PrimitiveType::String) return {true}; @@ -303,42 +495,123 @@ SubtypingGraph Subtyping::isSubtype(const SingletonType* subSingleton, const Pri return {false}; } -SubtypingGraph Subtyping::isSubtype(const SingletonType* subSingleton, const SingletonType* superSingleton) +SubtypingResult Subtyping::isSubtype_(const SingletonType* subSingleton, const SingletonType* superSingleton) { return {*subSingleton == *superSingleton}; } -SubtypingGraph Subtyping::isSubtype(const FunctionType* subFunction, const FunctionType* superFunction) +SubtypingResult Subtyping::isSubtype_(const TableType* subTable, const TableType* superTable) { - SubtypingGraph argResult = isSubtype(superFunction->argTypes, subFunction->argTypes); - SubtypingGraph retResult = isSubtype(subFunction->retTypes, superFunction->retTypes); + SubtypingResult result{true}; - return argResult.and_(retResult); -} - -SubtypingGraph Subtyping::isSubtype(const NormalizedType* subNorm, const NormalizedType* superNorm) -{ - if (!subNorm || !superNorm) - return {false, true, true}; - - SubtypingGraph result{true}; - result = result.and_(isSubtype(subNorm->tops, superNorm->tops)); - result = result.and_(isSubtype(subNorm->booleans, superNorm->booleans)); - // isSubtype(subNorm->classes, superNorm->classes); - // isSubtype(subNorm->classes, superNorm->tables); - result = result.and_(isSubtype(subNorm->errors, superNorm->errors)); - result = result.and_(isSubtype(subNorm->nils, superNorm->nils)); - result = result.and_(isSubtype(subNorm->numbers, superNorm->numbers)); - result.isSubtype &= Luau::isSubtype(subNorm->strings, superNorm->strings); - // isSubtype(subNorm->strings, superNorm->tables); - result = result.and_(isSubtype(subNorm->threads, superNorm->threads)); - // isSubtype(subNorm->tables, superNorm->tables); - // isSubtype(subNorm->tables, superNorm->strings); - // isSubtype(subNorm->tables, superNorm->classes); - // isSubtype(subNorm->functions, superNorm->functions); - // isSubtype(subNorm->tyvars, superNorm->tyvars); + for (const auto& [name, prop]: superTable->props) + { + auto it = subTable->props.find(name); + if (it != subTable->props.end()) + { + // Table properties are invariant + result.andAlso(isSubtype(it->second.type(), prop.type())); + result.andAlso(isSubtype(prop.type(), it->second.type())); + } + else + return SubtypingResult{false}; + } return result; } +SubtypingResult Subtyping::isSubtype_(const FunctionType* subFunction, const FunctionType* superFunction) +{ + SubtypingResult result; + { + VarianceFlipper vf{&variance}; + result.orElse(isSubtype_(superFunction->argTypes, subFunction->argTypes)); + } + + result.andAlso(isSubtype_(subFunction->retTypes, superFunction->retTypes)); + + return result; +} + +SubtypingResult Subtyping::isSubtype_(const NormalizedType* subNorm, const NormalizedType* superNorm) +{ + if (!subNorm || !superNorm) + return {false, true, true}; + + SubtypingResult result = isSubtype_(subNorm->tops, superNorm->tops); + result.andAlso(isSubtype_(subNorm->booleans, superNorm->booleans)); + // isSubtype_(subNorm->classes, superNorm->classes); + // isSubtype_(subNorm->classes, superNorm->tables); + result.andAlso(isSubtype_(subNorm->errors, superNorm->errors)); + result.andAlso(isSubtype_(subNorm->nils, superNorm->nils)); + result.andAlso(isSubtype_(subNorm->numbers, superNorm->numbers)); + result.isSubtype &= Luau::isSubtype(subNorm->strings, superNorm->strings); + // isSubtype_(subNorm->strings, superNorm->tables); + result.andAlso(isSubtype_(subNorm->threads, superNorm->threads)); + // isSubtype_(subNorm->tables, superNorm->tables); + // isSubtype_(subNorm->tables, superNorm->strings); + // isSubtype_(subNorm->tables, superNorm->classes); + // isSubtype_(subNorm->functions, superNorm->functions); + // isSubtype_(subNorm->tyvars, superNorm->tyvars); + + return result; +} + +bool Subtyping::bindGeneric(TypeId subTy, TypeId superTy) +{ + if (variance == Variance::Covariant) + { + if (!get(subTy)) + return false; + + mappedGenerics[subTy].upperBound.insert(superTy); + } + else + { + if (!get(superTy)) + return false; + + mappedGenerics[superTy].lowerBound.insert(subTy); + } + + return true; +} + +/* + * If, when performing a subtyping test, we encounter a generic on the left + * side, it is permissible to tentatively bind that generic to the right side + * type. + */ +bool Subtyping::bindGeneric(TypePackId subTp, TypePackId superTp) +{ + if (variance == Variance::Contravariant) + std::swap(superTp, subTp); + + if (!get(subTp)) + return false; + + if (TypePackId* m = mappedGenericPacks.find(subTp)) + return *m == superTp; + + mappedGenericPacks[subTp] = superTp; + + return true; +} + +template +TypeId Subtyping::makeAggregateType(const Container& container, TypeId orElse) +{ + if (container.empty()) + return orElse; + else if (container.size() == 1) + return *begin(container); + else + return arena->addType(T{std::vector(begin(container), end(container))}); +} + +void Subtyping::unexpected(TypePackId tp) +{ + iceReporter->ice(format("Unexpected type pack %s", toString(tp).c_str())); +} + } // namespace Luau diff --git a/Analysis/src/TypeInfer.cpp b/Analysis/src/TypeInfer.cpp index 00cf4cd0..569f9720 100644 --- a/Analysis/src/TypeInfer.cpp +++ b/Analysis/src/TypeInfer.cpp @@ -36,7 +36,6 @@ LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false) LUAU_FASTFLAGVARIABLE(DebugLuauSharedSelf, false) LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAGVARIABLE(LuauAllowIndexClassParameters, false) -LUAU_FASTFLAGVARIABLE(LuauFixCyclicModuleExports, false) LUAU_FASTFLAG(LuauOccursIsntAlwaysFailure) LUAU_FASTFLAGVARIABLE(LuauTinyControlFlowAnalysis, false) LUAU_FASTFLAGVARIABLE(LuauAlwaysCommitInferencesOfFunctionCalls, false) @@ -1195,16 +1194,13 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatLocal& local) scope->importedTypeBindings[name] = module->exportedTypeBindings; scope->importedModules[name] = moduleInfo->name; - if (FFlag::LuauFixCyclicModuleExports) + // Imported types of requires that transitively refer to current module have to be replaced with 'any' + for (const auto& [location, path] : requireCycles) { - // Imported types of requires that transitively refer to current module have to be replaced with 'any' - for (const auto& [location, path] : requireCycles) + if (!path.empty() && path.front() == moduleInfo->name) { - if (!path.empty() && path.front() == moduleInfo->name) - { - for (auto& [name, tf] : scope->importedTypeBindings[name]) - tf = TypeFun{{}, {}, anyType}; - } + for (auto& [name, tf] : scope->importedTypeBindings[name]) + tf = TypeFun{{}, {}, anyType}; } } } diff --git a/Analysis/src/Unifier.cpp b/Analysis/src/Unifier.cpp index db8e2008..bc8ef018 100644 --- a/Analysis/src/Unifier.cpp +++ b/Analysis/src/Unifier.cpp @@ -605,6 +605,10 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool { // TODO: there are probably cheaper ways to check if any <: T. const NormalizedType* superNorm = normalizer->normalize(superTy); + + if (!superNorm) + return reportError(location, UnificationTooComplex{}); + if (!log.get(superNorm->tops)) failure = true; } diff --git a/Ast/include/Luau/Ast.h b/Ast/include/Luau/Ast.h index f9f9ab41..7478e15d 100644 --- a/Ast/include/Luau/Ast.h +++ b/Ast/include/Luau/Ast.h @@ -272,11 +272,18 @@ class AstExprConstantString : public AstExpr public: LUAU_RTTI(AstExprConstantString) - AstExprConstantString(const Location& location, const AstArray& value); + enum QuoteStyle + { + Quoted, + Unquoted + }; + + AstExprConstantString(const Location& location, const AstArray& value, QuoteStyle quoteStyle = Quoted); void visit(AstVisitor* visitor) override; AstArray value; + QuoteStyle quoteStyle = Quoted; }; class AstExprLocal : public AstExpr diff --git a/Ast/include/Luau/Lexer.h b/Ast/include/Luau/Lexer.h index 929402b3..e1415183 100644 --- a/Ast/include/Luau/Lexer.h +++ b/Ast/include/Luau/Lexer.h @@ -204,7 +204,9 @@ private: Position position() const; + // consume() assumes current character is not a newline for performance; when that is not known, consumeAny() should be used instead. void consume(); + void consumeAny(); Lexeme readCommentBody(); diff --git a/Ast/src/Ast.cpp b/Ast/src/Ast.cpp index 3c87e36c..9b3acb7f 100644 --- a/Ast/src/Ast.cpp +++ b/Ast/src/Ast.cpp @@ -62,9 +62,10 @@ void AstExprConstantNumber::visit(AstVisitor* visitor) visitor->visit(this); } -AstExprConstantString::AstExprConstantString(const Location& location, const AstArray& value) +AstExprConstantString::AstExprConstantString(const Location& location, const AstArray& value, QuoteStyle quoteStyle) : AstExpr(ClassIndex(), location) , value(value) + , quoteStyle(quoteStyle) { } diff --git a/Ast/src/Lexer.cpp b/Ast/src/Lexer.cpp index 75b4fe30..fe32e2a1 100644 --- a/Ast/src/Lexer.cpp +++ b/Ast/src/Lexer.cpp @@ -6,6 +6,8 @@ #include +LUAU_FASTFLAGVARIABLE(LuauLexerConsumeFast, false) + namespace Luau { @@ -373,7 +375,7 @@ const Lexeme& Lexer::next(bool skipComments, bool updatePrevLocation) { // consume whitespace before the token while (isSpace(peekch())) - consume(); + consumeAny(); if (updatePrevLocation) prevLocation = lexeme.location; @@ -438,7 +440,28 @@ Position Lexer::position() const return Position(line, offset - lineOffset); } +LUAU_FORCEINLINE void Lexer::consume() +{ + if (isNewline(buffer[offset])) + { + // TODO: When the flag is removed, remove the outer condition + if (FFlag::LuauLexerConsumeFast) + { + LUAU_ASSERT(!isNewline(buffer[offset])); + } + else + { + line++; + lineOffset = offset + 1; + } + } + + offset++; +} + +LUAU_FORCEINLINE +void Lexer::consumeAny() { if (isNewline(buffer[offset])) { @@ -524,7 +547,7 @@ Lexeme Lexer::readLongString(const Position& start, int sep, Lexeme::Type ok, Le } else { - consume(); + consumeAny(); } } @@ -540,7 +563,7 @@ void Lexer::readBackslashInString() case '\r': consume(); if (peekch() == '\n') - consume(); + consumeAny(); break; case 0: @@ -549,11 +572,11 @@ void Lexer::readBackslashInString() case 'z': consume(); while (isSpace(peekch())) - consume(); + consumeAny(); break; default: - consume(); + consumeAny(); } } @@ -939,6 +962,9 @@ Lexeme Lexer::readNext() case ';': case ',': case '#': + case '?': + case '&': + case '|': { char ch = peekch(); consume(); diff --git a/Ast/src/Parser.cpp b/Ast/src/Parser.cpp index cc5d7b38..20186dfc 100644 --- a/Ast/src/Parser.cpp +++ b/Ast/src/Parser.cpp @@ -15,8 +15,6 @@ LUAU_FASTINTVARIABLE(LuauRecursionLimit, 1000) LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100) LUAU_FASTFLAGVARIABLE(LuauParseDeclareClassIndexer, false) -#define ERROR_INVALID_INTERP_DOUBLE_BRACE "Double braces are not permitted within interpolated strings. Did you mean '\\{'?" - namespace Luau { @@ -899,13 +897,13 @@ AstStat* Parser::parseDeclaration(const Location& start) expectAndConsume(':', "property type annotation"); AstType* type = parseType(); - // TODO: since AstName conains a char*, it can't contain null + // since AstName contains a char*, it can't contain null bool containsNull = chars && (strnlen(chars->data, chars->size) < chars->size); if (chars && !containsNull) props.push_back(AstDeclaredClassProp{AstName(chars->data), type, false}); else - report(begin.location, "String literal contains malformed escape sequence"); + report(begin.location, "String literal contains malformed escape sequence or \\0"); } else if (lexer.current().type == '[' && FFlag::LuauParseDeclareClassIndexer) { @@ -1328,13 +1326,13 @@ AstType* Parser::parseTableType() AstType* type = parseType(); - // TODO: since AstName conains a char*, it can't contain null + // since AstName contains a char*, it can't contain null bool containsNull = chars && (strnlen(chars->data, chars->size) < chars->size); if (chars && !containsNull) props.push_back({AstName(chars->data), begin.location, type}); else - report(begin.location, "String literal contains malformed escape sequence"); + report(begin.location, "String literal contains malformed escape sequence or \\0"); } else if (lexer.current().type == '[') { @@ -1622,7 +1620,7 @@ AstTypeOrPack Parser::parseSimpleType(bool allowPack) else if (lexer.current().type == Lexeme::BrokenString) { nextLexeme(); - return {reportTypeError(start, {}, "Malformed string")}; + return {reportTypeError(start, {}, "Malformed string; did you forget to finish it?")}; } else if (lexer.current().type == Lexeme::Name) { @@ -1741,7 +1739,8 @@ AstTypePack* Parser::parseTypePack() return allocator.alloc(Location(name.location, end), name.name); } - // No type pack annotation exists here. + // TODO: shouldParseTypePack can be removed and parseTypePack can be called unconditionally instead + LUAU_ASSERT(!"parseTypePack can't be called if shouldParseTypePack() returned false"); return nullptr; } @@ -1826,7 +1825,7 @@ std::optional Parser::checkUnaryConfusables() if (curr.type == '!') { - report(start, "Unexpected '!', did you mean 'not'?"); + report(start, "Unexpected '!'; did you mean 'not'?"); return AstExprUnary::Not; } @@ -1848,20 +1847,20 @@ std::optional Parser::checkBinaryConfusables(const BinaryOpPr if (curr.type == '&' && next.type == '&' && curr.location.end == next.location.begin && binaryPriority[AstExprBinary::And].left > limit) { nextLexeme(); - report(Location(start, next.location), "Unexpected '&&', did you mean 'and'?"); + report(Location(start, next.location), "Unexpected '&&'; did you mean 'and'?"); return AstExprBinary::And; } else if (curr.type == '|' && next.type == '|' && curr.location.end == next.location.begin && binaryPriority[AstExprBinary::Or].left > limit) { nextLexeme(); - report(Location(start, next.location), "Unexpected '||', did you mean 'or'?"); + report(Location(start, next.location), "Unexpected '||'; did you mean 'or'?"); return AstExprBinary::Or; } else if (curr.type == '!' && next.type == '=' && curr.location.end == next.location.begin && binaryPriority[AstExprBinary::CompareNe].left > limit) { nextLexeme(); - report(Location(start, next.location), "Unexpected '!=', did you mean '~='?"); + report(Location(start, next.location), "Unexpected '!='; did you mean '~='?"); return AstExprBinary::CompareNe; } @@ -2169,12 +2168,12 @@ AstExpr* Parser::parseSimpleExpr() else if (lexer.current().type == Lexeme::BrokenString) { nextLexeme(); - return reportExprError(start, {}, "Malformed string"); + return reportExprError(start, {}, "Malformed string; did you forget to finish it?"); } else if (lexer.current().type == Lexeme::BrokenInterpDoubleBrace) { nextLexeme(); - return reportExprError(start, {}, ERROR_INVALID_INTERP_DOUBLE_BRACE); + return reportExprError(start, {}, "Double braces are not permitted within interpolated strings; did you mean '\\{'?"); } else if (lexer.current().type == Lexeme::Dot3) { @@ -2312,7 +2311,7 @@ AstExpr* Parser::parseTableConstructor() nameString.data = const_cast(name.name.value); nameString.size = strlen(name.name.value); - AstExpr* key = allocator.alloc(name.location, nameString); + AstExpr* key = allocator.alloc(name.location, nameString, AstExprConstantString::Unquoted); AstExpr* value = parseExpr(); if (AstExprFunction* func = value->as()) @@ -2661,7 +2660,7 @@ AstExpr* Parser::parseInterpString() { errorWhileChecking = true; nextLexeme(); - expressions.push_back(reportExprError(endLocation, {}, "Malformed interpolated string, did you forget to add a '`'?")); + expressions.push_back(reportExprError(endLocation, {}, "Malformed interpolated string; did you forget to add a '`'?")); break; } default: @@ -2681,10 +2680,10 @@ AstExpr* Parser::parseInterpString() break; case Lexeme::BrokenInterpDoubleBrace: nextLexeme(); - return reportExprError(endLocation, {}, ERROR_INVALID_INTERP_DOUBLE_BRACE); + return reportExprError(endLocation, {}, "Double braces are not permitted within interpolated strings; did you mean '\\{'?"); case Lexeme::BrokenString: nextLexeme(); - return reportExprError(endLocation, {}, "Malformed interpolated string, did you forget to add a '}'?"); + return reportExprError(endLocation, {}, "Malformed interpolated string; did you forget to add a '}'?"); default: return reportExprError(endLocation, {}, "Malformed interpolated string, got %s", lexer.current().toString().c_str()); } diff --git a/CLI/Repl.cpp b/CLI/Repl.cpp index b2a523d9..19083bfc 100644 --- a/CLI/Repl.cpp +++ b/CLI/Repl.cpp @@ -757,14 +757,6 @@ int replMain(int argc, char** argv) } #endif -#if !LUA_CUSTOM_EXECUTION - if (codegen) - { - fprintf(stderr, "To run with --codegen, Luau has to be built with LUA_CUSTOM_EXECUTION enabled\n"); - return 1; - } -#endif - if (codegenPerf) { #if __linux__ @@ -784,10 +776,7 @@ int replMain(int argc, char** argv) } if (codegen && !Luau::CodeGen::isSupported()) - { - fprintf(stderr, "Cannot enable --codegen, native code generation is not supported in current configuration\n"); - return 1; - } + fprintf(stderr, "Warning: Native code generation is not supported in current configuration\n"); const std::vector files = getSourceFiles(argc, argv); diff --git a/CMakeLists.txt b/CMakeLists.txt index f0f0497d..15a1b8a5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,7 +12,6 @@ option(LUAU_BUILD_WEB "Build Web module" OFF) option(LUAU_WERROR "Warnings as errors" OFF) option(LUAU_STATIC_CRT "Link with the static CRT (/MT)" OFF) option(LUAU_EXTERN_C "Use extern C for all APIs" OFF) -option(LUAU_NATIVE "Enable support for native code generation" OFF) cmake_policy(SET CMP0054 NEW) cmake_policy(SET CMP0091 NEW) @@ -146,13 +145,7 @@ if(LUAU_EXTERN_C) target_compile_definitions(Luau.VM PUBLIC LUA_USE_LONGJMP=1) target_compile_definitions(Luau.VM PUBLIC LUA_API=extern\"C\") target_compile_definitions(Luau.Compiler PUBLIC LUACODE_API=extern\"C\") -endif() - -if(LUAU_NATIVE) - target_compile_definitions(Luau.VM PUBLIC LUA_CUSTOM_EXECUTION=1) - if(LUAU_EXTERN_C) - target_compile_definitions(Luau.CodeGen PUBLIC LUACODEGEN_API=extern\"C\") - endif() + target_compile_definitions(Luau.CodeGen PUBLIC LUACODEGEN_API=extern\"C\") endif() if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" AND MSVC_VERSION GREATER_EQUAL 1924) diff --git a/CodeGen/include/Luau/CodeGen.h b/CodeGen/include/Luau/CodeGen.h index c11f9628..85f19d01 100644 --- a/CodeGen/include/Luau/CodeGen.h +++ b/CodeGen/include/Luau/CodeGen.h @@ -18,6 +18,16 @@ enum CodeGenFlags CodeGen_OnlyNativeModules = 1 << 0, }; +enum class CodeGenCompilationResult +{ + Success, // Successfully generated code for at least one function + NothingToCompile, // There were no new functions to compile + + CodeGenNotInitialized, // Native codegen system is not initialized + CodeGenFailed, // Native codegen failed due to an internal compiler error + AllocationFailed, // Native codegen failed due to an allocation error +}; + struct CompilationStats { size_t bytecodeSizeBytes = 0; @@ -36,7 +46,7 @@ void create(lua_State* L, AllocationCallback* allocationCallback, void* allocati void create(lua_State* L); // Builds target function and all inner functions -void compile(lua_State* L, int idx, unsigned int flags = 0, CompilationStats* stats = nullptr); +CodeGenCompilationResult compile(lua_State* L, int idx, unsigned int flags = 0, CompilationStats* stats = nullptr); using AnnotatorFn = void (*)(void* context, std::string& result, int fid, int instpos); diff --git a/CodeGen/include/Luau/IrData.h b/CodeGen/include/Luau/IrData.h index 5ac5b2ac..e8e56d19 100644 --- a/CodeGen/include/Luau/IrData.h +++ b/CodeGen/include/Luau/IrData.h @@ -245,8 +245,8 @@ enum class IrCmd : uint8_t STRING_LEN, // Allocate new table - // A: int (array element count) - // B: int (node element count) + // A: unsigned int (array element count) + // B: unsigned int (node element count) NEW_TABLE, // Duplicate a table @@ -359,12 +359,6 @@ enum class IrCmd : uint8_t // C: tag/undef (tag of the value that was written) SET_UPVALUE, - // Convert TValues into numbers for a numerical for loop - // A: Rn (start) - // B: Rn (end) - // C: Rn (step) - PREPARE_FORN, - // Guards and checks (these instructions are not block terminators even though they jump to fallback) // Guard against tag mismatch @@ -463,6 +457,7 @@ enum class IrCmd : uint8_t // C: Rn (source start) // D: int (count or -1 to assign values up to stack top) // E: unsigned int (table index to start from) + // F: undef/unsigned int (target table known size) SETLIST, // Call specified function diff --git a/CodeGen/include/Luau/IrRegAllocX64.h b/CodeGen/include/Luau/IrRegAllocX64.h index 95930811..665b5229 100644 --- a/CodeGen/include/Luau/IrRegAllocX64.h +++ b/CodeGen/include/Luau/IrRegAllocX64.h @@ -77,6 +77,7 @@ struct IrRegAllocX64 std::array gprInstUsers; std::array freeXmmMap; std::array xmmInstUsers; + uint8_t usableXmmRegCount = 0; std::bitset<256> usedSpillSlots; unsigned maxUsedSlot = 0; diff --git a/CodeGen/include/Luau/UnwindBuilder.h b/CodeGen/include/Luau/UnwindBuilder.h index 8a44629f..1ba377ba 100644 --- a/CodeGen/include/Luau/UnwindBuilder.h +++ b/CodeGen/include/Luau/UnwindBuilder.h @@ -5,6 +5,7 @@ #include "Luau/RegisterX64.h" #include +#include #include #include @@ -48,7 +49,8 @@ public: // mov rbp, rsp // push reg in the order specified in regs // sub rsp, stackSize - virtual void prologueX64(uint32_t prologueSize, uint32_t stackSize, bool setupFrame, std::initializer_list regs) = 0; + virtual void prologueX64(uint32_t prologueSize, uint32_t stackSize, bool setupFrame, std::initializer_list gpr, + const std::vector& simd) = 0; virtual size_t getSize() const = 0; virtual size_t getFunctionCount() const = 0; diff --git a/CodeGen/include/Luau/UnwindBuilderDwarf2.h b/CodeGen/include/Luau/UnwindBuilderDwarf2.h index 66749bfc..741aaed2 100644 --- a/CodeGen/include/Luau/UnwindBuilderDwarf2.h +++ b/CodeGen/include/Luau/UnwindBuilderDwarf2.h @@ -30,7 +30,8 @@ public: void finishInfo() override; void prologueA64(uint32_t prologueSize, uint32_t stackSize, std::initializer_list regs) override; - void prologueX64(uint32_t prologueSize, uint32_t stackSize, bool setupFrame, std::initializer_list regs) override; + void prologueX64(uint32_t prologueSize, uint32_t stackSize, bool setupFrame, std::initializer_list gpr, + const std::vector& simd) override; size_t getSize() const override; size_t getFunctionCount() const override; diff --git a/CodeGen/include/Luau/UnwindBuilderWin.h b/CodeGen/include/Luau/UnwindBuilderWin.h index 5afed693..3a7e1b5a 100644 --- a/CodeGen/include/Luau/UnwindBuilderWin.h +++ b/CodeGen/include/Luau/UnwindBuilderWin.h @@ -50,7 +50,8 @@ public: void finishInfo() override; void prologueA64(uint32_t prologueSize, uint32_t stackSize, std::initializer_list regs) override; - void prologueX64(uint32_t prologueSize, uint32_t stackSize, bool setupFrame, std::initializer_list regs) override; + void prologueX64(uint32_t prologueSize, uint32_t stackSize, bool setupFrame, std::initializer_list gpr, + const std::vector& simd) override; size_t getSize() const override; size_t getFunctionCount() const override; diff --git a/CodeGen/src/CodeGen.cpp b/CodeGen/src/CodeGen.cpp index 10c3dc79..9d117b1d 100644 --- a/CodeGen/src/CodeGen.cpp +++ b/CodeGen/src/CodeGen.cpp @@ -162,9 +162,6 @@ unsigned int getCpuFeaturesA64() bool isSupported() { - if (!LUA_CUSTOM_EXECUTION) - return false; - if (LUA_EXTRA_SIZE != 1) return false; @@ -247,23 +244,33 @@ void create(lua_State* L) create(L, nullptr, nullptr); } -void compile(lua_State* L, int idx, unsigned int flags, CompilationStats* stats) +CodeGenCompilationResult compile(lua_State* L, int idx, unsigned int flags, CompilationStats* stats) { LUAU_ASSERT(lua_isLfunction(L, idx)); const TValue* func = luaA_toobject(L, idx); + Proto* root = clvalue(func)->l.p; + if ((flags & CodeGen_OnlyNativeModules) != 0 && (root->flags & LPF_NATIVE_MODULE) == 0) + return CodeGenCompilationResult::NothingToCompile; + // If initialization has failed, do not compile any functions NativeState* data = getNativeState(L); if (!data) - return; - - Proto* root = clvalue(func)->l.p; - if ((flags & CodeGen_OnlyNativeModules) != 0 && (root->flags & LPF_NATIVE_MODULE) == 0) - return; + return CodeGenCompilationResult::CodeGenNotInitialized; std::vector protos; gatherFunctions(protos, root); + // Skip protos that have been compiled during previous invocations of CodeGen::compile + protos.erase(std::remove_if(protos.begin(), protos.end(), + [](Proto* p) { + return p == nullptr || p->execdata != nullptr; + }), + protos.end()); + + if (protos.empty()) + return CodeGenCompilationResult::NothingToCompile; + #if defined(__aarch64__) static unsigned int cpuFeatures = getCpuFeaturesA64(); A64::AssemblyBuilderA64 build(/* logText= */ false, cpuFeatures); @@ -281,11 +288,9 @@ void compile(lua_State* L, int idx, unsigned int flags, CompilationStats* stats) std::vector results; results.reserve(protos.size()); - // Skip protos that have been compiled during previous invocations of CodeGen::compile for (Proto* p : protos) - if (p && p->execdata == nullptr) - if (std::optional np = createNativeFunction(build, helpers, p)) - results.push_back(*np); + if (std::optional np = createNativeFunction(build, helpers, p)) + results.push_back(*np); // Very large modules might result in overflowing a jump offset; in this case we currently abandon the entire module if (!build.finalize()) @@ -293,12 +298,12 @@ void compile(lua_State* L, int idx, unsigned int flags, CompilationStats* stats) for (NativeProto result : results) destroyExecData(result.execdata); - return; + return CodeGenCompilationResult::CodeGenFailed; } // If no functions were assembled, we don't need to allocate/copy executable pages for helpers if (results.empty()) - return; + return CodeGenCompilationResult::CodeGenFailed; uint8_t* nativeData = nullptr; size_t sizeNativeData = 0; @@ -309,7 +314,7 @@ void compile(lua_State* L, int idx, unsigned int flags, CompilationStats* stats) for (NativeProto result : results) destroyExecData(result.execdata); - return; + return CodeGenCompilationResult::AllocationFailed; } if (gPerfLogFn && results.size() > 0) @@ -348,6 +353,8 @@ void compile(lua_State* L, int idx, unsigned int flags, CompilationStats* stats) stats->nativeCodeSizeBytes += build.code.size(); stats->nativeDataSizeBytes += build.data.size(); } + + return CodeGenCompilationResult::Success; } void setPerfLog(void* context, PerfLogFn logFn) diff --git a/CodeGen/src/CodeGenX64.cpp b/CodeGen/src/CodeGenX64.cpp index ef655a24..a8cf2e73 100644 --- a/CodeGen/src/CodeGenX64.cpp +++ b/CodeGen/src/CodeGenX64.cpp @@ -16,10 +16,24 @@ * | rdx home space | (unused) * | rcx home space | (unused) * | return address | - * | ... saved non-volatile registers ... <-- rsp + kStackSize + kLocalsSize - * | unused | for 16 byte alignment of the stack + * | ... saved non-volatile registers ... <-- rsp + kStackSizeFull + * | alignment | + * | xmm9 non-vol | + * | xmm9 cont. | + * | xmm8 non-vol | + * | xmm8 cont. | + * | xmm7 non-vol | + * | xmm7 cont. | + * | xmm6 non-vol | + * | xmm6 cont. | + * | spill slot 5 | + * | spill slot 4 | + * | spill slot 3 | + * | spill slot 2 | + * | spill slot 1 | <-- rsp + kStackOffsetToSpillSlots + * | sTemporarySlot | * | sCode | - * | sClosure | <-- rsp + kStackSize + * | sClosure | <-- rsp + kStackOffsetToLocals * | argument 6 | <-- rsp + 40 * | argument 5 | <-- rsp + 32 * | r9 home space | @@ -81,24 +95,43 @@ static EntryLocations buildEntryFunction(AssemblyBuilderX64& build, UnwindBuilde build.push(rdi); build.push(rsi); - // On Windows, rbp is available as a general-purpose non-volatile register; we currently don't use it, but we need to push an even number - // of registers for stack alignment... + // On Windows, rbp is available as a general-purpose non-volatile register and this might be freed up build.push(rbp); - - // TODO: once we start using non-volatile SIMD registers on Windows, we will save those here } - // Allocate stack space (reg home area + local data) - build.sub(rsp, kStackSize + kLocalsSize); + // Allocate stack space + uint8_t usableXmmRegCount = getXmmRegisterCount(build.abi); + unsigned xmmStorageSize = getNonVolXmmStorageSize(build.abi, usableXmmRegCount); + unsigned fullStackSize = getFullStackSize(build.abi, usableXmmRegCount); + + build.sub(rsp, fullStackSize); + + OperandX64 xmmStorageOffset = rsp + (fullStackSize - (kStackAlign + xmmStorageSize)); + + // On Windows, we have to save non-volatile xmm registers + std::vector savedXmmRegs; + + if (build.abi == ABIX64::Windows) + { + if (usableXmmRegCount > kWindowsFirstNonVolXmmReg) + savedXmmRegs.reserve(usableXmmRegCount - kWindowsFirstNonVolXmmReg); + + for (uint8_t i = kWindowsFirstNonVolXmmReg, offset = 0; i < usableXmmRegCount; i++, offset += 16) + { + RegisterX64 xmmReg = RegisterX64{SizeX64::xmmword, i}; + build.vmovaps(xmmword[xmmStorageOffset + offset], xmmReg); + savedXmmRegs.push_back(xmmReg); + } + } locations.prologueEnd = build.setLabel(); uint32_t prologueSize = build.getLabelOffset(locations.prologueEnd) - build.getLabelOffset(locations.start); if (build.abi == ABIX64::SystemV) - unwind.prologueX64(prologueSize, kStackSize + kLocalsSize, /* setupFrame= */ true, {rbx, r12, r13, r14, r15}); + unwind.prologueX64(prologueSize, fullStackSize, /* setupFrame= */ true, {rbx, r12, r13, r14, r15}, {}); else if (build.abi == ABIX64::Windows) - unwind.prologueX64(prologueSize, kStackSize + kLocalsSize, /* setupFrame= */ false, {rbx, r12, r13, r14, r15, rdi, rsi, rbp}); + unwind.prologueX64(prologueSize, fullStackSize, /* setupFrame= */ false, {rbx, r12, r13, r14, r15, rdi, rsi, rbp}, savedXmmRegs); // Setup native execution environment build.mov(rState, rArg1); @@ -118,8 +151,15 @@ static EntryLocations buildEntryFunction(AssemblyBuilderX64& build, UnwindBuilde // Even though we jumped away, we will return here in the end locations.epilogueStart = build.setLabel(); - // Cleanup and exit - build.add(rsp, kStackSize + kLocalsSize); + // Epilogue and exit + if (build.abi == ABIX64::Windows) + { + // xmm registers are restored before the official epilogue that has to start with 'add rsp/lea rsp' + for (uint8_t i = kWindowsFirstNonVolXmmReg, offset = 0; i < usableXmmRegCount; i++, offset += 16) + build.vmovaps(RegisterX64{SizeX64::xmmword, i}, xmmword[xmmStorageOffset + offset]); + } + + build.add(rsp, fullStackSize); if (build.abi == ABIX64::Windows) { diff --git a/CodeGen/src/EmitCommonX64.cpp b/CodeGen/src/EmitCommonX64.cpp index 43568035..c4da5467 100644 --- a/CodeGen/src/EmitCommonX64.cpp +++ b/CodeGen/src/EmitCommonX64.cpp @@ -123,16 +123,6 @@ void callLengthHelper(IrRegAllocX64& regs, AssemblyBuilderX64& build, int ra, in emitUpdateBase(build); } -void callPrepareForN(IrRegAllocX64& regs, AssemblyBuilderX64& build, int limit, int step, int init) -{ - IrCallWrapperX64 callWrap(regs, build); - callWrap.addArgument(SizeX64::qword, rState); - callWrap.addArgument(SizeX64::qword, luauRegAddress(limit)); - callWrap.addArgument(SizeX64::qword, luauRegAddress(step)); - callWrap.addArgument(SizeX64::qword, luauRegAddress(init)); - callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaV_prepareFORN)]); -} - void callGetTable(IrRegAllocX64& regs, AssemblyBuilderX64& build, int rb, OperandX64 c, int ra) { IrCallWrapperX64 callWrap(regs, build); diff --git a/CodeGen/src/EmitCommonX64.h b/CodeGen/src/EmitCommonX64.h index 782f2084..dd9b082b 100644 --- a/CodeGen/src/EmitCommonX64.h +++ b/CodeGen/src/EmitCommonX64.h @@ -42,16 +42,55 @@ constexpr RegisterX64 rBase = r14; // StkId base constexpr RegisterX64 rNativeContext = r13; // NativeContext* context constexpr RegisterX64 rConstants = r12; // TValue* k -// Native code is as stackless as the interpreter, so we can place some data on the stack once and have it accessible at any point -// See CodeGenX64.cpp for layout -constexpr unsigned kStackSize = 32 + 16; // 4 home locations for registers, 16 bytes for additional function call arguments -constexpr unsigned kSpillSlots = 4; // locations for register allocator to spill data into -constexpr unsigned kLocalsSize = 24 + 8 * kSpillSlots; // 3 extra slots for our custom locals (also aligns the stack to 16 byte boundary) +constexpr unsigned kExtraLocals = 3; // Number of 8 byte slots available for specialized local variables specified below +constexpr unsigned kSpillSlots = 5; // Number of 8 byte slots available for register allocator to spill data into +static_assert((kExtraLocals + kSpillSlots) * 8 % 16 == 0, "locals have to preserve 16 byte alignment"); -constexpr OperandX64 sClosure = qword[rsp + kStackSize + 0]; // Closure* cl -constexpr OperandX64 sCode = qword[rsp + kStackSize + 8]; // Instruction* code -constexpr OperandX64 sTemporarySlot = addr[rsp + kStackSize + 16]; -constexpr OperandX64 sSpillArea = addr[rsp + kStackSize + 24]; +constexpr uint8_t kWindowsFirstNonVolXmmReg = 6; + +constexpr uint8_t kWindowsUsableXmmRegs = 10; // Some xmm regs are non-volatile, we have to balance how many we want to use/preserve +constexpr uint8_t kSystemVUsableXmmRegs = 16; // All xmm regs are volatile + +inline uint8_t getXmmRegisterCount(ABIX64 abi) +{ + return abi == ABIX64::SystemV ? kSystemVUsableXmmRegs : kWindowsUsableXmmRegs; +} + +// Native code is as stackless as the interpreter, so we can place some data on the stack once and have it accessible at any point +// Stack is separated into sections for different data. See CodeGenX64.cpp for layout overview +constexpr unsigned kStackAlign = 8; // Bytes we need to align the stack for non-vol xmm register storage +constexpr unsigned kStackLocalStorage = 8 * kExtraLocals; +constexpr unsigned kStackSpillStorage = 8 * kSpillSlots; +constexpr unsigned kStackExtraArgumentStorage = 2 * 8; // Bytes for 5th and 6th function call arguments used under Windows ABI +constexpr unsigned kStackRegHomeStorage = 4 * 8; // Register 'home' locations that can be used by callees under Windows ABI + +inline unsigned getNonVolXmmStorageSize(ABIX64 abi, uint8_t xmmRegCount) +{ + if (abi == ABIX64::SystemV) + return 0; + + // First 6 are volatile + if (xmmRegCount <= kWindowsFirstNonVolXmmReg) + return 0; + + LUAU_ASSERT(xmmRegCount <= 16); + return (xmmRegCount - kWindowsFirstNonVolXmmReg) * 16; +} + +// Useful offsets to specific parts +constexpr unsigned kStackOffsetToLocals = kStackExtraArgumentStorage + kStackRegHomeStorage; +constexpr unsigned kStackOffsetToSpillSlots = kStackOffsetToLocals + kStackLocalStorage; + +inline unsigned getFullStackSize(ABIX64 abi, uint8_t xmmRegCount) +{ + return kStackOffsetToSpillSlots + kStackSpillStorage + getNonVolXmmStorageSize(abi, xmmRegCount) + kStackAlign; +} + +constexpr OperandX64 sClosure = qword[rsp + kStackOffsetToLocals + 0]; // Closure* cl +constexpr OperandX64 sCode = qword[rsp + kStackOffsetToLocals + 8]; // Instruction* code +constexpr OperandX64 sTemporarySlot = addr[rsp + kStackOffsetToLocals + 16]; + +constexpr OperandX64 sSpillArea = addr[rsp + kStackOffsetToSpillSlots]; inline OperandX64 luauReg(int ri) { @@ -161,7 +200,6 @@ void convertNumberToIndexOrJump(AssemblyBuilderX64& build, RegisterX64 tmp, Regi void callArithHelper(IrRegAllocX64& regs, AssemblyBuilderX64& build, int ra, int rb, OperandX64 c, TMS tm); void callLengthHelper(IrRegAllocX64& regs, AssemblyBuilderX64& build, int ra, int rb); -void callPrepareForN(IrRegAllocX64& regs, AssemblyBuilderX64& build, int limit, int step, int init); void callGetTable(IrRegAllocX64& regs, AssemblyBuilderX64& build, int rb, OperandX64 c, int ra); void callSetTable(IrRegAllocX64& regs, AssemblyBuilderX64& build, int rb, OperandX64 c, int ra); void checkObjectBarrierConditions(AssemblyBuilderX64& build, RegisterX64 tmp, RegisterX64 object, int ra, int ratag, Label& skip); diff --git a/CodeGen/src/EmitInstructionX64.cpp b/CodeGen/src/EmitInstructionX64.cpp index ea511958..bccdc8f0 100644 --- a/CodeGen/src/EmitInstructionX64.cpp +++ b/CodeGen/src/EmitInstructionX64.cpp @@ -251,7 +251,7 @@ void emitInstReturn(AssemblyBuilderX64& build, ModuleHelpers& helpers, int ra, i } } -void emitInstSetList(IrRegAllocX64& regs, AssemblyBuilderX64& build, int ra, int rb, int count, uint32_t index) +void emitInstSetList(IrRegAllocX64& regs, AssemblyBuilderX64& build, int ra, int rb, int count, uint32_t index, int knownSize) { // TODO: This should use IrCallWrapperX64 RegisterX64 rArg1 = (build.abi == ABIX64::Windows) ? rcx : rdi; @@ -285,25 +285,28 @@ void emitInstSetList(IrRegAllocX64& regs, AssemblyBuilderX64& build, int ra, int build.add(last, index - 1); } - Label skipResize; - RegisterX64 table = regs.takeReg(rax, kInvalidInstIdx); build.mov(table, luauRegValue(ra)); - // Resize if h->sizearray < last - build.cmp(dword[table + offsetof(Table, sizearray)], last); - build.jcc(ConditionX64::NotBelow, skipResize); + if (count == LUA_MULTRET || knownSize < 0 || knownSize < int(index + count - 1)) + { + Label skipResize; - // Argument setup reordered to avoid conflicts - LUAU_ASSERT(rArg3 != table); - build.mov(dwordReg(rArg3), last); - build.mov(rArg2, table); - build.mov(rArg1, rState); - build.call(qword[rNativeContext + offsetof(NativeContext, luaH_resizearray)]); - build.mov(table, luauRegValue(ra)); // Reload cloberred register value + // Resize if h->sizearray < last + build.cmp(dword[table + offsetof(Table, sizearray)], last); + build.jcc(ConditionX64::NotBelow, skipResize); - build.setLabel(skipResize); + // Argument setup reordered to avoid conflicts + LUAU_ASSERT(rArg3 != table); + build.mov(dwordReg(rArg3), last); + build.mov(rArg2, table); + build.mov(rArg1, rState); + build.call(qword[rNativeContext + offsetof(NativeContext, luaH_resizearray)]); + build.mov(table, luauRegValue(ra)); // Reload clobbered register value + + build.setLabel(skipResize); + } RegisterX64 arrayDst = rdx; RegisterX64 offset = rcx; diff --git a/CodeGen/src/EmitInstructionX64.h b/CodeGen/src/EmitInstructionX64.h index b248b7e8..59fd8e41 100644 --- a/CodeGen/src/EmitInstructionX64.h +++ b/CodeGen/src/EmitInstructionX64.h @@ -19,7 +19,7 @@ struct IrRegAllocX64; void emitInstCall(AssemblyBuilderX64& build, ModuleHelpers& helpers, int ra, int nparams, int nresults); void emitInstReturn(AssemblyBuilderX64& build, ModuleHelpers& helpers, int ra, int actualResults, bool functionVariadic); -void emitInstSetList(IrRegAllocX64& regs, AssemblyBuilderX64& build, int ra, int rb, int count, uint32_t index); +void emitInstSetList(IrRegAllocX64& regs, AssemblyBuilderX64& build, int ra, int rb, int count, uint32_t index, int knownSize); void emitInstForGLoop(AssemblyBuilderX64& build, int ra, int aux, Label& loopRepeat); } // namespace X64 diff --git a/CodeGen/src/IrAnalysis.cpp b/CodeGen/src/IrAnalysis.cpp index b29927bb..eb4630dd 100644 --- a/CodeGen/src/IrAnalysis.cpp +++ b/CodeGen/src/IrAnalysis.cpp @@ -253,15 +253,6 @@ static void visitVmRegDefsUses(T& visitor, IrFunction& function, const IrBlock& case IrCmd::SET_UPVALUE: visitor.use(inst.b); break; - case IrCmd::PREPARE_FORN: - visitor.use(inst.a); - visitor.use(inst.b); - visitor.use(inst.c); - - visitor.def(inst.a); - visitor.def(inst.b); - visitor.def(inst.c); - break; case IrCmd::INTERRUPT: break; case IrCmd::BARRIER_OBJ: diff --git a/CodeGen/src/IrBuilder.cpp b/CodeGen/src/IrBuilder.cpp index d34dfb57..aebc0ba7 100644 --- a/CodeGen/src/IrBuilder.cpp +++ b/CodeGen/src/IrBuilder.cpp @@ -373,7 +373,7 @@ void IrBuilder::translateInst(LuauOpcode op, const Instruction* pc, int i) translateInstDupTable(*this, pc, i); break; case LOP_SETLIST: - inst(IrCmd::SETLIST, constUint(i), vmReg(LUAU_INSN_A(*pc)), vmReg(LUAU_INSN_B(*pc)), constInt(LUAU_INSN_C(*pc) - 1), constUint(pc[1])); + inst(IrCmd::SETLIST, constUint(i), vmReg(LUAU_INSN_A(*pc)), vmReg(LUAU_INSN_B(*pc)), constInt(LUAU_INSN_C(*pc) - 1), constUint(pc[1]), undef()); break; case LOP_GETUPVAL: translateInstGetUpval(*this, pc, i); diff --git a/CodeGen/src/IrCallWrapperX64.cpp b/CodeGen/src/IrCallWrapperX64.cpp index 816e0184..15fabf09 100644 --- a/CodeGen/src/IrCallWrapperX64.cpp +++ b/CodeGen/src/IrCallWrapperX64.cpp @@ -13,7 +13,7 @@ namespace CodeGen namespace X64 { -static const std::array kWindowsGprOrder = {rcx, rdx, r8, r9, addr[rsp + 32], addr[rsp + 40]}; +static const std::array kWindowsGprOrder = {rcx, rdx, r8, r9, addr[rsp + kStackRegHomeStorage], addr[rsp + kStackRegHomeStorage + 8]}; static const std::array kSystemvGprOrder = {rdi, rsi, rdx, rcx, r8, r9}; static const std::array kXmmOrder = {xmm0, xmm1, xmm2, xmm3}; // Common order for first 4 fp arguments on Windows/SystemV diff --git a/CodeGen/src/IrDump.cpp b/CodeGen/src/IrDump.cpp index 50d5012e..67f77b64 100644 --- a/CodeGen/src/IrDump.cpp +++ b/CodeGen/src/IrDump.cpp @@ -213,8 +213,6 @@ const char* getCmdName(IrCmd cmd) return "GET_UPVALUE"; case IrCmd::SET_UPVALUE: return "SET_UPVALUE"; - case IrCmd::PREPARE_FORN: - return "PREPARE_FORN"; case IrCmd::CHECK_TAG: return "CHECK_TAG"; case IrCmd::CHECK_TRUTHY: diff --git a/CodeGen/src/IrLoweringA64.cpp b/CodeGen/src/IrLoweringA64.cpp index 03006e30..d944a766 100644 --- a/CodeGen/src/IrLoweringA64.cpp +++ b/CodeGen/src/IrLoweringA64.cpp @@ -1080,16 +1080,6 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) } break; } - case IrCmd::PREPARE_FORN: - regs.spill(build, index); - build.mov(x0, rState); - build.add(x1, rBase, uint16_t(vmRegOp(inst.a) * sizeof(TValue))); - build.add(x2, rBase, uint16_t(vmRegOp(inst.b) * sizeof(TValue))); - build.add(x3, rBase, uint16_t(vmRegOp(inst.c) * sizeof(TValue))); - build.ldr(x4, mem(rNativeContext, offsetof(NativeContext, luaV_prepareFORN))); - build.blr(x4); - // note: no emitUpdateBase necessary because prepareFORN does not reallocate stack - break; case IrCmd::CHECK_TAG: { Label fresh; // used when guard aborts execution or jumps to a VM exit diff --git a/CodeGen/src/IrLoweringX64.cpp b/CodeGen/src/IrLoweringX64.cpp index ad18b849..2a436d54 100644 --- a/CodeGen/src/IrLoweringX64.cpp +++ b/CodeGen/src/IrLoweringX64.cpp @@ -622,7 +622,10 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) LUAU_ASSERT(inst.b.kind == IrOpKind::Inst || inst.b.kind == IrOpKind::Constant); OperandX64 opb = inst.b.kind == IrOpKind::Inst ? regOp(inst.b) : OperandX64(tagOp(inst.b)); - build.cmp(memRegTagOp(inst.a), opb); + if (inst.a.kind == IrOpKind::Constant) + build.cmp(opb, tagOp(inst.a)); + else + build.cmp(memRegTagOp(inst.a), opb); if (isFallthroughBlock(blockOp(inst.d), next)) { @@ -997,9 +1000,6 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) callBarrierObject(regs, build, tmp2.release(), {}, vmRegOp(inst.b), inst.c.kind == IrOpKind::Undef ? -1 : tagOp(inst.c)); break; } - case IrCmd::PREPARE_FORN: - callPrepareForN(regs, build, vmRegOp(inst.a), vmRegOp(inst.b), vmRegOp(inst.c)); - break; case IrCmd::CHECK_TAG: build.cmp(memRegTagOp(inst.a), tagOp(inst.b)); jumpOrAbortOnUndef(ConditionX64::NotEqual, inst.c, next); @@ -1205,7 +1205,8 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) // Fallbacks to non-IR instruction implementations case IrCmd::SETLIST: regs.assertAllFree(); - emitInstSetList(regs, build, vmRegOp(inst.b), vmRegOp(inst.c), intOp(inst.d), uintOp(inst.e)); + emitInstSetList( + regs, build, vmRegOp(inst.b), vmRegOp(inst.c), intOp(inst.d), uintOp(inst.e), inst.f.kind == IrOpKind::Undef ? -1 : int(uintOp(inst.f))); break; case IrCmd::CALL: regs.assertAllFree(); diff --git a/CodeGen/src/IrRegAllocX64.cpp b/CodeGen/src/IrRegAllocX64.cpp index 607c975f..091def39 100644 --- a/CodeGen/src/IrRegAllocX64.cpp +++ b/CodeGen/src/IrRegAllocX64.cpp @@ -17,6 +17,7 @@ static const RegisterX64 kGprAllocOrder[] = {rax, rdx, rcx, rbx, rsi, rdi, r8, r IrRegAllocX64::IrRegAllocX64(AssemblyBuilderX64& build, IrFunction& function) : build(build) , function(function) + , usableXmmRegCount(getXmmRegisterCount(build.abi)) { freeGprMap.fill(true); gprInstUsers.fill(kInvalidInstIdx); @@ -28,7 +29,7 @@ RegisterX64 IrRegAllocX64::allocReg(SizeX64 size, uint32_t instIdx) { if (size == SizeX64::xmmword) { - for (size_t i = 0; i < freeXmmMap.size(); ++i) + for (size_t i = 0; i < usableXmmRegCount; ++i) { if (freeXmmMap[i]) { diff --git a/CodeGen/src/IrTranslation.cpp b/CodeGen/src/IrTranslation.cpp index 38922131..5a92132f 100644 --- a/CodeGen/src/IrTranslation.cpp +++ b/CodeGen/src/IrTranslation.cpp @@ -609,22 +609,19 @@ void translateInstForNPrep(IrBuilder& build, const Instruction* pc, int pcpos) IrOp loopStart = build.blockAtInst(pcpos + getOpLength(LuauOpcode(LUAU_INSN_OP(*pc)))); IrOp loopExit = build.blockAtInst(getJumpTarget(*pc, pcpos)); - IrOp fallback = build.block(IrBlockKind::Fallback); - IrOp nextStep = build.block(IrBlockKind::Internal); IrOp direct = build.block(IrBlockKind::Internal); IrOp reverse = build.block(IrBlockKind::Internal); + // When loop parameters are not numbers, VM tries to perform type coercion from string and raises an exception if that fails + // Performing that fallback in native code increases code size and complicates CFG, obscuring the values when they are constant + // To avoid that overhead for an extreemely rare case (that doesn't even typecheck), we exit to VM to handle it IrOp tagLimit = build.inst(IrCmd::LOAD_TAG, build.vmReg(ra + 0)); - build.inst(IrCmd::CHECK_TAG, tagLimit, build.constTag(LUA_TNUMBER), fallback); + build.inst(IrCmd::CHECK_TAG, tagLimit, build.constTag(LUA_TNUMBER), build.vmExit(pcpos)); IrOp tagStep = build.inst(IrCmd::LOAD_TAG, build.vmReg(ra + 1)); - build.inst(IrCmd::CHECK_TAG, tagStep, build.constTag(LUA_TNUMBER), fallback); + build.inst(IrCmd::CHECK_TAG, tagStep, build.constTag(LUA_TNUMBER), build.vmExit(pcpos)); IrOp tagIdx = build.inst(IrCmd::LOAD_TAG, build.vmReg(ra + 2)); - build.inst(IrCmd::CHECK_TAG, tagIdx, build.constTag(LUA_TNUMBER), fallback); - build.inst(IrCmd::JUMP, nextStep); - - // After successful conversion of arguments to number in a fallback, we return here - build.beginBlock(nextStep); + build.inst(IrCmd::CHECK_TAG, tagIdx, build.constTag(LUA_TNUMBER), build.vmExit(pcpos)); IrOp zero = build.constDouble(0.0); IrOp limit = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(ra + 0)); @@ -644,12 +641,6 @@ void translateInstForNPrep(IrBuilder& build, const Instruction* pc, int pcpos) build.beginBlock(reverse); build.inst(IrCmd::JUMP_CMP_NUM, limit, idx, build.cond(IrCondition::LessEqual), loopStart, loopExit); - // Fallback will try to convert loop variables to numbers or throw an error - build.beginBlock(fallback); - build.inst(IrCmd::SET_SAVEDPC, build.constUint(pcpos + 1)); - build.inst(IrCmd::PREPARE_FORN, build.vmReg(ra + 0), build.vmReg(ra + 1), build.vmReg(ra + 2)); - build.inst(IrCmd::JUMP, nextStep); - // Fallthrough in original bytecode is implicit, so we start next internal block here if (build.isInternalBlock(loopStart)) build.beginBlock(loopStart); diff --git a/CodeGen/src/IrUtils.cpp b/CodeGen/src/IrUtils.cpp index e5a55f11..e51dca99 100644 --- a/CodeGen/src/IrUtils.cpp +++ b/CodeGen/src/IrUtils.cpp @@ -111,7 +111,6 @@ IrValueKind getCmdValueKind(IrCmd cmd) case IrCmd::CONCAT: case IrCmd::GET_UPVALUE: case IrCmd::SET_UPVALUE: - case IrCmd::PREPARE_FORN: case IrCmd::CHECK_TAG: case IrCmd::CHECK_TRUTHY: case IrCmd::CHECK_READONLY: diff --git a/CodeGen/src/IrValueLocationTracking.cpp b/CodeGen/src/IrValueLocationTracking.cpp index e781bda3..c32d718c 100644 --- a/CodeGen/src/IrValueLocationTracking.cpp +++ b/CodeGen/src/IrValueLocationTracking.cpp @@ -54,11 +54,6 @@ void IrValueLocationTracking::beforeInstLowering(IrInst& inst) case IrCmd::GET_UPVALUE: invalidateRestoreOp(inst.a); break; - case IrCmd::PREPARE_FORN: - invalidateRestoreOp(inst.a); - invalidateRestoreOp(inst.b); - invalidateRestoreOp(inst.c); - break; case IrCmd::CALL: // Even if result count is limited, all registers starting from function (ra) might be modified invalidateRestoreVmRegs(vmRegOp(inst.a), -1); diff --git a/CodeGen/src/NativeState.cpp b/CodeGen/src/NativeState.cpp index 5a71345e..13ef33d3 100644 --- a/CodeGen/src/NativeState.cpp +++ b/CodeGen/src/NativeState.cpp @@ -44,7 +44,6 @@ void initFunctions(NativeState& data) data.context.luaV_equalval = luaV_equalval; data.context.luaV_doarith = luaV_doarith; data.context.luaV_dolen = luaV_dolen; - data.context.luaV_prepareFORN = luaV_prepareFORN; data.context.luaV_gettable = luaV_gettable; data.context.luaV_settable = luaV_settable; data.context.luaV_getimport = luaV_getimport; diff --git a/CodeGen/src/NativeState.h b/CodeGen/src/NativeState.h index a9ba7cfd..85b7a3a3 100644 --- a/CodeGen/src/NativeState.h +++ b/CodeGen/src/NativeState.h @@ -35,7 +35,6 @@ struct NativeContext int (*luaV_equalval)(lua_State* L, const TValue* t1, const TValue* t2) = nullptr; void (*luaV_doarith)(lua_State* L, StkId ra, const TValue* rb, const TValue* rc, TMS op) = nullptr; void (*luaV_dolen)(lua_State* L, StkId ra, const TValue* rb) = nullptr; - void (*luaV_prepareFORN)(lua_State* L, StkId plimit, StkId pstep, StkId pinit) = nullptr; void (*luaV_gettable)(lua_State* L, const TValue* t, TValue* key, StkId val) = nullptr; void (*luaV_settable)(lua_State* L, const TValue* t, TValue* key, StkId val) = nullptr; void (*luaV_getimport)(lua_State* L, Table* env, TValue* k, StkId res, uint32_t id, bool propagatenil) = nullptr; diff --git a/CodeGen/src/OptimizeConstProp.cpp b/CodeGen/src/OptimizeConstProp.cpp index 9ef57afa..4b09d423 100644 --- a/CodeGen/src/OptimizeConstProp.cpp +++ b/CodeGen/src/OptimizeConstProp.cpp @@ -992,25 +992,20 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& state.invalidateRegisterRange(vmRegOp(inst.a), function.uintOp(inst.b)); state.invalidateUserCall(); // TODO: if only strings and numbers are concatenated, there will be no user calls break; - case IrCmd::PREPARE_FORN: - state.invalidateValue(inst.a); - state.saveTag(inst.a, LUA_TNUMBER); - state.invalidateValue(inst.b); - state.saveTag(inst.b, LUA_TNUMBER); - state.invalidateValue(inst.c); - state.saveTag(inst.c, LUA_TNUMBER); - break; case IrCmd::INTERRUPT: state.invalidateUserCall(); break; case IrCmd::SETLIST: + if (RegisterInfo* info = state.tryGetRegisterInfo(inst.b); info && info->knownTableArraySize >= 0) + replace(function, inst.f, build.constUint(info->knownTableArraySize)); + state.valueMap.clear(); // TODO: this can be relaxed when x64 emitInstSetList becomes aware of register allocator break; case IrCmd::CALL: state.invalidateRegistersFrom(vmRegOp(inst.a)); state.invalidateUserCall(); - // We cannot guarantee right now that all live values can be remeterialized from non-stack memory locations + // We cannot guarantee right now that all live values can be rematerialized from non-stack memory locations // To prevent earlier values from being propagated to after the call, we have to clear the map // TODO: remove only the values that don't have a guaranteed restore location state.valueMap.clear(); diff --git a/CodeGen/src/UnwindBuilderDwarf2.cpp b/CodeGen/src/UnwindBuilderDwarf2.cpp index e9df184d..08c8e831 100644 --- a/CodeGen/src/UnwindBuilderDwarf2.cpp +++ b/CodeGen/src/UnwindBuilderDwarf2.cpp @@ -225,9 +225,10 @@ void UnwindBuilderDwarf2::prologueA64(uint32_t prologueSize, uint32_t stackSize, } } -void UnwindBuilderDwarf2::prologueX64(uint32_t prologueSize, uint32_t stackSize, bool setupFrame, std::initializer_list regs) +void UnwindBuilderDwarf2::prologueX64(uint32_t prologueSize, uint32_t stackSize, bool setupFrame, std::initializer_list gpr, + const std::vector& simd) { - LUAU_ASSERT(stackSize > 0 && stackSize <= 128 && stackSize % 8 == 0); + LUAU_ASSERT(stackSize > 0 && stackSize < 4096 && stackSize % 8 == 0); unsigned int stackOffset = 8; // Return address was pushed by calling the function unsigned int prologueOffset = 0; @@ -247,7 +248,7 @@ void UnwindBuilderDwarf2::prologueX64(uint32_t prologueSize, uint32_t stackSize, } // push reg - for (X64::RegisterX64 reg : regs) + for (X64::RegisterX64 reg : gpr) { LUAU_ASSERT(reg.size == X64::SizeX64::qword); @@ -258,9 +259,11 @@ void UnwindBuilderDwarf2::prologueX64(uint32_t prologueSize, uint32_t stackSize, pos = defineSavedRegisterLocation(pos, regIndexToDwRegX64[reg.index], stackOffset); } + LUAU_ASSERT(simd.empty()); + // sub rsp, stackSize stackOffset += stackSize; - prologueOffset += 4; + prologueOffset += stackSize >= 128 ? 7 : 4; pos = advanceLocation(pos, 4); pos = defineCfaExpressionOffset(pos, stackOffset); diff --git a/CodeGen/src/UnwindBuilderWin.cpp b/CodeGen/src/UnwindBuilderWin.cpp index f9b927c5..336a4e3f 100644 --- a/CodeGen/src/UnwindBuilderWin.cpp +++ b/CodeGen/src/UnwindBuilderWin.cpp @@ -82,7 +82,7 @@ void UnwindBuilderWin::finishFunction(uint32_t beginOffset, uint32_t endOffset) if (!unwindCodes.empty()) { // Copy unwind codes in reverse order - // Some unwind codes take up two array slots, but we don't use those atm + // Some unwind codes take up two array slots, we write those in reverse order uint8_t* unwindCodePos = rawDataPos + sizeof(UnwindCodeWin) * (unwindCodes.size() - 1); LUAU_ASSERT(unwindCodePos <= rawData + kRawDataLimit); @@ -109,9 +109,10 @@ void UnwindBuilderWin::prologueA64(uint32_t prologueSize, uint32_t stackSize, st LUAU_ASSERT(!"Not implemented"); } -void UnwindBuilderWin::prologueX64(uint32_t prologueSize, uint32_t stackSize, bool setupFrame, std::initializer_list regs) +void UnwindBuilderWin::prologueX64(uint32_t prologueSize, uint32_t stackSize, bool setupFrame, std::initializer_list gpr, + const std::vector& simd) { - LUAU_ASSERT(stackSize > 0 && stackSize <= 128 && stackSize % 8 == 0); + LUAU_ASSERT(stackSize > 0 && stackSize < 4096 && stackSize % 8 == 0); LUAU_ASSERT(prologueSize < 256); unsigned int stackOffset = 8; // Return address was pushed by calling the function @@ -132,7 +133,7 @@ void UnwindBuilderWin::prologueX64(uint32_t prologueSize, uint32_t stackSize, bo } // push reg - for (X64::RegisterX64 reg : regs) + for (X64::RegisterX64 reg : gpr) { LUAU_ASSERT(reg.size == X64::SizeX64::qword); @@ -141,10 +142,51 @@ void UnwindBuilderWin::prologueX64(uint32_t prologueSize, uint32_t stackSize, bo unwindCodes.push_back({uint8_t(prologueOffset), UWOP_PUSH_NONVOL, reg.index}); } + // If frame pointer is used, simd register storage is not implemented, it will require reworking store offsets + LUAU_ASSERT(!setupFrame || simd.size() == 0); + + unsigned int simdStorageSize = unsigned(simd.size()) * 16; + + // It's the responsibility of the caller to provide simd register storage in 'stackSize', including alignment to 16 bytes + if (!simd.empty() && stackOffset % 16 == 8) + simdStorageSize += 8; + // sub rsp, stackSize - stackOffset += stackSize; - prologueOffset += 4; - unwindCodes.push_back({uint8_t(prologueOffset), UWOP_ALLOC_SMALL, uint8_t((stackSize - 8) / 8)}); + if (stackSize <= 128) + { + stackOffset += stackSize; + prologueOffset += stackSize == 128 ? 7 : 4; + unwindCodes.push_back({uint8_t(prologueOffset), UWOP_ALLOC_SMALL, uint8_t((stackSize - 8) / 8)}); + } + else + { + // This command can handle allocations up to 512K-8 bytes, but that potentially requires stack probing + LUAU_ASSERT(stackSize < 4096); + + stackOffset += stackSize; + prologueOffset += 7; + + uint16_t encodedOffset = stackSize / 8; + unwindCodes.push_back(UnwindCodeWin()); + memcpy(&unwindCodes.back(), &encodedOffset, sizeof(encodedOffset)); + + unwindCodes.push_back({uint8_t(prologueOffset), UWOP_ALLOC_LARGE, 0}); + } + + // It's the responsibility of the caller to provide simd register storage in 'stackSize' + unsigned int xmmStoreOffset = stackSize - simdStorageSize; + + // vmovaps [rsp+n], xmm + for (X64::RegisterX64 reg : simd) + { + LUAU_ASSERT(reg.size == X64::SizeX64::xmmword); + LUAU_ASSERT(xmmStoreOffset % 16 == 0 && "simd stores have to be performed to aligned locations"); + + prologueOffset += xmmStoreOffset >= 128 ? 10 : 7; + unwindCodes.push_back({uint8_t(xmmStoreOffset / 16), 0, 0}); + unwindCodes.push_back({uint8_t(prologueOffset), UWOP_SAVE_XMM128, reg.index}); + xmmStoreOffset += 16; + } LUAU_ASSERT(stackOffset % 16 == 0); LUAU_ASSERT(prologueOffset == prologueSize); diff --git a/Common/include/Luau/DenseHash.h b/Common/include/Luau/DenseHash.h index 997e090f..72aa6ec5 100644 --- a/Common/include/Luau/DenseHash.h +++ b/Common/include/Luau/DenseHash.h @@ -282,6 +282,13 @@ public: class const_iterator { public: + using value_type = Item; + using reference = Item&; + using pointer = Item*; + using iterator = pointer; + using difference_type = size_t; + using iterator_category = std::input_iterator_tag; + const_iterator() : set(0) , index(0) diff --git a/Compiler/src/Builtins.cpp b/Compiler/src/Builtins.cpp index 4ec083bb..a15c8f08 100644 --- a/Compiler/src/Builtins.cpp +++ b/Compiler/src/Builtins.cpp @@ -4,9 +4,6 @@ #include "Luau/Bytecode.h" #include "Luau/Compiler.h" -LUAU_FASTFLAGVARIABLE(LuauCompileBuiltinTonumber, false) -LUAU_FASTFLAGVARIABLE(LuauCompileBuiltinTostring, false) - namespace Luau { namespace Compile @@ -72,9 +69,9 @@ static int getBuiltinFunctionId(const Builtin& builtin, const CompileOptions& op if (builtin.isGlobal("setmetatable")) return LBF_SETMETATABLE; - if (FFlag::LuauCompileBuiltinTonumber && builtin.isGlobal("tonumber")) + if (builtin.isGlobal("tonumber")) return LBF_TONUMBER; - if (FFlag::LuauCompileBuiltinTostring && builtin.isGlobal("tostring")) + if (builtin.isGlobal("tostring")) return LBF_TOSTRING; if (builtin.object == "math") diff --git a/Compiler/src/Compiler.cpp b/Compiler/src/Compiler.cpp index b673ffc2..f9a00f64 100644 --- a/Compiler/src/Compiler.cpp +++ b/Compiler/src/Compiler.cpp @@ -26,8 +26,6 @@ LUAU_FASTINTVARIABLE(LuauCompileInlineThreshold, 25) LUAU_FASTINTVARIABLE(LuauCompileInlineThresholdMaxBoost, 300) LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5) -LUAU_FASTFLAGVARIABLE(LuauCompileFoldMathK, false) - namespace Luau { @@ -3871,9 +3869,8 @@ void compileOrThrow(BytecodeBuilder& bytecode, const ParseResult& parseResult, c { compiler.builtinsFold = &compiler.builtins; - if (FFlag::LuauCompileFoldMathK) - if (AstName math = names.get("math"); math.value && getGlobalState(compiler.globals, math) == Global::Default) - compiler.builtinsFoldMathK = true; + if (AstName math = names.get("math"); math.value && getGlobalState(compiler.globals, math) == Global::Default) + compiler.builtinsFoldMathK = true; } if (options.optimizationLevel >= 1) diff --git a/Makefile b/Makefile index d1c2ac90..f0f008be 100644 --- a/Makefile +++ b/Makefile @@ -121,12 +121,11 @@ ifeq ($(protobuf),download) endif ifneq ($(native),) - CXXFLAGS+=-DLUA_CUSTOM_EXECUTION=1 TESTS_ARGS+=--codegen endif ifneq ($(nativelj),) - CXXFLAGS+=-DLUA_CUSTOM_EXECUTION=1 -DLUA_USE_LONGJMP=1 + CXXFLAGS+=-DLUA_USE_LONGJMP=1 TESTS_ARGS+=--codegen endif @@ -142,7 +141,7 @@ $(TESTS_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler $(REPL_CLI_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IVM/include -ICodeGen/include -Iextern -Iextern/isocline/include $(ANALYZE_CLI_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -IAnalysis/include -IConfig/include -Iextern $(COMPILE_CLI_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IVM/include -ICodeGen/include -$(FUZZ_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IAnalysis/include -IVM/include -ICodeGen/include +$(FUZZ_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IAnalysis/include -IVM/include -ICodeGen/include -IConfig/include $(TESTS_TARGET): LDFLAGS+=-lpthread $(REPL_CLI_TARGET): LDFLAGS+=-lpthread @@ -219,11 +218,11 @@ $(TESTS_TARGET) $(REPL_CLI_TARGET) $(ANALYZE_CLI_TARGET) $(COMPILE_CLI_TARGET): $(CXX) $^ $(LDFLAGS) -o $@ # executable targets for fuzzing -fuzz-%: $(BUILD)/fuzz/%.cpp.o $(ANALYSIS_TARGET) $(COMPILER_TARGET) $(AST_TARGET) $(CODEGEN_TARGET) $(VM_TARGET) +fuzz-%: $(BUILD)/fuzz/%.cpp.o $(ANALYSIS_TARGET) $(COMPILER_TARGET) $(AST_TARGET) $(CONFIG_TARGET) $(CODEGEN_TARGET) $(VM_TARGET) $(CXX) $^ $(LDFLAGS) -o $@ -fuzz-proto: $(BUILD)/fuzz/proto.cpp.o $(BUILD)/fuzz/protoprint.cpp.o $(BUILD)/fuzz/luau.pb.cpp.o $(ANALYSIS_TARGET) $(COMPILER_TARGET) $(AST_TARGET) $(VM_TARGET) | build/libprotobuf-mutator -fuzz-prototest: $(BUILD)/fuzz/prototest.cpp.o $(BUILD)/fuzz/protoprint.cpp.o $(BUILD)/fuzz/luau.pb.cpp.o $(ANALYSIS_TARGET) $(COMPILER_TARGET) $(AST_TARGET) $(VM_TARGET) | build/libprotobuf-mutator +fuzz-proto: $(BUILD)/fuzz/proto.cpp.o $(BUILD)/fuzz/protoprint.cpp.o $(BUILD)/fuzz/luau.pb.cpp.o $(ANALYSIS_TARGET) $(COMPILER_TARGET) $(AST_TARGET) $(CONFIG_TARGET) $(VM_TARGET) | build/libprotobuf-mutator +fuzz-prototest: $(BUILD)/fuzz/prototest.cpp.o $(BUILD)/fuzz/protoprint.cpp.o $(BUILD)/fuzz/luau.pb.cpp.o $(ANALYSIS_TARGET) $(COMPILER_TARGET) $(AST_TARGET) $(CONFIG_TARGET) $(VM_TARGET) | build/libprotobuf-mutator # static library targets $(AST_TARGET): $(AST_OBJECTS) diff --git a/VM/include/luaconf.h b/VM/include/luaconf.h index dcb785b6..7a1bbb95 100644 --- a/VM/include/luaconf.h +++ b/VM/include/luaconf.h @@ -121,11 +121,6 @@ #define LUA_MAXCAPTURES 32 #endif -// enables callbacks to redirect code execution from Luau VM to a custom implementation -#ifndef LUA_CUSTOM_EXECUTION -#define LUA_CUSTOM_EXECUTION 1 -#endif - // }================================================================== /* diff --git a/VM/src/laux.cpp b/VM/src/laux.cpp index 951b3028..63da1810 100644 --- a/VM/src/laux.cpp +++ b/VM/src/laux.cpp @@ -11,8 +11,6 @@ #include -LUAU_FASTFLAG(LuauFasterInterp) - // convert a stack index to positive #define abs_index(L, i) ((i) > 0 || (i) <= LUA_REGISTRYINDEX ? (i) : lua_gettop(L) + (i) + 1) @@ -524,19 +522,10 @@ const char* luaL_tolstring(lua_State* L, int idx, size_t* len) { if (luaL_callmeta(L, idx, "__tostring")) // is there a metafield? { - if (FFlag::LuauFasterInterp) - { - const char* s = lua_tolstring(L, -1, len); - if (!s) - luaL_error(L, "'__tostring' must return a string"); - return s; - } - else - { - if (!lua_isstring(L, -1)) - luaL_error(L, "'__tostring' must return a string"); - return lua_tolstring(L, -1, len); - } + const char* s = lua_tolstring(L, -1, len); + if (!s) + luaL_error(L, "'__tostring' must return a string"); + return s; } switch (lua_type(L, idx)) diff --git a/VM/src/lbuiltins.cpp b/VM/src/lbuiltins.cpp index c893d603..a916f73a 100644 --- a/VM/src/lbuiltins.cpp +++ b/VM/src/lbuiltins.cpp @@ -23,8 +23,6 @@ #endif #endif -LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauFastcallGC, false) - // luauF functions implement FASTCALL instruction that performs a direct execution of some builtin functions from the VM // The rule of thumb is that FASTCALL functions can not call user code, yield, fail, or reallocate stack. // If types of the arguments mismatch, luauF_* needs to return -1 and the execution will fall back to the usual call path @@ -832,7 +830,7 @@ static int luauF_char(lua_State* L, StkId res, TValue* arg0, int nresults, StkId if (nparams < int(sizeof(buffer)) && nresults <= 1) { - if (DFFlag::LuauFastcallGC && luaC_needsGC(L)) + if (luaC_needsGC(L)) return -1; // we can't call luaC_checkGC so fall back to C implementation if (nparams >= 1) @@ -904,7 +902,7 @@ static int luauF_sub(lua_State* L, StkId res, TValue* arg0, int nresults, StkId int i = int(nvalue(args)); int j = int(nvalue(args + 1)); - if (DFFlag::LuauFastcallGC && luaC_needsGC(L)) + if (luaC_needsGC(L)) return -1; // we can't call luaC_checkGC so fall back to C implementation if (i >= 1 && j >= i && unsigned(j - 1) < unsigned(ts->len)) @@ -1300,7 +1298,7 @@ static int luauF_tostring(lua_State* L, StkId res, TValue* arg0, int nresults, S } case LUA_TNUMBER: { - if (DFFlag::LuauFastcallGC && luaC_needsGC(L)) + if (luaC_needsGC(L)) return -1; // we can't call luaC_checkGC so fall back to C implementation char s[LUAI_MAXNUM2STR]; diff --git a/VM/src/lstrlib.cpp b/VM/src/lstrlib.cpp index 90b30ead..ca57786e 100644 --- a/VM/src/lstrlib.cpp +++ b/VM/src/lstrlib.cpp @@ -8,9 +8,6 @@ #include #include -LUAU_FASTFLAGVARIABLE(LuauFasterInterp, false) -LUAU_FASTFLAGVARIABLE(LuauFasterFormatS, false) - // macro to `unsign' a character #define uchar(c) ((unsigned char)(c)) @@ -969,7 +966,7 @@ static int str_format(lua_State* L) luaL_addchar(&b, *strfrmt++); else if (*++strfrmt == L_ESC) luaL_addchar(&b, *strfrmt++); // %% - else if (FFlag::LuauFasterInterp && *strfrmt == '*') + else if (*strfrmt == '*') { strfrmt++; if (++arg > top) @@ -1029,49 +1026,22 @@ static int str_format(lua_State* L) { size_t l; const char* s = luaL_checklstring(L, arg, &l); - if (FFlag::LuauFasterFormatS) + // no precision and string is too long to be formatted, or no format necessary to begin with + if (form[2] == '\0' || (!strchr(form, '.') && l >= 100)) { - // no precision and string is too long to be formatted, or no format necessary to begin with - if (form[2] == '\0' || (!strchr(form, '.') && l >= 100)) - { - luaL_addlstring(&b, s, l, -1); - continue; // skip the `luaL_addlstring' at the end - } - else - { - snprintf(buff, sizeof(buff), form, s); - break; - } + luaL_addlstring(&b, s, l, -1); + continue; // skip the `luaL_addlstring' at the end } else { - if (!strchr(form, '.') && l >= 100) - { - /* no precision and string is too long to be formatted; - keep original string */ - lua_pushvalue(L, arg); - luaL_addvalue(&b); - continue; // skip the `luaL_addlstring' at the end - } - else - { - snprintf(buff, sizeof(buff), form, s); - break; - } + snprintf(buff, sizeof(buff), form, s); + break; } } case '*': { - if (FFlag::LuauFasterInterp || formatItemSize != 1) - luaL_error(L, "'%%*' does not take a form"); - - size_t length; - const char* string = luaL_tolstring(L, arg, &length); - - luaL_addlstring(&b, string, length, -2); - lua_pop(L, 1); - - continue; // skip the `luaL_addlstring' at the end + // %* is parsed above, so if we got here we must have %...* + luaL_error(L, "'%%*' does not take a form"); } default: { // also treat cases `pnLlh' diff --git a/VM/src/lvmexecute.cpp b/VM/src/lvmexecute.cpp index 2909d477..0d5a53df 100644 --- a/VM/src/lvmexecute.cpp +++ b/VM/src/lvmexecute.cpp @@ -132,7 +132,7 @@ #endif // Does VM support native execution via ExecutionCallbacks? We mostly assume it does but keep the define to make it easy to quantify the cost. -#define VM_HAS_NATIVE LUA_CUSTOM_EXECUTION +#define VM_HAS_NATIVE 1 LUAU_NOINLINE void luau_callhook(lua_State* L, lua_Hook hook, void* userdata) { @@ -2380,7 +2380,7 @@ reentry: else goto exit; #else - LUAU_ASSERT(!"Opcode is only valid when LUA_CUSTOM_EXECUTION is defined"); + LUAU_ASSERT(!"Opcode is only valid when VM_HAS_NATIVE is defined"); LUAU_UNREACHABLE(); #endif } diff --git a/fuzz/proto.cpp b/fuzz/proto.cpp index e1ecaf65..1ad7852d 100644 --- a/fuzz/proto.cpp +++ b/fuzz/proto.cpp @@ -7,6 +7,7 @@ #include "Luau/CodeGen.h" #include "Luau/Common.h" #include "Luau/Compiler.h" +#include "Luau/Config.h" #include "Luau/Frontend.h" #include "Luau/Linter.h" #include "Luau/ModuleResolver.h" diff --git a/tests/AstQuery.test.cpp b/tests/AstQuery.test.cpp index 25521e35..723e107e 100644 --- a/tests/AstQuery.test.cpp +++ b/tests/AstQuery.test.cpp @@ -295,4 +295,34 @@ TEST_CASE_FIXTURE(Fixture, "include_types_ancestry") CHECK(ancestryTypes.back()->asType()); } +TEST_CASE_FIXTURE(Fixture, "find_name_ancestry") +{ + ScopedFastFlag sff{"FixFindBindingAtFunctionName", true}; + check(R"( + local tbl = {} + function tbl:abc() end + )"); + const Position pos(2, 18); + + std::vector ancestry = findAstAncestryOfPosition(*getMainSourceModule(), pos); + + REQUIRE(!ancestry.empty()); + CHECK(ancestry.back()->is()); +} + +TEST_CASE_FIXTURE(Fixture, "find_expr_ancestry") +{ + ScopedFastFlag sff{"FixFindBindingAtFunctionName", true}; + check(R"( + local tbl = {} + function tbl:abc() end + )"); + const Position pos(2, 29); + + std::vector ancestry = findAstAncestryOfPosition(*getMainSourceModule(), pos); + + REQUIRE(!ancestry.empty()); + CHECK(ancestry.back()->is()); +} + TEST_SUITE_END(); diff --git a/tests/Autocomplete.test.cpp b/tests/Autocomplete.test.cpp index b8171a75..fac23e88 100644 --- a/tests/Autocomplete.test.cpp +++ b/tests/Autocomplete.test.cpp @@ -80,7 +80,7 @@ struct ACFixtureImpl : BaseType { if (prevChar == '@') { - LUAU_ASSERT("Illegal marker character" && c >= '0' && c <= '9'); + LUAU_ASSERT("Illegal marker character" && ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z'))); LUAU_ASSERT("Duplicate marker found" && markerPosition.count(c) == 0); markerPosition.insert(std::pair{c, curPos}); } @@ -126,7 +126,6 @@ struct ACFixtureImpl : BaseType LUAU_ASSERT(i != markerPosition.end()); return i->second; } - ScopedFastFlag flag{"LuauAutocompleteHideSelfArg", true}; // Maps a marker character (0-9 inclusive) to a position in the source code. std::map markerPosition; }; @@ -3083,6 +3082,86 @@ TEST_CASE_FIXTURE(ACFixture, "string_singleton_as_table_key") CHECK(ac.entryMap.count("\"down\"")); } +// https://github.com/Roblox/luau/issues/858 +TEST_CASE_FIXTURE(ACFixture, "string_singleton_in_if_statement") +{ + ScopedFastFlag sff{"LuauAutocompleteStringLiteralBounds", true}; + + check(R"( + --!strict + + type Direction = "left" | "right" + + local dir: Direction = "left" + + if dir == @1"@2"@3 then end + local a: {[Direction]: boolean} = {[@4"@5"@6]} + + if dir == @7`@8`@9 then end + local a: {[Direction]: boolean} = {[@A`@B`@C]} + )"); + + auto ac = autocomplete('1'); + + CHECK(!ac.entryMap.count("left")); + CHECK(!ac.entryMap.count("right")); + + ac = autocomplete('2'); + + CHECK(ac.entryMap.count("left")); + CHECK(ac.entryMap.count("right")); + + ac = autocomplete('3'); + + CHECK(!ac.entryMap.count("left")); + CHECK(!ac.entryMap.count("right")); + + ac = autocomplete('4'); + + CHECK(!ac.entryMap.count("left")); + CHECK(!ac.entryMap.count("right")); + + ac = autocomplete('5'); + + CHECK(ac.entryMap.count("left")); + CHECK(ac.entryMap.count("right")); + + ac = autocomplete('6'); + + CHECK(!ac.entryMap.count("left")); + CHECK(!ac.entryMap.count("right")); + + ac = autocomplete('7'); + + CHECK(!ac.entryMap.count("left")); + CHECK(!ac.entryMap.count("right")); + + ac = autocomplete('8'); + + CHECK(ac.entryMap.count("left")); + CHECK(ac.entryMap.count("right")); + + ac = autocomplete('9'); + + CHECK(!ac.entryMap.count("left")); + CHECK(!ac.entryMap.count("right")); + + ac = autocomplete('A'); + + CHECK(!ac.entryMap.count("left")); + CHECK(!ac.entryMap.count("right")); + + ac = autocomplete('B'); + + CHECK(ac.entryMap.count("left")); + CHECK(ac.entryMap.count("right")); + + ac = autocomplete('C'); + + CHECK(!ac.entryMap.count("left")); + CHECK(!ac.entryMap.count("right")); +} + TEST_CASE_FIXTURE(ACFixture, "autocomplete_string_singleton_equality") { check(R"( diff --git a/tests/CodeAllocator.test.cpp b/tests/CodeAllocator.test.cpp index b44ca6d5..298035c2 100644 --- a/tests/CodeAllocator.test.cpp +++ b/tests/CodeAllocator.test.cpp @@ -187,7 +187,7 @@ TEST_CASE("WindowsUnwindCodesX64") unwind.startInfo(UnwindBuilder::X64); unwind.startFunction(); - unwind.prologueX64(/* prologueSize= */ 23, /* stackSize= */ 72, /* setupFrame= */ true, {rdi, rsi, rbx, r12, r13, r14, r15}); + unwind.prologueX64(/* prologueSize= */ 23, /* stackSize= */ 72, /* setupFrame= */ true, {rdi, rsi, rbx, r12, r13, r14, r15}, {}); unwind.finishFunction(0x11223344, 0x55443322); unwind.finishInfo(); @@ -211,7 +211,7 @@ TEST_CASE("Dwarf2UnwindCodesX64") unwind.startInfo(UnwindBuilder::X64); unwind.startFunction(); - unwind.prologueX64(/* prologueSize= */ 23, /* stackSize= */ 72, /* setupFrame= */ true, {rdi, rsi, rbx, r12, r13, r14, r15}); + unwind.prologueX64(/* prologueSize= */ 23, /* stackSize= */ 72, /* setupFrame= */ true, {rdi, rsi, rbx, r12, r13, r14, r15}, {}); unwind.finishFunction(0, 0); unwind.finishInfo(); @@ -309,6 +309,11 @@ static void throwing(int64_t arg) throw std::runtime_error("testing"); } +static void nonthrowing(int64_t arg) +{ + CHECK(arg == 25); +} + TEST_CASE("GeneratedCodeExecutionWithThrowX64") { using namespace X64; @@ -339,7 +344,7 @@ TEST_CASE("GeneratedCodeExecutionWithThrowX64") uint32_t prologueSize = build.setLabel().location; - unwind->prologueX64(prologueSize, stackSize + localsSize, /* setupFrame= */ true, {rNonVol1, rNonVol2}); + unwind->prologueX64(prologueSize, stackSize + localsSize, /* setupFrame= */ true, {rNonVol1, rNonVol2}, {}); // Body build.mov(rNonVol1, rArg1); @@ -379,6 +384,8 @@ TEST_CASE("GeneratedCodeExecutionWithThrowX64") using FunctionType = int64_t(int64_t, void (*)(int64_t)); FunctionType* f = (FunctionType*)nativeEntry; + f(10, nonthrowing); + // To simplify debugging, CHECK_THROWS_WITH_AS is not used here try { @@ -390,6 +397,121 @@ TEST_CASE("GeneratedCodeExecutionWithThrowX64") } } +static void obscureThrowCase(int64_t (*f)(int64_t, void (*)(int64_t))) +{ + // To simplify debugging, CHECK_THROWS_WITH_AS is not used here + try + { + f(10, throwing); + } + catch (const std::runtime_error& error) + { + CHECK(strcmp(error.what(), "testing") == 0); + } +} + +TEST_CASE("GeneratedCodeExecutionWithThrowX64Simd") +{ + // This test requires AVX + if (!Luau::CodeGen::isSupported()) + return; + + using namespace X64; + + AssemblyBuilderX64 build(/* logText= */ false); + +#if defined(_WIN32) + std::unique_ptr unwind = std::make_unique(); +#else + std::unique_ptr unwind = std::make_unique(); +#endif + + unwind->startInfo(UnwindBuilder::X64); + + Label functionBegin = build.setLabel(); + unwind->startFunction(); + + int stackSize = 32 + 64; + int localsSize = 16; + + // Prologue + build.push(rNonVol1); + build.push(rNonVol2); + build.push(rbp); + build.sub(rsp, stackSize + localsSize); + + if (build.abi == ABIX64::Windows) + { + build.vmovaps(xmmword[rsp + ((stackSize + localsSize) - 0x40)], xmm6); + build.vmovaps(xmmword[rsp + ((stackSize + localsSize) - 0x30)], xmm7); + build.vmovaps(xmmword[rsp + ((stackSize + localsSize) - 0x20)], xmm8); + build.vmovaps(xmmword[rsp + ((stackSize + localsSize) - 0x10)], xmm9); + } + + uint32_t prologueSize = build.setLabel().location; + + if (build.abi == ABIX64::Windows) + unwind->prologueX64(prologueSize, stackSize + localsSize, /* setupFrame= */ false, {rNonVol1, rNonVol2, rbp}, {xmm6, xmm7, xmm8, xmm9}); + else + unwind->prologueX64(prologueSize, stackSize + localsSize, /* setupFrame= */ false, {rNonVol1, rNonVol2, rbp}, {}); + + // Body + build.vxorpd(xmm0, xmm0, xmm0); + build.vmovsd(xmm6, xmm0, xmm0); + build.vmovsd(xmm7, xmm0, xmm0); + build.vmovsd(xmm8, xmm0, xmm0); + build.vmovsd(xmm9, xmm0, xmm0); + + build.mov(rNonVol1, rArg1); + build.mov(rNonVol2, rArg2); + + build.add(rNonVol1, 15); + build.mov(rArg1, rNonVol1); + build.call(rNonVol2); + + // Epilogue + if (build.abi == ABIX64::Windows) + { + build.vmovaps(xmm6, xmmword[rsp + ((stackSize + localsSize) - 0x40)]); + build.vmovaps(xmm7, xmmword[rsp + ((stackSize + localsSize) - 0x30)]); + build.vmovaps(xmm8, xmmword[rsp + ((stackSize + localsSize) - 0x20)]); + build.vmovaps(xmm9, xmmword[rsp + ((stackSize + localsSize) - 0x10)]); + } + + build.add(rsp, stackSize + localsSize); + build.pop(rbp); + build.pop(rNonVol2); + build.pop(rNonVol1); + build.ret(); + + unwind->finishFunction(build.getLabelOffset(functionBegin), ~0u); + + build.finalize(); + + unwind->finishInfo(); + + size_t blockSize = 1024 * 1024; + size_t maxTotalSize = 1024 * 1024; + CodeAllocator allocator(blockSize, maxTotalSize); + + allocator.context = unwind.get(); + allocator.createBlockUnwindInfo = createBlockUnwindInfo; + allocator.destroyBlockUnwindInfo = destroyBlockUnwindInfo; + + uint8_t* nativeData; + size_t sizeNativeData; + uint8_t* nativeEntry; + REQUIRE(allocator.allocate(build.data.data(), build.data.size(), build.code.data(), build.code.size(), nativeData, sizeNativeData, nativeEntry)); + REQUIRE(nativeEntry); + + using FunctionType = int64_t(int64_t, void (*)(int64_t)); + FunctionType* f = (FunctionType*)nativeEntry; + + f(10, nonthrowing); + + obscureThrowCase(f); +} + TEST_CASE("GeneratedCodeExecutionMultipleFunctionsWithThrowX64") { using namespace X64; @@ -425,7 +547,7 @@ TEST_CASE("GeneratedCodeExecutionMultipleFunctionsWithThrowX64") uint32_t prologueSize = build.setLabel().location - start1.location; - unwind->prologueX64(prologueSize, stackSize + localsSize, /* setupFrame= */ true, {rNonVol1, rNonVol2}); + unwind->prologueX64(prologueSize, stackSize + localsSize, /* setupFrame= */ true, {rNonVol1, rNonVol2}, {}); // Body build.mov(rNonVol1, rArg1); @@ -464,7 +586,7 @@ TEST_CASE("GeneratedCodeExecutionMultipleFunctionsWithThrowX64") uint32_t prologueSize = build.setLabel().location - start2.location; - unwind->prologueX64(prologueSize, stackSize + localsSize, /* setupFrame= */ false, {rNonVol1, rNonVol2, rNonVol3, rNonVol4}); + unwind->prologueX64(prologueSize, stackSize + localsSize, /* setupFrame= */ false, {rNonVol1, rNonVol2, rNonVol3, rNonVol4}, {}); // Body build.mov(rNonVol3, rArg1); @@ -561,7 +683,7 @@ TEST_CASE("GeneratedCodeExecutionWithThrowOutsideTheGateX64") uint32_t prologueSize = build.setLabel().location; - unwind->prologueX64(prologueSize, stackSize + localsSize, /* setupFrame= */ true, {r10, r11, r12, r13, r14, r15}); + unwind->prologueX64(prologueSize, stackSize + localsSize, /* setupFrame= */ true, {r10, r11, r12, r13, r14, r15}, {}); // Body build.mov(rax, rArg1); diff --git a/tests/Compiler.test.cpp b/tests/Compiler.test.cpp index 078b8af6..93290567 100644 --- a/tests/Compiler.test.cpp +++ b/tests/Compiler.test.cpp @@ -63,6 +63,35 @@ static std::string compileTypeTable(const char* source) TEST_SUITE_BEGIN("Compiler"); +TEST_CASE("BytecodeIsStable") +{ + // As noted in Bytecode.h, all enums used for bytecode storage and serialization are order-sensitive + // Adding entries in the middle will typically pass the tests but break compatibility + // This test codifies this by validating that in each enum, the last (or close-to-last) entry has a fixed encoding + + // This test will need to get occasionally revised to "move" the checked enum entries forward as we ship newer versions + // When doing so, please add *new* checks for more recent bytecode versions and keep existing checks in place. + + // Bytecode ops (serialized & in-memory) + CHECK(LOP_FASTCALL2K == 75); // bytecode v1 + CHECK(LOP_JUMPXEQKS == 80); // bytecode v3 + + // Bytecode fastcall ids (serialized & in-memory) + // Note: these aren't strictly bound to specific bytecode versions, but must monotonically increase to keep backwards compat + CHECK(LBF_VECTOR == 54); + CHECK(LBF_TOSTRING == 63); + + // Bytecode capture type (serialized & in-memory) + CHECK(LCT_UPVAL == 2); // bytecode v1 + + // Bytecode constants (serialized) + CHECK(LBC_CONSTANT_CLOSURE == 6); // bytecode v1 + + // Bytecode type encoding (serialized & in-memory) + // Note: these *can* change retroactively *if* type version is bumped, but probably shouldn't + LUAU_ASSERT(LBC_TYPE_VECTOR == 8); // type version 1 +} + TEST_CASE("CompileToBytecode") { Luau::BytecodeBuilder bcb; @@ -5085,7 +5114,7 @@ RETURN R1 1 )"); } -TEST_CASE("InlineBasicProhibited") +TEST_CASE("InlineProhibited") { // we can't inline variadic functions CHECK_EQ("\n" + compileFunction(R"( @@ -5125,6 +5154,66 @@ RETURN R1 1 )"); } +TEST_CASE("InlineProhibitedRecursion") +{ + // we can't inline recursive invocations of functions in the functions + // this is actually profitable in certain cases, but it complicates the compiler as it means a local has multiple registers/values + + // in this example, inlining is blocked because we're compiling fact() and we don't yet have the cost model / profitability data for fact() + CHECK_EQ("\n" + compileFunction(R"( +local function fact(n) + return if n <= 1 then 1 else fact(n-1)*n +end + +return fact +)", + 0, 2), + R"( +LOADN R2 1 +JUMPIFNOTLE R0 R2 L0 +LOADN R1 1 +RETURN R1 1 +L0: GETUPVAL R2 0 +SUBK R3 R0 K0 [1] +CALL R2 1 1 +MUL R1 R2 R0 +RETURN R1 1 +)"); + + // in this example, inlining of fact() succeeds, but the nested call to fact() fails since fact is already on the inline stack + CHECK_EQ("\n" + compileFunction(R"( +local function fact(n) + return if n <= 1 then 1 else fact(n-1)*n +end + +local function factsafe(n) + assert(n >= 1) + return fact(n) +end + +return factsafe +)", + 1, 2), + R"( +LOADN R3 1 +JUMPIFLE R3 R0 L0 +LOADB R2 0 +1 +L0: LOADB R2 1 +L1: FASTCALL1 1 R2 L2 +GETIMPORT R1 1 [assert] +CALL R1 1 0 +L2: LOADN R2 1 +JUMPIFNOTLE R0 R2 L3 +LOADN R1 1 +RETURN R1 1 +L3: GETUPVAL R2 0 +SUBK R3 R0 K2 [1] +CALL R2 1 1 +MUL R1 R2 R0 +RETURN R1 1 +)"); +} + TEST_CASE("InlineNestedLoops") { // functions with basic loops get inlined @@ -7252,10 +7341,31 @@ end )"); } +TEST_CASE("TypeUnionIntersection") +{ + CHECK_EQ("\n" + compileTypeTable(R"( +function myfunc(test: string | nil, foo: nil) +end + +function myfunc2(test: string & nil, foo: nil) +end + +function myfunc3(test: string | number, foo: nil) +end + +function myfunc4(test: string & number, foo: nil) +end +)"), + R"( +0: function(string?, nil) +1: function(any, nil) +2: function(any, nil) +3: function(any, nil) +)"); +} + TEST_CASE("BuiltinFoldMathK") { - ScopedFastFlag sff("LuauCompileFoldMathK", true); - // we can fold math.pi at optimization level 2 CHECK_EQ("\n" + compileFunction(R"( function test() diff --git a/tests/Frontend.test.cpp b/tests/Frontend.test.cpp index fda0a6f0..ec0c213a 100644 --- a/tests/Frontend.test.cpp +++ b/tests/Frontend.test.cpp @@ -446,8 +446,6 @@ TEST_CASE_FIXTURE(FrontendFixture, "cycle_incremental_type_surface_longer") TEST_CASE_FIXTURE(FrontendFixture, "cycle_incremental_type_surface_exports") { - ScopedFastFlag luauFixCyclicModuleExports{"LuauFixCyclicModuleExports", true}; - fileResolver.source["game/A"] = R"( local b = require(game.B) export type atype = { x: b.btype } diff --git a/tests/Parser.test.cpp b/tests/Parser.test.cpp index 3798082b..26a157e4 100644 --- a/tests/Parser.test.cpp +++ b/tests/Parser.test.cpp @@ -900,7 +900,7 @@ TEST_CASE_FIXTURE(Fixture, "parse_interpolated_string_double_brace_begin") } catch (const ParseErrors& e) { - CHECK_EQ("Double braces are not permitted within interpolated strings. Did you mean '\\{'?", e.getErrors().front().getMessage()); + CHECK_EQ("Double braces are not permitted within interpolated strings; did you mean '\\{'?", e.getErrors().front().getMessage()); } } @@ -915,7 +915,7 @@ TEST_CASE_FIXTURE(Fixture, "parse_interpolated_string_double_brace_mid") } catch (const ParseErrors& e) { - CHECK_EQ("Double braces are not permitted within interpolated strings. Did you mean '\\{'?", e.getErrors().front().getMessage()); + CHECK_EQ("Double braces are not permitted within interpolated strings; did you mean '\\{'?", e.getErrors().front().getMessage()); } } @@ -933,7 +933,7 @@ TEST_CASE_FIXTURE(Fixture, "parse_interpolated_string_without_end_brace") CHECK_EQ(e.getErrors().size(), 1); auto error = e.getErrors().front(); - CHECK_EQ("Malformed interpolated string, did you forget to add a '}'?", error.getMessage()); + CHECK_EQ("Malformed interpolated string; did you forget to add a '}'?", error.getMessage()); return error.getLocation().begin.column; } }; @@ -956,7 +956,7 @@ TEST_CASE_FIXTURE(Fixture, "parse_interpolated_string_without_end_brace_in_table { CHECK_EQ(e.getErrors().size(), 2); - CHECK_EQ("Malformed interpolated string, did you forget to add a '}'?", e.getErrors().front().getMessage()); + CHECK_EQ("Malformed interpolated string; did you forget to add a '}'?", e.getErrors().front().getMessage()); CHECK_EQ("Expected '}' (to close '{' at line 2), got ", e.getErrors().back().getMessage()); } } @@ -974,7 +974,7 @@ TEST_CASE_FIXTURE(Fixture, "parse_interpolated_string_mid_without_end_brace_in_t { CHECK_EQ(e.getErrors().size(), 2); - CHECK_EQ("Malformed interpolated string, did you forget to add a '}'?", e.getErrors().front().getMessage()); + CHECK_EQ("Malformed interpolated string; did you forget to add a '}'?", e.getErrors().front().getMessage()); CHECK_EQ("Expected '}' (to close '{' at line 2), got ", e.getErrors().back().getMessage()); } } @@ -1041,6 +1041,36 @@ TEST_CASE_FIXTURE(Fixture, "parse_interpolated_string_without_expression") } } +TEST_CASE_FIXTURE(Fixture, "parse_interpolated_string_malformed_escape") +{ + try + { + parse(R"( + local a = `???\xQQ {1}` + )"); + FAIL("Expected ParseErrors to be thrown"); + } + catch (const ParseErrors& e) + { + CHECK_EQ("Interpolated string literal contains malformed escape sequence", e.getErrors().front().getMessage()); + } +} + +TEST_CASE_FIXTURE(Fixture, "parse_interpolated_string_weird_token") +{ + try + { + parse(R"( + local a = `??? {42 !!}` + )"); + FAIL("Expected ParseErrors to be thrown"); + } + catch (const ParseErrors& e) + { + CHECK_EQ("Malformed interpolated string, got '!'", e.getErrors().front().getMessage()); + } +} + TEST_CASE_FIXTURE(Fixture, "parse_nesting_based_end_detection") { try @@ -1569,9 +1599,9 @@ TEST_CASE_FIXTURE(Fixture, "string_literals_escapes_broken") TEST_CASE_FIXTURE(Fixture, "string_literals_broken") { - matchParseError("return \"", "Malformed string"); - matchParseError("return \"\\", "Malformed string"); - matchParseError("return \"\r\r", "Malformed string"); + matchParseError("return \"", "Malformed string; did you forget to finish it?"); + matchParseError("return \"\\", "Malformed string; did you forget to finish it?"); + matchParseError("return \"\r\r", "Malformed string; did you forget to finish it?"); } TEST_CASE_FIXTURE(Fixture, "number_literals") @@ -2530,12 +2560,12 @@ TEST_CASE_FIXTURE(Fixture, "incomplete_method_call_still_yields_an_AstExprIndexN TEST_CASE_FIXTURE(Fixture, "recover_confusables") { // Binary - matchParseError("local a = 4 != 10", "Unexpected '!=', did you mean '~='?"); - matchParseError("local a = true && false", "Unexpected '&&', did you mean 'and'?"); - matchParseError("local a = false || true", "Unexpected '||', did you mean 'or'?"); + matchParseError("local a = 4 != 10", "Unexpected '!='; did you mean '~='?"); + matchParseError("local a = true && false", "Unexpected '&&'; did you mean 'and'?"); + matchParseError("local a = false || true", "Unexpected '||'; did you mean 'or'?"); // Unary - matchParseError("local a = !false", "Unexpected '!', did you mean 'not'?"); + matchParseError("local a = !false", "Unexpected '!'; did you mean 'not'?"); // Check that separate tokens are not considered as a single one matchParseError("local a = 4 ! = 10", "Expected identifier when parsing expression, got '!'"); @@ -2880,4 +2910,64 @@ TEST_CASE_FIXTURE(Fixture, "missing_default_type_pack_argument_after_variadic_ty CHECK_EQ("Expected type pack after '=', got type", result.errors[1].getMessage()); } +TEST_CASE_FIXTURE(Fixture, "table_type_keys_cant_contain_nul") +{ + ParseResult result = tryParse(R"( + type Foo = { ["\0"]: number } + )"); + + REQUIRE_EQ(1, result.errors.size()); + + CHECK_EQ(Location{{1, 21}, {1, 22}}, result.errors[0].getLocation()); + CHECK_EQ("String literal contains malformed escape sequence or \\0", result.errors[0].getMessage()); +} + +TEST_CASE_FIXTURE(Fixture, "invalid_escape_literals_get_reported_but_parsing_continues") +{ + ParseResult result = tryParse(R"( + local foo = "\xQQ" + print(foo) + )"); + + REQUIRE_EQ(1, result.errors.size()); + + CHECK_EQ(Location{{1, 20}, {1, 26}}, result.errors[0].getLocation()); + CHECK_EQ("String literal contains malformed escape sequence", result.errors[0].getMessage()); + + REQUIRE(result.root); + CHECK_EQ(result.root->body.size, 2); +} + +TEST_CASE_FIXTURE(Fixture, "unfinished_string_literals_get_reported_but_parsing_continues") +{ + ParseResult result = tryParse(R"( + local foo = "hi + print(foo) + )"); + + REQUIRE_EQ(1, result.errors.size()); + + CHECK_EQ(Location{{1, 20}, {1, 23}}, result.errors[0].getLocation()); + CHECK_EQ("Malformed string; did you forget to finish it?", result.errors[0].getMessage()); + + REQUIRE(result.root); + CHECK_EQ(result.root->body.size, 2); +} + +TEST_CASE_FIXTURE(Fixture, "unfinished_string_literal_types_get_reported_but_parsing_continues") +{ + ParseResult result = tryParse(R"( + type Foo = "hi + print(foo) + )"); + + REQUIRE_EQ(1, result.errors.size()); + + CHECK_EQ(Location{{1, 19}, {1, 22}}, result.errors[0].getLocation()); + CHECK_EQ("Malformed string; did you forget to finish it?", result.errors[0].getMessage()); + + REQUIRE(result.root); + CHECK_EQ(result.root->body.size, 2); +} + TEST_SUITE_END(); diff --git a/tests/Subtyping.test.cpp b/tests/Subtyping.test.cpp index f7f24d0f..e1fa0e5a 100644 --- a/tests/Subtyping.test.cpp +++ b/tests/Subtyping.test.cpp @@ -4,16 +4,18 @@ #include "Fixture.h" #include "Luau/Subtyping.h" +#include "Luau/TypePack.h" using namespace Luau; struct SubtypeFixture : Fixture { TypeArena arena; - InternalErrorReporter ice; + InternalErrorReporter iceReporter; UnifierSharedState sharedState{&ice}; Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}}; - Subtyping subtyping{builtinTypes, NotNull{&normalizer}}; + + Subtyping subtyping{builtinTypes, NotNull{&arena}, NotNull{&normalizer}, NotNull{&iceReporter}}; TypePackId pack(std::initializer_list tys) { @@ -45,7 +47,28 @@ struct SubtypeFixture : Fixture return arena.addType(FunctionType{pack(argHead, std::move(argTail)), pack(retHead, std::move(retTail))}); } - SubtypingGraph isSubtype(TypeId subTy, TypeId superTy) + TypeId tbl(TableType::Props&& props) + { + return arena.addType(TableType{std::move(props), std::nullopt, {}, TableState::Sealed}); + } + + TypeId cyclicTable(std::function&& cb) + { + TypeId res = arena.addType(GenericType{}); + TableType tt{}; + cb(res, &tt); + emplaceType(asMutable(res), std::move(tt)); + return res; + } + + TypeId genericT = arena.addType(GenericType{"T"}); + TypeId genericU = arena.addType(GenericType{"U"}); + + TypePackId genericAs = arena.addTypePack(GenericTypePack{"A"}); + TypePackId genericBs = arena.addTypePack(GenericTypePack{"B"}); + TypePackId genericCs = arena.addTypePack(GenericTypePack{"C"}); + + SubtypingResult isSubtype(TypeId subTy, TypeId superTy) { return subtyping.isSubtype(subTy, superTy); } @@ -57,7 +80,16 @@ struct SubtypeFixture : Fixture TypeId helloOrWorldType = arena.addType(UnionType{{helloType, worldType}}); TypeId trueOrFalseType = arena.addType(UnionType{{builtinTypes->trueType, builtinTypes->falseType}}); + // "hello" | "hello" + TypeId helloOrHelloType = arena.addType(UnionType{{helloType, helloType}}); + + // () -> () + const TypeId nothingToNothingType = fn({}, {}); + + // ("hello") -> "world" TypeId helloAndWorldType = arena.addType(IntersectionType{{helloType, worldType}}); + + // (boolean) -> true TypeId booleanAndTrueType = arena.addType(IntersectionType{{builtinTypes->booleanType, builtinTypes->trueType}}); // (number) -> string @@ -72,6 +104,24 @@ struct SubtypeFixture : Fixture {builtinTypes->stringType} ); + // (number) -> () + const TypeId numberToNothingType = fn( + {builtinTypes->numberType}, + {} + ); + + // () -> number + const TypeId nothingToNumberType = fn( + {}, + {builtinTypes->numberType} + ); + + // (number) -> number + const TypeId numberToNumberType = fn( + {builtinTypes->numberType}, + {builtinTypes->numberType} + ); + // (number) -> unknown const TypeId numberToUnknownType = fn( {builtinTypes->numberType}, @@ -120,6 +170,83 @@ struct SubtypeFixture : Fixture {builtinTypes->stringType} ); + // (...number) -> number + const TypeId numbersToNumberType = arena.addType(FunctionType{ + arena.addTypePack(VariadicTypePack{builtinTypes->numberType}), + arena.addTypePack({builtinTypes->numberType}) + }); + + // (T) -> () + const TypeId genericTToNothingType = arena.addType(FunctionType{ + {genericT}, + {}, + arena.addTypePack({genericT}), + builtinTypes->emptyTypePack + }); + + // (T) -> T + const TypeId genericTToTType = arena.addType(FunctionType{ + {genericT}, + {}, + arena.addTypePack({genericT}), + arena.addTypePack({genericT}) + }); + + // (U) -> () + const TypeId genericUToNothingType = arena.addType(FunctionType{ + {genericU}, + {}, + arena.addTypePack({genericU}), + builtinTypes->emptyTypePack + }); + + // () -> T + const TypeId genericNothingToTType = arena.addType(FunctionType{ + {genericT}, + {}, + builtinTypes->emptyTypePack, + arena.addTypePack({genericT}) + }); + + // (A...) -> A... + const TypeId genericAsToAsType = arena.addType(FunctionType{ + {}, + {genericAs}, + genericAs, + genericAs + }); + + // (A...) -> number + const TypeId genericAsToNumberType = arena.addType(FunctionType{ + {}, + {genericAs}, + genericAs, + arena.addTypePack({builtinTypes->numberType}) + }); + + // (B...) -> B... + const TypeId genericBsToBsType = arena.addType(FunctionType{ + {}, + {genericBs}, + genericBs, + genericBs + }); + + // (B...) -> C... + const TypeId genericBsToCsType = arena.addType(FunctionType{ + {}, + {genericBs, genericCs}, + genericBs, + genericCs + }); + + // () -> A... + const TypeId genericNothingToAsType = arena.addType(FunctionType{ + {}, + {genericAs}, + builtinTypes->emptyTypePack, + genericAs + }); }; #define CHECK_IS_SUBTYPE(left, right) \ @@ -127,7 +254,7 @@ struct SubtypeFixture : Fixture { \ const auto& leftTy = (left); \ const auto& rightTy = (right); \ - SubtypingGraph result = isSubtype(leftTy, rightTy); \ + SubtypingResult result = isSubtype(leftTy, rightTy); \ CHECK_MESSAGE(result.isSubtype, "Expected " << leftTy << " <: " << rightTy); \ } while (0) @@ -136,7 +263,7 @@ struct SubtypeFixture : Fixture { \ const auto& leftTy = (left); \ const auto& rightTy = (right); \ - SubtypingGraph result = isSubtype(leftTy, rightTy); \ + SubtypingResult result = isSubtype(leftTy, rightTy); \ CHECK_MESSAGE(!result.isSubtype, "Expected " << leftTy << " anyType, builtinTypes->unknownType); + SubtypingResult result = isSubtype(builtinTypes->anyType, builtinTypes->unknownType); CHECK(!result.isSubtype); CHECK(result.isErrorSuppressing); } @@ -244,6 +371,11 @@ TEST_CASE_FIXTURE(SubtypeFixture, "\"hello\" | \"world\" <: number") CHECK_IS_NOT_SUBTYPE(helloOrWorldType, builtinTypes->numberType); } +TEST_CASE_FIXTURE(SubtypeFixture, "string stringType, helloOrHelloType); +} + TEST_CASE_FIXTURE(SubtypeFixture, "true <: boolean & true") { CHECK_IS_SUBTYPE(builtinTypes->trueType, booleanAndTrueType); @@ -349,4 +481,206 @@ TEST_CASE_FIXTURE(SubtypeFixture, "(number, string) -> string () -> T <: () -> number") +{ + CHECK_IS_SUBTYPE(genericNothingToTType, nothingToNumberType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "(T) -> () <: (U) -> ()") +{ + CHECK_IS_SUBTYPE(genericTToNothingType, genericUToNothingType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "() -> number () -> T") +{ + CHECK_IS_NOT_SUBTYPE(nothingToNumberType, genericNothingToTType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "(T) -> () <: (number) -> ()") +{ + CHECK_IS_SUBTYPE(genericTToNothingType, numberToNothingType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "(T) -> T <: (number) -> number") +{ + CHECK_IS_SUBTYPE(genericTToTType, numberToNumberType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "(T) -> T string") +{ + CHECK_IS_NOT_SUBTYPE(genericTToTType, numberToStringType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "(T) -> () <: (U) -> ()") +{ + CHECK_IS_SUBTYPE(genericTToNothingType, genericUToNothingType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "(number) -> () (T) -> ()") +{ + CHECK_IS_NOT_SUBTYPE(numberToNothingType, genericTToNothingType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "(A...) -> A... <: (number) -> number") +{ + CHECK_IS_SUBTYPE(genericAsToAsType, numberToNumberType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "(number) -> number (A...) -> A...") +{ + CHECK_IS_NOT_SUBTYPE(numberToNumberType, genericAsToAsType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "(A...) -> A... <: (B...) -> B...") +{ + CHECK_IS_SUBTYPE(genericAsToAsType, genericBsToBsType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "(B...) -> C... <: (A...) -> A...") +{ + CHECK_IS_SUBTYPE(genericBsToCsType, genericAsToAsType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "(A...) -> A... (B...) -> C...") +{ + CHECK_IS_NOT_SUBTYPE(genericAsToAsType, genericBsToCsType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "(A...) -> number <: (number) -> number") +{ + CHECK_IS_SUBTYPE(genericAsToNumberType, numberToNumberType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "(number) -> number (A...) -> number") +{ + CHECK_IS_NOT_SUBTYPE(numberToNumberType, genericAsToNumberType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "(A...) -> number <: (...number) -> number") +{ + CHECK_IS_SUBTYPE(genericAsToNumberType, numbersToNumberType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "(...number) -> number (A...) -> number") +{ + CHECK_IS_NOT_SUBTYPE(numbersToNumberType, genericAsToNumberType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "() -> A... <: () -> ()") +{ + CHECK_IS_SUBTYPE(genericNothingToAsType, nothingToNothingType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "() -> () () -> A...") +{ + CHECK_IS_NOT_SUBTYPE(nothingToNothingType, genericNothingToAsType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "(A...) -> A... <: () -> ()") +{ + CHECK_IS_SUBTYPE(genericAsToAsType, nothingToNothingType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "() -> () (A...) -> A...") +{ + CHECK_IS_NOT_SUBTYPE(nothingToNothingType, genericAsToAsType); +} + + +TEST_CASE_FIXTURE(SubtypeFixture, "{} <: {}") +{ + CHECK_IS_SUBTYPE(tbl({}), tbl({})); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "{x: number} <: {}") +{ + CHECK_IS_SUBTYPE(tbl({{"x", builtinTypes->numberType}}), tbl({})); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "{x: number} numberType}}), tbl({{"x", builtinTypes->stringType}})); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "{x: number} numberType}}), tbl({{"x", builtinTypes->optionalNumberType}})); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "{x: number?} optionalNumberType}}), tbl({{"x", builtinTypes->numberType}})); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "{x: (T) -> ()} <: {x: (U) -> ()}") +{ + CHECK_IS_SUBTYPE( + tbl({{"x", genericTToNothingType}}), + tbl({{"x", genericUToNothingType}}) + ); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "t1 where t1 = {trim: (t1) -> string} <: t2 where t2 = {trim: (t2) -> string}") +{ + TypeId t1 = cyclicTable([&](TypeId ty, TableType* tt) + { + tt->props["trim"] = fn({ty}, {builtinTypes->stringType}); + }); + + TypeId t2 = cyclicTable([&](TypeId ty, TableType* tt) + { + tt->props["trim"] = fn({ty}, {builtinTypes->stringType}); + }); + + CHECK_IS_SUBTYPE(t1, t2); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "t1 where t1 = {trim: (t1) -> string} t2}") +{ + TypeId t1 = cyclicTable([&](TypeId ty, TableType* tt) + { + tt->props["trim"] = fn({ty}, {builtinTypes->stringType}); + }); + + TypeId t2 = cyclicTable([&](TypeId ty, TableType* tt) + { + tt->props["trim"] = fn({ty}, {ty}); + }); + + CHECK_IS_NOT_SUBTYPE(t1, t2); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "t1 where t1 = {trim: (t1) -> t1} string}") +{ + TypeId t1 = cyclicTable([&](TypeId ty, TableType* tt) + { + tt->props["trim"] = fn({ty}, {ty}); + }); + + TypeId t2 = cyclicTable([&](TypeId ty, TableType* tt) + { + tt->props["trim"] = fn({ty}, {builtinTypes->stringType}); + }); + + CHECK_IS_NOT_SUBTYPE(t1, t2); +} + +/* + * (A) -> A <: (X) -> X + * A can be bound to X. + * + * (A) -> A (X) -> number + * A can be bound to X, but A number (A) -> A + * Only generics on the left side can be bound. + * number (A, B) -> boolean <: (X, X) -> boolean + * It is ok to bind both A and B to X. + * + * (A, A) -> boolean (X, Y) -> boolean + * A cannot be bound to both X and Y. + */ + TEST_SUITE_END(); diff --git a/tests/TypeInfer.intersectionTypes.test.cpp b/tests/TypeInfer.intersectionTypes.test.cpp index b390a816..c285edf9 100644 --- a/tests/TypeInfer.intersectionTypes.test.cpp +++ b/tests/TypeInfer.intersectionTypes.test.cpp @@ -1017,4 +1017,54 @@ local y = x["Bar"] LUAU_REQUIRE_NO_ERRORS(result); } +TEST_CASE_FIXTURE(Fixture, "cli_80596_simplify_degenerate_intersections") +{ + ScopedFastFlag dcr{"DebugLuauDeferredConstraintResolution", true}; + + CheckResult result = check(R"( + type A = { + x: number?, + } + + type B = { + x: number?, + } + + type C = A & B + local obj: C = { + x = 3, + } + + local x: number = obj.x or 3 + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(Fixture, "cli_80596_simplify_more_realistic_intersections") +{ + ScopedFastFlag dcr{"DebugLuauDeferredConstraintResolution", true}; + + CheckResult result = check(R"( + type A = { + x: number?, + y: string?, + } + + type B = { + x: number?, + z: string?, + } + + type C = A & B + local obj: C = { + x = 3, + } + + local x: number = obj.x or 3 + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + TEST_SUITE_END(); From 551a43c4247e928ecfaa432b419495ac755c7d84 Mon Sep 17 00:00:00 2001 From: Lily Brown Date: Fri, 1 Sep 2023 10:58:27 -0700 Subject: [PATCH 12/20] Sync to upstream/release/593 (#1024) - Updated Roblox copyright to 2023 - Floor division operator `//` (implements #832) - Autocomplete now shows `end` within `do` blocks - Restore BraceType when using `Lexer::lookahead` (fixes #1019) # New typechecker - Subtyping tests between metatables and tables - Subtyping tests between string singletons and tables - Subtyping tests for class types # Native codegen - Fixed macOS test failure (wrong spill restore offset) - Fixed clobbering of non-volatile xmm registers on Windows - Fixed wrong storage location of SSA reg spills - Implemented A64 support for add/sub extended - Eliminated zextReg from A64 lowering - Remove identical table slot lookups - Propagate values from predecessor into the linear block - Disabled reuse slot optimization - Keep `LuaNode::val` check for nil when optimizing `CHECK_SLOT_MATCH` - Implemented IR translation of `table.insert` builtin - Fixed mmap error handling on macOS/Linux # Tooling - Used `|` as a column separator instead of `+` in `bench.py` - Added a `table.sort` micro-benchmark - Switched `libprotobuf-mutator` to a less problematic version --- Analysis/include/Luau/Metamethods.h | 1 + Analysis/include/Luau/Subtyping.h | 18 +- Analysis/src/AstJsonEncoder.cpp | 7 + Analysis/src/Autocomplete.cpp | 14 +- Analysis/src/ConstraintGraphBuilder.cpp | 4 +- Analysis/src/ConstraintSolver.cpp | 6 + Analysis/src/Error.cpp | 22 +- Analysis/src/Subtyping.cpp | 181 ++++++++++++++- Analysis/src/Transpiler.cpp | 13 ++ Analysis/src/TypeChecker2.cpp | 6 + Analysis/src/TypeInfer.cpp | 10 +- Ast/include/Luau/Ast.h | 8 +- Ast/include/Luau/Lexer.h | 2 + Ast/src/Ast.cpp | 8 +- Ast/src/Lexer.cpp | 58 ++++- Ast/src/Parser.cpp | 28 ++- CodeGen/include/Luau/AssemblyBuilderA64.h | 1 + CodeGen/include/Luau/IrData.h | 30 ++- CodeGen/include/Luau/IrUtils.h | 3 + CodeGen/include/Luau/RegisterA64.h | 12 - CodeGen/src/AssemblyBuilderA64.cpp | 30 ++- CodeGen/src/CodeAllocator.cpp | 6 +- CodeGen/src/CodeGenLower.h | 14 +- CodeGen/src/EmitCommonX64.cpp | 11 +- CodeGen/src/EmitCommonX64.h | 4 +- CodeGen/src/IrAnalysis.cpp | 2 +- CodeGen/src/IrBuilder.cpp | 6 + CodeGen/src/IrDump.cpp | 6 + CodeGen/src/IrLoweringA64.cpp | 147 ++++++++---- CodeGen/src/IrLoweringX64.cpp | 43 +++- CodeGen/src/IrRegAllocA64.cpp | 8 +- CodeGen/src/IrRegAllocX64.cpp | 11 +- CodeGen/src/IrTranslateBuiltins.cpp | 24 ++ CodeGen/src/IrTranslation.cpp | 18 +- CodeGen/src/IrUtils.cpp | 10 +- CodeGen/src/IrValueLocationTracking.cpp | 3 +- CodeGen/src/NativeState.cpp | 1 + CodeGen/src/NativeState.h | 1 + CodeGen/src/OptimizeConstProp.cpp | 139 +++++++++++- CodeGen/src/OptimizeFinalX64.cpp | 1 + Common/include/Luau/Bytecode.h | 14 +- Compiler/include/Luau/BytecodeBuilder.h | 1 - Compiler/src/BytecodeBuilder.cpp | 50 ++--- Compiler/src/Compiler.cpp | 13 ++ Compiler/src/ConstantFolding.cpp | 8 + Config/src/Config.cpp | 20 +- LICENSE.txt | 4 +- Makefile | 1 + VM/include/lua.h | 2 +- VM/src/lapi.cpp | 2 +- VM/src/lnumutils.h | 7 + VM/src/ltm.cpp | 1 + VM/src/ltm.h | 1 + VM/src/lvmexecute.cpp | 98 +++++++- VM/src/lvmutils.cpp | 22 +- bench/micro_tests/test_TableSort.lua | 22 ++ bench/tabulate.py | 2 +- fuzz/luau.proto | 23 +- fuzz/protoprint.cpp | 2 + tests/AssemblyBuilderA64.test.cpp | 10 + tests/Autocomplete.test.cpp | 27 +++ tests/Conformance.test.cpp | 21 ++ tests/IrBuilder.test.cpp | 130 +++++++++++ tests/Parser.test.cpp | 36 +++ tests/Subtyping.test.cpp | 246 ++++++++++++++++++++- tests/ToString.test.cpp | 8 - tests/Transpiler.test.cpp | 5 +- tests/TypeInfer.aliases.test.cpp | 5 - tests/TypeInfer.builtins.test.cpp | 2 - tests/TypeInfer.classes.test.cpp | 4 - tests/TypeInfer.functions.test.cpp | 28 --- tests/TypeInfer.generics.test.cpp | 3 - tests/TypeInfer.intersectionTypes.test.cpp | 53 ----- tests/TypeInfer.modules.test.cpp | 6 - tests/TypeInfer.operators.test.cpp | 20 ++ tests/TypeInfer.provisional.test.cpp | 41 +++- tests/TypeInfer.singletons.test.cpp | 8 - tests/TypeInfer.tables.test.cpp | 19 -- tests/TypeInfer.test.cpp | 3 - tests/TypeInfer.tryUnify.test.cpp | 2 - tests/TypeInfer.typePacks.cpp | 3 - tests/TypeInfer.unionTypes.test.cpp | 18 -- tests/conformance/basic.lua | 13 ++ tests/conformance/events.lua | 3 + tests/conformance/math.lua | 1 + tests/conformance/native.lua | 41 ++++ tests/conformance/userdata.lua | 6 + tests/conformance/vector.lua | 12 + 88 files changed, 1606 insertions(+), 378 deletions(-) create mode 100644 bench/micro_tests/test_TableSort.lua diff --git a/Analysis/include/Luau/Metamethods.h b/Analysis/include/Luau/Metamethods.h index 84b0092f..747b7201 100644 --- a/Analysis/include/Luau/Metamethods.h +++ b/Analysis/include/Luau/Metamethods.h @@ -19,6 +19,7 @@ static const std::unordered_map kBinaryOpMetamet {AstExprBinary::Op::Sub, "__sub"}, {AstExprBinary::Op::Mul, "__mul"}, {AstExprBinary::Op::Div, "__div"}, + {AstExprBinary::Op::FloorDiv, "__idiv"}, {AstExprBinary::Op::Pow, "__pow"}, {AstExprBinary::Op::Mod, "__mod"}, {AstExprBinary::Op::Concat, "__concat"}, diff --git a/Analysis/include/Luau/Subtyping.h b/Analysis/include/Luau/Subtyping.h index 8864ef81..a69952f6 100644 --- a/Analysis/include/Luau/Subtyping.h +++ b/Analysis/include/Luau/Subtyping.h @@ -2,6 +2,7 @@ #pragma once #include "Luau/Type.h" +#include "Luau/TypePack.h" #include "Luau/UnifierSharedState.h" #include @@ -14,8 +15,10 @@ template struct TryPair; struct InternalErrorReporter; +class TypeIds; class Normalizer; struct NormalizedType; +struct NormalizedClassType; struct SubtypingResult { @@ -30,6 +33,8 @@ struct SubtypingResult void andAlso(const SubtypingResult& other); void orElse(const SubtypingResult& other); + // Only negates the `isSubtype`. + static SubtypingResult negate(const SubtypingResult& result); static SubtypingResult all(const std::vector& results); static SubtypingResult any(const std::vector& results); }; @@ -63,7 +68,7 @@ struct Subtyping DenseHashMap mappedGenericPacks{nullptr}; using SeenSet = std::unordered_set, TypeIdPairHash>; - + SeenSet seenTypes; // TODO cache @@ -88,8 +93,19 @@ private: SubtypingResult isSubtype_(const SingletonType* subSingleton, const PrimitiveType* superPrim); SubtypingResult isSubtype_(const SingletonType* subSingleton, const SingletonType* superSingleton); SubtypingResult isSubtype_(const TableType* subTable, const TableType* superTable); + SubtypingResult isSubtype_(const MetatableType* subMt, const MetatableType* superMt); + SubtypingResult isSubtype_(const MetatableType* subMt, const TableType* superTable); + SubtypingResult isSubtype_(const ClassType* subClass, const ClassType* superClass); + SubtypingResult isSubtype_(const ClassType* subClass, const TableType* superTable); // Actually a class <: shape. SubtypingResult isSubtype_(const FunctionType* subFunction, const FunctionType* superFunction); + SubtypingResult isSubtype_(const PrimitiveType* subPrim, const TableType* superTable); + SubtypingResult isSubtype_(const SingletonType* subSingleton, const TableType* superTable); + SubtypingResult isSubtype_(const NormalizedType* subNorm, const NormalizedType* superNorm); + SubtypingResult isSubtype_(const NormalizedClassType& subClass, const NormalizedClassType& superClass, const TypeIds& superTables); + SubtypingResult isSubtype_(const TypeIds& subTypes, const TypeIds& superTypes); + + SubtypingResult isSubtype_(const VariadicTypePack* subVariadic, const VariadicTypePack* superVariadic); bool bindGeneric(TypeId subTp, TypeId superTp); bool bindGeneric(TypePackId subTp, TypePackId superTp); diff --git a/Analysis/src/AstJsonEncoder.cpp b/Analysis/src/AstJsonEncoder.cpp index f2943c4d..5c4f9504 100644 --- a/Analysis/src/AstJsonEncoder.cpp +++ b/Analysis/src/AstJsonEncoder.cpp @@ -8,6 +8,8 @@ #include +LUAU_FASTFLAG(LuauFloorDivision) + namespace Luau { @@ -514,6 +516,9 @@ struct AstJsonEncoder : public AstVisitor return writeString("Mul"); case AstExprBinary::Div: return writeString("Div"); + case AstExprBinary::FloorDiv: + LUAU_ASSERT(FFlag::LuauFloorDivision); + return writeString("FloorDiv"); case AstExprBinary::Mod: return writeString("Mod"); case AstExprBinary::Pow: @@ -536,6 +541,8 @@ struct AstJsonEncoder : public AstVisitor return writeString("And"); case AstExprBinary::Or: return writeString("Or"); + default: + LUAU_ASSERT(!"Unknown Op"); } } diff --git a/Analysis/src/Autocomplete.cpp b/Analysis/src/Autocomplete.cpp index 471fd006..baeac469 100644 --- a/Analysis/src/Autocomplete.cpp +++ b/Analysis/src/Autocomplete.cpp @@ -15,6 +15,7 @@ LUAU_FASTFLAG(DebugLuauReadWriteProperties) LUAU_FASTFLAGVARIABLE(LuauAnonymousAutofilled1, false); LUAU_FASTFLAGVARIABLE(LuauAutocompleteLastTypecheck, false) +LUAU_FASTFLAGVARIABLE(LuauAutocompleteDoEnd, false) LUAU_FASTFLAGVARIABLE(LuauAutocompleteStringLiteralBounds, false); static const std::unordered_set kStatementStartingKeywords = { @@ -1089,14 +1090,19 @@ static AutocompleteEntryMap autocompleteStatement( { if (AstStatForIn* statForIn = (*it)->as(); statForIn && !statForIn->hasEnd) result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword}); - if (AstStatFor* statFor = (*it)->as(); statFor && !statFor->hasEnd) + else if (AstStatFor* statFor = (*it)->as(); statFor && !statFor->hasEnd) result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword}); - if (AstStatIf* statIf = (*it)->as(); statIf && !statIf->hasEnd) + else if (AstStatIf* statIf = (*it)->as(); statIf && !statIf->hasEnd) result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword}); - if (AstStatWhile* statWhile = (*it)->as(); statWhile && !statWhile->hasEnd) + else if (AstStatWhile* statWhile = (*it)->as(); statWhile && !statWhile->hasEnd) result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword}); - if (AstExprFunction* exprFunction = (*it)->as(); exprFunction && !exprFunction->hasEnd) + else if (AstExprFunction* exprFunction = (*it)->as(); exprFunction && !exprFunction->hasEnd) result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword}); + if (FFlag::LuauAutocompleteDoEnd) + { + if (AstStatBlock* exprBlock = (*it)->as(); exprBlock && !exprBlock->hasEnd) + result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword}); + } } if (ancestry.size() >= 2) diff --git a/Analysis/src/ConstraintGraphBuilder.cpp b/Analysis/src/ConstraintGraphBuilder.cpp index 7d35ebc6..d2413adb 100644 --- a/Analysis/src/ConstraintGraphBuilder.cpp +++ b/Analysis/src/ConstraintGraphBuilder.cpp @@ -24,6 +24,7 @@ LUAU_FASTINT(LuauCheckRecursionLimit); LUAU_FASTFLAG(DebugLuauLogSolverToJson); LUAU_FASTFLAG(DebugLuauMagicTypes); LUAU_FASTFLAG(LuauParseDeclareClassIndexer); +LUAU_FASTFLAG(LuauFloorDivision); namespace Luau { @@ -1170,7 +1171,8 @@ static bool isMetamethod(const Name& name) { return name == "__index" || name == "__newindex" || name == "__call" || name == "__concat" || name == "__unm" || name == "__add" || name == "__sub" || name == "__mul" || name == "__div" || name == "__mod" || name == "__pow" || name == "__tostring" || - name == "__metatable" || name == "__eq" || name == "__lt" || name == "__le" || name == "__mode" || name == "__iter" || name == "__len"; + name == "__metatable" || name == "__eq" || name == "__lt" || name == "__le" || name == "__mode" || name == "__iter" || name == "__len" || + (FFlag::LuauFloorDivision && name == "__idiv"); } ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatDeclareClass* declaredClass) diff --git a/Analysis/src/ConstraintSolver.cpp b/Analysis/src/ConstraintSolver.cpp index 78dc0d54..fd0c1c00 100644 --- a/Analysis/src/ConstraintSolver.cpp +++ b/Analysis/src/ConstraintSolver.cpp @@ -22,6 +22,7 @@ #include "Luau/VisitType.h" LUAU_FASTFLAGVARIABLE(DebugLuauLogSolver, false); +LUAU_FASTFLAG(LuauFloorDivision); namespace Luau { @@ -719,6 +720,8 @@ bool ConstraintSolver::tryDispatch(const BinaryConstraint& c, NotNullnormalize(leftType); if (hasTypeInIntersection(leftType) && force) asMutable(leftType)->ty.emplace(anyPresent ? builtinTypes->anyType : builtinTypes->numberType); diff --git a/Analysis/src/Error.cpp b/Analysis/src/Error.cpp index 615ce41e..4505f627 100644 --- a/Analysis/src/Error.cpp +++ b/Analysis/src/Error.cpp @@ -12,7 +12,6 @@ #include #include -LUAU_FASTFLAGVARIABLE(LuauIndentTypeMismatch, false) LUAU_FASTINTVARIABLE(LuauIndentTypeMismatchMaxTypeLength, 10) static std::string wrongNumberOfArgsString( @@ -94,31 +93,18 @@ struct ErrorConverter { std::string givenModuleName = fileResolver->getHumanReadableModuleName(*givenDefinitionModule); std::string wantedModuleName = fileResolver->getHumanReadableModuleName(*wantedDefinitionModule); - if (FFlag::LuauIndentTypeMismatch) - result = constructErrorMessage(givenTypeName, wantedTypeName, givenModuleName, wantedModuleName); - else - result = "Type '" + givenTypeName + "' from '" + givenModuleName + "' could not be converted into '" + wantedTypeName + - "' from '" + wantedModuleName + "'"; + result = constructErrorMessage(givenTypeName, wantedTypeName, givenModuleName, wantedModuleName); } else { - if (FFlag::LuauIndentTypeMismatch) - result = constructErrorMessage(givenTypeName, wantedTypeName, *givenDefinitionModule, *wantedDefinitionModule); - else - result = "Type '" + givenTypeName + "' from '" + *givenDefinitionModule + "' could not be converted into '" + - wantedTypeName + "' from '" + *wantedDefinitionModule + "'"; + result = constructErrorMessage(givenTypeName, wantedTypeName, *givenDefinitionModule, *wantedDefinitionModule); } } } } if (result.empty()) - { - if (FFlag::LuauIndentTypeMismatch) - result = constructErrorMessage(givenTypeName, wantedTypeName, std::nullopt, std::nullopt); - else - result = "Type '" + givenTypeName + "' could not be converted into '" + wantedTypeName + "'"; - } + result = constructErrorMessage(givenTypeName, wantedTypeName, std::nullopt, std::nullopt); if (tm.error) @@ -126,7 +112,7 @@ struct ErrorConverter result += "\ncaused by:\n "; if (!tm.reason.empty()) - result += tm.reason + (FFlag::LuauIndentTypeMismatch ? " \n" : " "); + result += tm.reason + " \n"; result += Luau::toString(*tm.error, TypeErrorToStringOptions{fileResolver}); } diff --git a/Analysis/src/Subtyping.cpp b/Analysis/src/Subtyping.cpp index b55686b9..3f3c9319 100644 --- a/Analysis/src/Subtyping.cpp +++ b/Analysis/src/Subtyping.cpp @@ -58,6 +58,15 @@ void SubtypingResult::orElse(const SubtypingResult& other) normalizationTooComplex |= other.normalizationTooComplex; } +SubtypingResult SubtypingResult::negate(const SubtypingResult& result) +{ + return SubtypingResult{ + !result.isSubtype, + result.isErrorSuppressing, + result.normalizationTooComplex, + }; +} + SubtypingResult SubtypingResult::all(const std::vector& results) { SubtypingResult acc{true, false}; @@ -137,10 +146,10 @@ SubtypingResult Subtyping::isSubtype_(TypeId subTy, TypeId superTy) SeenSetPopper ssp{&seenTypes, typePair}; - if (auto superUnion = get(superTy)) - return isSubtype_(subTy, superUnion); - else if (auto subUnion = get(subTy)) + if (auto subUnion = get(subTy)) return isSubtype_(subUnion, superTy); + else if (auto superUnion = get(superTy)) + return isSubtype_(subTy, superUnion); else if (auto superIntersection = get(superTy)) return isSubtype_(subTy, superIntersection); else if (auto subIntersection = get(subTy)) @@ -196,6 +205,18 @@ SubtypingResult Subtyping::isSubtype_(TypeId subTy, TypeId superTy) return isSubtype_(p); else if (auto p = get2(subTy, superTy)) return isSubtype_(p); + else if (auto p = get2(subTy, superTy)) + return isSubtype_(p); + else if (auto p = get2(subTy, superTy)) + return isSubtype_(p); + else if (auto p = get2(subTy, superTy)) + return isSubtype_(p); + else if (auto p = get2(subTy, superTy)) + return isSubtype_(p); + else if (auto p = get2(subTy, superTy)) + return isSubtype_(p); + else if (auto p = get2(subTy, superTy)) + return isSubtype_(p); return {false}; } @@ -323,7 +344,7 @@ SubtypingResult Subtyping::isSubtype_(TypePackId subTp, TypePackId superTp) { if (auto p = get2(*subTail, *superTail)) { - results.push_back(isSubtype_(p.first->ty, p.second->ty)); + results.push_back(isSubtype_(p)); } else if (auto p = get2(*subTail, *superTail)) { @@ -472,7 +493,6 @@ SubtypingResult Subtyping::isSubtype_(TypeId subTy, const IntersectionType* supe SubtypingResult Subtyping::isSubtype_(const IntersectionType* subIntersection, TypeId superTy) { - // TODO: Semantic subtyping here. // As per TAPL: A & B <: T iff A <: T || B <: T std::vector subtypings; for (TypeId ty : subIntersection) @@ -520,6 +540,59 @@ SubtypingResult Subtyping::isSubtype_(const TableType* subTable, const TableType return result; } +SubtypingResult Subtyping::isSubtype_(const MetatableType* subMt, const MetatableType* superMt) +{ + return SubtypingResult::all({ + isSubtype_(subMt->table, superMt->table), + isSubtype_(subMt->metatable, superMt->metatable), + }); +} + +SubtypingResult Subtyping::isSubtype_(const MetatableType* subMt, const TableType* superTable) +{ + if (auto subTable = get(subMt->table)) { + // Metatables cannot erase properties from the table they're attached to, so + // the subtyping rule for this is just if the table component is a subtype + // of the supertype table. + // + // There's a flaw here in that if the __index metamethod contributes a new + // field that would satisfy the subtyping relationship, we'll erronously say + // that the metatable isn't a subtype of the table, even though they have + // compatible properties/shapes. We'll revisit this later when we have a + // better understanding of how important this is. + return isSubtype_(subTable, superTable); + } + else + { + // TODO: This may be a case we actually hit? + return {false}; + } +} + +SubtypingResult Subtyping::isSubtype_(const ClassType* subClass, const ClassType* superClass) +{ + return {isSubclass(subClass, superClass)}; +} + +SubtypingResult Subtyping::isSubtype_(const ClassType* subClass, const TableType* superTable) +{ + SubtypingResult result{true}; + + for (const auto& [name, prop]: superTable->props) + { + if (auto classProp = lookupClassProp(subClass, name)) + { + // Table properties are invariant + result.andAlso(isSubtype_(classProp->type(), prop.type())); + result.andAlso(isSubtype_(prop.type(), classProp->type())); + } + else + return SubtypingResult{false}; + } + + return result; +} + SubtypingResult Subtyping::isSubtype_(const FunctionType* subFunction, const FunctionType* superFunction) { SubtypingResult result; @@ -533,6 +606,47 @@ SubtypingResult Subtyping::isSubtype_(const FunctionType* subFunction, const Fun return result; } +SubtypingResult Subtyping::isSubtype_(const PrimitiveType* subPrim, const TableType* superTable) +{ + SubtypingResult result{false}; + if (subPrim->type == PrimitiveType::String) + { + if (auto metatable = getMetatable(builtinTypes->stringType, builtinTypes)) + { + if (auto mttv = get(follow(metatable))) + { + if (auto it = mttv->props.find("__index"); it != mttv->props.end()) + { + if (auto stringTable = get(it->second.type())) + result.orElse(isSubtype_(stringTable, superTable)); + } + } + } + } + + return result; +} + +SubtypingResult Subtyping::isSubtype_(const SingletonType* subSingleton, const TableType* superTable) +{ + SubtypingResult result{false}; + if (auto stringleton = get(subSingleton)) + { + if (auto metatable = getMetatable(builtinTypes->stringType, builtinTypes)) + { + if (auto mttv = get(follow(metatable))) + { + if (auto it = mttv->props.find("__index"); it != mttv->props.end()) + { + if (auto stringTable = get(it->second.type())) + result.orElse(isSubtype_(stringTable, superTable)); + } + } + } + } + return result; +} + SubtypingResult Subtyping::isSubtype_(const NormalizedType* subNorm, const NormalizedType* superNorm) { if (!subNorm || !superNorm) @@ -540,15 +654,14 @@ SubtypingResult Subtyping::isSubtype_(const NormalizedType* subNorm, const Norma SubtypingResult result = isSubtype_(subNorm->tops, superNorm->tops); result.andAlso(isSubtype_(subNorm->booleans, superNorm->booleans)); - // isSubtype_(subNorm->classes, superNorm->classes); - // isSubtype_(subNorm->classes, superNorm->tables); + result.andAlso(isSubtype_(subNorm->classes, superNorm->classes, superNorm->tables)); result.andAlso(isSubtype_(subNorm->errors, superNorm->errors)); result.andAlso(isSubtype_(subNorm->nils, superNorm->nils)); result.andAlso(isSubtype_(subNorm->numbers, superNorm->numbers)); result.isSubtype &= Luau::isSubtype(subNorm->strings, superNorm->strings); // isSubtype_(subNorm->strings, superNorm->tables); result.andAlso(isSubtype_(subNorm->threads, superNorm->threads)); - // isSubtype_(subNorm->tables, superNorm->tables); + result.andAlso(isSubtype_(subNorm->tables, superNorm->tables)); // isSubtype_(subNorm->tables, superNorm->strings); // isSubtype_(subNorm->tables, superNorm->classes); // isSubtype_(subNorm->functions, superNorm->functions); @@ -557,6 +670,58 @@ SubtypingResult Subtyping::isSubtype_(const NormalizedType* subNorm, const Norma return result; } +SubtypingResult Subtyping::isSubtype_(const NormalizedClassType& subClass, const NormalizedClassType& superClass, const TypeIds& superTables) +{ + for (const auto& [subClassTy, _] : subClass.classes) + { + SubtypingResult result; + + for (const auto& [superClassTy, superNegations] : superClass.classes) + { + result.orElse(isSubtype_(subClassTy, superClassTy)); + if (!result.isSubtype) + continue; + + for (TypeId negation : superNegations) + { + result.andAlso(SubtypingResult::negate(isSubtype_(subClassTy, negation))); + if (result.isSubtype) + break; + } + } + + if (result.isSubtype) + continue; + + for (TypeId superTableTy : superTables) + result.orElse(isSubtype_(subClassTy, superTableTy)); + + if (!result.isSubtype) + return result; + } + + return {true}; +} + +SubtypingResult Subtyping::isSubtype_(const TypeIds& subTypes, const TypeIds& superTypes) +{ + std::vector results; + + for (TypeId subTy : subTypes) + { + results.emplace_back(); + for (TypeId superTy : superTypes) + results.back().orElse(isSubtype_(subTy, superTy)); + } + + return SubtypingResult::all(results); +} + +SubtypingResult Subtyping::isSubtype_(const VariadicTypePack* subVariadic, const VariadicTypePack* superVariadic) +{ + return isSubtype_(subVariadic->ty, superVariadic->ty); +} + bool Subtyping::bindGeneric(TypeId subTy, TypeId superTy) { if (variance == Variance::Covariant) diff --git a/Analysis/src/Transpiler.cpp b/Analysis/src/Transpiler.cpp index cdfe6549..8fd40772 100644 --- a/Analysis/src/Transpiler.cpp +++ b/Analysis/src/Transpiler.cpp @@ -10,6 +10,8 @@ #include #include +LUAU_FASTFLAG(LuauFloorDivision) + namespace { bool isIdentifierStartChar(char c) @@ -467,10 +469,13 @@ struct Printer case AstExprBinary::Sub: case AstExprBinary::Mul: case AstExprBinary::Div: + case AstExprBinary::FloorDiv: case AstExprBinary::Mod: case AstExprBinary::Pow: case AstExprBinary::CompareLt: case AstExprBinary::CompareGt: + LUAU_ASSERT(FFlag::LuauFloorDivision || a->op != AstExprBinary::FloorDiv); + writer.maybeSpace(a->right->location.begin, 2); writer.symbol(toString(a->op)); break; @@ -487,6 +492,8 @@ struct Printer writer.maybeSpace(a->right->location.begin, 4); writer.keyword(toString(a->op)); break; + default: + LUAU_ASSERT(!"Unknown Op"); } visualize(*a->right); @@ -753,6 +760,12 @@ struct Printer writer.maybeSpace(a->value->location.begin, 2); writer.symbol("/="); break; + case AstExprBinary::FloorDiv: + LUAU_ASSERT(FFlag::LuauFloorDivision); + + writer.maybeSpace(a->value->location.begin, 2); + writer.symbol("//="); + break; case AstExprBinary::Mod: writer.maybeSpace(a->value->location.begin, 2); writer.symbol("%="); diff --git a/Analysis/src/TypeChecker2.cpp b/Analysis/src/TypeChecker2.cpp index a7653b7c..65da9dfa 100644 --- a/Analysis/src/TypeChecker2.cpp +++ b/Analysis/src/TypeChecker2.cpp @@ -23,6 +23,7 @@ #include LUAU_FASTFLAG(DebugLuauMagicTypes) +LUAU_FASTFLAG(LuauFloorDivision); namespace Luau { @@ -1817,6 +1818,8 @@ struct TypeChecker2 bool typesHaveIntersection = normalizer.isIntersectionInhabited(leftType, rightType); if (auto it = kBinaryOpMetamethods.find(expr->op); it != kBinaryOpMetamethods.end()) { + LUAU_ASSERT(FFlag::LuauFloorDivision || expr->op != AstExprBinary::Op::FloorDiv); + std::optional leftMt = getMetatable(leftType, builtinTypes); std::optional rightMt = getMetatable(rightType, builtinTypes); bool matches = leftMt == rightMt; @@ -2002,8 +2005,11 @@ struct TypeChecker2 case AstExprBinary::Op::Sub: case AstExprBinary::Op::Mul: case AstExprBinary::Op::Div: + case AstExprBinary::Op::FloorDiv: case AstExprBinary::Op::Pow: case AstExprBinary::Op::Mod: + LUAU_ASSERT(FFlag::LuauFloorDivision || expr->op != AstExprBinary::Op::FloorDiv); + reportErrors(tryUnify(scope, expr->left->location, leftType, builtinTypes->numberType)); reportErrors(tryUnify(scope, expr->right->location, rightType, builtinTypes->numberType)); diff --git a/Analysis/src/TypeInfer.cpp b/Analysis/src/TypeInfer.cpp index 569f9720..5349f16a 100644 --- a/Analysis/src/TypeInfer.cpp +++ b/Analysis/src/TypeInfer.cpp @@ -40,6 +40,7 @@ LUAU_FASTFLAG(LuauOccursIsntAlwaysFailure) LUAU_FASTFLAGVARIABLE(LuauTinyControlFlowAnalysis, false) LUAU_FASTFLAGVARIABLE(LuauAlwaysCommitInferencesOfFunctionCalls, false) LUAU_FASTFLAG(LuauParseDeclareClassIndexer) +LUAU_FASTFLAG(LuauFloorDivision); namespace Luau { @@ -200,7 +201,8 @@ static bool isMetamethod(const Name& name) { return name == "__index" || name == "__newindex" || name == "__call" || name == "__concat" || name == "__unm" || name == "__add" || name == "__sub" || name == "__mul" || name == "__div" || name == "__mod" || name == "__pow" || name == "__tostring" || - name == "__metatable" || name == "__eq" || name == "__lt" || name == "__le" || name == "__mode" || name == "__iter" || name == "__len"; + name == "__metatable" || name == "__eq" || name == "__lt" || name == "__le" || name == "__mode" || name == "__iter" || name == "__len" || + (FFlag::LuauFloorDivision && name == "__idiv"); } size_t HashBoolNamePair::operator()(const std::pair& pair) const @@ -2571,6 +2573,9 @@ std::string opToMetaTableEntry(const AstExprBinary::Op& op) return "__mul"; case AstExprBinary::Div: return "__div"; + case AstExprBinary::FloorDiv: + LUAU_ASSERT(FFlag::LuauFloorDivision); + return "__idiv"; case AstExprBinary::Mod: return "__mod"; case AstExprBinary::Pow: @@ -3064,8 +3069,11 @@ TypeId TypeChecker::checkBinaryOperation( case AstExprBinary::Sub: case AstExprBinary::Mul: case AstExprBinary::Div: + case AstExprBinary::FloorDiv: case AstExprBinary::Mod: case AstExprBinary::Pow: + LUAU_ASSERT(FFlag::LuauFloorDivision || expr.op != AstExprBinary::FloorDiv); + reportErrors(tryUnify(lhsType, numberType, scope, expr.left->location)); reportErrors(tryUnify(rhsType, numberType, scope, expr.right->location)); return numberType; diff --git a/Ast/include/Luau/Ast.h b/Ast/include/Luau/Ast.h index 7478e15d..b60fec28 100644 --- a/Ast/include/Luau/Ast.h +++ b/Ast/include/Luau/Ast.h @@ -457,6 +457,7 @@ public: Sub, Mul, Div, + FloorDiv, Mod, Pow, Concat, @@ -467,7 +468,9 @@ public: CompareGt, CompareGe, And, - Or + Or, + + Op__Count }; AstExprBinary(const Location& location, Op op, AstExpr* left, AstExpr* right); @@ -531,11 +534,12 @@ class AstStatBlock : public AstStat public: LUAU_RTTI(AstStatBlock) - AstStatBlock(const Location& location, const AstArray& body); + AstStatBlock(const Location& location, const AstArray& body, bool hasEnd=true); void visit(AstVisitor* visitor) override; AstArray body; + bool hasEnd = false; }; class AstStatIf : public AstStat diff --git a/Ast/include/Luau/Lexer.h b/Ast/include/Luau/Lexer.h index e1415183..7d15212a 100644 --- a/Ast/include/Luau/Lexer.h +++ b/Ast/include/Luau/Lexer.h @@ -62,6 +62,7 @@ struct Lexeme Dot3, SkinnyArrow, DoubleColon, + FloorDiv, InterpStringBegin, InterpStringMid, @@ -73,6 +74,7 @@ struct Lexeme SubAssign, MulAssign, DivAssign, + FloorDivAssign, ModAssign, PowAssign, ConcatAssign, diff --git a/Ast/src/Ast.cpp b/Ast/src/Ast.cpp index 9b3acb7f..52b77de3 100644 --- a/Ast/src/Ast.cpp +++ b/Ast/src/Ast.cpp @@ -3,6 +3,8 @@ #include "Luau/Common.h" +LUAU_FASTFLAG(LuauFloorDivision) + namespace Luau { @@ -279,6 +281,9 @@ std::string toString(AstExprBinary::Op op) return "*"; case AstExprBinary::Div: return "/"; + case AstExprBinary::FloorDiv: + LUAU_ASSERT(FFlag::LuauFloorDivision); + return "//"; case AstExprBinary::Mod: return "%"; case AstExprBinary::Pow: @@ -375,9 +380,10 @@ void AstExprError::visit(AstVisitor* visitor) } } -AstStatBlock::AstStatBlock(const Location& location, const AstArray& body) +AstStatBlock::AstStatBlock(const Location& location, const AstArray& body, bool hasEnd) : AstStat(ClassIndex(), location) , body(body) + , hasEnd(hasEnd) { } diff --git a/Ast/src/Lexer.cpp b/Ast/src/Lexer.cpp index fe32e2a1..894d2dd7 100644 --- a/Ast/src/Lexer.cpp +++ b/Ast/src/Lexer.cpp @@ -6,7 +6,9 @@ #include +LUAU_FASTFLAGVARIABLE(LuauFloorDivision, false) LUAU_FASTFLAGVARIABLE(LuauLexerConsumeFast, false) +LUAU_FASTFLAGVARIABLE(LuauLexerLookaheadRemembersBraceType, false) namespace Luau { @@ -138,6 +140,9 @@ std::string Lexeme::toString() const case DoubleColon: return "'::'"; + case FloorDiv: + return FFlag::LuauFloorDivision ? "'//'" : ""; + case AddAssign: return "'+='"; @@ -150,6 +155,9 @@ std::string Lexeme::toString() const case DivAssign: return "'/='"; + case FloorDivAssign: + return FFlag::LuauFloorDivision ? "'//='" : ""; + case ModAssign: return "'%='"; @@ -402,6 +410,8 @@ Lexeme Lexer::lookahead() unsigned int currentLineOffset = lineOffset; Lexeme currentLexeme = lexeme; Location currentPrevLocation = prevLocation; + size_t currentBraceStackSize = braceStack.size(); + BraceType currentBraceType = braceStack.empty() ? BraceType::Normal : braceStack.back(); Lexeme result = next(); @@ -410,6 +420,13 @@ Lexeme Lexer::lookahead() lineOffset = currentLineOffset; lexeme = currentLexeme; prevLocation = currentPrevLocation; + if (FFlag::LuauLexerLookaheadRemembersBraceType) + { + if (braceStack.size() < currentBraceStackSize) + braceStack.push_back(currentBraceType); + else if (braceStack.size() > currentBraceStackSize) + braceStack.pop_back(); + } return result; } @@ -901,15 +918,46 @@ Lexeme Lexer::readNext() return Lexeme(Location(start, 1), '+'); case '/': - consume(); - - if (peekch() == '=') + { + if (FFlag::LuauFloorDivision) { consume(); - return Lexeme(Location(start, 2), Lexeme::DivAssign); + + char ch = peekch(); + + if (ch == '=') + { + consume(); + return Lexeme(Location(start, 2), Lexeme::DivAssign); + } + else if (ch == '/') + { + consume(); + + if (peekch() == '=') + { + consume(); + return Lexeme(Location(start, 3), Lexeme::FloorDivAssign); + } + else + return Lexeme(Location(start, 2), Lexeme::FloorDiv); + } + else + return Lexeme(Location(start, 1), '/'); } else - return Lexeme(Location(start, 1), '/'); + { + consume(); + + if (peekch() == '=') + { + consume(); + return Lexeme(Location(start, 2), Lexeme::DivAssign); + } + else + return Lexeme(Location(start, 1), '/'); + } + } case '*': consume(); diff --git a/Ast/src/Parser.cpp b/Ast/src/Parser.cpp index 20186dfc..d59b6b40 100644 --- a/Ast/src/Parser.cpp +++ b/Ast/src/Parser.cpp @@ -14,6 +14,7 @@ LUAU_FASTINTVARIABLE(LuauRecursionLimit, 1000) LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100) LUAU_FASTFLAGVARIABLE(LuauParseDeclareClassIndexer, false) +LUAU_FASTFLAG(LuauFloorDivision) namespace Luau { @@ -460,11 +461,11 @@ AstStat* Parser::parseDo() Lexeme matchDo = lexer.current(); nextLexeme(); // do - AstStat* body = parseBlock(); + AstStatBlock* body = parseBlock(); body->location.begin = start.begin; - expectMatchEndAndConsume(Lexeme::ReservedEnd, matchDo); + body->hasEnd = expectMatchEndAndConsume(Lexeme::ReservedEnd, matchDo); return body; } @@ -1766,6 +1767,12 @@ std::optional Parser::parseBinaryOp(const Lexeme& l) return AstExprBinary::Mul; else if (l.type == '/') return AstExprBinary::Div; + else if (l.type == Lexeme::FloorDiv) + { + LUAU_ASSERT(FFlag::LuauFloorDivision); + + return AstExprBinary::FloorDiv; + } else if (l.type == '%') return AstExprBinary::Mod; else if (l.type == '^') @@ -1802,6 +1809,12 @@ std::optional Parser::parseCompoundOp(const Lexeme& l) return AstExprBinary::Mul; else if (l.type == Lexeme::DivAssign) return AstExprBinary::Div; + else if (l.type == Lexeme::FloorDivAssign) + { + LUAU_ASSERT(FFlag::LuauFloorDivision); + + return AstExprBinary::FloorDiv; + } else if (l.type == Lexeme::ModAssign) return AstExprBinary::Mod; else if (l.type == Lexeme::PowAssign) @@ -1872,12 +1885,13 @@ std::optional Parser::checkBinaryConfusables(const BinaryOpPr AstExpr* Parser::parseExpr(unsigned int limit) { static const BinaryOpPriority binaryPriority[] = { - {6, 6}, {6, 6}, {7, 7}, {7, 7}, {7, 7}, // `+' `-' `*' `/' `%' - {10, 9}, {5, 4}, // power and concat (right associative) - {3, 3}, {3, 3}, // equality and inequality - {3, 3}, {3, 3}, {3, 3}, {3, 3}, // order - {2, 2}, {1, 1} // logical (and/or) + {6, 6}, {6, 6}, {7, 7}, {7, 7}, {7, 7}, {7, 7}, // `+' `-' `*' `/' `//' `%' + {10, 9}, {5, 4}, // power and concat (right associative) + {3, 3}, {3, 3}, // equality and inequality + {3, 3}, {3, 3}, {3, 3}, {3, 3}, // order + {2, 2}, {1, 1} // logical (and/or) }; + static_assert(sizeof(binaryPriority) / sizeof(binaryPriority[0]) == size_t(AstExprBinary::Op__Count), "binaryPriority needs an entry per op"); unsigned int recursionCounterOld = recursionCounter; diff --git a/CodeGen/include/Luau/AssemblyBuilderA64.h b/CodeGen/include/Luau/AssemblyBuilderA64.h index 3fc37d1d..931003b3 100644 --- a/CodeGen/include/Luau/AssemblyBuilderA64.h +++ b/CodeGen/include/Luau/AssemblyBuilderA64.h @@ -221,6 +221,7 @@ private: void placeFMOV(const char* name, RegisterA64 dst, double src, uint32_t op); void placeBM(const char* name, RegisterA64 dst, RegisterA64 src1, uint32_t src2, uint8_t op); void placeBFM(const char* name, RegisterA64 dst, RegisterA64 src1, int src2, uint8_t op, int immr, int imms); + void placeER(const char* name, RegisterA64 dst, RegisterA64 src1, RegisterA64 src2, uint8_t op, int shift); void place(uint32_t word); diff --git a/CodeGen/include/Luau/IrData.h b/CodeGen/include/Luau/IrData.h index e8e56d19..12465906 100644 --- a/CodeGen/include/Luau/IrData.h +++ b/CodeGen/include/Luau/IrData.h @@ -67,6 +67,7 @@ enum class IrCmd : uint8_t // Get pointer (LuaNode) to table node element at the active cached slot index // A: pointer (Table) // B: unsigned int (pcpos) + // C: Kn GET_SLOT_NODE_ADDR, // Get pointer (LuaNode) to table node element at the main position of the specified key hash @@ -132,6 +133,7 @@ enum class IrCmd : uint8_t SUB_NUM, MUL_NUM, DIV_NUM, + IDIV_NUM, MOD_NUM, // Get the minimum/maximum of two numbers @@ -253,6 +255,11 @@ enum class IrCmd : uint8_t // A: pointer (Table) DUP_TABLE, + // Insert an integer key into a table + // A: pointer (Table) + // B: int (key) + TABLE_SETNUM, + // Try to convert a double number into a table index (int) or jump if it's not an integer // A: double // B: block @@ -411,6 +418,12 @@ enum class IrCmd : uint8_t // When undef is specified instead of a block, execution is aborted on check failure CHECK_NODE_NO_NEXT, + // Guard against table node with 'nil' value + // A: pointer (LuaNode) + // B: block/vmexit/undef + // When undef is specified instead of a block, execution is aborted on check failure + CHECK_NODE_VALUE, + // Special operations // Check interrupt handler @@ -832,6 +845,8 @@ struct IrBlock uint32_t finish = ~0u; uint32_t sortkey = ~0u; + uint32_t chainkey = 0; + uint32_t expectedNextBlock = ~0u; Label label; }; @@ -993,23 +1008,26 @@ struct IrFunction valueRestoreOps[instIdx] = location; } - IrOp findRestoreOp(uint32_t instIdx) const + IrOp findRestoreOp(uint32_t instIdx, bool limitToCurrentBlock) const { if (instIdx >= valueRestoreOps.size()) return {}; const IrBlock& block = blocks[validRestoreOpBlockIdx]; - // Values can only reference restore operands in the current block - if (instIdx < block.start || instIdx > block.finish) - return {}; + // When spilled, values can only reference restore operands in the current block + if (limitToCurrentBlock) + { + if (instIdx < block.start || instIdx > block.finish) + return {}; + } return valueRestoreOps[instIdx]; } - IrOp findRestoreOp(const IrInst& inst) const + IrOp findRestoreOp(const IrInst& inst, bool limitToCurrentBlock) const { - return findRestoreOp(getInstIndex(inst)); + return findRestoreOp(getInstIndex(inst), limitToCurrentBlock); } }; diff --git a/CodeGen/include/Luau/IrUtils.h b/CodeGen/include/Luau/IrUtils.h index 9c077914..3def51a8 100644 --- a/CodeGen/include/Luau/IrUtils.h +++ b/CodeGen/include/Luau/IrUtils.h @@ -128,6 +128,7 @@ inline bool isNonTerminatingJump(IrCmd cmd) case IrCmd::CHECK_ARRAY_SIZE: case IrCmd::CHECK_SLOT_MATCH: case IrCmd::CHECK_NODE_NO_NEXT: + case IrCmd::CHECK_NODE_VALUE: return true; default: break; @@ -156,6 +157,7 @@ inline bool hasResult(IrCmd cmd) case IrCmd::SUB_NUM: case IrCmd::MUL_NUM: case IrCmd::DIV_NUM: + case IrCmd::IDIV_NUM: case IrCmd::MOD_NUM: case IrCmd::MIN_NUM: case IrCmd::MAX_NUM: @@ -168,6 +170,7 @@ inline bool hasResult(IrCmd cmd) case IrCmd::NOT_ANY: case IrCmd::CMP_ANY: case IrCmd::TABLE_LEN: + case IrCmd::TABLE_SETNUM: case IrCmd::STRING_LEN: case IrCmd::NEW_TABLE: case IrCmd::DUP_TABLE: diff --git a/CodeGen/include/Luau/RegisterA64.h b/CodeGen/include/Luau/RegisterA64.h index d50369e3..beb34ca7 100644 --- a/CodeGen/include/Luau/RegisterA64.h +++ b/CodeGen/include/Luau/RegisterA64.h @@ -47,18 +47,6 @@ constexpr RegisterA64 castReg(KindA64 kind, RegisterA64 reg) return RegisterA64{kind, reg.index}; } -// This is equivalent to castReg(KindA64::x), but is separate because it implies different semantics -// Specifically, there are cases when it's useful to treat a wN register as an xN register *after* it has been assigned a value -// Since all A64 instructions that write to wN implicitly zero the top half, this works when we need zero extension semantics -// Crucially, this is *not* safe on an ABI boundary - an int parameter in wN register may have anything in its top half in certain cases -// However, as long as our codegen doesn't use 32-bit truncation by using castReg x=>w, we can safely rely on this. -constexpr RegisterA64 zextReg(RegisterA64 reg) -{ - LUAU_ASSERT(reg.kind == KindA64::w); - - return RegisterA64{KindA64::x, reg.index}; -} - constexpr RegisterA64 noreg{KindA64::none, 0}; constexpr RegisterA64 w0{KindA64::w, 0}; diff --git a/CodeGen/src/AssemblyBuilderA64.cpp b/CodeGen/src/AssemblyBuilderA64.cpp index e2e713ee..f385cd0a 100644 --- a/CodeGen/src/AssemblyBuilderA64.cpp +++ b/CodeGen/src/AssemblyBuilderA64.cpp @@ -105,7 +105,10 @@ void AssemblyBuilderA64::movk(RegisterA64 dst, uint16_t src, int shift) void AssemblyBuilderA64::add(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2, int shift) { - placeSR3("add", dst, src1, src2, 0b00'01011, shift); + if (src1.kind == KindA64::x && src2.kind == KindA64::w) + placeER("add", dst, src1, src2, 0b00'01011, shift); + else + placeSR3("add", dst, src1, src2, 0b00'01011, shift); } void AssemblyBuilderA64::add(RegisterA64 dst, RegisterA64 src1, uint16_t src2) @@ -115,7 +118,10 @@ void AssemblyBuilderA64::add(RegisterA64 dst, RegisterA64 src1, uint16_t src2) void AssemblyBuilderA64::sub(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2, int shift) { - placeSR3("sub", dst, src1, src2, 0b10'01011, shift); + if (src1.kind == KindA64::x && src2.kind == KindA64::w) + placeER("sub", dst, src1, src2, 0b10'01011, shift); + else + placeSR3("sub", dst, src1, src2, 0b10'01011, shift); } void AssemblyBuilderA64::sub(RegisterA64 dst, RegisterA64 src1, uint16_t src2) @@ -1075,6 +1081,22 @@ void AssemblyBuilderA64::placeBFM(const char* name, RegisterA64 dst, RegisterA64 commit(); } +void AssemblyBuilderA64::placeER(const char* name, RegisterA64 dst, RegisterA64 src1, RegisterA64 src2, uint8_t op, int shift) +{ + if (logText) + log(name, dst, src1, src2, shift); + + LUAU_ASSERT(dst.kind == KindA64::x && src1.kind == KindA64::x); + LUAU_ASSERT(src2.kind == KindA64::w); + LUAU_ASSERT(shift >= 0 && shift <= 4); + + uint32_t sf = (dst.kind == KindA64::x) ? 0x80000000 : 0; // could be useful in the future for byte->word extends + int option = 0b010; // UXTW + + place(dst.index | (src1.index << 5) | (shift << 10) | (option << 13) | (src2.index << 16) | (1 << 21) | (op << 24) | sf); + commit(); +} + void AssemblyBuilderA64::place(uint32_t word) { LUAU_ASSERT(codePos < codeEnd); @@ -1167,7 +1189,9 @@ void AssemblyBuilderA64::log(const char* opcode, RegisterA64 dst, RegisterA64 sr log(src1); text.append(","); log(src2); - if (shift > 0) + if (src1.kind == KindA64::x && src2.kind == KindA64::w) + logAppend(" UXTW #%d", shift); + else if (shift > 0) logAppend(" LSL #%d", shift); else if (shift < 0) logAppend(" LSR #%d", -shift); diff --git a/CodeGen/src/CodeAllocator.cpp b/CodeGen/src/CodeAllocator.cpp index fe45b9a4..84e48a1b 100644 --- a/CodeGen/src/CodeAllocator.cpp +++ b/CodeGen/src/CodeAllocator.cpp @@ -71,10 +71,12 @@ static uint8_t* allocatePagesImpl(size_t size) LUAU_ASSERT(size == alignToPageSize(size)); #ifdef __APPLE__ - return (uint8_t*)mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON | MAP_JIT, -1, 0); + void* result = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON | MAP_JIT, -1, 0); #else - return (uint8_t*)mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); + void* result = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); #endif + + return (result == MAP_FAILED) ? nullptr : static_cast(result); } static void freePagesImpl(uint8_t* mem, size_t size) diff --git a/CodeGen/src/CodeGenLower.h b/CodeGen/src/CodeGenLower.h index 171a8c0e..24e1c38c 100644 --- a/CodeGen/src/CodeGenLower.h +++ b/CodeGen/src/CodeGenLower.h @@ -74,7 +74,11 @@ inline bool lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction& return (a.kind == IrBlockKind::Fallback) < (b.kind == IrBlockKind::Fallback); // Try to order by instruction order - return a.sortkey < b.sortkey; + if (a.sortkey != b.sortkey) + return a.sortkey < b.sortkey; + + // Chains of blocks are merged together by having the same sort key and consecutive chain key + return a.chainkey < b.chainkey; }); // For each IR instruction that begins a bytecode instruction, which bytecode instruction is it? @@ -100,6 +104,9 @@ inline bool lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction& IrBlock dummy; dummy.start = ~0u; + // Make sure entry block is first + LUAU_ASSERT(sortedBlocks[0] == 0); + for (size_t i = 0; i < sortedBlocks.size(); ++i) { uint32_t blockIndex = sortedBlocks[i]; @@ -137,6 +144,11 @@ inline bool lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction& IrBlock& nextBlock = getNextBlock(function, sortedBlocks, dummy, i); + // Optimizations often propagate information between blocks + // To make sure the register and spill state is correct when blocks are lowered, we check that sorted block order matches the expected one + if (block.expectedNextBlock != ~0u) + LUAU_ASSERT(function.getBlockIndex(nextBlock) == block.expectedNextBlock); + for (uint32_t index = block.start; index <= block.finish; index++) { LUAU_ASSERT(index < function.instructions.size()); diff --git a/CodeGen/src/EmitCommonX64.cpp b/CodeGen/src/EmitCommonX64.cpp index c4da5467..c831c0bc 100644 --- a/CodeGen/src/EmitCommonX64.cpp +++ b/CodeGen/src/EmitCommonX64.cpp @@ -147,13 +147,14 @@ void callSetTable(IrRegAllocX64& regs, AssemblyBuilderX64& build, int rb, Operan emitUpdateBase(build); } -void checkObjectBarrierConditions(AssemblyBuilderX64& build, RegisterX64 tmp, RegisterX64 object, int ra, int ratag, Label& skip) +void checkObjectBarrierConditions(AssemblyBuilderX64& build, RegisterX64 tmp, RegisterX64 object, IrOp ra, int ratag, Label& skip) { // Barrier should've been optimized away if we know that it's not collectable, checking for correctness if (ratag == -1 || !isGCO(ratag)) { // iscollectable(ra) - build.cmp(luauRegTag(ra), LUA_TSTRING); + OperandX64 tag = (ra.kind == IrOpKind::VmReg) ? luauRegTag(vmRegOp(ra)) : luauConstantTag(vmConstOp(ra)); + build.cmp(tag, LUA_TSTRING); build.jcc(ConditionX64::Less, skip); } @@ -162,12 +163,14 @@ void checkObjectBarrierConditions(AssemblyBuilderX64& build, RegisterX64 tmp, Re build.jcc(ConditionX64::Zero, skip); // iswhite(gcvalue(ra)) - build.mov(tmp, luauRegValue(ra)); + OperandX64 value = (ra.kind == IrOpKind::VmReg) ? luauRegValue(vmRegOp(ra)) : luauConstantValue(vmConstOp(ra)); + build.mov(tmp, value); build.test(byte[tmp + offsetof(GCheader, marked)], bit2mask(WHITE0BIT, WHITE1BIT)); build.jcc(ConditionX64::Zero, skip); } -void callBarrierObject(IrRegAllocX64& regs, AssemblyBuilderX64& build, RegisterX64 object, IrOp objectOp, int ra, int ratag) + +void callBarrierObject(IrRegAllocX64& regs, AssemblyBuilderX64& build, RegisterX64 object, IrOp objectOp, IrOp ra, int ratag) { Label skip; diff --git a/CodeGen/src/EmitCommonX64.h b/CodeGen/src/EmitCommonX64.h index dd9b082b..3288a164 100644 --- a/CodeGen/src/EmitCommonX64.h +++ b/CodeGen/src/EmitCommonX64.h @@ -202,8 +202,8 @@ void callArithHelper(IrRegAllocX64& regs, AssemblyBuilderX64& build, int ra, int void callLengthHelper(IrRegAllocX64& regs, AssemblyBuilderX64& build, int ra, int rb); void callGetTable(IrRegAllocX64& regs, AssemblyBuilderX64& build, int rb, OperandX64 c, int ra); void callSetTable(IrRegAllocX64& regs, AssemblyBuilderX64& build, int rb, OperandX64 c, int ra); -void checkObjectBarrierConditions(AssemblyBuilderX64& build, RegisterX64 tmp, RegisterX64 object, int ra, int ratag, Label& skip); -void callBarrierObject(IrRegAllocX64& regs, AssemblyBuilderX64& build, RegisterX64 object, IrOp objectOp, int ra, int ratag); +void checkObjectBarrierConditions(AssemblyBuilderX64& build, RegisterX64 tmp, RegisterX64 object, IrOp ra, int ratag, Label& skip); +void callBarrierObject(IrRegAllocX64& regs, AssemblyBuilderX64& build, RegisterX64 object, IrOp objectOp, IrOp ra, int ratag); void callBarrierTableFast(IrRegAllocX64& regs, AssemblyBuilderX64& build, RegisterX64 table, IrOp tableOp); void callStepGc(IrRegAllocX64& regs, AssemblyBuilderX64& build); diff --git a/CodeGen/src/IrAnalysis.cpp b/CodeGen/src/IrAnalysis.cpp index eb4630dd..b14f1470 100644 --- a/CodeGen/src/IrAnalysis.cpp +++ b/CodeGen/src/IrAnalysis.cpp @@ -257,7 +257,7 @@ static void visitVmRegDefsUses(T& visitor, IrFunction& function, const IrBlock& break; case IrCmd::BARRIER_OBJ: case IrCmd::BARRIER_TABLE_FORWARD: - visitor.use(inst.b); + visitor.maybeUse(inst.b); break; case IrCmd::CLOSE_UPVALS: // Closing an upvalue should be counted as a register use (it copies the fresh register value) diff --git a/CodeGen/src/IrBuilder.cpp b/CodeGen/src/IrBuilder.cpp index aebc0ba7..3ee82c76 100644 --- a/CodeGen/src/IrBuilder.cpp +++ b/CodeGen/src/IrBuilder.cpp @@ -333,6 +333,9 @@ void IrBuilder::translateInst(LuauOpcode op, const Instruction* pc, int i) case LOP_DIV: translateInstBinary(*this, pc, i, TM_DIV); break; + case LOP_IDIV: + translateInstBinary(*this, pc, i, TM_IDIV); + break; case LOP_MOD: translateInstBinary(*this, pc, i, TM_MOD); break; @@ -351,6 +354,9 @@ void IrBuilder::translateInst(LuauOpcode op, const Instruction* pc, int i) case LOP_DIVK: translateInstBinaryK(*this, pc, i, TM_DIV); break; + case LOP_IDIVK: + translateInstBinaryK(*this, pc, i, TM_IDIV); + break; case LOP_MODK: translateInstBinaryK(*this, pc, i, TM_MOD); break; diff --git a/CodeGen/src/IrDump.cpp b/CodeGen/src/IrDump.cpp index 67f77b64..7ed1a295 100644 --- a/CodeGen/src/IrDump.cpp +++ b/CodeGen/src/IrDump.cpp @@ -125,6 +125,8 @@ const char* getCmdName(IrCmd cmd) return "MUL_NUM"; case IrCmd::DIV_NUM: return "DIV_NUM"; + case IrCmd::IDIV_NUM: + return "IDIV_NUM"; case IrCmd::MOD_NUM: return "MOD_NUM"; case IrCmd::MIN_NUM: @@ -169,6 +171,8 @@ const char* getCmdName(IrCmd cmd) return "JUMP_SLOT_MATCH"; case IrCmd::TABLE_LEN: return "TABLE_LEN"; + case IrCmd::TABLE_SETNUM: + return "TABLE_SETNUM"; case IrCmd::STRING_LEN: return "STRING_LEN"; case IrCmd::NEW_TABLE: @@ -229,6 +233,8 @@ const char* getCmdName(IrCmd cmd) return "CHECK_SLOT_MATCH"; case IrCmd::CHECK_NODE_NO_NEXT: return "CHECK_NODE_NO_NEXT"; + case IrCmd::CHECK_NODE_VALUE: + return "CHECK_NODE_VALUE"; case IrCmd::INTERRUPT: return "INTERRUPT"; case IrCmd::CHECK_GC: diff --git a/CodeGen/src/IrLoweringA64.cpp b/CodeGen/src/IrLoweringA64.cpp index d944a766..4369d120 100644 --- a/CodeGen/src/IrLoweringA64.cpp +++ b/CodeGen/src/IrLoweringA64.cpp @@ -58,30 +58,6 @@ inline ConditionA64 getConditionFP(IrCondition cond) } } -static void checkObjectBarrierConditions(AssemblyBuilderA64& build, RegisterA64 object, RegisterA64 temp, int ra, int ratag, Label& skip) -{ - RegisterA64 tempw = castReg(KindA64::w, temp); - - // Barrier should've been optimized away if we know that it's not collectable, checking for correctness - if (ratag == -1 || !isGCO(ratag)) - { - // iscollectable(ra) - build.ldr(tempw, mem(rBase, ra * sizeof(TValue) + offsetof(TValue, tt))); - build.cmp(tempw, LUA_TSTRING); - build.b(ConditionA64::Less, skip); - } - - // isblack(obj2gco(o)) - build.ldrb(tempw, mem(object, offsetof(GCheader, marked))); - build.tbz(tempw, BLACKBIT, skip); - - // iswhite(gcvalue(ra)) - build.ldr(temp, mem(rBase, ra * sizeof(TValue) + offsetof(TValue, value))); - build.ldrb(tempw, mem(temp, offsetof(GCheader, marked))); - build.tst(tempw, bit2mask(WHITE0BIT, WHITE1BIT)); - build.b(ConditionA64::Equal, skip); // Equal = Zero after tst -} - static void emitAddOffset(AssemblyBuilderA64& build, RegisterA64 dst, RegisterA64 src, size_t offset) { LUAU_ASSERT(dst != src); @@ -98,6 +74,47 @@ static void emitAddOffset(AssemblyBuilderA64& build, RegisterA64 dst, RegisterA6 } } +static void checkObjectBarrierConditions(AssemblyBuilderA64& build, RegisterA64 object, RegisterA64 temp, IrOp ra, int ratag, Label& skip) +{ + RegisterA64 tempw = castReg(KindA64::w, temp); + AddressA64 addr = temp; + + // iscollectable(ra) + if (ratag == -1 || !isGCO(ratag)) + { + if (ra.kind == IrOpKind::VmReg) + { + addr = mem(rBase, vmRegOp(ra) * sizeof(TValue) + offsetof(TValue, tt)); + } + else if (ra.kind == IrOpKind::VmConst) + { + emitAddOffset(build, temp, rConstants, vmConstOp(ra) * sizeof(TValue) + offsetof(TValue, tt)); + } + + build.ldr(tempw, addr); + build.cmp(tempw, LUA_TSTRING); + build.b(ConditionA64::Less, skip); + } + + // isblack(obj2gco(o)) + build.ldrb(tempw, mem(object, offsetof(GCheader, marked))); + build.tbz(tempw, BLACKBIT, skip); + + // iswhite(gcvalue(ra)) + if (ra.kind == IrOpKind::VmReg) + { + addr = mem(rBase, vmRegOp(ra) * sizeof(TValue) + offsetof(TValue, value)); + } + else if (ra.kind == IrOpKind::VmConst) + { + emitAddOffset(build, temp, rConstants, vmConstOp(ra) * sizeof(TValue) + offsetof(TValue, value)); + } + build.ldr(temp, addr); + build.ldrb(tempw, mem(temp, offsetof(GCheader, marked))); + build.tst(tempw, bit2mask(WHITE0BIT, WHITE1BIT)); + build.b(ConditionA64::Equal, skip); // Equal = Zero after tst +} + static void emitAbort(AssemblyBuilderA64& build, Label& abort) { Label skip; @@ -242,7 +259,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) if (inst.b.kind == IrOpKind::Inst) { - build.add(inst.regA64, inst.regA64, zextReg(regOp(inst.b)), kTValueSizeLog2); + build.add(inst.regA64, inst.regA64, regOp(inst.b), kTValueSizeLog2); } else if (inst.b.kind == IrOpKind::Constant) { @@ -271,6 +288,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) RegisterA64 temp1 = regs.allocTemp(KindA64::x); RegisterA64 temp1w = castReg(KindA64::w, temp1); RegisterA64 temp2 = regs.allocTemp(KindA64::w); + RegisterA64 temp2x = castReg(KindA64::x, temp2); // note: since the stride of the load is the same as the destination register size, we can range check the array index, not the byte offset if (uintOp(inst.b) <= AddressA64::kMaxOffset) @@ -288,7 +306,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) // note: this may clobber inst.a, so it's important that we don't use it after this build.ldr(inst.regA64, mem(regOp(inst.a), offsetof(Table, node))); - build.add(inst.regA64, inst.regA64, zextReg(temp2), kLuaNodeSizeLog2); + build.add(inst.regA64, inst.regA64, temp2x, kLuaNodeSizeLog2); // "zero extend" temp2 to get a larger shift (top 32 bits are zero) break; } case IrCmd::GET_HASH_NODE_ADDR: @@ -296,6 +314,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) inst.regA64 = regs.allocReuse(KindA64::x, index, {inst.a}); RegisterA64 temp1 = regs.allocTemp(KindA64::w); RegisterA64 temp2 = regs.allocTemp(KindA64::w); + RegisterA64 temp2x = castReg(KindA64::x, temp2); // hash & ((1 << lsizenode) - 1) == hash & ~(-1 << lsizenode) build.mov(temp1, -1); @@ -306,7 +325,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) // note: this may clobber inst.a, so it's important that we don't use it after this build.ldr(inst.regA64, mem(regOp(inst.a), offsetof(Table, node))); - build.add(inst.regA64, inst.regA64, zextReg(temp2), kLuaNodeSizeLog2); + build.add(inst.regA64, inst.regA64, temp2x, kLuaNodeSizeLog2); // "zero extend" temp2 to get a larger shift (top 32 bits are zero) break; } case IrCmd::GET_CLOSURE_UPVAL_ADDR: @@ -477,6 +496,15 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) build.fdiv(inst.regA64, temp1, temp2); break; } + case IrCmd::IDIV_NUM: + { + inst.regA64 = regs.allocReuse(KindA64::d, index, {inst.a, inst.b}); + RegisterA64 temp1 = tempDouble(inst.a); + RegisterA64 temp2 = tempDouble(inst.b); + build.fdiv(inst.regA64, temp1, temp2); + build.frintm(inst.regA64, inst.regA64); + break; + } case IrCmd::MOD_NUM: { inst.regA64 = regs.allocReg(KindA64::d, index); // can't allocReuse because both A and B are used twice @@ -604,9 +632,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) emitUpdateBase(build); - // since w0 came from a call, we need to move it so that we don't violate zextReg safety contract - inst.regA64 = regs.allocReg(KindA64::w, index); - build.mov(inst.regA64, w0); + inst.regA64 = regs.takeReg(w0, index); break; } case IrCmd::JUMP: @@ -750,8 +776,8 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) build.mov(x0, reg); build.ldr(x1, mem(rNativeContext, offsetof(NativeContext, luaH_getn))); build.blr(x1); - inst.regA64 = regs.allocReg(KindA64::d, index); - build.scvtf(inst.regA64, w0); + + inst.regA64 = regs.takeReg(w0, index); break; } case IrCmd::STRING_LEN: @@ -761,6 +787,33 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) build.ldr(inst.regA64, mem(regOp(inst.a), offsetof(TString, len))); break; } + case IrCmd::TABLE_SETNUM: + { + // note: we need to call regOp before spill so that we don't do redundant reloads + RegisterA64 table = regOp(inst.a); + RegisterA64 key = regOp(inst.b); + RegisterA64 temp = regs.allocTemp(KindA64::w); + + regs.spill(build, index, {table, key}); + + if (w1 != key) + { + build.mov(x1, table); + build.mov(w2, key); + } + else + { + build.mov(temp, w1); + build.mov(x1, table); + build.mov(w2, temp); + } + + build.mov(x0, rState); + build.ldr(x3, mem(rNativeContext, offsetof(NativeContext, luaH_setnum))); + build.blr(x3); + inst.regA64 = regs.takeReg(x0, index); + break; + } case IrCmd::NEW_TABLE: { regs.spill(build, index); @@ -854,8 +907,6 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) inst.regA64 = regs.allocReg(KindA64::w, index); RegisterA64 temp = tempDouble(inst.a); build.fcvtzs(castReg(KindA64::x, inst.regA64), temp); - // truncation needs to clear high bits to preserve zextReg safety contract - build.mov(inst.regA64, inst.regA64); break; } case IrCmd::ADJUST_STACK_TO_REG: @@ -870,7 +921,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) else if (inst.b.kind == IrOpKind::Inst) { build.add(temp, rBase, uint16_t(vmRegOp(inst.a) * sizeof(TValue))); - build.add(temp, temp, zextReg(regOp(inst.b)), kTValueSizeLog2); + build.add(temp, temp, regOp(inst.b), kTValueSizeLog2); build.str(temp, mem(rState, offsetof(lua_State, top))); } else @@ -919,9 +970,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) build.ldr(x6, mem(rNativeContext, offsetof(NativeContext, luauF_table) + uintOp(inst.a) * sizeof(luau_FastFunction))); build.blr(x6); - // since w0 came from a call, we need to move it so that we don't violate zextReg safety contract - inst.regA64 = regs.allocReg(KindA64::w, index); - build.mov(inst.regA64, w0); + inst.regA64 = regs.takeReg(w0, index); break; } case IrCmd::CHECK_FASTCALL_RES: @@ -1063,7 +1112,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) if (inst.c.kind == IrOpKind::Undef || isGCO(tagOp(inst.c))) { Label skip; - checkObjectBarrierConditions(build, temp1, temp2, vmRegOp(inst.b), inst.c.kind == IrOpKind::Undef ? -1 : tagOp(inst.c), skip); + checkObjectBarrierConditions(build, temp1, temp2, inst.b, inst.c.kind == IrOpKind::Undef ? -1 : tagOp(inst.c), skip); size_t spills = regs.spill(build, index, {temp1}); @@ -1244,6 +1293,17 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) finalizeTargetLabel(inst.b, fresh); break; } + case IrCmd::CHECK_NODE_VALUE: + { + Label fresh; // used when guard aborts execution or jumps to a VM exit + RegisterA64 temp = regs.allocTemp(KindA64::w); + + build.ldr(temp, mem(regOp(inst.a), offsetof(LuaNode, val.tt))); + LUAU_ASSERT(LUA_TNIL == 0); + build.cbz(temp, getTargetLabel(inst.b, fresh)); + finalizeTargetLabel(inst.b, fresh); + break; + } case IrCmd::INTERRUPT: { regs.spill(build, index); @@ -1288,7 +1348,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) RegisterA64 temp = regs.allocTemp(KindA64::x); Label skip; - checkObjectBarrierConditions(build, regOp(inst.a), temp, vmRegOp(inst.b), inst.c.kind == IrOpKind::Undef ? -1 : tagOp(inst.c), skip); + checkObjectBarrierConditions(build, regOp(inst.a), temp, inst.b, inst.c.kind == IrOpKind::Undef ? -1 : tagOp(inst.c), skip); RegisterA64 reg = regOp(inst.a); // note: we need to call regOp before spill so that we don't do redundant reloads size_t spills = regs.spill(build, index, {reg}); @@ -1332,13 +1392,14 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) RegisterA64 temp = regs.allocTemp(KindA64::x); Label skip; - checkObjectBarrierConditions(build, regOp(inst.a), temp, vmRegOp(inst.b), inst.c.kind == IrOpKind::Undef ? -1 : tagOp(inst.c), skip); + checkObjectBarrierConditions(build, regOp(inst.a), temp, inst.b, inst.c.kind == IrOpKind::Undef ? -1 : tagOp(inst.c), skip); RegisterA64 reg = regOp(inst.a); // note: we need to call regOp before spill so that we don't do redundant reloads + AddressA64 addr = tempAddr(inst.b, offsetof(TValue, value)); size_t spills = regs.spill(build, index, {reg}); build.mov(x1, reg); build.mov(x0, rState); - build.ldr(x2, mem(rBase, vmRegOp(inst.b) * sizeof(TValue) + offsetof(TValue, value))); + build.ldr(x2, addr); build.ldr(x3, mem(rNativeContext, offsetof(NativeContext, luaC_barriertable))); build.blr(x3); @@ -1829,7 +1890,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) LUAU_ASSERT(sizeof(TString*) == 8); if (inst.a.kind == IrOpKind::Inst) - build.add(inst.regA64, rGlobalState, zextReg(regOp(inst.a)), 3); + build.add(inst.regA64, rGlobalState, regOp(inst.a), 3); else if (inst.a.kind == IrOpKind::Constant) build.add(inst.regA64, rGlobalState, uint16_t(tagOp(inst.a)) * 8); else diff --git a/CodeGen/src/IrLoweringX64.cpp b/CodeGen/src/IrLoweringX64.cpp index 2a436d54..261f5717 100644 --- a/CodeGen/src/IrLoweringX64.cpp +++ b/CodeGen/src/IrLoweringX64.cpp @@ -407,6 +407,22 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) build.vdivsd(inst.regX64, regOp(inst.a), memRegDoubleOp(inst.b)); } break; + case IrCmd::IDIV_NUM: + inst.regX64 = regs.allocRegOrReuse(SizeX64::xmmword, index, {inst.a, inst.b}); + + if (inst.a.kind == IrOpKind::Constant) + { + ScopedRegX64 tmp{regs, SizeX64::xmmword}; + + build.vmovsd(tmp.reg, memRegDoubleOp(inst.a)); + build.vdivsd(inst.regX64, tmp.reg, memRegDoubleOp(inst.b)); + } + else + { + build.vdivsd(inst.regX64, regOp(inst.a), memRegDoubleOp(inst.b)); + } + build.vroundsd(inst.regX64, inst.regX64, inst.regX64, RoundingModeX64::RoundToNegativeInfinity); + break; case IrCmd::MOD_NUM: { inst.regX64 = regs.allocRegOrReuse(SizeX64::xmmword, index, {inst.a, inst.b}); @@ -697,9 +713,17 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) IrCallWrapperX64 callWrap(regs, build, index); callWrap.addArgument(SizeX64::qword, regOp(inst.a), inst.a); callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaH_getn)]); - - inst.regX64 = regs.allocReg(SizeX64::xmmword, index); - build.vcvtsi2sd(inst.regX64, inst.regX64, eax); + inst.regX64 = regs.takeReg(eax, index); + break; + } + case IrCmd::TABLE_SETNUM: + { + IrCallWrapperX64 callWrap(regs, build, index); + callWrap.addArgument(SizeX64::qword, rState); + callWrap.addArgument(SizeX64::qword, regOp(inst.a), inst.a); + callWrap.addArgument(SizeX64::dword, regOp(inst.b), inst.b); + callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaH_setnum)]); + inst.regX64 = regs.takeReg(rax, index); break; } case IrCmd::STRING_LEN: @@ -997,7 +1021,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) tmp1.free(); if (inst.c.kind == IrOpKind::Undef || isGCO(tagOp(inst.c))) - callBarrierObject(regs, build, tmp2.release(), {}, vmRegOp(inst.b), inst.c.kind == IrOpKind::Undef ? -1 : tagOp(inst.c)); + callBarrierObject(regs, build, tmp2.release(), {}, inst.b, inst.c.kind == IrOpKind::Undef ? -1 : tagOp(inst.c)); break; } case IrCmd::CHECK_TAG: @@ -1106,6 +1130,12 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) jumpOrAbortOnUndef(ConditionX64::NotZero, inst.b, next); break; } + case IrCmd::CHECK_NODE_VALUE: + { + build.cmp(dword[regOp(inst.a) + offsetof(LuaNode, val) + offsetof(TValue, tt)], LUA_TNIL); + jumpOrAbortOnUndef(ConditionX64::Equal, inst.b, next); + break; + } case IrCmd::INTERRUPT: { unsigned pcpos = uintOp(inst.a); @@ -1132,7 +1162,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) callStepGc(regs, build); break; case IrCmd::BARRIER_OBJ: - callBarrierObject(regs, build, regOp(inst.a), inst.a, vmRegOp(inst.b), inst.c.kind == IrOpKind::Undef ? -1 : tagOp(inst.c)); + callBarrierObject(regs, build, regOp(inst.a), inst.a, inst.b, inst.c.kind == IrOpKind::Undef ? -1 : tagOp(inst.c)); break; case IrCmd::BARRIER_TABLE_BACK: callBarrierTableFast(regs, build, regOp(inst.a), inst.a); @@ -1142,7 +1172,8 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) Label skip; ScopedRegX64 tmp{regs, SizeX64::qword}; - checkObjectBarrierConditions(build, tmp.reg, regOp(inst.a), vmRegOp(inst.b), inst.c.kind == IrOpKind::Undef ? -1 : tagOp(inst.c), skip); + + checkObjectBarrierConditions(build, tmp.reg, regOp(inst.a), inst.b, inst.c.kind == IrOpKind::Undef ? -1 : tagOp(inst.c), skip); { ScopedSpills spillGuard(regs); diff --git a/CodeGen/src/IrRegAllocA64.cpp b/CodeGen/src/IrRegAllocA64.cpp index 5afcc8dd..f552c17f 100644 --- a/CodeGen/src/IrRegAllocA64.cpp +++ b/CodeGen/src/IrRegAllocA64.cpp @@ -70,9 +70,9 @@ static int getReloadOffset(IrCmd cmd) LUAU_UNREACHABLE(); } -static AddressA64 getReloadAddress(const IrFunction& function, const IrInst& inst) +static AddressA64 getReloadAddress(const IrFunction& function, const IrInst& inst, bool limitToCurrentBlock) { - IrOp location = function.findRestoreOp(inst); + IrOp location = function.findRestoreOp(inst, limitToCurrentBlock); if (location.kind == IrOpKind::VmReg) return mem(rBase, vmRegOp(location) * sizeof(TValue) + getReloadOffset(inst.cmd)); @@ -99,7 +99,7 @@ static void restoreInst(AssemblyBuilderA64& build, uint32_t& freeSpillSlots, IrF else { LUAU_ASSERT(!inst.spilled && inst.needsReload); - AddressA64 addr = getReloadAddress(function, function.instructions[s.inst]); + AddressA64 addr = getReloadAddress(function, function.instructions[s.inst], /*limitToCurrentBlock*/ false); LUAU_ASSERT(addr.base != xzr); build.ldr(reg, addr); } @@ -321,7 +321,7 @@ size_t IrRegAllocA64::spill(AssemblyBuilderA64& build, uint32_t index, std::init { // instead of spilling the register to never reload it, we assume the register is not needed anymore } - else if (getReloadAddress(function, def).base != xzr) + else if (getReloadAddress(function, def, /*limitToCurrentBlock*/ true).base != xzr) { // instead of spilling the register to stack, we can reload it from VM stack/constants // we still need to record the spill for restore(start) to work diff --git a/CodeGen/src/IrRegAllocX64.cpp b/CodeGen/src/IrRegAllocX64.cpp index 091def39..7690f69a 100644 --- a/CodeGen/src/IrRegAllocX64.cpp +++ b/CodeGen/src/IrRegAllocX64.cpp @@ -338,7 +338,9 @@ unsigned IrRegAllocX64::findSpillStackSlot(IrValueKind valueKind) IrOp IrRegAllocX64::getRestoreOp(const IrInst& inst) const { - if (IrOp location = function.findRestoreOp(inst); location.kind == IrOpKind::VmReg || location.kind == IrOpKind::VmConst) + // When restoring the value, we allow cross-block restore because we have commited to the target location at spill time + if (IrOp location = function.findRestoreOp(inst, /*limitToCurrentBlock*/ false); + location.kind == IrOpKind::VmReg || location.kind == IrOpKind::VmConst) return location; return IrOp(); @@ -346,11 +348,16 @@ IrOp IrRegAllocX64::getRestoreOp(const IrInst& inst) const bool IrRegAllocX64::hasRestoreOp(const IrInst& inst) const { - return getRestoreOp(inst).kind != IrOpKind::None; + // When checking if value has a restore operation to spill it, we only allow it in the same block + IrOp location = function.findRestoreOp(inst, /*limitToCurrentBlock*/ true); + + return location.kind == IrOpKind::VmReg || location.kind == IrOpKind::VmConst; } OperandX64 IrRegAllocX64::getRestoreAddress(const IrInst& inst, IrOp restoreOp) { + LUAU_ASSERT(restoreOp.kind != IrOpKind::None); + switch (getCmdValueKind(inst.cmd)) { case IrValueKind::Unknown: diff --git a/CodeGen/src/IrTranslateBuiltins.cpp b/CodeGen/src/IrTranslateBuiltins.cpp index 8392ad84..8513f786 100644 --- a/CodeGen/src/IrTranslateBuiltins.cpp +++ b/CodeGen/src/IrTranslateBuiltins.cpp @@ -748,6 +748,28 @@ static BuiltinImplResult translateBuiltinVector(IrBuilder& build, int nparams, i return {BuiltinImplType::Full, 1}; } +static BuiltinImplResult translateBuiltinTableInsert(IrBuilder& build, int nparams, int ra, int arg, IrOp args, int nresults, int pcpos) +{ + if (nparams != 2 || nresults > 0) + return {BuiltinImplType::None, -1}; + + build.loadAndCheckTag(build.vmReg(arg), LUA_TTABLE, build.vmExit(pcpos)); + + IrOp table = build.inst(IrCmd::LOAD_POINTER, build.vmReg(arg)); + build.inst(IrCmd::CHECK_READONLY, table, build.vmExit(pcpos)); + + IrOp pos = build.inst(IrCmd::ADD_INT, build.inst(IrCmd::TABLE_LEN, table), build.constInt(1)); + + IrOp setnum = build.inst(IrCmd::TABLE_SETNUM, table, pos); + + IrOp va = build.inst(IrCmd::LOAD_TVALUE, args); + build.inst(IrCmd::STORE_TVALUE, setnum, va); + + build.inst(IrCmd::BARRIER_TABLE_FORWARD, table, args, build.undef()); + + return {BuiltinImplType::Full, 0}; +} + static BuiltinImplResult translateBuiltinStringLen(IrBuilder& build, int nparams, int ra, int arg, IrOp args, int nresults, int pcpos) { if (nparams < 1 || nresults > 1) @@ -849,6 +871,8 @@ BuiltinImplResult translateBuiltin(IrBuilder& build, int bfid, int ra, int arg, return translateBuiltinTypeof(build, nparams, ra, arg, args, nresults); case LBF_VECTOR: return translateBuiltinVector(build, nparams, ra, arg, args, nresults, pcpos); + case LBF_TABLE_INSERT: + return translateBuiltinTableInsert(build, nparams, ra, arg, args, nresults, pcpos); case LBF_STRING_LEN: return translateBuiltinStringLen(build, nparams, ra, arg, args, nresults, pcpos); default: diff --git a/CodeGen/src/IrTranslation.cpp b/CodeGen/src/IrTranslation.cpp index 5a92132f..26ad727a 100644 --- a/CodeGen/src/IrTranslation.cpp +++ b/CodeGen/src/IrTranslation.cpp @@ -382,6 +382,9 @@ static void translateInstBinaryNumeric(IrBuilder& build, int ra, int rb, int rc, case TM_DIV: result = build.inst(IrCmd::DIV_NUM, vb, vc); break; + case TM_IDIV: + result = build.inst(IrCmd::IDIV_NUM, vb, vc); + break; case TM_MOD: result = build.inst(IrCmd::MOD_NUM, vb, vc); break; @@ -472,8 +475,9 @@ void translateInstLength(IrBuilder& build, const Instruction* pc, int pcpos) build.inst(IrCmd::CHECK_NO_METATABLE, vb, fallback); IrOp va = build.inst(IrCmd::TABLE_LEN, vb); + IrOp vai = build.inst(IrCmd::INT_TO_NUM, va); - build.inst(IrCmd::STORE_DOUBLE, build.vmReg(ra), va); + build.inst(IrCmd::STORE_DOUBLE, build.vmReg(ra), vai); build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TNUMBER)); IrOp next = build.blockAtInst(pcpos + 1); @@ -554,7 +558,7 @@ IrOp translateFastCallN(IrBuilder& build, const Instruction* pc, int pcpos, bool IrOp builtinArgs = args; - if (customArgs.kind == IrOpKind::VmConst) + if (customArgs.kind == IrOpKind::VmConst && bfid != LBF_TABLE_INSERT) { TValue protok = build.function.proto->k[customArgs.index]; @@ -976,7 +980,7 @@ void translateInstGetTableKS(IrBuilder& build, const Instruction* pc, int pcpos) IrOp vb = build.inst(IrCmd::LOAD_POINTER, build.vmReg(rb)); - IrOp addrSlotEl = build.inst(IrCmd::GET_SLOT_NODE_ADDR, vb, build.constUint(pcpos)); + IrOp addrSlotEl = build.inst(IrCmd::GET_SLOT_NODE_ADDR, vb, build.constUint(pcpos), build.vmConst(aux)); build.inst(IrCmd::CHECK_SLOT_MATCH, addrSlotEl, build.vmConst(aux), fallback); @@ -1003,7 +1007,7 @@ void translateInstSetTableKS(IrBuilder& build, const Instruction* pc, int pcpos) IrOp vb = build.inst(IrCmd::LOAD_POINTER, build.vmReg(rb)); - IrOp addrSlotEl = build.inst(IrCmd::GET_SLOT_NODE_ADDR, vb, build.constUint(pcpos)); + IrOp addrSlotEl = build.inst(IrCmd::GET_SLOT_NODE_ADDR, vb, build.constUint(pcpos), build.vmConst(aux)); build.inst(IrCmd::CHECK_SLOT_MATCH, addrSlotEl, build.vmConst(aux), fallback); build.inst(IrCmd::CHECK_READONLY, vb, fallback); @@ -1028,7 +1032,7 @@ void translateInstGetGlobal(IrBuilder& build, const Instruction* pc, int pcpos) IrOp fallback = build.block(IrBlockKind::Fallback); IrOp env = build.inst(IrCmd::LOAD_ENV); - IrOp addrSlotEl = build.inst(IrCmd::GET_SLOT_NODE_ADDR, env, build.constUint(pcpos)); + IrOp addrSlotEl = build.inst(IrCmd::GET_SLOT_NODE_ADDR, env, build.constUint(pcpos), build.vmConst(aux)); build.inst(IrCmd::CHECK_SLOT_MATCH, addrSlotEl, build.vmConst(aux), fallback); @@ -1050,7 +1054,7 @@ void translateInstSetGlobal(IrBuilder& build, const Instruction* pc, int pcpos) IrOp fallback = build.block(IrBlockKind::Fallback); IrOp env = build.inst(IrCmd::LOAD_ENV); - IrOp addrSlotEl = build.inst(IrCmd::GET_SLOT_NODE_ADDR, env, build.constUint(pcpos)); + IrOp addrSlotEl = build.inst(IrCmd::GET_SLOT_NODE_ADDR, env, build.constUint(pcpos), build.vmConst(aux)); build.inst(IrCmd::CHECK_SLOT_MATCH, addrSlotEl, build.vmConst(aux), fallback); build.inst(IrCmd::CHECK_READONLY, env, fallback); @@ -1141,7 +1145,7 @@ void translateInstNamecall(IrBuilder& build, const Instruction* pc, int pcpos) build.loadAndCheckTag(indexPtr, LUA_TTABLE, fallback); IrOp index = build.inst(IrCmd::LOAD_POINTER, indexPtr); - IrOp addrIndexNodeEl = build.inst(IrCmd::GET_SLOT_NODE_ADDR, index, build.constUint(pcpos)); + IrOp addrIndexNodeEl = build.inst(IrCmd::GET_SLOT_NODE_ADDR, index, build.constUint(pcpos), build.vmConst(aux)); build.inst(IrCmd::CHECK_SLOT_MATCH, addrIndexNodeEl, build.vmConst(aux), fallback); // TODO: original 'table' was clobbered by a call inside 'FASTGETTM' diff --git a/CodeGen/src/IrUtils.cpp b/CodeGen/src/IrUtils.cpp index e51dca99..07704388 100644 --- a/CodeGen/src/IrUtils.cpp +++ b/CodeGen/src/IrUtils.cpp @@ -54,6 +54,7 @@ IrValueKind getCmdValueKind(IrCmd cmd) case IrCmd::SUB_NUM: case IrCmd::MUL_NUM: case IrCmd::DIV_NUM: + case IrCmd::IDIV_NUM: case IrCmd::MOD_NUM: case IrCmd::MIN_NUM: case IrCmd::MAX_NUM: @@ -79,7 +80,9 @@ IrValueKind getCmdValueKind(IrCmd cmd) case IrCmd::JUMP_SLOT_MATCH: return IrValueKind::None; case IrCmd::TABLE_LEN: - return IrValueKind::Double; + return IrValueKind::Int; + case IrCmd::TABLE_SETNUM: + return IrValueKind::Pointer; case IrCmd::STRING_LEN: return IrValueKind::Int; case IrCmd::NEW_TABLE: @@ -119,6 +122,7 @@ IrValueKind getCmdValueKind(IrCmd cmd) case IrCmd::CHECK_ARRAY_SIZE: case IrCmd::CHECK_SLOT_MATCH: case IrCmd::CHECK_NODE_NO_NEXT: + case IrCmd::CHECK_NODE_VALUE: case IrCmd::INTERRUPT: case IrCmd::CHECK_GC: case IrCmd::BARRIER_OBJ: @@ -464,6 +468,10 @@ void foldConstants(IrBuilder& build, IrFunction& function, IrBlock& block, uint3 if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant) substitute(function, inst, build.constDouble(function.doubleOp(inst.a) / function.doubleOp(inst.b))); break; + case IrCmd::IDIV_NUM: + if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant) + substitute(function, inst, build.constDouble(luai_numidiv(function.doubleOp(inst.a), function.doubleOp(inst.b)))); + break; case IrCmd::MOD_NUM: if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant) substitute(function, inst, build.constDouble(luai_nummod(function.doubleOp(inst.a), function.doubleOp(inst.b)))); diff --git a/CodeGen/src/IrValueLocationTracking.cpp b/CodeGen/src/IrValueLocationTracking.cpp index c32d718c..04c210d9 100644 --- a/CodeGen/src/IrValueLocationTracking.cpp +++ b/CodeGen/src/IrValueLocationTracking.cpp @@ -108,13 +108,14 @@ void IrValueLocationTracking::beforeInstLowering(IrInst& inst) case IrCmd::FINDUPVAL: break; - // These instrucitons read VmReg only after optimizeMemoryOperandsX64 + // These instructions read VmReg only after optimizeMemoryOperandsX64 case IrCmd::CHECK_TAG: case IrCmd::CHECK_TRUTHY: case IrCmd::ADD_NUM: case IrCmd::SUB_NUM: case IrCmd::MUL_NUM: case IrCmd::DIV_NUM: + case IrCmd::IDIV_NUM: case IrCmd::MOD_NUM: case IrCmd::MIN_NUM: case IrCmd::MAX_NUM: diff --git a/CodeGen/src/NativeState.cpp b/CodeGen/src/NativeState.cpp index 13ef33d3..6f567a2b 100644 --- a/CodeGen/src/NativeState.cpp +++ b/CodeGen/src/NativeState.cpp @@ -53,6 +53,7 @@ void initFunctions(NativeState& data) data.context.luaH_new = luaH_new; data.context.luaH_clone = luaH_clone; data.context.luaH_resizearray = luaH_resizearray; + data.context.luaH_setnum = luaH_setnum; data.context.luaC_barriertable = luaC_barriertable; data.context.luaC_barrierf = luaC_barrierf; diff --git a/CodeGen/src/NativeState.h b/CodeGen/src/NativeState.h index 85b7a3a3..f0b8561c 100644 --- a/CodeGen/src/NativeState.h +++ b/CodeGen/src/NativeState.h @@ -44,6 +44,7 @@ struct NativeContext Table* (*luaH_new)(lua_State* L, int narray, int lnhash) = nullptr; Table* (*luaH_clone)(lua_State* L, Table* tt) = nullptr; void (*luaH_resizearray)(lua_State* L, Table* t, int nasize) = nullptr; + TValue* (*luaH_setnum)(lua_State* L, Table* t, int key); void (*luaC_barriertable)(lua_State* L, Table* t, GCObject* v) = nullptr; void (*luaC_barrierf)(lua_State* L, GCObject* o, GCObject* v) = nullptr; diff --git a/CodeGen/src/OptimizeConstProp.cpp b/CodeGen/src/OptimizeConstProp.cpp index 4b09d423..03c26cdd 100644 --- a/CodeGen/src/OptimizeConstProp.cpp +++ b/CodeGen/src/OptimizeConstProp.cpp @@ -13,7 +13,10 @@ #include LUAU_FASTINTVARIABLE(LuauCodeGenMinLinearBlockPath, 3) +LUAU_FASTINTVARIABLE(LuauCodeGenReuseSlotLimit, 64) LUAU_FASTFLAGVARIABLE(DebugLuauAbortingChecks, false) +LUAU_FASTFLAGVARIABLE(LuauReuseHashSlots2, false) +LUAU_FASTFLAGVARIABLE(LuauKeepVmapLinear, false) namespace Luau { @@ -174,6 +177,10 @@ struct ConstPropState { for (int i = 0; i <= maxReg; ++i) invalidateHeap(regs[i]); + + // If table memory has changed, we can't reuse previously computed and validated table slot lookups + getSlotNodeCache.clear(); + checkSlotMatchCache.clear(); } void invalidateHeap(RegisterInfo& reg) @@ -190,6 +197,21 @@ struct ConstPropState inSafeEnv = false; } + void invalidateTableArraySize() + { + for (int i = 0; i <= maxReg; ++i) + invalidateTableArraySize(regs[i]); + + // If table memory has changed, we can't reuse previously computed and validated table slot lookups + getSlotNodeCache.clear(); + checkSlotMatchCache.clear(); + } + + void invalidateTableArraySize(RegisterInfo& reg) + { + reg.knownTableArraySize = -1; + } + void createRegLink(uint32_t instIdx, IrOp regOp) { LUAU_ASSERT(!instLink.contains(instIdx)); @@ -367,6 +389,8 @@ struct ConstPropState instLink.clear(); valueMap.clear(); + getSlotNodeCache.clear(); + checkSlotMatchCache.clear(); } IrFunction& function; @@ -384,6 +408,9 @@ struct ConstPropState DenseHashMap instLink{~0u}; DenseHashMap valueMap; + + std::vector getSlotNodeCache; + std::vector checkSlotMatchCache; }; static void handleBuiltinEffects(ConstPropState& state, LuauBuiltinFunction bfid, uint32_t firstReturnReg, int nresults) @@ -863,7 +890,25 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& case IrCmd::NOP: case IrCmd::LOAD_ENV: case IrCmd::GET_ARR_ADDR: + break; case IrCmd::GET_SLOT_NODE_ADDR: + if (!FFlag::LuauReuseHashSlots2) + break; + + for (uint32_t prevIdx : state.getSlotNodeCache) + { + const IrInst& prev = function.instructions[prevIdx]; + + if (prev.a == inst.a && prev.c == inst.c) + { + substitute(function, inst, IrOp{IrOpKind::Inst, prevIdx}); + return; // Break out from both the loop and the switch + } + } + + if (int(state.getSlotNodeCache.size()) < FInt::LuauCodeGenReuseSlotLimit) + state.getSlotNodeCache.push_back(index); + break; case IrCmd::GET_HASH_NODE_ADDR: case IrCmd::GET_CLOSURE_UPVAL_ADDR: break; @@ -873,6 +918,7 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& case IrCmd::SUB_NUM: case IrCmd::MUL_NUM: case IrCmd::DIV_NUM: + case IrCmd::IDIV_NUM: case IrCmd::MOD_NUM: case IrCmd::MIN_NUM: case IrCmd::MAX_NUM: @@ -892,6 +938,10 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& case IrCmd::JUMP_EQ_POINTER: case IrCmd::JUMP_SLOT_MATCH: case IrCmd::TABLE_LEN: + break; + case IrCmd::TABLE_SETNUM: + state.invalidateTableArraySize(); + break; case IrCmd::STRING_LEN: case IrCmd::NEW_TABLE: case IrCmd::DUP_TABLE: @@ -938,7 +988,26 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& break; } case IrCmd::CHECK_SLOT_MATCH: + if (!FFlag::LuauReuseHashSlots2) + break; + + for (uint32_t prevIdx : state.checkSlotMatchCache) + { + const IrInst& prev = function.instructions[prevIdx]; + + if (prev.a == inst.a && prev.b == inst.b) + { + // Only a check for 'nil' value is left + replace(function, block, index, {IrCmd::CHECK_NODE_VALUE, inst.a, inst.c}); + return; // Break out from both the loop and the switch + } + } + + if (int(state.checkSlotMatchCache.size()) < FInt::LuauCodeGenReuseSlotLimit) + state.checkSlotMatchCache.push_back(index); + break; case IrCmd::CHECK_NODE_NO_NEXT: + case IrCmd::CHECK_NODE_VALUE: case IrCmd::BARRIER_TABLE_BACK: case IrCmd::RETURN: case IrCmd::COVERAGE: @@ -999,7 +1068,10 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& if (RegisterInfo* info = state.tryGetRegisterInfo(inst.b); info && info->knownTableArraySize >= 0) replace(function, inst.f, build.constUint(info->knownTableArraySize)); - state.valueMap.clear(); // TODO: this can be relaxed when x64 emitInstSetList becomes aware of register allocator + // TODO: this can be relaxed when x64 emitInstSetList becomes aware of register allocator + state.valueMap.clear(); + state.getSlotNodeCache.clear(); + state.checkSlotMatchCache.clear(); break; case IrCmd::CALL: state.invalidateRegistersFrom(vmRegOp(inst.a)); @@ -1012,7 +1084,11 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& break; case IrCmd::FORGLOOP: state.invalidateRegistersFrom(vmRegOp(inst.a) + 2); // Rn and Rn+1 are not modified - state.valueMap.clear(); // TODO: this can be relaxed when x64 emitInstForGLoop becomes aware of register allocator + + // TODO: this can be relaxed when x64 emitInstForGLoop becomes aware of register allocator + state.valueMap.clear(); + state.getSlotNodeCache.clear(); + state.checkSlotMatchCache.clear(); break; case IrCmd::FORGLOOP_FALLBACK: state.invalidateRegistersFrom(vmRegOp(inst.a) + 2); // Rn and Rn+1 are not modified @@ -1076,8 +1152,15 @@ static void constPropInBlock(IrBuilder& build, IrBlock& block, ConstPropState& s constPropInInst(state, build, function, block, inst, index); } - // Value numbering and load/store propagation is not performed between blocks - state.valueMap.clear(); + if (!FFlag::LuauKeepVmapLinear) + { + // Value numbering and load/store propagation is not performed between blocks + state.valueMap.clear(); + + // Same for table slot data propagation + state.getSlotNodeCache.clear(); + state.checkSlotMatchCache.clear(); + } } static void constPropInBlockChain(IrBuilder& build, std::vector& visited, IrBlock* block, ConstPropState& state) @@ -1086,6 +1169,9 @@ static void constPropInBlockChain(IrBuilder& build, std::vector& visite state.clear(); + const uint32_t startSortkey = block->sortkey; + uint32_t chainPos = 0; + while (block) { uint32_t blockIdx = function.getBlockIndex(*block); @@ -1094,19 +1180,40 @@ static void constPropInBlockChain(IrBuilder& build, std::vector& visite constPropInBlock(build, *block, state); + if (FFlag::LuauKeepVmapLinear) + { + // Value numbering and load/store propagation is not performed between blocks right now + // This is because cross-block value uses limit creation of linear block (restriction in collectDirectBlockJumpPath) + state.valueMap.clear(); + + // Same for table slot data propagation + state.getSlotNodeCache.clear(); + state.checkSlotMatchCache.clear(); + } + + // Blocks in a chain are guaranteed to follow each other + // We force that by giving all blocks the same sorting key, but consecutive chain keys + block->sortkey = startSortkey; + block->chainkey = chainPos++; + IrInst& termInst = function.instructions[block->finish]; IrBlock* nextBlock = nullptr; // Unconditional jump into a block with a single user (current block) allows us to continue optimization // with the information we have gathered so far (unless we have already visited that block earlier) - if (termInst.cmd == IrCmd::JUMP && termInst.a.kind != IrOpKind::VmExit) + if (termInst.cmd == IrCmd::JUMP && termInst.a.kind == IrOpKind::Block) { IrBlock& target = function.blockOp(termInst.a); uint32_t targetIdx = function.getBlockIndex(target); if (target.useCount == 1 && !visited[targetIdx] && target.kind != IrBlockKind::Fallback) + { + // Make sure block ordering guarantee is checked at lowering time + block->expectedNextBlock = function.getBlockIndex(target); + nextBlock = ⌖ + } } block = nextBlock; @@ -1134,7 +1241,7 @@ static std::vector collectDirectBlockJumpPath(IrFunction& function, st IrBlock* nextBlock = nullptr; // A chain is made from internal blocks that were not a part of bytecode CFG - if (termInst.cmd == IrCmd::JUMP && termInst.a.kind != IrOpKind::VmExit) + if (termInst.cmd == IrCmd::JUMP && termInst.a.kind == IrOpKind::Block) { IrBlock& target = function.blockOp(termInst.a); uint32_t targetIdx = function.getBlockIndex(target); @@ -1175,8 +1282,8 @@ static void tryCreateLinearBlock(IrBuilder& build, std::vector& visited if (termInst.cmd != IrCmd::JUMP) return; - // And it can't be jump to a VM exit - if (termInst.a.kind == IrOpKind::VmExit) + // And it can't be jump to a VM exit or undef + if (termInst.a.kind != IrOpKind::Block) return; // And it has to jump to a block with more than one user @@ -1196,14 +1303,14 @@ static void tryCreateLinearBlock(IrBuilder& build, std::vector& visited // Initialize state with the knowledge of our current block state.clear(); - // TODO: using values from the first block can cause 'live out' of the linear block predecessor to not have all required registers constPropInBlock(build, startingBlock, state); // Verify that target hasn't changed LUAU_ASSERT(function.instructions[startingBlock.finish].a.index == targetBlockIdx); // Note: using startingBlock after this line is unsafe as the reference may be reallocated by build.block() below - uint32_t startingInsn = startingBlock.start; + const uint32_t startingSortKey = startingBlock.sortkey; + const uint32_t startingChainKey = startingBlock.chainkey; // Create new linearized block into which we are going to redirect starting block jump IrOp newBlock = build.block(IrBlockKind::Linearized); @@ -1213,7 +1320,11 @@ static void tryCreateLinearBlock(IrBuilder& build, std::vector& visited // By default, blocks are ordered according to start instruction; we alter sort order to make sure linearized block is placed right after the // starting block - function.blocks[newBlock.index].sortkey = startingInsn + 1; + function.blocks[newBlock.index].sortkey = startingSortKey; + function.blocks[newBlock.index].chainkey = startingChainKey + 1; + + // Make sure block ordering guarantee is checked at lowering time + function.blocks[blockIdx].expectedNextBlock = newBlock.index; replace(function, termInst.a, newBlock); @@ -1252,6 +1363,12 @@ static void tryCreateLinearBlock(IrBuilder& build, std::vector& visited def.varargStart = pathDef.varargStart; } } + + // Update predecessors + function.cfg.predecessorsOffsets.push_back(uint32_t(function.cfg.predecessors.size())); + function.cfg.predecessors.push_back(blockIdx); + + // Updating successors will require visiting the instructions again and we don't have a current use for linearized block successor list } // Optimize our linear block diff --git a/CodeGen/src/OptimizeFinalX64.cpp b/CodeGen/src/OptimizeFinalX64.cpp index 63642c46..3f0469fa 100644 --- a/CodeGen/src/OptimizeFinalX64.cpp +++ b/CodeGen/src/OptimizeFinalX64.cpp @@ -58,6 +58,7 @@ static void optimizeMemoryOperandsX64(IrFunction& function, IrBlock& block) case IrCmd::SUB_NUM: case IrCmd::MUL_NUM: case IrCmd::DIV_NUM: + case IrCmd::IDIV_NUM: case IrCmd::MOD_NUM: case IrCmd::MIN_NUM: case IrCmd::MAX_NUM: diff --git a/Common/include/Luau/Bytecode.h b/Common/include/Luau/Bytecode.h index 976dd04f..36b570ce 100644 --- a/Common/include/Luau/Bytecode.h +++ b/Common/include/Luau/Bytecode.h @@ -44,7 +44,7 @@ // Version 1: Baseline version for the open-source release. Supported until 0.521. // Version 2: Adds Proto::linedefined. Supported until 0.544. // Version 3: Adds FORGPREP/JUMPXEQK* and enhances AUX encoding for FORGLOOP. Removes FORGLOOP_NEXT/INEXT and JUMPIFEQK/JUMPIFNOTEQK. Currently supported. -// Version 4: Adds Proto::flags and typeinfo. Currently supported. +// Version 4: Adds Proto::flags, typeinfo, and floor division opcodes IDIV/IDIVK. Currently supported. // Bytecode opcode, part of the instruction header enum LuauOpcode @@ -390,6 +390,18 @@ enum LuauOpcode LOP_JUMPXEQKN, LOP_JUMPXEQKS, + // IDIV: compute floor division between two source registers and put the result into target register + // A: target register + // B: source register 1 + // C: source register 2 + LOP_IDIV, + + // IDIVK compute floor division between the source register and a constant and put the result into target register + // A: target register + // B: source register + // C: constant table index (0..255) + LOP_IDIVK, + // Enum entry for number of opcodes, not a valid opcode by itself! LOP__COUNT }; diff --git a/Compiler/include/Luau/BytecodeBuilder.h b/Compiler/include/Luau/BytecodeBuilder.h index 48b89404..f5098d17 100644 --- a/Compiler/include/Luau/BytecodeBuilder.h +++ b/Compiler/include/Luau/BytecodeBuilder.h @@ -15,7 +15,6 @@ class BytecodeEncoder public: virtual ~BytecodeEncoder() {} - virtual uint8_t encodeOp(uint8_t op) = 0; virtual void encode(uint32_t* data, size_t count) = 0; }; diff --git a/Compiler/src/BytecodeBuilder.cpp b/Compiler/src/BytecodeBuilder.cpp index 96754569..4ebed69a 100644 --- a/Compiler/src/BytecodeBuilder.cpp +++ b/Compiler/src/BytecodeBuilder.cpp @@ -8,7 +8,8 @@ #include LUAU_FASTFLAGVARIABLE(BytecodeVersion4, false) -LUAU_FASTFLAGVARIABLE(BytecodeEnc, false) + +LUAU_FASTFLAG(LuauFloorDivision) namespace Luau { @@ -238,7 +239,7 @@ void BytecodeBuilder::endFunction(uint8_t maxstacksize, uint8_t numupvalues, uin // very approximate: 4 bytes per instruction for code, 1 byte for debug line, and 1-2 bytes for aux data like constants plus overhead func.data.reserve(32 + insns.size() * 7); - if (FFlag::BytecodeEnc && encoder) + if (encoder) encoder->encode(insns.data(), insns.size()); writeFunction(func.data, currentFunction, flags); @@ -625,29 +626,8 @@ void BytecodeBuilder::writeFunction(std::string& ss, uint32_t id, uint8_t flags) // instructions writeVarInt(ss, uint32_t(insns.size())); - if (encoder && !FFlag::BytecodeEnc) - { - for (size_t i = 0; i < insns.size();) - { - uint8_t op = LUAU_INSN_OP(insns[i]); - LUAU_ASSERT(op < LOP__COUNT); - - int oplen = getOpLength(LuauOpcode(op)); - uint8_t openc = encoder->encodeOp(op); - - writeInt(ss, openc | (insns[i] & ~0xff)); - - for (int j = 1; j < oplen; ++j) - writeInt(ss, insns[i + j]); - - i += oplen; - } - } - else - { - for (uint32_t insn : insns) - writeInt(ss, insn); - } + for (uint32_t insn : insns) + writeInt(ss, insn); // constants writeVarInt(ss, uint32_t(constants.size())); @@ -1306,8 +1286,11 @@ void BytecodeBuilder::validateInstructions() const case LOP_SUB: case LOP_MUL: case LOP_DIV: + case LOP_IDIV: case LOP_MOD: case LOP_POW: + LUAU_ASSERT(FFlag::LuauFloorDivision || op != LOP_IDIV); + VREG(LUAU_INSN_A(insn)); VREG(LUAU_INSN_B(insn)); VREG(LUAU_INSN_C(insn)); @@ -1317,8 +1300,11 @@ void BytecodeBuilder::validateInstructions() const case LOP_SUBK: case LOP_MULK: case LOP_DIVK: + case LOP_IDIVK: case LOP_MODK: case LOP_POWK: + LUAU_ASSERT(FFlag::LuauFloorDivision || op != LOP_IDIVK); + VREG(LUAU_INSN_A(insn)); VREG(LUAU_INSN_B(insn)); VCONST(LUAU_INSN_C(insn), Number); @@ -1885,6 +1871,12 @@ void BytecodeBuilder::dumpInstruction(const uint32_t* code, std::string& result, formatAppend(result, "DIV R%d R%d R%d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn)); break; + case LOP_IDIV: + LUAU_ASSERT(FFlag::LuauFloorDivision); + + formatAppend(result, "IDIV R%d R%d R%d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn)); + break; + case LOP_MOD: formatAppend(result, "MOD R%d R%d R%d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn)); break; @@ -1917,6 +1909,14 @@ void BytecodeBuilder::dumpInstruction(const uint32_t* code, std::string& result, result.append("]\n"); break; + case LOP_IDIVK: + LUAU_ASSERT(FFlag::LuauFloorDivision); + + formatAppend(result, "IDIVK R%d R%d K%d [", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn)); + dumpConstant(result, LUAU_INSN_C(insn)); + result.append("]\n"); + break; + case LOP_MODK: formatAppend(result, "MODK R%d R%d K%d [", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn)); dumpConstant(result, LUAU_INSN_C(insn)); diff --git a/Compiler/src/Compiler.cpp b/Compiler/src/Compiler.cpp index f9a00f64..4fdf659b 100644 --- a/Compiler/src/Compiler.cpp +++ b/Compiler/src/Compiler.cpp @@ -26,6 +26,8 @@ LUAU_FASTINTVARIABLE(LuauCompileInlineThreshold, 25) LUAU_FASTINTVARIABLE(LuauCompileInlineThresholdMaxBoost, 300) LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5) +LUAU_FASTFLAG(LuauFloorDivision) + namespace Luau { @@ -1019,6 +1021,11 @@ struct Compiler case AstExprBinary::Div: return k ? LOP_DIVK : LOP_DIV; + case AstExprBinary::FloorDiv: + LUAU_ASSERT(FFlag::LuauFloorDivision); + + return k ? LOP_IDIVK : LOP_IDIV; + case AstExprBinary::Mod: return k ? LOP_MODK : LOP_MOD; @@ -1469,9 +1476,12 @@ struct Compiler case AstExprBinary::Sub: case AstExprBinary::Mul: case AstExprBinary::Div: + case AstExprBinary::FloorDiv: case AstExprBinary::Mod: case AstExprBinary::Pow: { + LUAU_ASSERT(FFlag::LuauFloorDivision || expr->op != AstExprBinary::FloorDiv); + int32_t rc = getConstantNumber(expr->right); if (rc >= 0 && rc <= 255) @@ -3192,9 +3202,12 @@ struct Compiler case AstExprBinary::Sub: case AstExprBinary::Mul: case AstExprBinary::Div: + case AstExprBinary::FloorDiv: case AstExprBinary::Mod: case AstExprBinary::Pow: { + LUAU_ASSERT(FFlag::LuauFloorDivision || stat->op != AstExprBinary::FloorDiv); + if (var.kind != LValue::Kind_Local) compileLValueUse(var, target, /* set= */ false); diff --git a/Compiler/src/ConstantFolding.cpp b/Compiler/src/ConstantFolding.cpp index a49a7748..5b098a11 100644 --- a/Compiler/src/ConstantFolding.cpp +++ b/Compiler/src/ConstantFolding.cpp @@ -104,6 +104,14 @@ static void foldBinary(Constant& result, AstExprBinary::Op op, const Constant& l } break; + case AstExprBinary::FloorDiv: + if (la.type == Constant::Type_Number && ra.type == Constant::Type_Number) + { + result.type = Constant::Type_Number; + result.valueNumber = floor(la.valueNumber / ra.valueNumber); + } + break; + case AstExprBinary::Mod: if (la.type == Constant::Type_Number && ra.type == Constant::Type_Number) { diff --git a/Config/src/Config.cpp b/Config/src/Config.cpp index 9369743e..8e9802cf 100644 --- a/Config/src/Config.cpp +++ b/Config/src/Config.cpp @@ -4,6 +4,7 @@ #include "Luau/Lexer.h" #include "Luau/StringUtils.h" +LUAU_FASTFLAG(LuauFloorDivision) namespace Luau { @@ -112,14 +113,23 @@ static void next(Lexer& lexer) lexer.next(); // skip C-style comments as Lexer only understands Lua-style comments atm - while (lexer.current().type == '/') + + if (FFlag::LuauFloorDivision) { - Lexeme peek = lexer.lookahead(); + while (lexer.current().type == Luau::Lexeme::FloorDiv) + lexer.nextline(); + } + else + { + while (lexer.current().type == '/') + { + Lexeme peek = lexer.lookahead(); - if (peek.type != '/' || peek.location.begin != lexer.current().location.end) - break; + if (peek.type != '/' || peek.location.begin != lexer.current().location.end) + break; - lexer.nextline(); + lexer.nextline(); + } } } diff --git a/LICENSE.txt b/LICENSE.txt index 40fc04c1..34496ced 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019-2022 Roblox Corporation +Copyright (c) 2019-2023 Roblox Corporation Copyright (c) 1994–2019 Lua.org, PUC-Rio. Permission is hereby granted, free of charge, to any person obtaining a copy of @@ -19,4 +19,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +SOFTWARE. diff --git a/Makefile b/Makefile index f0f008be..2e5e9791 100644 --- a/Makefile +++ b/Makefile @@ -255,6 +255,7 @@ $(BUILD)/fuzz/protoprint.cpp.o: fuzz/luau.pb.cpp build/libprotobuf-mutator: git clone https://github.com/google/libprotobuf-mutator build/libprotobuf-mutator + git -C build/libprotobuf-mutator checkout 212a7be1eb08e7f9c79732d2aab9b2097085d936 CXX= cmake -S build/libprotobuf-mutator -B build/libprotobuf-mutator $(DPROTOBUF) make -C build/libprotobuf-mutator -j8 diff --git a/VM/include/lua.h b/VM/include/lua.h index e49e6ad9..43c60d77 100644 --- a/VM/include/lua.h +++ b/VM/include/lua.h @@ -443,7 +443,7 @@ typedef struct lua_Callbacks lua_Callbacks; LUA_API lua_Callbacks* lua_callbacks(lua_State* L); /****************************************************************************** - * Copyright (c) 2019-2022 Roblox Corporation + * Copyright (c) 2019-2023 Roblox Corporation * Copyright (C) 1994-2008 Lua.org, PUC-Rio. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining diff --git a/VM/src/lapi.cpp b/VM/src/lapi.cpp index 054faa7c..2b98e47d 100644 --- a/VM/src/lapi.cpp +++ b/VM/src/lapi.cpp @@ -38,7 +38,7 @@ const char* lua_ident = "$Lua: Lua 5.1.4 Copyright (C) 1994-2008 Lua.org, PUC-Ri "$Authors: R. Ierusalimschy, L. H. de Figueiredo & W. Celes $\n" "$URL: www.lua.org $\n"; -const char* luau_ident = "$Luau: Copyright (C) 2019-2022 Roblox Corporation $\n" +const char* luau_ident = "$Luau: Copyright (C) 2019-2023 Roblox Corporation $\n" "$URL: luau-lang.org $\n"; #define api_checknelems(L, n) api_check(L, (n) <= (L->top - L->base)) diff --git a/VM/src/lnumutils.h b/VM/src/lnumutils.h index 5b27e2b8..38bfb322 100644 --- a/VM/src/lnumutils.h +++ b/VM/src/lnumutils.h @@ -40,6 +40,13 @@ inline double luai_nummod(double a, double b) } LUAU_FASTMATH_END +LUAU_FASTMATH_BEGIN +inline double luai_numidiv(double a, double b) +{ + return floor(a / b); +} +LUAU_FASTMATH_END + #define luai_num2int(i, d) ((i) = (int)(d)) // On MSVC in 32-bit, double to unsigned cast compiles into a call to __dtoui3, so we invoke x87->int64 conversion path manually diff --git a/VM/src/ltm.cpp b/VM/src/ltm.cpp index d753e8a4..d8c69a70 100644 --- a/VM/src/ltm.cpp +++ b/VM/src/ltm.cpp @@ -48,6 +48,7 @@ const char* const luaT_eventname[] = { "__sub", "__mul", "__div", + "__idiv", "__mod", "__pow", "__unm", diff --git a/VM/src/ltm.h b/VM/src/ltm.h index 4b1c2818..7dafd4ed 100644 --- a/VM/src/ltm.h +++ b/VM/src/ltm.h @@ -27,6 +27,7 @@ typedef enum TM_SUB, TM_MUL, TM_DIV, + TM_IDIV, TM_MOD, TM_POW, TM_UNM, diff --git a/VM/src/lvmexecute.cpp b/VM/src/lvmexecute.cpp index 0d5a53df..44bba9c0 100644 --- a/VM/src/lvmexecute.cpp +++ b/VM/src/lvmexecute.cpp @@ -103,7 +103,8 @@ VM_DISPATCH_OP(LOP_LOADKX), VM_DISPATCH_OP(LOP_JUMPX), VM_DISPATCH_OP(LOP_FASTCALL), VM_DISPATCH_OP(LOP_COVERAGE), \ VM_DISPATCH_OP(LOP_CAPTURE), VM_DISPATCH_OP(LOP_DEP_JUMPIFEQK), VM_DISPATCH_OP(LOP_DEP_JUMPIFNOTEQK), VM_DISPATCH_OP(LOP_FASTCALL1), \ VM_DISPATCH_OP(LOP_FASTCALL2), VM_DISPATCH_OP(LOP_FASTCALL2K), VM_DISPATCH_OP(LOP_FORGPREP), VM_DISPATCH_OP(LOP_JUMPXEQKNIL), \ - VM_DISPATCH_OP(LOP_JUMPXEQKB), VM_DISPATCH_OP(LOP_JUMPXEQKN), VM_DISPATCH_OP(LOP_JUMPXEQKS), + VM_DISPATCH_OP(LOP_JUMPXEQKB), VM_DISPATCH_OP(LOP_JUMPXEQKN), VM_DISPATCH_OP(LOP_JUMPXEQKS), VM_DISPATCH_OP(LOP_IDIV), \ + VM_DISPATCH_OP(LOP_IDIVK), #if defined(__GNUC__) || defined(__clang__) #define VM_USE_CGOTO 1 @@ -1660,6 +1661,54 @@ reentry: } } + VM_CASE(LOP_IDIV) + { + Instruction insn = *pc++; + StkId ra = VM_REG(LUAU_INSN_A(insn)); + StkId rb = VM_REG(LUAU_INSN_B(insn)); + StkId rc = VM_REG(LUAU_INSN_C(insn)); + + // fast-path + if (LUAU_LIKELY(ttisnumber(rb) && ttisnumber(rc))) + { + setnvalue(ra, luai_numidiv(nvalue(rb), nvalue(rc))); + VM_NEXT(); + } + else if (ttisvector(rb) && ttisnumber(rc)) + { + const float* vb = vvalue(rb); + float vc = cast_to(float, nvalue(rc)); + setvvalue(ra, float(luai_numidiv(vb[0], vc)), float(luai_numidiv(vb[1], vc)), float(luai_numidiv(vb[2], vc)), + float(luai_numidiv(vb[3], vc))); + VM_NEXT(); + } + else + { + // fast-path for userdata with C functions + StkId rbc = ttisnumber(rb) ? rc : rb; + const TValue* fn = 0; + if (ttisuserdata(rbc) && (fn = luaT_gettmbyobj(L, rbc, TM_IDIV)) && ttisfunction(fn) && clvalue(fn)->isC) + { + // note: it's safe to push arguments past top for complicated reasons (see top of the file) + LUAU_ASSERT(L->top + 3 < L->stack + L->stacksize); + StkId top = L->top; + setobj2s(L, top + 0, fn); + setobj2s(L, top + 1, rb); + setobj2s(L, top + 2, rc); + L->top = top + 3; + + VM_PROTECT(luaV_callTM(L, 2, LUAU_INSN_A(insn))); + VM_NEXT(); + } + else + { + // slow-path, may invoke C/Lua via metamethods + VM_PROTECT(luaV_doarith(L, ra, rb, rc, TM_IDIV)); + VM_NEXT(); + } + } + } + VM_CASE(LOP_MOD) { Instruction insn = *pc++; @@ -1838,6 +1887,53 @@ reentry: } } + VM_CASE(LOP_IDIVK) + { + Instruction insn = *pc++; + StkId ra = VM_REG(LUAU_INSN_A(insn)); + StkId rb = VM_REG(LUAU_INSN_B(insn)); + TValue* kv = VM_KV(LUAU_INSN_C(insn)); + + // fast-path + if (LUAU_LIKELY(ttisnumber(rb))) + { + setnvalue(ra, luai_numidiv(nvalue(rb), nvalue(kv))); + VM_NEXT(); + } + else if (ttisvector(rb)) + { + const float* vb = vvalue(rb); + float vc = cast_to(float, nvalue(kv)); + setvvalue(ra, float(luai_numidiv(vb[0], vc)), float(luai_numidiv(vb[1], vc)), float(luai_numidiv(vb[2], vc)), + float(luai_numidiv(vb[3], vc))); + VM_NEXT(); + } + else + { + // fast-path for userdata with C functions + const TValue* fn = 0; + if (ttisuserdata(rb) && (fn = luaT_gettmbyobj(L, rb, TM_IDIV)) && ttisfunction(fn) && clvalue(fn)->isC) + { + // note: it's safe to push arguments past top for complicated reasons (see top of the file) + LUAU_ASSERT(L->top + 3 < L->stack + L->stacksize); + StkId top = L->top; + setobj2s(L, top + 0, fn); + setobj2s(L, top + 1, rb); + setobj2s(L, top + 2, kv); + L->top = top + 3; + + VM_PROTECT(luaV_callTM(L, 2, LUAU_INSN_A(insn))); + VM_NEXT(); + } + else + { + // slow-path, may invoke C/Lua via metamethods + VM_PROTECT(luaV_doarith(L, ra, rb, kv, TM_IDIV)); + VM_NEXT(); + } + } + } + VM_CASE(LOP_MODK) { Instruction insn = *pc++; diff --git a/VM/src/lvmutils.cpp b/VM/src/lvmutils.cpp index b77207da..df30db2f 100644 --- a/VM/src/lvmutils.cpp +++ b/VM/src/lvmutils.cpp @@ -394,6 +394,9 @@ void luaV_doarith(lua_State* L, StkId ra, const TValue* rb, const TValue* rc, TM case TM_DIV: setnvalue(ra, luai_numdiv(nb, nc)); break; + case TM_IDIV: + setnvalue(ra, luai_numidiv(nb, nc)); + break; case TM_MOD: setnvalue(ra, luai_nummod(nb, nc)); break; @@ -410,7 +413,12 @@ void luaV_doarith(lua_State* L, StkId ra, const TValue* rb, const TValue* rc, TM } else { - // vector operations that we support: v + v, v - v, v * v, s * v, v * s, v / v, s / v, v / s, -v + // vector operations that we support: + // v+v v-v -v (add/sub/neg) + // v*v s*v v*s (mul) + // v/v s/v v/s (div) + // v//v s//v v//s (floor div) + const float* vb = luaV_tovector(rb); const float* vc = luaV_tovector(rc); @@ -430,6 +438,10 @@ void luaV_doarith(lua_State* L, StkId ra, const TValue* rb, const TValue* rc, TM case TM_DIV: setvvalue(ra, vb[0] / vc[0], vb[1] / vc[1], vb[2] / vc[2], vb[3] / vc[3]); return; + case TM_IDIV: + setvvalue(ra, float(luai_numidiv(vb[0], vc[0])), float(luai_numidiv(vb[1], vc[1])), float(luai_numidiv(vb[2], vc[2])), + float(luai_numidiv(vb[3], vc[3]))); + return; case TM_UNM: setvvalue(ra, -vb[0], -vb[1], -vb[2], -vb[3]); return; @@ -453,6 +465,10 @@ void luaV_doarith(lua_State* L, StkId ra, const TValue* rb, const TValue* rc, TM case TM_DIV: setvvalue(ra, vb[0] / nc, vb[1] / nc, vb[2] / nc, vb[3] / nc); return; + case TM_IDIV: + setvvalue(ra, float(luai_numidiv(vb[0], nc)), float(luai_numidiv(vb[1], nc)), float(luai_numidiv(vb[2], nc)), + float(luai_numidiv(vb[3], nc))); + return; default: break; } @@ -474,6 +490,10 @@ void luaV_doarith(lua_State* L, StkId ra, const TValue* rb, const TValue* rc, TM case TM_DIV: setvvalue(ra, nb / vc[0], nb / vc[1], nb / vc[2], nb / vc[3]); return; + case TM_IDIV: + setvvalue(ra, float(luai_numidiv(nb, vc[0])), float(luai_numidiv(nb, vc[1])), float(luai_numidiv(nb, vc[2])), + float(luai_numidiv(nb, vc[3]))); + return; default: break; } diff --git a/bench/micro_tests/test_TableSort.lua b/bench/micro_tests/test_TableSort.lua new file mode 100644 index 00000000..80031d1c --- /dev/null +++ b/bench/micro_tests/test_TableSort.lua @@ -0,0 +1,22 @@ +local bench = script and require(script.Parent.bench_support) or require("bench_support") + +local arr_months = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"} + +local arr_num = {} +for i=1,100 do table.insert(arr_num, math.sin(i)) end + +local arr_numk = {} +for i=1,10000 do table.insert(arr_numk, math.sin(i)) end + +function test(arr) + local t = table.create(#arr) + + for i=1,1e6/#arr do + table.move(arr, 1, #arr, 1, t) + table.sort(t) + end +end + +bench.runCode(function() test(arr_months) end, "table.sort: 12 strings") +bench.runCode(function() test(arr_num) end, "table.sort: 100 numbers") +bench.runCode(function() test(arr_numk) end, "table.sort: 10k numbers") diff --git a/bench/tabulate.py b/bench/tabulate.py index fc034171..0b77209f 100644 --- a/bench/tabulate.py +++ b/bench/tabulate.py @@ -45,7 +45,7 @@ class TablePrinter(object): def _print_horizontal_separator(self): for i, align_width in enumerate(self._widths): if i > 0: - print('-+-', end='') + print('-|-', end='') print('-' * (align_width+1), end='') print() pass diff --git a/fuzz/luau.proto b/fuzz/luau.proto index e51d687b..5fed9ddc 100644 --- a/fuzz/luau.proto +++ b/fuzz/luau.proto @@ -135,17 +135,18 @@ message ExprBinary { Sub = 1; Mul = 2; Div = 3; - Mod = 4; - Pow = 5; - Concat = 6; - CompareNe = 7; - CompareEq = 8; - CompareLt = 9; - CompareLe = 10; - CompareGt = 11; - CompareGe = 12; - And = 13; - Or = 14; + FloorDiv = 4; + Mod = 5; + Pow = 6; + Concat = 7; + CompareNe = 8; + CompareEq = 9; + CompareLt = 10; + CompareLe = 11; + CompareGt = 12; + CompareGe = 13; + And = 14; + Or = 15; } required Op op = 1; diff --git a/fuzz/protoprint.cpp b/fuzz/protoprint.cpp index 5c7c5bf6..4eab2893 100644 --- a/fuzz/protoprint.cpp +++ b/fuzz/protoprint.cpp @@ -495,6 +495,8 @@ struct ProtoToLuau source += " * "; else if (expr.op() == luau::ExprBinary::Div) source += " / "; + else if (expr.op() == luau::ExprBinary::FloorDiv) + source += " // "; else if (expr.op() == luau::ExprBinary::Mod) source += " % "; else if (expr.op() == luau::ExprBinary::Pow) diff --git a/tests/AssemblyBuilderA64.test.cpp b/tests/AssemblyBuilderA64.test.cpp index ba8d40c2..56722c00 100644 --- a/tests/AssemblyBuilderA64.test.cpp +++ b/tests/AssemblyBuilderA64.test.cpp @@ -107,6 +107,13 @@ TEST_CASE_FIXTURE(AssemblyBuilderA64Fixture, "Binary") SINGLE_COMPARE(cmp(w0, 42), 0x7100A81F); } +TEST_CASE_FIXTURE(AssemblyBuilderA64Fixture, "BinaryExtended") +{ + // reg, reg + SINGLE_COMPARE(add(x0, x1, w2, 3), 0x8B224C20); + SINGLE_COMPARE(sub(x0, x1, w2, 3), 0xCB224C20); +} + TEST_CASE_FIXTURE(AssemblyBuilderA64Fixture, "BinaryImm") { // instructions @@ -524,6 +531,8 @@ TEST_CASE("LogTest") build.ldr(x0, mem(x1, 1, AddressKindA64::pre)); build.ldr(x0, mem(x1, 1, AddressKindA64::post)); + build.add(x1, x2, w3, 3); + build.setLabel(l); build.ret(); @@ -560,6 +569,7 @@ TEST_CASE("LogTest") ldr x0,[x1,#1] ldr x0,[x1,#1]! ldr x0,[x1]!,#1 + add x1,x2,w3 UXTW #3 .L1: ret )"; diff --git a/tests/Autocomplete.test.cpp b/tests/Autocomplete.test.cpp index fac23e88..6bfdc0f4 100644 --- a/tests/Autocomplete.test.cpp +++ b/tests/Autocomplete.test.cpp @@ -986,6 +986,33 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_end_with_lambda") CHECK_EQ(ac.context, AutocompleteContext::Statement); } +TEST_CASE_FIXTURE(ACFixture, "autocomplete_end_of_do_block") +{ + ScopedFastFlag sff{"LuauAutocompleteDoEnd", true}; + + check("do @1"); + + auto ac = autocomplete('1'); + + CHECK(ac.entryMap.count("end")); + + check(R"( + function f() + do + @1 + end + @2 + )"); + + ac = autocomplete('1'); + + CHECK(ac.entryMap.count("end")); + + ac = autocomplete('2'); + + CHECK(ac.entryMap.count("end")); +} + TEST_CASE_FIXTURE(ACFixture, "stop_at_first_stat_when_recommending_keywords") { check(R"( diff --git a/tests/Conformance.test.cpp b/tests/Conformance.test.cpp index a06138fc..75c38762 100644 --- a/tests/Conformance.test.cpp +++ b/tests/Conformance.test.cpp @@ -24,6 +24,7 @@ extern bool codegen; extern int optimizationLevel; LUAU_FASTFLAG(LuauPCallDebuggerFix); +LUAU_FASTFLAG(LuauFloorDivision); static lua_CompileOptions defaultOptions() { @@ -280,6 +281,7 @@ TEST_CASE("Assert") TEST_CASE("Basic") { + ScopedFastFlag sffs{"LuauFloorDivision", true}; runConformance("basic.lua"); } @@ -363,6 +365,7 @@ TEST_CASE("Errors") TEST_CASE("Events") { + ScopedFastFlag sffs{"LuauFloorDivision", true}; runConformance("events.lua"); } @@ -444,6 +447,8 @@ TEST_CASE("Pack") TEST_CASE("Vector") { + ScopedFastFlag sffs{"LuauFloorDivision", true}; + lua_CompileOptions copts = defaultOptions(); copts.vectorCtor = "vector"; @@ -1616,6 +1621,9 @@ static void pushInt64(lua_State* L, int64_t value) TEST_CASE("Userdata") { + + ScopedFastFlag sffs{"LuauFloorDivision", true}; + runConformance("userdata.lua", [](lua_State* L) { // create metatable with all the metamethods lua_newtable(L); @@ -1735,6 +1743,19 @@ TEST_CASE("Userdata") nullptr); lua_setfield(L, -2, "__div"); + // __idiv + lua_pushcfunction( + L, + [](lua_State* L) { + // for testing we use different semantics here compared to __div: __idiv rounds to negative inf, __div truncates (rounds to zero) + // additionally, division loses precision here outside of 2^53 range + // we do not necessarily recommend this behavior in production code! + pushInt64(L, int64_t(floor(double(getInt64(L, 1)) / double(getInt64(L, 2))))); + return 1; + }, + nullptr); + lua_setfield(L, -2, "__idiv"); + // __mod lua_pushcfunction( L, diff --git a/tests/IrBuilder.test.cpp b/tests/IrBuilder.test.cpp index dff13342..a736f5cf 100644 --- a/tests/IrBuilder.test.cpp +++ b/tests/IrBuilder.test.cpp @@ -5,6 +5,7 @@ #include "Luau/IrUtils.h" #include "Luau/OptimizeConstProp.h" #include "Luau/OptimizeFinalX64.h" +#include "ScopedFlags.h" #include "doctest.h" @@ -1930,6 +1931,135 @@ bb_0: )"); } +TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateHashSlotChecks") +{ + ScopedFastFlag luauReuseHashSlots{"LuauReuseHashSlots2", true}; + + IrOp block = build.block(IrBlockKind::Internal); + IrOp fallback = build.block(IrBlockKind::Fallback); + + build.beginBlock(block); + + // This roughly corresponds to 'return t.a + t.a' + IrOp table1 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(1)); + IrOp slot1 = build.inst(IrCmd::GET_SLOT_NODE_ADDR, table1, build.constUint(3), build.vmConst(1)); + build.inst(IrCmd::CHECK_SLOT_MATCH, slot1, build.vmConst(1), fallback); + IrOp value1 = build.inst(IrCmd::LOAD_TVALUE, slot1, build.constInt(0)); + build.inst(IrCmd::STORE_TVALUE, build.vmReg(3), value1); + + IrOp slot1b = build.inst(IrCmd::GET_SLOT_NODE_ADDR, table1, build.constUint(8), build.vmConst(1)); // This will be removed + build.inst(IrCmd::CHECK_SLOT_MATCH, slot1b, build.vmConst(1), fallback); // Key will be replaced with undef here + IrOp value1b = build.inst(IrCmd::LOAD_TVALUE, slot1b, build.constInt(0)); + build.inst(IrCmd::STORE_TVALUE, build.vmReg(4), value1b); + + IrOp a = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(3)); + IrOp b = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(4)); + IrOp sum = build.inst(IrCmd::ADD_NUM, a, b); + build.inst(IrCmd::STORE_DOUBLE, build.vmReg(2), sum); + + build.inst(IrCmd::RETURN, build.vmReg(2), build.constUint(1)); + + build.beginBlock(fallback); + build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1)); + + updateUseCounts(build.function); + constPropInBlockChains(build, true); + + // In the future, we might even see duplicate identical TValue loads go away + // In the future, we might even see loads of different VM regs with the same value go away + CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( +bb_0: + %0 = LOAD_POINTER R1 + %1 = GET_SLOT_NODE_ADDR %0, 3u, K1 + CHECK_SLOT_MATCH %1, K1, bb_fallback_1 + %3 = LOAD_TVALUE %1, 0i + STORE_TVALUE R3, %3 + CHECK_NODE_VALUE %1, bb_fallback_1 + %7 = LOAD_TVALUE %1, 0i + STORE_TVALUE R4, %7 + %9 = LOAD_DOUBLE R3 + %10 = LOAD_DOUBLE R4 + %11 = ADD_NUM %9, %10 + STORE_DOUBLE R2, %11 + RETURN R2, 1u + +bb_fallback_1: + RETURN R0, 1u + +)"); +} + +TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateHashSlotChecksAvoidNil") +{ + ScopedFastFlag luauReuseHashSlots{"LuauReuseHashSlots2", true}; + + IrOp block = build.block(IrBlockKind::Internal); + IrOp fallback = build.block(IrBlockKind::Fallback); + + build.beginBlock(block); + + IrOp table1 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(1)); + IrOp slot1 = build.inst(IrCmd::GET_SLOT_NODE_ADDR, table1, build.constUint(3), build.vmConst(1)); + build.inst(IrCmd::CHECK_SLOT_MATCH, slot1, build.vmConst(1), fallback); + IrOp value1 = build.inst(IrCmd::LOAD_TVALUE, slot1, build.constInt(0)); + build.inst(IrCmd::STORE_TVALUE, build.vmReg(3), value1); + + IrOp table2 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(2)); + IrOp slot2 = build.inst(IrCmd::GET_SLOT_NODE_ADDR, table2, build.constUint(6), build.vmConst(1)); + build.inst(IrCmd::CHECK_SLOT_MATCH, slot2, build.vmConst(1), fallback); + build.inst(IrCmd::CHECK_READONLY, table2, fallback); + + build.inst(IrCmd::STORE_TAG, build.vmReg(4), build.constTag(tnil)); + IrOp valueNil = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(4)); + build.inst(IrCmd::STORE_TVALUE, slot2, valueNil, build.constInt(0)); + + // In the future, we might get to track that value became 'nil' and that fallback will be taken + IrOp slot1b = build.inst(IrCmd::GET_SLOT_NODE_ADDR, table1, build.constUint(8), build.vmConst(1)); // This will be removed + build.inst(IrCmd::CHECK_SLOT_MATCH, slot1b, build.vmConst(1), fallback); // Key will be replaced with undef here + IrOp value1b = build.inst(IrCmd::LOAD_TVALUE, slot1b, build.constInt(0)); + build.inst(IrCmd::STORE_TVALUE, build.vmReg(3), value1b); + + IrOp slot2b = build.inst(IrCmd::GET_SLOT_NODE_ADDR, table2, build.constUint(11), build.vmConst(1)); // This will be removed + build.inst(IrCmd::CHECK_SLOT_MATCH, slot2b, build.vmConst(1), fallback); // Key will be replaced with undef here + build.inst(IrCmd::CHECK_READONLY, table2, fallback); + + build.inst(IrCmd::STORE_SPLIT_TVALUE, slot2b, build.constTag(tnumber), build.constDouble(1), build.constInt(0)); + + build.inst(IrCmd::RETURN, build.vmReg(3), build.constUint(2)); + + build.beginBlock(fallback); + build.inst(IrCmd::RETURN, build.vmReg(1), build.constUint(2)); + + updateUseCounts(build.function); + constPropInBlockChains(build, true); + + CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( +bb_0: + %0 = LOAD_POINTER R1 + %1 = GET_SLOT_NODE_ADDR %0, 3u, K1 + CHECK_SLOT_MATCH %1, K1, bb_fallback_1 + %3 = LOAD_TVALUE %1, 0i + STORE_TVALUE R3, %3 + %5 = LOAD_POINTER R2 + %6 = GET_SLOT_NODE_ADDR %5, 6u, K1 + CHECK_SLOT_MATCH %6, K1, bb_fallback_1 + CHECK_READONLY %5, bb_fallback_1 + STORE_TAG R4, tnil + %10 = LOAD_TVALUE R4 + STORE_TVALUE %6, %10, 0i + CHECK_NODE_VALUE %1, bb_fallback_1 + %14 = LOAD_TVALUE %1, 0i + STORE_TVALUE R3, %14 + CHECK_NODE_VALUE %6, bb_fallback_1 + STORE_SPLIT_TVALUE %6, tnumber, 1, 0i + RETURN R3, 2u + +bb_fallback_1: + RETURN R1, 2u + +)"); +} + TEST_SUITE_END(); TEST_SUITE_BEGIN("Analysis"); diff --git a/tests/Parser.test.cpp b/tests/Parser.test.cpp index 26a157e4..2492c4a0 100644 --- a/tests/Parser.test.cpp +++ b/tests/Parser.test.cpp @@ -2970,4 +2970,40 @@ TEST_CASE_FIXTURE(Fixture, "unfinished_string_literal_types_get_reported_but_par CHECK_EQ(result.root->body.size, 2); } +TEST_CASE_FIXTURE(Fixture, "do_block_with_no_end") +{ + ParseResult result = tryParse(R"( + do + )"); + + REQUIRE_EQ(1, result.errors.size()); + + AstStatBlock* stat0 = result.root->body.data[0]->as(); + REQUIRE(stat0); + + CHECK(!stat0->hasEnd); +} + +TEST_CASE_FIXTURE(Fixture, "parse_interpolated_string_with_lookahead_involved") +{ + ScopedFastFlag sff{"LuauLexerLookaheadRemembersBraceType", true}; + + ParseResult result = tryParse(R"( + local x = `{ {y} }` + )"); + + REQUIRE_MESSAGE(result.errors.empty(), result.errors[0].getMessage()); +} + +TEST_CASE_FIXTURE(Fixture, "parse_interpolated_string_with_lookahead_involved2") +{ + ScopedFastFlag sff{"LuauLexerLookaheadRemembersBraceType", true}; + + ParseResult result = tryParse(R"( + local x = `{ { y{} } }` + )"); + + REQUIRE_MESSAGE(result.errors.empty(), result.errors[0].getMessage()); +} + TEST_SUITE_END(); diff --git a/tests/Subtyping.test.cpp b/tests/Subtyping.test.cpp index e1fa0e5a..23d05f91 100644 --- a/tests/Subtyping.test.cpp +++ b/tests/Subtyping.test.cpp @@ -52,6 +52,35 @@ struct SubtypeFixture : Fixture return arena.addType(TableType{std::move(props), std::nullopt, {}, TableState::Sealed}); } + // `&` + TypeId meet(TypeId a, TypeId b) + { + return arena.addType(IntersectionType{{a, b}}); + } + + // `|` + TypeId join(TypeId a, TypeId b) + { + return arena.addType(UnionType{{a, b}}); + } + + TypeId negate(TypeId ty) + { + return arena.addType(NegationType{ty}); + } + + TypeId cls(const std::string& name, std::optional parent = std::nullopt) + { + return arena.addType(ClassType{name, {}, parent.value_or(builtinTypes->classType), {}, {}, nullptr, ""}); + } + + TypeId cls(const std::string& name, ClassType::Props&& props) + { + TypeId ty = cls(name); + getMutable(ty)->props = std::move(props); + return ty; + } + TypeId cyclicTable(std::function&& cb) { TypeId res = arena.addType(GenericType{}); @@ -61,6 +90,11 @@ struct SubtypeFixture : Fixture return res; } + TypeId meta(TableType::Props&& metaProps, TableType::Props&& tableProps = {}) + { + return arena.addType(MetatableType{tbl(std::move(tableProps)), tbl(std::move(metaProps))}); + } + TypeId genericT = arena.addType(GenericType{"T"}); TypeId genericU = arena.addType(GenericType{"U"}); @@ -77,8 +111,34 @@ struct SubtypeFixture : Fixture TypeId helloType2 = arena.addType(SingletonType{StringSingleton{"hello"}}); TypeId worldType = arena.addType(SingletonType{StringSingleton{"world"}}); - TypeId helloOrWorldType = arena.addType(UnionType{{helloType, worldType}}); - TypeId trueOrFalseType = arena.addType(UnionType{{builtinTypes->trueType, builtinTypes->falseType}}); + TypeId helloOrWorldType = join(helloType, worldType); + TypeId trueOrFalseType = join(builtinTypes->trueType, builtinTypes->falseType); + + TypeId helloAndWorldType = meet(helloType, worldType); + TypeId booleanAndTrueType = meet(builtinTypes->booleanType, builtinTypes->trueType); + + /** + * class + * \- Root + * |- Child + * | |-GrandchildOne + * | \-GrandchildTwo + * \- AnotherChild + * |- AnotherGrandchildOne + * \- AnotherGrandchildTwo + */ + TypeId rootClass = cls("Root"); + TypeId childClass = cls("Child", rootClass); + TypeId grandchildOneClass = cls("GrandchildOne", childClass); + TypeId grandchildTwoClass = cls("GrandchildTwo", childClass); + TypeId anotherChildClass = cls("AnotherChild", rootClass); + TypeId anotherGrandchildOneClass = cls("AnotherGrandchildOne", anotherChildClass); + TypeId anotherGrandchildTwoClass = cls("AnotherGrandchildTwo", anotherChildClass); + + TypeId vec2Class = cls("Vec2", { + {"X", builtinTypes->numberType}, + {"Y", builtinTypes->numberType}, + }); // "hello" | "hello" TypeId helloOrHelloType = arena.addType(UnionType{{helloType, helloType}}); @@ -86,12 +146,6 @@ struct SubtypeFixture : Fixture // () -> () const TypeId nothingToNothingType = fn({}, {}); - // ("hello") -> "world" - TypeId helloAndWorldType = arena.addType(IntersectionType{{helloType, worldType}}); - - // (boolean) -> true - TypeId booleanAndTrueType = arena.addType(IntersectionType{{builtinTypes->booleanType, builtinTypes->trueType}}); - // (number) -> string const TypeId numberToStringType = fn( {builtinTypes->numberType}, @@ -247,6 +301,11 @@ struct SubtypeFixture : Fixture builtinTypes->emptyTypePack, genericAs }); + + // { lower : string -> string } + TypeId tableWithLower = tbl(TableType::Props{{"lower", fn({builtinTypes->stringType}, {builtinTypes->stringType})}}); + // { insaneThingNoScalarHas : () -> () } + TypeId tableWithoutScalarProp = tbl(TableType::Props{{"insaneThingNoScalarHas", fn({}, {})}}); }; #define CHECK_IS_SUBTYPE(left, right) \ @@ -620,6 +679,99 @@ TEST_CASE_FIXTURE(SubtypeFixture, "{x: (T) -> ()} <: {x: (U) -> ()}") ); } +TEST_CASE_FIXTURE(SubtypeFixture, "{ @metatable { x: number } } <: { @metatable {} }") +{ + CHECK_IS_SUBTYPE( + meta({{"x", builtinTypes->numberType}}), + meta({}) + ); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "{ @metatable { x: number } } numberType}}), + meta({{"x", builtinTypes->booleanType}}) + ); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "{ @metatable {} } booleanType}}) + ); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "{ @metatable {} } <: {}") +{ + CHECK_IS_SUBTYPE( + meta({}), + tbl({}) + ); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "{ @metatable { u: boolean }, x: number } <: { x: number }") +{ + CHECK_IS_SUBTYPE( + meta({{"u", builtinTypes->booleanType}}, {{"x", builtinTypes->numberType}}), + tbl({{"x", builtinTypes->numberType}}) + ); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "{ @metatable { x: number } } numberType}}), + tbl({{"x", builtinTypes->numberType}}) + ); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "Root <: class") +{ + CHECK_IS_SUBTYPE(rootClass, builtinTypes->classType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "Child | AnotherChild <: class") +{ + CHECK_IS_SUBTYPE(join(childClass, anotherChildClass), builtinTypes->classType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "Child | AnotherChild <: Child | AnotherChild") +{ + CHECK_IS_SUBTYPE(join(childClass, anotherChildClass), join(childClass, anotherChildClass)); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "Child | Root <: Root") +{ + CHECK_IS_SUBTYPE(join(childClass, rootClass), rootClass); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "Child & AnotherChild <: class") +{ + CHECK_IS_SUBTYPE(meet(childClass, anotherChildClass), builtinTypes->classType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "Child & Root <: class") +{ + CHECK_IS_SUBTYPE(meet(childClass, rootClass), builtinTypes->classType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "Child & ~Root <: class") +{ + CHECK_IS_SUBTYPE(meet(childClass, negate(rootClass)), builtinTypes->classType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "Child & AnotherChild <: number") +{ + CHECK_IS_SUBTYPE(meet(childClass, anotherChildClass), builtinTypes->numberType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "Child & ~GrandchildOne numberType); +} + TEST_CASE_FIXTURE(SubtypeFixture, "t1 where t1 = {trim: (t1) -> string} <: t2 where t2 = {trim: (t2) -> string}") { TypeId t1 = cyclicTable([&](TypeId ty, TableType* tt) @@ -665,6 +817,84 @@ TEST_CASE_FIXTURE(SubtypeFixture, "t1 where t1 = {trim: (t1) -> t1} numberType}, + {"Y", builtinTypes->numberType}, + }); + + CHECK_IS_SUBTYPE(vec2Class, xy); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "Vec2 <: { X: number }") +{ + TypeId x = tbl({ + {"X", builtinTypes->numberType}, + }); + + CHECK_IS_SUBTYPE(vec2Class, x); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "{ X: number, Y: number } numberType}, + {"Y", builtinTypes->numberType}, + }); + + CHECK_IS_NOT_SUBTYPE(xy, vec2Class); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "{ X: number } numberType}, + }); + + CHECK_IS_NOT_SUBTYPE(x, vec2Class); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "table & { X: number, Y: number } numberType}, + {"Y", builtinTypes->numberType}, + }); + + CHECK_IS_NOT_SUBTYPE(meet(builtinTypes->tableType, x), vec2Class); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "Vec2 numberType}, + {"Y", builtinTypes->numberType}, + }); + + CHECK_IS_NOT_SUBTYPE(vec2Class, meet(builtinTypes->tableType, xy)); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "\"hello\" <: { lower : (string) -> string }") +{ + CHECK_IS_SUBTYPE(helloType, tableWithLower); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "\"hello\" () }") +{ + CHECK_IS_NOT_SUBTYPE(helloType, tableWithoutScalarProp); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "string <: { lower : (string) -> string }") +{ + CHECK_IS_SUBTYPE(builtinTypes->stringType, tableWithLower); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "string () }") +{ + CHECK_IS_NOT_SUBTYPE(builtinTypes->stringType, tableWithoutScalarProp); +} + /* * (A) -> A <: (X) -> X * A can be bound to X. diff --git a/tests/ToString.test.cpp b/tests/ToString.test.cpp index b0592b4c..cb1576f8 100644 --- a/tests/ToString.test.cpp +++ b/tests/ToString.test.cpp @@ -833,14 +833,6 @@ TEST_CASE_FIXTURE(Fixture, "tostring_unsee_ttv_if_array") TEST_CASE_FIXTURE(Fixture, "tostring_error_mismatch") { - ScopedFastFlag sff[] = { - {"LuauIndentTypeMismatch", true}, - }; - - ScopedFastInt sfi[] = { - {"LuauIndentTypeMismatchMaxTypeLength", 10}, - }; - CheckResult result = check(R"( --!strict function f1() : {a : number, b : string, c : { d : number}} diff --git a/tests/Transpiler.test.cpp b/tests/Transpiler.test.cpp index 87f0b2b8..ae7a925c 100644 --- a/tests/Transpiler.test.cpp +++ b/tests/Transpiler.test.cpp @@ -529,14 +529,17 @@ until c CHECK_EQ(code, transpile(code, {}, true).code); } -TEST_CASE_FIXTURE(Fixture, "transpile_compound_assignmenr") +TEST_CASE_FIXTURE(Fixture, "transpile_compound_assignment") { + ScopedFastFlag sffs{"LuauFloorDivision", true}; + std::string code = R"( local a = 1 a += 2 a -= 3 a *= 4 a /= 5 +a //= 5 a %= 6 a ^= 7 a ..= ' - result' diff --git a/tests/TypeInfer.aliases.test.cpp b/tests/TypeInfer.aliases.test.cpp index bebc8942..29dcff83 100644 --- a/tests/TypeInfer.aliases.test.cpp +++ b/tests/TypeInfer.aliases.test.cpp @@ -189,9 +189,7 @@ TEST_CASE_FIXTURE(Fixture, "generic_aliases") { ScopedFastFlag sff[] = { {"DebugLuauDeferredConstraintResolution", true}, - {"LuauIndentTypeMismatch", true}, }; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( type T = { v: a } local x: T = { v = 123 } @@ -212,10 +210,7 @@ TEST_CASE_FIXTURE(Fixture, "dependent_generic_aliases") { ScopedFastFlag sff[] = { {"DebugLuauDeferredConstraintResolution", true}, - {"LuauIndentTypeMismatch", true}, }; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( type T = { v: a } diff --git a/tests/TypeInfer.builtins.test.cpp b/tests/TypeInfer.builtins.test.cpp index e64bf9e9..de29812b 100644 --- a/tests/TypeInfer.builtins.test.cpp +++ b/tests/TypeInfer.builtins.test.cpp @@ -134,9 +134,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "sort_with_bad_predicate") { ScopedFastFlag sff[] = { {"LuauAlwaysCommitInferencesOfFunctionCalls", true}, - {"LuauIndentTypeMismatch", true}, }; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( --!strict diff --git a/tests/TypeInfer.classes.test.cpp b/tests/TypeInfer.classes.test.cpp index 7055c27e..412ec1d0 100644 --- a/tests/TypeInfer.classes.test.cpp +++ b/tests/TypeInfer.classes.test.cpp @@ -369,9 +369,7 @@ TEST_CASE_FIXTURE(ClassFixture, "detailed_class_unification_error") { ScopedFastFlag sff[] = { {"LuauAlwaysCommitInferencesOfFunctionCalls", true}, - {"LuauIndentTypeMismatch", true}, }; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( local function foo(v) return v.X :: number + string.len(v.Y) @@ -457,8 +455,6 @@ TEST_CASE_FIXTURE(ClassFixture, "index_instance_property_nonstrict") TEST_CASE_FIXTURE(ClassFixture, "type_mismatch_invariance_required_for_error") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( type A = { x: ChildClass } type B = { x: BaseClass } diff --git a/tests/TypeInfer.functions.test.cpp b/tests/TypeInfer.functions.test.cpp index 1c46b91a..dbd3f258 100644 --- a/tests/TypeInfer.functions.test.cpp +++ b/tests/TypeInfer.functions.test.cpp @@ -1095,9 +1095,6 @@ TEST_CASE_FIXTURE(Fixture, "return_type_by_overload") TEST_CASE_FIXTURE(BuiltinsFixture, "infer_anonymous_function_arguments") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - // Simple direct arg to arg propagation CheckResult result = check(R"( type Table = { x: number, y: number } @@ -1342,8 +1339,6 @@ end TEST_CASE_FIXTURE(Fixture, "error_detailed_function_mismatch_arg_count") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( type A = (number, number) -> string type B = (number) -> string @@ -1364,9 +1359,6 @@ caused by: TEST_CASE_FIXTURE(Fixture, "error_detailed_function_mismatch_arg") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( type A = (number, number) -> string type B = (number, string) -> string @@ -1388,9 +1380,6 @@ Type 'string' could not be converted into 'number')"; TEST_CASE_FIXTURE(Fixture, "error_detailed_function_mismatch_ret_count") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( type A = (number, number) -> (number) type B = (number, number) -> (number, boolean) @@ -1411,9 +1400,6 @@ caused by: TEST_CASE_FIXTURE(Fixture, "error_detailed_function_mismatch_ret") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( type A = (number, number) -> string type B = (number, number) -> number @@ -1435,9 +1421,6 @@ Type 'string' could not be converted into 'number')"; TEST_CASE_FIXTURE(Fixture, "error_detailed_function_mismatch_ret_mult") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( type A = (number, number) -> (number, string) type B = (number, number) -> (number, boolean) @@ -1563,9 +1546,6 @@ TEST_CASE_FIXTURE(Fixture, "inferred_higher_order_functions_are_quantified_at_th TEST_CASE_FIXTURE(BuiltinsFixture, "function_decl_non_self_unsealed_overwrite") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( local t = { f = nil :: ((x: number) -> number)? } @@ -1608,9 +1588,6 @@ TEST_CASE_FIXTURE(Fixture, "strict_mode_ok_with_missing_arguments") TEST_CASE_FIXTURE(Fixture, "function_statement_sealed_table_assignment_through_indexer") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( local t: {[string]: () -> number} = {} @@ -1819,9 +1796,6 @@ foo(string.find("hello", "e")) TEST_CASE_FIXTURE(Fixture, "luau_subtyping_is_np_hard") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( --!strict @@ -2017,10 +1991,8 @@ TEST_CASE_FIXTURE(Fixture, "function_exprs_are_generalized_at_signature_scope_no TEST_CASE_FIXTURE(BuiltinsFixture, "param_1_and_2_both_takes_the_same_generic_but_their_arguments_are_incompatible") { ScopedFastFlag sff[] = { - {"LuauIndentTypeMismatch", true}, {"LuauAlwaysCommitInferencesOfFunctionCalls", true}, }; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( local function foo(x: a, y: a?) diff --git a/tests/TypeInfer.generics.test.cpp b/tests/TypeInfer.generics.test.cpp index f7904d45..ea62f0bf 100644 --- a/tests/TypeInfer.generics.test.cpp +++ b/tests/TypeInfer.generics.test.cpp @@ -713,9 +713,6 @@ end TEST_CASE_FIXTURE(Fixture, "generic_functions_should_be_memory_safe") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( --!strict -- At one point this produced a UAF diff --git a/tests/TypeInfer.intersectionTypes.test.cpp b/tests/TypeInfer.intersectionTypes.test.cpp index c285edf9..9e685881 100644 --- a/tests/TypeInfer.intersectionTypes.test.cpp +++ b/tests/TypeInfer.intersectionTypes.test.cpp @@ -317,9 +317,6 @@ TEST_CASE_FIXTURE(Fixture, "table_intersection_write_sealed") TEST_CASE_FIXTURE(Fixture, "table_intersection_write_sealed_indirect") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( type X = { x: (number) -> number } type Y = { y: (string) -> string } @@ -350,9 +347,6 @@ caused by: TEST_CASE_FIXTURE(Fixture, "table_write_sealed_indirect") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - // After normalization, previous 'table_intersection_write_sealed_indirect' is identical to this one CheckResult result = check(R"( type XY = { x: (number) -> number, y: (string) -> string } @@ -392,9 +386,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_intersection_setmetatable") TEST_CASE_FIXTURE(Fixture, "error_detailed_intersection_part") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( type X = { x: number } type Y = { y: number } @@ -482,9 +473,6 @@ TEST_CASE_FIXTURE(Fixture, "intersect_false_and_bool_and_false") TEST_CASE_FIXTURE(Fixture, "intersect_saturate_overloaded_functions") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( local x : ((number?) -> number?) & ((string?) -> string?) local y : (nil) -> nil = x -- OK @@ -501,8 +489,6 @@ could not be converted into TEST_CASE_FIXTURE(Fixture, "union_saturate_overloaded_functions") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( local x : ((number) -> number) & ((string) -> string) @@ -520,9 +506,6 @@ could not be converted into TEST_CASE_FIXTURE(Fixture, "intersection_of_tables") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( local x : { p : number?, q : string? } & { p : number?, q : number?, r : number? } local y : { p : number?, q : nil, r : number? } = x -- OK @@ -539,9 +522,6 @@ could not be converted into TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_top_properties") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( local x : { p : number?, q : any } & { p : unknown, q : string? } local y : { p : number?, q : string? } = x -- OK @@ -594,9 +574,6 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_never_properties") TEST_CASE_FIXTURE(Fixture, "overloaded_functions_returning_intersections") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( local x : ((number?) -> ({ p : number } & { q : number })) & ((string?) -> ({ p : number } & { r : number })) local y : (nil) -> { p : number, q : number, r : number} = x -- OK @@ -613,9 +590,6 @@ could not be converted into TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( function f() local x : ((number?) -> (a | number)) & ((string?) -> (a | string)) @@ -634,9 +608,6 @@ could not be converted into TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generics") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( function f() local x : ((a?) -> (a | b)) & ((c?) -> (b | c)) @@ -655,9 +626,6 @@ could not be converted into TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic_packs") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( function f() local x : ((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...)) @@ -676,9 +644,6 @@ could not be converted into TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_result") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( function f() local x : ((number) -> number) & ((nil) -> unknown) @@ -697,9 +662,6 @@ could not be converted into TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_arguments") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( function f() local x : ((number) -> number?) & ((unknown) -> string?) @@ -718,9 +680,6 @@ could not be converted into TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_result") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( function f() local x : ((number) -> number) & ((nil) -> never) @@ -739,9 +698,6 @@ could not be converted into TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_arguments") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( function f() local x : ((number) -> number?) & ((never) -> string?) @@ -760,9 +716,6 @@ could not be converted into TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_overlapping_results_and_variadics") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( local x : ((string?) -> (string | number)) & ((number?) -> ...number) local y : ((nil) -> (number, number?)) = x -- OK @@ -809,9 +762,6 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_2") TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_3") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( function f() local x : (() -> a...) & (() -> (number?,a...)) @@ -830,9 +780,6 @@ could not be converted into TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_4") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( function f() local x : ((a...) -> ()) & ((number,a...) -> number) diff --git a/tests/TypeInfer.modules.test.cpp b/tests/TypeInfer.modules.test.cpp index 7c3d4808..08291b44 100644 --- a/tests/TypeInfer.modules.test.cpp +++ b/tests/TypeInfer.modules.test.cpp @@ -389,9 +389,6 @@ type Table = typeof(tbl) TEST_CASE_FIXTURE(BuiltinsFixture, "module_type_conflict") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - fileResolver.source["game/A"] = R"( export type T = { x: number } return {} @@ -420,9 +417,6 @@ Type 'number' could not be converted into 'string' in an invariant context)"; TEST_CASE_FIXTURE(BuiltinsFixture, "module_type_conflict_instantiated") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - fileResolver.source["game/A"] = R"( export type Wrap = { x: T } return {} diff --git a/tests/TypeInfer.operators.test.cpp b/tests/TypeInfer.operators.test.cpp index 0b7a8311..6a551811 100644 --- a/tests/TypeInfer.operators.test.cpp +++ b/tests/TypeInfer.operators.test.cpp @@ -12,6 +12,8 @@ #include "doctest.h" +#include "ScopedFlags.h" + using namespace Luau; LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution) @@ -143,6 +145,24 @@ TEST_CASE_FIXTURE(Fixture, "some_primitive_binary_ops") CHECK_EQ("number", toString(requireType("c"))); } +TEST_CASE_FIXTURE(Fixture, "floor_division_binary_op") +{ + ScopedFastFlag sffs{"LuauFloorDivision", true}; + + CheckResult result = check(R"( + local a = 4 // 8 + local b = -4 // 9 + local c = 9 + c //= -6.5 + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + + CHECK_EQ("number", toString(requireType("a"))); + CHECK_EQ("number", toString(requireType("b"))); + CHECK_EQ("number", toString(requireType("c"))); +} + TEST_CASE_FIXTURE(BuiltinsFixture, "typecheck_overloaded_multiply_that_is_an_intersection") { CheckResult result = check(R"( diff --git a/tests/TypeInfer.provisional.test.cpp b/tests/TypeInfer.provisional.test.cpp index c33fe170..794171fb 100644 --- a/tests/TypeInfer.provisional.test.cpp +++ b/tests/TypeInfer.provisional.test.cpp @@ -787,9 +787,6 @@ TEST_CASE_FIXTURE(IsSubtypeFixture, "functions_with_mismatching_arity_but_any_is TEST_CASE_FIXTURE(Fixture, "assign_table_with_refined_property_with_a_similar_type_is_illegal") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( local t: {x: number?} = {x = nil} @@ -1050,4 +1047,42 @@ tbl:f3() } } +// Ideally, unification with any will not cause a 2^n normalization of a function overload +TEST_CASE_FIXTURE(BuiltinsFixture, "normalization_limit_in_unify_with_any") +{ + ScopedFastFlag sff[] = { + {"LuauTransitiveSubtyping", true}, + {"DebugLuauDeferredConstraintResolution", true}, + }; + + // With default limit, this test will take 10 seconds in NoOpt + ScopedFastInt luauNormalizeCacheLimit{"LuauNormalizeCacheLimit", 1000}; + + // Build a function type with a large overload set + const int parts = 100; + std::string source; + + for (int i = 0; i < parts; i++) + formatAppend(source, "type T%d = { f%d: number }\n", i, i); + + source += "type Instance = { new: (('s0', extra: Instance?) -> T0)"; + + for (int i = 1; i < parts; i++) + formatAppend(source, " & (('s%d', extra: Instance?) -> T%d)", i, i); + + source += " }\n"; + + source += R"( +local Instance: Instance = {} :: any + +local function foo(a: typeof(Instance.new)) return if a then 2 else 3 end + +foo(1 :: any) +)"; + + CheckResult result = check(source); + + LUAU_REQUIRE_ERRORS(result); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.singletons.test.cpp b/tests/TypeInfer.singletons.test.cpp index 2156d1c6..d085f900 100644 --- a/tests/TypeInfer.singletons.test.cpp +++ b/tests/TypeInfer.singletons.test.cpp @@ -316,9 +316,6 @@ TEST_CASE_FIXTURE(Fixture, "table_properties_type_error_escapes") TEST_CASE_FIXTURE(Fixture, "error_detailed_tagged_union_mismatch_string") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( type Cat = { tag: 'cat', catfood: string } type Dog = { tag: 'dog', dogfood: string } @@ -337,9 +334,6 @@ Table type 'a' not compatible with type 'Cat' because the former is missing fiel TEST_CASE_FIXTURE(Fixture, "error_detailed_tagged_union_mismatch_bool") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( type Good = { success: true, result: string } type Bad = { success: false, error: string } @@ -360,9 +354,7 @@ TEST_CASE_FIXTURE(Fixture, "parametric_tagged_union_alias") { ScopedFastFlag sff[] = { {"DebugLuauDeferredConstraintResolution", true}, - {"LuauIndentTypeMismatch", true}, }; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( type Ok = {success: true, result: T} type Err = {success: false, error: T} diff --git a/tests/TypeInfer.tables.test.cpp b/tests/TypeInfer.tables.test.cpp index ca137b15..315798c2 100644 --- a/tests/TypeInfer.tables.test.cpp +++ b/tests/TypeInfer.tables.test.cpp @@ -2083,8 +2083,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_should_cope_with_optional_prope TEST_CASE_FIXTURE(Fixture, "error_detailed_prop") { - ScopedFastFlag sff[] = {{"LuauIndentTypeMismatch", true}}; - ScopedFastInt sfi[] = {{"LuauIndentTypeMismatchMaxTypeLength", 10}}; CheckResult result = check(R"( type A = { x: number, y: number } type B = { x: number, y: string } @@ -2103,8 +2101,6 @@ Type 'number' could not be converted into 'string' in an invariant context)"; TEST_CASE_FIXTURE(Fixture, "error_detailed_prop_nested") { - ScopedFastFlag sff[] = {{"LuauIndentTypeMismatch", true}}; - ScopedFastInt sfi[] = {{"LuauIndentTypeMismatchMaxTypeLength", 10}}; CheckResult result = check(R"( type AS = { x: number, y: number } type BS = { x: number, y: string } @@ -2129,9 +2125,6 @@ Type 'number' could not be converted into 'string' in an invariant context)"; TEST_CASE_FIXTURE(BuiltinsFixture, "error_detailed_metatable_prop") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( local a1 = setmetatable({ x = 2, y = 3 }, { __call = function(s) end }); local b1 = setmetatable({ x = 2, y = "hello" }, { __call = function(s) end }); @@ -2202,8 +2195,6 @@ could not be converted into TEST_CASE_FIXTURE(Fixture, "error_detailed_indexer_key") { - ScopedFastFlag sff[] = {{"LuauIndentTypeMismatch", true}}; - ScopedFastInt sfi[] = {{"LuauIndentTypeMismatchMaxTypeLength", 10}}; CheckResult result = check(R"( type A = { [number]: string } type B = { [string]: string } @@ -2222,8 +2213,6 @@ Type 'number' could not be converted into 'string' in an invariant context)"; TEST_CASE_FIXTURE(Fixture, "error_detailed_indexer_value") { - ScopedFastFlag sff[] = {{"LuauIndentTypeMismatch", true}}; - ScopedFastInt sfi[] = {{"LuauIndentTypeMismatchMaxTypeLength", 10}}; CheckResult result = check(R"( type A = { [number]: number } type B = { [number]: string } @@ -2257,8 +2246,6 @@ a.p = { x = 9 } TEST_CASE_FIXTURE(Fixture, "explicitly_typed_table_error") { - ScopedFastFlag sff[] = {{"LuauIndentTypeMismatch", true}}; - ScopedFastInt sfi[] = {{"LuauIndentTypeMismatchMaxTypeLength", 10}}; CheckResult result = check(R"( --!strict type Super = { x : number } @@ -3359,14 +3346,10 @@ TEST_CASE_FIXTURE(Fixture, "scalar_is_a_subtype_of_a_compatible_polymorphic_shap TEST_CASE_FIXTURE(Fixture, "scalar_is_not_a_subtype_of_a_compatible_polymorphic_shape_type") { - ScopedFastInt sfi[] = {{"LuauIndentTypeMismatchMaxTypeLength", 10}}; ScopedFastFlag sff[] = { {"LuauAlwaysCommitInferencesOfFunctionCalls", true}, - {"LuauIndentTypeMismatch", true}, }; - - CheckResult result = check(R"( local function f(s) return s:absolutely_no_scalar_has_this_method() @@ -3422,8 +3405,6 @@ TEST_CASE_FIXTURE(Fixture, "a_free_shape_can_turn_into_a_scalar_if_it_is_compati TEST_CASE_FIXTURE(Fixture, "a_free_shape_cannot_turn_into_a_scalar_if_it_is_not_compatible") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( local function f(s): string diff --git a/tests/TypeInfer.test.cpp b/tests/TypeInfer.test.cpp index cb7429aa..8af5c684 100644 --- a/tests/TypeInfer.test.cpp +++ b/tests/TypeInfer.test.cpp @@ -992,9 +992,6 @@ end TEST_CASE_FIXTURE(Fixture, "cli_50041_committing_txnlog_in_apollo_client_error") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( --!strict --!nolint diff --git a/tests/TypeInfer.tryUnify.test.cpp b/tests/TypeInfer.tryUnify.test.cpp index 7910cf6d..1f0b04c6 100644 --- a/tests/TypeInfer.tryUnify.test.cpp +++ b/tests/TypeInfer.tryUnify.test.cpp @@ -345,9 +345,7 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "metatables_unify_against_shape_of_free_table { ScopedFastFlag sff[] = { {"LuauTransitiveSubtyping", true}, - {"LuauIndentTypeMismatch", true}, }; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; TableType::Props freeProps{ {"foo", {builtinTypes->numberType}}, diff --git a/tests/TypeInfer.typePacks.cpp b/tests/TypeInfer.typePacks.cpp index 37aaea7a..92d736f8 100644 --- a/tests/TypeInfer.typePacks.cpp +++ b/tests/TypeInfer.typePacks.cpp @@ -872,9 +872,6 @@ type R = { m: F } TEST_CASE_FIXTURE(Fixture, "pack_tail_unification_check") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( local a: () -> (number, ...string) local b: () -> (number, ...boolean) diff --git a/tests/TypeInfer.unionTypes.test.cpp b/tests/TypeInfer.unionTypes.test.cpp index 271841e9..b455903c 100644 --- a/tests/TypeInfer.unionTypes.test.cpp +++ b/tests/TypeInfer.unionTypes.test.cpp @@ -459,8 +459,6 @@ local oh : boolean = t.y TEST_CASE_FIXTURE(Fixture, "error_detailed_union_part") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( type X = { x: number } type Y = { y: number } @@ -498,8 +496,6 @@ local a: XYZ = { w = 4 } TEST_CASE_FIXTURE(Fixture, "error_detailed_optional") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( type X = { x: number } @@ -532,8 +528,6 @@ TEST_CASE_FIXTURE(Fixture, "dont_allow_cyclic_unions_to_be_inferred") TEST_CASE_FIXTURE(BuiltinsFixture, "table_union_write_indirect") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( type A = { x: number, y: (number) -> string } | { z: number, y: (number) -> string } @@ -620,8 +614,6 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_mentioning_generics") TEST_CASE_FIXTURE(Fixture, "union_of_functions_mentioning_generic_typepacks") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( function f() local x : (number, a...) -> (number?, a...) @@ -640,8 +632,6 @@ could not be converted into TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_arities") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( local x : (number) -> number? local y : ((number?) -> number) | ((number | string) -> nil) = x -- OK @@ -658,8 +648,6 @@ could not be converted into TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_arities") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( local x : () -> (number | string) @@ -677,8 +665,6 @@ could not be converted into TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_variadics") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( local x : (...nil) -> (...number?) @@ -696,8 +682,6 @@ could not be converted into TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_variadics") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( local x : (number) -> () @@ -715,8 +699,6 @@ could not be converted into TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_variadics") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( local x : () -> (number?, ...number) diff --git a/tests/conformance/basic.lua b/tests/conformance/basic.lua index 07150ba8..42030c55 100644 --- a/tests/conformance/basic.lua +++ b/tests/conformance/basic.lua @@ -91,6 +91,15 @@ assert((function() local a = 1 a = a + 2 return a end)() == 3) assert((function() local a = 1 a = a - 2 return a end)() == -1) assert((function() local a = 1 a = a * 2 return a end)() == 2) assert((function() local a = 1 a = a / 2 return a end)() == 0.5) + +-- floor division should always round towards -Infinity +assert((function() local a = 1 a = a // 2 return a end)() == 0) +assert((function() local a = 3 a = a // 2 return a end)() == 1) +assert((function() local a = 3.5 a = a // 2 return a end)() == 1) +assert((function() local a = -1 a = a // 2 return a end)() == -1) +assert((function() local a = -3 a = a // 2 return a end)() == -2) +assert((function() local a = -3.5 a = a // 2 return a end)() == -2) + assert((function() local a = 5 a = a % 2 return a end)() == 1) assert((function() local a = 3 a = a ^ 2 return a end)() == 9) assert((function() local a = 3 a = a ^ 3 return a end)() == 27) @@ -494,6 +503,7 @@ local function vec3t(x, y, z) __sub = function(l, r) return vec3t(l.x - r.x, l.y - r.y, l.z - r.z) end, __mul = function(l, r) return type(r) == "number" and vec3t(l.x * r, l.y * r, l.z * r) or vec3t(l.x * r.x, l.y * r.y, l.z * r.z) end, __div = function(l, r) return type(r) == "number" and vec3t(l.x / r, l.y / r, l.z / r) or vec3t(l.x / r.x, l.y / r.y, l.z / r.z) end, + __idiv = function(l, r) return type(r) == "number" and vec3t(l.x // r, l.y // r, l.z // r) or vec3t(l.x // r.x, l.y // r.y, l.z // r.z) end, __unm = function(v) return vec3t(-v.x, -v.y, -v.z) end, __tostring = function(v) return string.format("%g, %g, %g", v.x, v.y, v.z) end }) @@ -504,10 +514,13 @@ assert((function() return tostring(vec3t(1,2,3) + vec3t(4,5,6)) end)() == "5, 7, assert((function() return tostring(vec3t(1,2,3) - vec3t(4,5,6)) end)() == "-3, -3, -3") assert((function() return tostring(vec3t(1,2,3) * vec3t(4,5,6)) end)() == "4, 10, 18") assert((function() return tostring(vec3t(1,2,3) / vec3t(2,4,8)) end)() == "0.5, 0.5, 0.375") +assert((function() return tostring(vec3t(1,2,3) // vec3t(2,4,2)) end)() == "0, 0, 1") +assert((function() return tostring(vec3t(1,2,3) // vec3t(-2,-4,-2)) end)() == "-1, -1, -2") -- reg vs constant assert((function() return tostring(vec3t(1,2,3) * 2) end)() == "2, 4, 6") assert((function() return tostring(vec3t(1,2,3) / 2) end)() == "0.5, 1, 1.5") +assert((function() return tostring(vec3t(1,2,3) // 2) end)() == "0, 1, 1") -- unary assert((function() return tostring(-vec3t(1,2,3)) end)() == "-1, -2, -3") diff --git a/tests/conformance/events.lua b/tests/conformance/events.lua index 94314c3f..4ee801f0 100644 --- a/tests/conformance/events.lua +++ b/tests/conformance/events.lua @@ -107,6 +107,7 @@ t.__add = f("add") t.__sub = f("sub") t.__mul = f("mul") t.__div = f("div") +t.__idiv = f("idiv") t.__mod = f("mod") t.__unm = f("unm") t.__pow = f("pow") @@ -128,6 +129,8 @@ assert(a*a == a) assert(cap[0] == "mul" and cap[1] == a and cap[2] == a and cap[3]==nil) assert(a/0 == a) assert(cap[0] == "div" and cap[1] == a and cap[2] == 0 and cap[3]==nil) +assert(a//0 == a) +assert(cap[0] == "idiv" and cap[1] == a and cap[2] == 0 and cap[3]==nil) assert(a%2 == a) assert(cap[0] == "mod" and cap[1] == a and cap[2] == 2 and cap[3]==nil) assert(-a == a) diff --git a/tests/conformance/math.lua b/tests/conformance/math.lua index 9e9ae384..6b5bfc5f 100644 --- a/tests/conformance/math.lua +++ b/tests/conformance/math.lua @@ -82,6 +82,7 @@ assert(not(1>1) and not(1>2) and (2>1)) assert(not('a'>'a') and not('a'>'b') and ('b'>'a')) assert((1>=1) and not(1>=2) and (2>=1)) assert(('a'>='a') and not('a'>='b') and ('b'>='a')) +assert((unk and unk > 0) == nil) -- validate precedence between and and > -- testing mod operator assert(-4%3 == 2) diff --git a/tests/conformance/native.lua b/tests/conformance/native.lua index 9b5bb884..7a77edec 100644 --- a/tests/conformance/native.lua +++ b/tests/conformance/native.lua @@ -130,4 +130,45 @@ end assert(pcall(fuzzfail13) == true) +local function arraySizeInv1() + local t = {1, 2, nil, nil, nil, nil, nil, nil, nil, true} + + table.insert(t, 3) + + return t[10] +end + +assert(arraySizeInv1() == true) + +local function arraySizeInv2() + local t = {1, 2, nil, nil, nil, nil, nil, nil, nil, true} + + local u = {a = t} + table.insert(u.a, 3) -- aliased modifiction of 't' register through other value + + return t[10] +end + +assert(arraySizeInv2() == true) + +local function nilInvalidatesSlot() + local function tabs() + local t = { x=1, y=2, z=3 } + setmetatable(t, { __index = function(t, k) return 42 end }) + return t, t + end + + local t1, t2 = tabs() + + for i=1,2 do + local a = t1.x + t2.x = nil + local b = t1.x + t2.x = 1 + assert(a == 1 and b == 42) + end +end + +nilInvalidatesSlot() + return('OK') diff --git a/tests/conformance/userdata.lua b/tests/conformance/userdata.lua index 98392e25..32759ad1 100644 --- a/tests/conformance/userdata.lua +++ b/tests/conformance/userdata.lua @@ -30,6 +30,12 @@ assert(int64(4) / 2 == int64(2)) assert(int64(4) % 3 == int64(1)) assert(int64(2) ^ 3 == int64(8)) +-- / and // round in different directions in our test implementation +assert(int64(5) / int64(2) == int64(2)) +assert(int64(5) // int64(2) == int64(2)) +assert(int64(-5) / int64(2) == int64(-2)) +assert(int64(-5) // int64(2) == int64(-3)) + -- tostring assert(tostring(int64(2)) == "2") diff --git a/tests/conformance/vector.lua b/tests/conformance/vector.lua index 6164e929..c9cc47aa 100644 --- a/tests/conformance/vector.lua +++ b/tests/conformance/vector.lua @@ -67,6 +67,18 @@ assert(vector(1, 2, 4) / '8' == vector(1/8, 1/4, 1/2)); assert(-vector(1, 2, 4) == vector(-1, -2, -4)); +-- test floor division +assert(vector(1, 3, 5) // 2 == vector(0, 1, 2)) +assert(vector(1, 3, 5) // val == vector(8, 24, 40)) + +if vector_size == 4 then + assert(10 // vector(1, 2, 3, 4) == vector(10, 5, 3, 2)) + assert(vector(10, 9, 8, 7) // vector(1, 2, 3, 4) == vector(10, 4, 2, 1)) +else + assert(10 // vector(1, 2, 3) == vector(10, 5, 3)) + assert(vector(10, 9, 8) // vector(1, 2, 3) == vector(10, 4, 2)) +end + -- test NaN comparison local nanv = vector(0/0, 0/0, 0/0) assert(nanv ~= nanv); From cb18b53396d80f69e57d28195ecc4aa70b37f5f0 Mon Sep 17 00:00:00 2001 From: Arseny Kapoulkine Date: Fri, 1 Sep 2023 14:09:18 -0700 Subject: [PATCH 13/20] Update compatibility.md Mark floor division operator as implemented --- docs/_pages/compatibility.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_pages/compatibility.md b/docs/_pages/compatibility.md index b57a9af6..94f8809b 100644 --- a/docs/_pages/compatibility.md +++ b/docs/_pages/compatibility.md @@ -87,7 +87,7 @@ Ephemeron tables may be implemented at some point since they do have valid uses | bitwise operators | ❌ | `bit32` library covers this in absence of 64-bit integers | | basic utf-8 support | ✔️ | we include `utf8` library and other UTF8 features | | functions for packing and unpacking values (string.pack/unpack/packsize) | ✔️ | | -| floor division | 🔜 | | +| floor division | ✔️ | | | `ipairs` and the `table` library respect metamethods | ❌ | no strong use cases, performance implications | | new function `table.move` | ✔️ | | | `collectgarbage("count")` now returns only one result | ✔️ | | From f0a2e79365462c2769e590a0a61849dbfe776d34 Mon Sep 17 00:00:00 2001 From: Arseny Kapoulkine Date: Fri, 1 Sep 2023 14:10:22 -0700 Subject: [PATCH 14/20] rfcs: Mark floor division RFC as implemented --- rfcs/syntax-floor-division-operator.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rfcs/syntax-floor-division-operator.md b/rfcs/syntax-floor-division-operator.md index 8ec3913f..b84ddd18 100644 --- a/rfcs/syntax-floor-division-operator.md +++ b/rfcs/syntax-floor-division-operator.md @@ -1,5 +1,7 @@ # Floor division operator +**Status**: Implemented + ## Summary Add floor division operator `//` to ease computing with integers. From 105f54b2330d0227059c08d2e03454efa934c5db Mon Sep 17 00:00:00 2001 From: Arseny Kapoulkine Date: Tue, 5 Sep 2023 10:22:35 -0700 Subject: [PATCH 15/20] fix build & test conformance issues on mingw32/gcc and mingw64/clang (#1034) CLI/Reduce.cpp: Never worked under MinGW as-is. Add check for MinGW as they're defined. VM/src/lmem.cpp: MinGW layout follows more closely MSVC. Checks before were only correct for 32-bit gcc / clang in non-Windows. NOTES: __MINGW32__ is defined on both 32-bit and 64-bit, __MINGW64__ is 64-bit only. 32-bit MinGW compilers (both Clang & GCC) have a floating point test error on math.noise, specifically math.lua:258. All other test cases / unit tests pass modulo stack size issues (so requires optimized build). --------- Co-authored-by: jdp_ <42700985+jdpatdiscord@users.noreply.github.com> --- CLI/Reduce.cpp | 2 +- VM/src/lmem.cpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CLI/Reduce.cpp b/CLI/Reduce.cpp index 38133e04..721bb51c 100644 --- a/CLI/Reduce.cpp +++ b/CLI/Reduce.cpp @@ -15,7 +15,7 @@ #define VERBOSE 0 // 1 - print out commandline invocations. 2 - print out stdout -#ifdef _WIN32 +#if defined(_WIN32) && !defined(__MINGW32__) const auto popen = &_popen; const auto pclose = &_pclose; diff --git a/VM/src/lmem.cpp b/VM/src/lmem.cpp index 3cbdafff..25ca999d 100644 --- a/VM/src/lmem.cpp +++ b/VM/src/lmem.cpp @@ -98,6 +98,8 @@ */ #if defined(__APPLE__) #define ABISWITCH(x64, ms32, gcc32) (sizeof(void*) == 8 ? x64 : gcc32) +#elif defined(__i386__) && defined(__MINGW32__) && !defined(__MINGW64__) +#define ABISWITCH(x64, ms32, gcc32) (ms32) #elif defined(__i386__) && !defined(_MSC_VER) #define ABISWITCH(x64, ms32, gcc32) (gcc32) #else From bf1fb8f1e4e4f21b229775c4d988cb2de624967f Mon Sep 17 00:00:00 2001 From: Arseny Kapoulkine Date: Wed, 6 Sep 2023 18:14:03 -0700 Subject: [PATCH 16/20] Update syntax.md (#1035) Add a note about floor division support. --- docs/_pages/syntax.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/_pages/syntax.md b/docs/_pages/syntax.md index 595a4804..42fbf897 100644 --- a/docs/_pages/syntax.md +++ b/docs/_pages/syntax.md @@ -273,3 +273,9 @@ This restriction is made to prevent developers using other programming languages Luau currently does not support backtick string literals as a type annotation, so `` type Foo = `Foo` `` is invalid. Function calls with a backtick string literal without parenthesis is not supported, so `` print`hello` `` is invalid. + +## Floor division (`//`) + +Luau implements support for floor division operator (`//`) for numbers as well as support for `__idiv` metamethod. The syntax and semantics follow [Lua 5.3](https://www.lua.org/manual/5.3/manual.html#3.4.1). + +For numbers, `a // b` is equal to `math.floor(a / b)`; when `b` is 0, `a // b` results in infinity or NaN as appropriate. From c7c986b99644ae769ca8b3e1b0ee35736a7dc239 Mon Sep 17 00:00:00 2001 From: vegorov-rbx <75688451+vegorov-rbx@users.noreply.github.com> Date: Thu, 7 Sep 2023 17:13:49 -0700 Subject: [PATCH 17/20] Sync to upstream/release/594 (#1036) * Fixed `Frontend::markDirty` not working on modules that were not typechecked yet * Fixed generic variadic function unification succeeding when it should have reported an error New Type Solver: * Implemented semantic subtyping check for function types Native Code Generation: * Improved performance of numerical loops with a constant step * Simplified IR for `bit32.extract` calls extracting first/last bits * Improved performance of NaN checks --- Analysis/include/Luau/BuiltinDefinitions.h | 2 - Analysis/include/Luau/Frontend.h | 2 +- Analysis/include/Luau/GlobalTypes.h | 26 + Analysis/include/Luau/Subtyping.h | 2 + Analysis/include/Luau/Type.h | 5 +- Analysis/include/Luau/TypeInfer.h | 11 - Analysis/src/Autocomplete.cpp | 47 +- Analysis/src/BuiltinDefinitions.cpp | 526 +++++++++++++++++++- Analysis/src/Frontend.cpp | 14 +- Analysis/src/GlobalTypes.cpp | 34 ++ Analysis/src/Subtyping.cpp | 13 +- Analysis/src/ToDot.cpp | 346 +++++++------ Analysis/src/Type.cpp | 534 +-------------------- Analysis/src/TypeInfer.cpp | 24 +- Analysis/src/Unifier.cpp | 41 +- Ast/src/Lexer.cpp | 16 +- CodeGen/include/Luau/IrBuilder.h | 4 + CodeGen/include/Luau/IrData.h | 16 +- CodeGen/include/Luau/IrUtils.h | 4 +- CodeGen/src/EmitCommonX64.cpp | 49 ++ CodeGen/src/EmitCommonX64.h | 2 + CodeGen/src/IrBuilder.cpp | 6 + CodeGen/src/IrDump.cpp | 8 +- CodeGen/src/IrLoweringA64.cpp | 82 +++- CodeGen/src/IrLoweringX64.cpp | 37 +- CodeGen/src/IrTranslateBuiltins.cpp | 63 ++- CodeGen/src/IrTranslation.cpp | 221 +++++++-- CodeGen/src/IrUtils.cpp | 67 ++- CodeGen/src/OptimizeConstProp.cpp | 46 +- Sources.cmake | 8 + VM/src/lvmexecute.cpp | 6 + tests/Autocomplete.test.cpp | 66 +-- tests/Conformance.test.cpp | 2 + tests/Fixture.cpp | 3 +- tests/Frontend.test.cpp | 24 + tests/IrBuilder.test.cpp | 41 +- tests/RegisterCallbacks.cpp | 20 + tests/RegisterCallbacks.h | 22 + tests/Subtyping.test.cpp | 83 +++- tests/ToDot.test.cpp | 82 +++- tests/TypeFamily.test.cpp | 2 + tests/TypeInfer.provisional.test.cpp | 2 - tests/TypeInfer.test.cpp | 28 ++ tests/conformance/basic.lua | 31 ++ tests/conformance/math.lua | 20 + tests/main.cpp | 10 + 46 files changed, 1628 insertions(+), 1070 deletions(-) create mode 100644 Analysis/include/Luau/GlobalTypes.h create mode 100644 Analysis/src/GlobalTypes.cpp create mode 100644 tests/RegisterCallbacks.cpp create mode 100644 tests/RegisterCallbacks.h diff --git a/Analysis/include/Luau/BuiltinDefinitions.h b/Analysis/include/Luau/BuiltinDefinitions.h index d4457638..6154f3d1 100644 --- a/Analysis/include/Luau/BuiltinDefinitions.h +++ b/Analysis/include/Luau/BuiltinDefinitions.h @@ -14,8 +14,6 @@ struct GlobalTypes; struct TypeChecker; struct TypeArena; -void registerBuiltinTypes(GlobalTypes& globals); - void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeCheckForAutocomplete = false); TypeId makeUnion(TypeArena& arena, std::vector&& types); TypeId makeIntersection(TypeArena& arena, std::vector&& types); diff --git a/Analysis/include/Luau/Frontend.h b/Analysis/include/Luau/Frontend.h index 3404c6a2..afe47322 100644 --- a/Analysis/include/Luau/Frontend.h +++ b/Analysis/include/Luau/Frontend.h @@ -2,12 +2,12 @@ #pragma once #include "Luau/Config.h" +#include "Luau/GlobalTypes.h" #include "Luau/Module.h" #include "Luau/ModuleResolver.h" #include "Luau/RequireTracer.h" #include "Luau/Scope.h" #include "Luau/TypeCheckLimits.h" -#include "Luau/TypeInfer.h" #include "Luau/Variant.h" #include diff --git a/Analysis/include/Luau/GlobalTypes.h b/Analysis/include/Luau/GlobalTypes.h new file mode 100644 index 00000000..86bfd943 --- /dev/null +++ b/Analysis/include/Luau/GlobalTypes.h @@ -0,0 +1,26 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details + +#pragma once + +#include "Luau/Module.h" +#include "Luau/NotNull.h" +#include "Luau/Scope.h" +#include "Luau/TypeArena.h" + +namespace Luau +{ + +struct BuiltinTypes; + +struct GlobalTypes +{ + explicit GlobalTypes(NotNull builtinTypes); + + NotNull builtinTypes; // Global types are based on builtin types + + TypeArena globalTypes; + SourceModule globalNames; // names for symbols entered into globalScope + ScopePtr globalScope; // shared by all modules +}; + +} diff --git a/Analysis/include/Luau/Subtyping.h b/Analysis/include/Luau/Subtyping.h index a69952f6..70cd8bae 100644 --- a/Analysis/include/Luau/Subtyping.h +++ b/Analysis/include/Luau/Subtyping.h @@ -19,6 +19,7 @@ class TypeIds; class Normalizer; struct NormalizedType; struct NormalizedClassType; +struct NormalizedFunctionType; struct SubtypingResult { @@ -103,6 +104,7 @@ private: SubtypingResult isSubtype_(const NormalizedType* subNorm, const NormalizedType* superNorm); SubtypingResult isSubtype_(const NormalizedClassType& subClass, const NormalizedClassType& superClass, const TypeIds& superTables); + SubtypingResult isSubtype_(const NormalizedFunctionType& subFunction, const NormalizedFunctionType& superFunction); SubtypingResult isSubtype_(const TypeIds& subTypes, const TypeIds& superTypes); SubtypingResult isSubtype_(const VariadicTypePack* subVariadic, const VariadicTypePack* superVariadic); diff --git a/Analysis/include/Luau/Type.h b/Analysis/include/Luau/Type.h index cc88d54b..ffbe3fa0 100644 --- a/Analysis/include/Luau/Type.h +++ b/Analysis/include/Luau/Type.h @@ -798,12 +798,13 @@ struct BuiltinTypes TypeId errorRecoveryType() const; TypePackId errorRecoveryTypePack() const; + friend TypeId makeStringMetatable(NotNull builtinTypes); + friend struct GlobalTypes; + private: std::unique_ptr arena; bool debugFreezeArena = false; - TypeId makeStringMetatable(); - public: const TypeId nilType; const TypeId numberType; diff --git a/Analysis/include/Luau/TypeInfer.h b/Analysis/include/Luau/TypeInfer.h index 9a44af49..abae8b92 100644 --- a/Analysis/include/Luau/TypeInfer.h +++ b/Analysis/include/Luau/TypeInfer.h @@ -57,17 +57,6 @@ struct HashBoolNamePair size_t operator()(const std::pair& pair) const; }; -struct GlobalTypes -{ - GlobalTypes(NotNull builtinTypes); - - NotNull builtinTypes; // Global types are based on builtin types - - TypeArena globalTypes; - SourceModule globalNames; // names for symbols entered into globalScope - ScopePtr globalScope; // shared by all modules -}; - // All Types are retained via Environment::types. All TypeIds // within a program are borrowed pointers into this set. struct TypeChecker diff --git a/Analysis/src/Autocomplete.cpp b/Analysis/src/Autocomplete.cpp index baeac469..4a5638b1 100644 --- a/Analysis/src/Autocomplete.cpp +++ b/Analysis/src/Autocomplete.cpp @@ -13,8 +13,6 @@ #include LUAU_FASTFLAG(DebugLuauReadWriteProperties) -LUAU_FASTFLAGVARIABLE(LuauAnonymousAutofilled1, false); -LUAU_FASTFLAGVARIABLE(LuauAutocompleteLastTypecheck, false) LUAU_FASTFLAGVARIABLE(LuauAutocompleteDoEnd, false) LUAU_FASTFLAGVARIABLE(LuauAutocompleteStringLiteralBounds, false); @@ -611,7 +609,6 @@ std::optional getLocalTypeInScopeAt(const Module& module, Position posit template static std::optional tryToStringDetailed(const ScopePtr& scope, T ty, bool functionTypeArguments) { - LUAU_ASSERT(FFlag::LuauAnonymousAutofilled1); ToStringOptions opts; opts.useLineBreaks = false; opts.hideTableKind = true; @@ -630,23 +627,7 @@ static std::optional tryGetTypeNameInScope(ScopePtr scope, TypeId ty, bool if (!canSuggestInferredType(scope, ty)) return std::nullopt; - if (FFlag::LuauAnonymousAutofilled1) - { - return tryToStringDetailed(scope, ty, functionTypeArguments); - } - else - { - ToStringOptions opts; - opts.useLineBreaks = false; - opts.hideTableKind = true; - opts.scope = scope; - ToStringResult name = toStringDetailed(ty, opts); - - if (name.error || name.invalid || name.cycle || name.truncated) - return std::nullopt; - - return name.name; - } + return tryToStringDetailed(scope, ty, functionTypeArguments); } static bool tryAddTypeCorrectSuggestion(AutocompleteEntryMap& result, ScopePtr scope, AstType* topType, TypeId inferredType, Position position) @@ -1417,7 +1398,6 @@ static AutocompleteResult autocompleteWhileLoopKeywords(std::vector an static std::string makeAnonymous(const ScopePtr& scope, const FunctionType& funcTy) { - LUAU_ASSERT(FFlag::LuauAnonymousAutofilled1); std::string result = "function("; auto [args, tail] = Luau::flatten(funcTy.argTypes); @@ -1483,7 +1463,6 @@ static std::string makeAnonymous(const ScopePtr& scope, const FunctionType& func static std::optional makeAnonymousAutofilled(const ModulePtr& module, Position position, const AstNode* node, const std::vector& ancestry) { - LUAU_ASSERT(FFlag::LuauAnonymousAutofilled1); const AstExprCall* call = node->as(); if (!call && ancestry.size() > 1) call = ancestry[ancestry.size() - 2]->as(); @@ -1801,17 +1780,10 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M if (node->asExpr()) { - if (FFlag::LuauAnonymousAutofilled1) - { - AutocompleteResult ret = autocompleteExpression(sourceModule, *module, builtinTypes, typeArena, ancestry, position); - if (std::optional generated = makeAnonymousAutofilled(module, position, node, ancestry)) - ret.entryMap[kGeneratedAnonymousFunctionEntryName] = std::move(*generated); - return ret; - } - else - { - return autocompleteExpression(sourceModule, *module, builtinTypes, typeArena, ancestry, position); - } + AutocompleteResult ret = autocompleteExpression(sourceModule, *module, builtinTypes, typeArena, ancestry, position); + if (std::optional generated = makeAnonymousAutofilled(module, position, node, ancestry)) + ret.entryMap[kGeneratedAnonymousFunctionEntryName] = std::move(*generated); + return ret; } else if (node->asStat()) return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry, AutocompleteContext::Statement}; @@ -1821,15 +1793,6 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M AutocompleteResult autocomplete(Frontend& frontend, const ModuleName& moduleName, Position position, StringCompletionCallback callback) { - if (!FFlag::LuauAutocompleteLastTypecheck) - { - // FIXME: We can improve performance here by parsing without checking. - // The old type graph is probably fine. (famous last words!) - FrontendOptions opts; - opts.forAutocomplete = true; - frontend.check(moduleName, opts); - } - const SourceModule* sourceModule = frontend.getSourceModule(moduleName); if (!sourceModule) return {}; diff --git a/Analysis/src/BuiltinDefinitions.cpp b/Analysis/src/BuiltinDefinitions.cpp index c55a88eb..0200ee3e 100644 --- a/Analysis/src/BuiltinDefinitions.cpp +++ b/Analysis/src/BuiltinDefinitions.cpp @@ -201,18 +201,6 @@ void assignPropDocumentationSymbols(TableType::Props& props, const std::string& } } -void registerBuiltinTypes(GlobalTypes& globals) -{ - globals.globalScope->addBuiltinTypeBinding("any", TypeFun{{}, globals.builtinTypes->anyType}); - globals.globalScope->addBuiltinTypeBinding("nil", TypeFun{{}, globals.builtinTypes->nilType}); - globals.globalScope->addBuiltinTypeBinding("number", TypeFun{{}, globals.builtinTypes->numberType}); - globals.globalScope->addBuiltinTypeBinding("string", TypeFun{{}, globals.builtinTypes->stringType}); - globals.globalScope->addBuiltinTypeBinding("boolean", TypeFun{{}, globals.builtinTypes->booleanType}); - globals.globalScope->addBuiltinTypeBinding("thread", TypeFun{{}, globals.builtinTypes->threadType}); - globals.globalScope->addBuiltinTypeBinding("unknown", TypeFun{{}, globals.builtinTypes->unknownType}); - globals.globalScope->addBuiltinTypeBinding("never", TypeFun{{}, globals.builtinTypes->neverType}); -} - void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeCheckForAutocomplete) { LUAU_ASSERT(!globals.globalTypes.types.isFrozen()); @@ -310,6 +298,520 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC attachDcrMagicFunction(getGlobalBinding(globals, "require"), dcrMagicFunctionRequire); } +static std::vector parseFormatString(NotNull builtinTypes, const char* data, size_t size) +{ + const char* options = "cdiouxXeEfgGqs*"; + + std::vector result; + + for (size_t i = 0; i < size; ++i) + { + if (data[i] == '%') + { + i++; + + if (i < size && data[i] == '%') + continue; + + // we just ignore all characters (including flags/precision) up until first alphabetic character + while (i < size && !(data[i] > 0 && (isalpha(data[i]) || data[i] == '*'))) + i++; + + if (i == size) + break; + + if (data[i] == 'q' || data[i] == 's') + result.push_back(builtinTypes->stringType); + else if (data[i] == '*') + result.push_back(builtinTypes->unknownType); + else if (strchr(options, data[i])) + result.push_back(builtinTypes->numberType); + else + result.push_back(builtinTypes->errorRecoveryType(builtinTypes->anyType)); + } + } + + return result; +} + +std::optional> magicFunctionFormat( + TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate withPredicate) +{ + auto [paramPack, _predicates] = withPredicate; + + TypeArena& arena = typechecker.currentModule->internalTypes; + + AstExprConstantString* fmt = nullptr; + if (auto index = expr.func->as(); index && expr.self) + { + if (auto group = index->expr->as()) + fmt = group->expr->as(); + else + fmt = index->expr->as(); + } + + if (!expr.self && expr.args.size > 0) + fmt = expr.args.data[0]->as(); + + if (!fmt) + return std::nullopt; + + std::vector expected = parseFormatString(typechecker.builtinTypes, fmt->value.data, fmt->value.size); + const auto& [params, tail] = flatten(paramPack); + + size_t paramOffset = 1; + size_t dataOffset = expr.self ? 0 : 1; + + // unify the prefix one argument at a time + for (size_t i = 0; i < expected.size() && i + paramOffset < params.size(); ++i) + { + Location location = expr.args.data[std::min(i + dataOffset, expr.args.size - 1)]->location; + + typechecker.unify(params[i + paramOffset], expected[i], scope, location); + } + + // if we know the argument count or if we have too many arguments for sure, we can issue an error + size_t numActualParams = params.size(); + size_t numExpectedParams = expected.size() + 1; // + 1 for the format string + + if (numExpectedParams != numActualParams && (!tail || numExpectedParams < numActualParams)) + typechecker.reportError(TypeError{expr.location, CountMismatch{numExpectedParams, std::nullopt, numActualParams}}); + + return WithPredicate{arena.addTypePack({typechecker.stringType})}; +} + +static bool dcrMagicFunctionFormat(MagicFunctionCallContext context) +{ + TypeArena* arena = context.solver->arena; + + AstExprConstantString* fmt = nullptr; + if (auto index = context.callSite->func->as(); index && context.callSite->self) + { + if (auto group = index->expr->as()) + fmt = group->expr->as(); + else + fmt = index->expr->as(); + } + + if (!context.callSite->self && context.callSite->args.size > 0) + fmt = context.callSite->args.data[0]->as(); + + if (!fmt) + return false; + + std::vector expected = parseFormatString(context.solver->builtinTypes, fmt->value.data, fmt->value.size); + const auto& [params, tail] = flatten(context.arguments); + + size_t paramOffset = 1; + + // unify the prefix one argument at a time + for (size_t i = 0; i < expected.size() && i + paramOffset < params.size(); ++i) + { + context.solver->unify(context.solver->rootScope, context.callSite->location, params[i + paramOffset], expected[i]); + } + + // if we know the argument count or if we have too many arguments for sure, we can issue an error + size_t numActualParams = params.size(); + size_t numExpectedParams = expected.size() + 1; // + 1 for the format string + + if (numExpectedParams != numActualParams && (!tail || numExpectedParams < numActualParams)) + context.solver->reportError(TypeError{context.callSite->location, CountMismatch{numExpectedParams, std::nullopt, numActualParams}}); + + TypePackId resultPack = arena->addTypePack({context.solver->builtinTypes->stringType}); + asMutable(context.result)->ty.emplace(resultPack); + + return true; +} + +static std::vector parsePatternString(NotNull builtinTypes, const char* data, size_t size) +{ + std::vector result; + int depth = 0; + bool parsingSet = false; + + for (size_t i = 0; i < size; ++i) + { + if (data[i] == '%') + { + ++i; + if (!parsingSet && i < size && data[i] == 'b') + i += 2; + } + else if (!parsingSet && data[i] == '[') + { + parsingSet = true; + if (i + 1 < size && data[i + 1] == ']') + i += 1; + } + else if (parsingSet && data[i] == ']') + { + parsingSet = false; + } + else if (data[i] == '(') + { + if (parsingSet) + continue; + + if (i + 1 < size && data[i + 1] == ')') + { + i++; + result.push_back(builtinTypes->optionalNumberType); + continue; + } + + ++depth; + result.push_back(builtinTypes->optionalStringType); + } + else if (data[i] == ')') + { + if (parsingSet) + continue; + + --depth; + + if (depth < 0) + break; + } + } + + if (depth != 0 || parsingSet) + return std::vector(); + + if (result.empty()) + result.push_back(builtinTypes->optionalStringType); + + return result; +} + +static std::optional> magicFunctionGmatch( + TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate withPredicate) +{ + auto [paramPack, _predicates] = withPredicate; + const auto& [params, tail] = flatten(paramPack); + + if (params.size() != 2) + return std::nullopt; + + TypeArena& arena = typechecker.currentModule->internalTypes; + + AstExprConstantString* pattern = nullptr; + size_t index = expr.self ? 0 : 1; + if (expr.args.size > index) + pattern = expr.args.data[index]->as(); + + if (!pattern) + return std::nullopt; + + std::vector returnTypes = parsePatternString(typechecker.builtinTypes, pattern->value.data, pattern->value.size); + + if (returnTypes.empty()) + return std::nullopt; + + typechecker.unify(params[0], typechecker.stringType, scope, expr.args.data[0]->location); + + const TypePackId emptyPack = arena.addTypePack({}); + const TypePackId returnList = arena.addTypePack(returnTypes); + const TypeId iteratorType = arena.addType(FunctionType{emptyPack, returnList}); + return WithPredicate{arena.addTypePack({iteratorType})}; +} + +static bool dcrMagicFunctionGmatch(MagicFunctionCallContext context) +{ + const auto& [params, tail] = flatten(context.arguments); + + if (params.size() != 2) + return false; + + TypeArena* arena = context.solver->arena; + + AstExprConstantString* pattern = nullptr; + size_t index = context.callSite->self ? 0 : 1; + if (context.callSite->args.size > index) + pattern = context.callSite->args.data[index]->as(); + + if (!pattern) + return false; + + std::vector returnTypes = parsePatternString(context.solver->builtinTypes, pattern->value.data, pattern->value.size); + + if (returnTypes.empty()) + return false; + + context.solver->unify(context.solver->rootScope, context.callSite->location, params[0], context.solver->builtinTypes->stringType); + + const TypePackId emptyPack = arena->addTypePack({}); + const TypePackId returnList = arena->addTypePack(returnTypes); + const TypeId iteratorType = arena->addType(FunctionType{emptyPack, returnList}); + const TypePackId resTypePack = arena->addTypePack({iteratorType}); + asMutable(context.result)->ty.emplace(resTypePack); + + return true; +} + +static std::optional> magicFunctionMatch( + TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate withPredicate) +{ + auto [paramPack, _predicates] = withPredicate; + const auto& [params, tail] = flatten(paramPack); + + if (params.size() < 2 || params.size() > 3) + return std::nullopt; + + TypeArena& arena = typechecker.currentModule->internalTypes; + + AstExprConstantString* pattern = nullptr; + size_t patternIndex = expr.self ? 0 : 1; + if (expr.args.size > patternIndex) + pattern = expr.args.data[patternIndex]->as(); + + if (!pattern) + return std::nullopt; + + std::vector returnTypes = parsePatternString(typechecker.builtinTypes, pattern->value.data, pattern->value.size); + + if (returnTypes.empty()) + return std::nullopt; + + typechecker.unify(params[0], typechecker.stringType, scope, expr.args.data[0]->location); + + const TypeId optionalNumber = arena.addType(UnionType{{typechecker.nilType, typechecker.numberType}}); + + size_t initIndex = expr.self ? 1 : 2; + if (params.size() == 3 && expr.args.size > initIndex) + typechecker.unify(params[2], optionalNumber, scope, expr.args.data[initIndex]->location); + + const TypePackId returnList = arena.addTypePack(returnTypes); + return WithPredicate{returnList}; +} + +static bool dcrMagicFunctionMatch(MagicFunctionCallContext context) +{ + const auto& [params, tail] = flatten(context.arguments); + + if (params.size() < 2 || params.size() > 3) + return false; + + TypeArena* arena = context.solver->arena; + + AstExprConstantString* pattern = nullptr; + size_t patternIndex = context.callSite->self ? 0 : 1; + if (context.callSite->args.size > patternIndex) + pattern = context.callSite->args.data[patternIndex]->as(); + + if (!pattern) + return false; + + std::vector returnTypes = parsePatternString(context.solver->builtinTypes, pattern->value.data, pattern->value.size); + + if (returnTypes.empty()) + return false; + + context.solver->unify(context.solver->rootScope, context.callSite->location, params[0], context.solver->builtinTypes->stringType); + + const TypeId optionalNumber = arena->addType(UnionType{{context.solver->builtinTypes->nilType, context.solver->builtinTypes->numberType}}); + + size_t initIndex = context.callSite->self ? 1 : 2; + if (params.size() == 3 && context.callSite->args.size > initIndex) + context.solver->unify(context.solver->rootScope, context.callSite->location, params[2], optionalNumber); + + const TypePackId returnList = arena->addTypePack(returnTypes); + asMutable(context.result)->ty.emplace(returnList); + + return true; +} + +static std::optional> magicFunctionFind( + TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate withPredicate) +{ + auto [paramPack, _predicates] = withPredicate; + const auto& [params, tail] = flatten(paramPack); + + if (params.size() < 2 || params.size() > 4) + return std::nullopt; + + TypeArena& arena = typechecker.currentModule->internalTypes; + + AstExprConstantString* pattern = nullptr; + size_t patternIndex = expr.self ? 0 : 1; + if (expr.args.size > patternIndex) + pattern = expr.args.data[patternIndex]->as(); + + if (!pattern) + return std::nullopt; + + bool plain = false; + size_t plainIndex = expr.self ? 2 : 3; + if (expr.args.size > plainIndex) + { + AstExprConstantBool* p = expr.args.data[plainIndex]->as(); + plain = p && p->value; + } + + std::vector returnTypes; + if (!plain) + { + returnTypes = parsePatternString(typechecker.builtinTypes, pattern->value.data, pattern->value.size); + + if (returnTypes.empty()) + return std::nullopt; + } + + typechecker.unify(params[0], typechecker.stringType, scope, expr.args.data[0]->location); + + const TypeId optionalNumber = arena.addType(UnionType{{typechecker.nilType, typechecker.numberType}}); + const TypeId optionalBoolean = arena.addType(UnionType{{typechecker.nilType, typechecker.booleanType}}); + + size_t initIndex = expr.self ? 1 : 2; + if (params.size() >= 3 && expr.args.size > initIndex) + typechecker.unify(params[2], optionalNumber, scope, expr.args.data[initIndex]->location); + + if (params.size() == 4 && expr.args.size > plainIndex) + typechecker.unify(params[3], optionalBoolean, scope, expr.args.data[plainIndex]->location); + + returnTypes.insert(returnTypes.begin(), {optionalNumber, optionalNumber}); + + const TypePackId returnList = arena.addTypePack(returnTypes); + return WithPredicate{returnList}; +} + +static bool dcrMagicFunctionFind(MagicFunctionCallContext context) +{ + const auto& [params, tail] = flatten(context.arguments); + + if (params.size() < 2 || params.size() > 4) + return false; + + TypeArena* arena = context.solver->arena; + NotNull builtinTypes = context.solver->builtinTypes; + + AstExprConstantString* pattern = nullptr; + size_t patternIndex = context.callSite->self ? 0 : 1; + if (context.callSite->args.size > patternIndex) + pattern = context.callSite->args.data[patternIndex]->as(); + + if (!pattern) + return false; + + bool plain = false; + size_t plainIndex = context.callSite->self ? 2 : 3; + if (context.callSite->args.size > plainIndex) + { + AstExprConstantBool* p = context.callSite->args.data[plainIndex]->as(); + plain = p && p->value; + } + + std::vector returnTypes; + if (!plain) + { + returnTypes = parsePatternString(builtinTypes, pattern->value.data, pattern->value.size); + + if (returnTypes.empty()) + return false; + } + + context.solver->unify(context.solver->rootScope, context.callSite->location, params[0], builtinTypes->stringType); + + const TypeId optionalNumber = arena->addType(UnionType{{builtinTypes->nilType, builtinTypes->numberType}}); + const TypeId optionalBoolean = arena->addType(UnionType{{builtinTypes->nilType, builtinTypes->booleanType}}); + + size_t initIndex = context.callSite->self ? 1 : 2; + if (params.size() >= 3 && context.callSite->args.size > initIndex) + context.solver->unify(context.solver->rootScope, context.callSite->location, params[2], optionalNumber); + + if (params.size() == 4 && context.callSite->args.size > plainIndex) + context.solver->unify(context.solver->rootScope, context.callSite->location, params[3], optionalBoolean); + + returnTypes.insert(returnTypes.begin(), {optionalNumber, optionalNumber}); + + const TypePackId returnList = arena->addTypePack(returnTypes); + asMutable(context.result)->ty.emplace(returnList); + return true; +} + +TypeId makeStringMetatable(NotNull builtinTypes) +{ + NotNull arena{builtinTypes->arena.get()}; + + const TypeId nilType = builtinTypes->nilType; + const TypeId numberType = builtinTypes->numberType; + const TypeId booleanType = builtinTypes->booleanType; + const TypeId stringType = builtinTypes->stringType; + const TypeId anyType = builtinTypes->anyType; + + const TypeId optionalNumber = arena->addType(UnionType{{nilType, numberType}}); + const TypeId optionalString = arena->addType(UnionType{{nilType, stringType}}); + const TypeId optionalBoolean = arena->addType(UnionType{{nilType, booleanType}}); + + const TypePackId oneStringPack = arena->addTypePack({stringType}); + const TypePackId anyTypePack = arena->addTypePack(TypePackVar{VariadicTypePack{anyType}, true}); + + FunctionType formatFTV{arena->addTypePack(TypePack{{stringType}, anyTypePack}), oneStringPack}; + formatFTV.magicFunction = &magicFunctionFormat; + const TypeId formatFn = arena->addType(formatFTV); + attachDcrMagicFunction(formatFn, dcrMagicFunctionFormat); + + const TypePackId emptyPack = arena->addTypePack({}); + const TypePackId stringVariadicList = arena->addTypePack(TypePackVar{VariadicTypePack{stringType}}); + const TypePackId numberVariadicList = arena->addTypePack(TypePackVar{VariadicTypePack{numberType}}); + + const TypeId stringToStringType = makeFunction(*arena, std::nullopt, {}, {}, {stringType}, {}, {stringType}); + + const TypeId replArgType = + arena->addType(UnionType{{stringType, arena->addType(TableType({}, TableIndexer(stringType, stringType), TypeLevel{}, TableState::Generic)), + makeFunction(*arena, std::nullopt, {}, {}, {stringType}, {}, {stringType})}}); + const TypeId gsubFunc = makeFunction(*arena, stringType, {}, {}, {stringType, replArgType, optionalNumber}, {}, {stringType, numberType}); + const TypeId gmatchFunc = + makeFunction(*arena, stringType, {}, {}, {stringType}, {}, {arena->addType(FunctionType{emptyPack, stringVariadicList})}); + attachMagicFunction(gmatchFunc, magicFunctionGmatch); + attachDcrMagicFunction(gmatchFunc, dcrMagicFunctionGmatch); + + const TypeId matchFunc = arena->addType( + FunctionType{arena->addTypePack({stringType, stringType, optionalNumber}), arena->addTypePack(TypePackVar{VariadicTypePack{stringType}})}); + attachMagicFunction(matchFunc, magicFunctionMatch); + attachDcrMagicFunction(matchFunc, dcrMagicFunctionMatch); + + const TypeId findFunc = arena->addType(FunctionType{arena->addTypePack({stringType, stringType, optionalNumber, optionalBoolean}), + arena->addTypePack(TypePack{{optionalNumber, optionalNumber}, stringVariadicList})}); + attachMagicFunction(findFunc, magicFunctionFind); + attachDcrMagicFunction(findFunc, dcrMagicFunctionFind); + + TableType::Props stringLib = { + {"byte", {arena->addType(FunctionType{arena->addTypePack({stringType, optionalNumber, optionalNumber}), numberVariadicList})}}, + {"char", {arena->addType(FunctionType{numberVariadicList, arena->addTypePack({stringType})})}}, + {"find", {findFunc}}, + {"format", {formatFn}}, // FIXME + {"gmatch", {gmatchFunc}}, + {"gsub", {gsubFunc}}, + {"len", {makeFunction(*arena, stringType, {}, {}, {}, {}, {numberType})}}, + {"lower", {stringToStringType}}, + {"match", {matchFunc}}, + {"rep", {makeFunction(*arena, stringType, {}, {}, {numberType}, {}, {stringType})}}, + {"reverse", {stringToStringType}}, + {"sub", {makeFunction(*arena, stringType, {}, {}, {numberType, optionalNumber}, {}, {stringType})}}, + {"upper", {stringToStringType}}, + {"split", {makeFunction(*arena, stringType, {}, {}, {optionalString}, {}, + {arena->addType(TableType{{}, TableIndexer{numberType, stringType}, TypeLevel{}, TableState::Sealed})})}}, + {"pack", {arena->addType(FunctionType{ + arena->addTypePack(TypePack{{stringType}, anyTypePack}), + oneStringPack, + })}}, + {"packsize", {makeFunction(*arena, stringType, {}, {}, {}, {}, {numberType})}}, + {"unpack", {arena->addType(FunctionType{ + arena->addTypePack(TypePack{{stringType, stringType, optionalNumber}}), + anyTypePack, + })}}, + }; + + assignPropDocumentationSymbols(stringLib, "@luau/global/string"); + + TypeId tableType = arena->addType(TableType{std::move(stringLib), std::nullopt, TypeLevel{}, TableState::Sealed}); + + if (TableType* ttv = getMutable(tableType)) + ttv->name = "typeof(string)"; + + return arena->addType(TableType{{{{"__index", {tableType}}}}, std::nullopt, TypeLevel{}, TableState::Sealed}); +} + static std::optional> magicFunctionSelect( TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate withPredicate) { diff --git a/Analysis/src/Frontend.cpp b/Analysis/src/Frontend.cpp index c6c360b8..b71a9354 100644 --- a/Analysis/src/Frontend.cpp +++ b/Analysis/src/Frontend.cpp @@ -36,6 +36,7 @@ LUAU_FASTFLAGVARIABLE(DebugLuauDeferredConstraintResolution, false) LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson, false) LUAU_FASTFLAGVARIABLE(DebugLuauReadWriteProperties, false) LUAU_FASTFLAGVARIABLE(LuauTypecheckLimitControls, false) +LUAU_FASTFLAGVARIABLE(CorrectEarlyReturnInMarkDirty, false) namespace Luau { @@ -928,7 +929,6 @@ void Frontend::checkBuildQueueItem(BuildQueueItem& item) { // The autocomplete typecheck is always in strict mode with DM awareness // to provide better type information for IDE features - TypeCheckLimits typeCheckLimits; if (autocompleteTimeLimit != 0.0) typeCheckLimits.finishTime = TimeTrace::getClock() + autocompleteTimeLimit; @@ -1149,8 +1149,16 @@ bool Frontend::isDirty(const ModuleName& name, bool forAutocomplete) const */ void Frontend::markDirty(const ModuleName& name, std::vector* markedDirty) { - if (!moduleResolver.getModule(name) && !moduleResolverForAutocomplete.getModule(name)) - return; + if (FFlag::CorrectEarlyReturnInMarkDirty) + { + if (sourceNodes.count(name) == 0) + return; + } + else + { + if (!moduleResolver.getModule(name) && !moduleResolverForAutocomplete.getModule(name)) + return; + } std::unordered_map> reverseDeps; for (const auto& module : sourceNodes) diff --git a/Analysis/src/GlobalTypes.cpp b/Analysis/src/GlobalTypes.cpp new file mode 100644 index 00000000..9e26a2e3 --- /dev/null +++ b/Analysis/src/GlobalTypes.cpp @@ -0,0 +1,34 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details + +#include "Luau/GlobalTypes.h" + +LUAU_FASTFLAG(LuauInitializeStringMetatableInGlobalTypes) + +namespace Luau +{ + +GlobalTypes::GlobalTypes(NotNull builtinTypes) + : builtinTypes(builtinTypes) +{ + globalScope = std::make_shared(globalTypes.addTypePack(TypePackVar{FreeTypePack{TypeLevel{}}})); + + globalScope->addBuiltinTypeBinding("any", TypeFun{{}, builtinTypes->anyType}); + globalScope->addBuiltinTypeBinding("nil", TypeFun{{}, builtinTypes->nilType}); + globalScope->addBuiltinTypeBinding("number", TypeFun{{}, builtinTypes->numberType}); + globalScope->addBuiltinTypeBinding("string", TypeFun{{}, builtinTypes->stringType}); + globalScope->addBuiltinTypeBinding("boolean", TypeFun{{}, builtinTypes->booleanType}); + globalScope->addBuiltinTypeBinding("thread", TypeFun{{}, builtinTypes->threadType}); + globalScope->addBuiltinTypeBinding("unknown", TypeFun{{}, builtinTypes->unknownType}); + globalScope->addBuiltinTypeBinding("never", TypeFun{{}, builtinTypes->neverType}); + + if (FFlag::LuauInitializeStringMetatableInGlobalTypes) + { + unfreeze(*builtinTypes->arena); + TypeId stringMetatableTy = makeStringMetatable(builtinTypes); + asMutable(builtinTypes->stringType)->ty.emplace(PrimitiveType::String, stringMetatableTy); + persist(stringMetatableTy); + freeze(*builtinTypes->arena); + } +} + +} diff --git a/Analysis/src/Subtyping.cpp b/Analysis/src/Subtyping.cpp index 3f3c9319..8012bac7 100644 --- a/Analysis/src/Subtyping.cpp +++ b/Analysis/src/Subtyping.cpp @@ -664,9 +664,8 @@ SubtypingResult Subtyping::isSubtype_(const NormalizedType* subNorm, const Norma result.andAlso(isSubtype_(subNorm->tables, superNorm->tables)); // isSubtype_(subNorm->tables, superNorm->strings); // isSubtype_(subNorm->tables, superNorm->classes); - // isSubtype_(subNorm->functions, superNorm->functions); + result.andAlso(isSubtype_(subNorm->functions, superNorm->functions)); // isSubtype_(subNorm->tyvars, superNorm->tyvars); - return result; } @@ -703,6 +702,16 @@ SubtypingResult Subtyping::isSubtype_(const NormalizedClassType& subClass, const return {true}; } +SubtypingResult Subtyping::isSubtype_(const NormalizedFunctionType& subFunction, const NormalizedFunctionType& superFunction) +{ + if (subFunction.isNever()) + return {true}; + else if (superFunction.isTop) + return {true}; + else + return isSubtype_(subFunction.parts, superFunction.parts); +} + SubtypingResult Subtyping::isSubtype_(const TypeIds& subTypes, const TypeIds& superTypes) { std::vector results; diff --git a/Analysis/src/ToDot.cpp b/Analysis/src/ToDot.cpp index c3a1db4c..04d04470 100644 --- a/Analysis/src/ToDot.cpp +++ b/Analysis/src/ToDot.cpp @@ -9,6 +9,8 @@ #include #include +LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution); + namespace Luau { @@ -52,7 +54,7 @@ bool StateDot::canDuplicatePrimitive(TypeId ty) if (get(ty)) return false; - return get(ty) || get(ty); + return get(ty) || get(ty) || get(ty) || get(ty); } void StateDot::visitChild(TypeId ty, int parentIndex, const char* linkName) @@ -76,6 +78,10 @@ void StateDot::visitChild(TypeId ty, int parentIndex, const char* linkName) formatAppend(result, "n%d [label=\"%s\"];\n", index, toString(ty).c_str()); else if (get(ty)) formatAppend(result, "n%d [label=\"any\"];\n", index); + else if (get(ty)) + formatAppend(result, "n%d [label=\"unknown\"];\n", index); + else if (get(ty)) + formatAppend(result, "n%d [label=\"never\"];\n", index); } else { @@ -139,159 +145,215 @@ void StateDot::visitChildren(TypeId ty, int index) startNode(index); startNodeLabel(); - if (const BoundType* btv = get(ty)) + auto go = [&](auto&& t) { - formatAppend(result, "BoundType %d", index); - finishNodeLabel(ty); - finishNode(); + using T = std::decay_t; - visitChild(btv->boundTo, index); - } - else if (const FunctionType* ftv = get(ty)) - { - formatAppend(result, "FunctionType %d", index); - finishNodeLabel(ty); - finishNode(); - - visitChild(ftv->argTypes, index, "arg"); - visitChild(ftv->retTypes, index, "ret"); - } - else if (const TableType* ttv = get(ty)) - { - if (ttv->name) - formatAppend(result, "TableType %s", ttv->name->c_str()); - else if (ttv->syntheticName) - formatAppend(result, "TableType %s", ttv->syntheticName->c_str()); - else - formatAppend(result, "TableType %d", index); - finishNodeLabel(ty); - finishNode(); - - if (ttv->boundTo) - return visitChild(*ttv->boundTo, index, "boundTo"); - - for (const auto& [name, prop] : ttv->props) - visitChild(prop.type(), index, name.c_str()); - if (ttv->indexer) + if constexpr (std::is_same_v) { - visitChild(ttv->indexer->indexType, index, "[index]"); - visitChild(ttv->indexer->indexResultType, index, "[value]"); + formatAppend(result, "BoundType %d", index); + finishNodeLabel(ty); + finishNode(); + + visitChild(t.boundTo, index); } - for (TypeId itp : ttv->instantiatedTypeParams) - visitChild(itp, index, "typeParam"); - - for (TypePackId itp : ttv->instantiatedTypePackParams) - visitChild(itp, index, "typePackParam"); - } - else if (const MetatableType* mtv = get(ty)) - { - formatAppend(result, "MetatableType %d", index); - finishNodeLabel(ty); - finishNode(); - - visitChild(mtv->table, index, "table"); - visitChild(mtv->metatable, index, "metatable"); - } - else if (const UnionType* utv = get(ty)) - { - formatAppend(result, "UnionType %d", index); - finishNodeLabel(ty); - finishNode(); - - for (TypeId opt : utv->options) - visitChild(opt, index); - } - else if (const IntersectionType* itv = get(ty)) - { - formatAppend(result, "IntersectionType %d", index); - finishNodeLabel(ty); - finishNode(); - - for (TypeId part : itv->parts) - visitChild(part, index); - } - else if (const GenericType* gtv = get(ty)) - { - if (gtv->explicitName) - formatAppend(result, "GenericType %s", gtv->name.c_str()); - else - formatAppend(result, "GenericType %d", index); - finishNodeLabel(ty); - finishNode(); - } - else if (const FreeType* ftv = get(ty)) - { - formatAppend(result, "FreeType %d", index); - finishNodeLabel(ty); - finishNode(); - } - else if (get(ty)) - { - formatAppend(result, "AnyType %d", index); - finishNodeLabel(ty); - finishNode(); - } - else if (get(ty)) - { - formatAppend(result, "PrimitiveType %s", toString(ty).c_str()); - finishNodeLabel(ty); - finishNode(); - } - else if (get(ty)) - { - formatAppend(result, "ErrorType %d", index); - finishNodeLabel(ty); - finishNode(); - } - else if (const ClassType* ctv = get(ty)) - { - formatAppend(result, "ClassType %s", ctv->name.c_str()); - finishNodeLabel(ty); - finishNode(); - - for (const auto& [name, prop] : ctv->props) - visitChild(prop.type(), index, name.c_str()); - - if (ctv->parent) - visitChild(*ctv->parent, index, "[parent]"); - - if (ctv->metatable) - visitChild(*ctv->metatable, index, "[metatable]"); - - if (ctv->indexer) + else if constexpr (std::is_same_v) { - visitChild(ctv->indexer->indexType, index, "[index]"); - visitChild(ctv->indexer->indexResultType, index, "[value]"); + formatAppend(result, "BlockedType %d", index); + finishNodeLabel(ty); + finishNode(); } - } - else if (const SingletonType* stv = get(ty)) - { - std::string res; + else if constexpr (std::is_same_v) + { + formatAppend(result, "FunctionType %d", index); + finishNodeLabel(ty); + finishNode(); - if (const StringSingleton* ss = get(stv)) - { - // Don't put in quotes anywhere. If it's outside of the call to escape, - // then it's invalid syntax. If it's inside, then escaping is super noisy. - res = "string: " + escape(ss->value); + visitChild(t.argTypes, index, "arg"); + visitChild(t.retTypes, index, "ret"); } - else if (const BooleanSingleton* bs = get(stv)) + else if constexpr (std::is_same_v) { - res = "boolean: "; - res += bs->value ? "true" : "false"; + if (t.name) + formatAppend(result, "TableType %s", t.name->c_str()); + else if (t.syntheticName) + formatAppend(result, "TableType %s", t.syntheticName->c_str()); + else + formatAppend(result, "TableType %d", index); + finishNodeLabel(ty); + finishNode(); + + if (t.boundTo) + return visitChild(*t.boundTo, index, "boundTo"); + + for (const auto& [name, prop] : t.props) + visitChild(prop.type(), index, name.c_str()); + if (t.indexer) + { + visitChild(t.indexer->indexType, index, "[index]"); + visitChild(t.indexer->indexResultType, index, "[value]"); + } + for (TypeId itp : t.instantiatedTypeParams) + visitChild(itp, index, "typeParam"); + + for (TypePackId itp : t.instantiatedTypePackParams) + visitChild(itp, index, "typePackParam"); + } + else if constexpr (std::is_same_v) + { + formatAppend(result, "MetatableType %d", index); + finishNodeLabel(ty); + finishNode(); + + visitChild(t.table, index, "table"); + visitChild(t.metatable, index, "metatable"); + } + else if constexpr (std::is_same_v) + { + formatAppend(result, "UnionType %d", index); + finishNodeLabel(ty); + finishNode(); + + for (TypeId opt : t.options) + visitChild(opt, index); + } + else if constexpr (std::is_same_v) + { + formatAppend(result, "IntersectionType %d", index); + finishNodeLabel(ty); + finishNode(); + + for (TypeId part : t.parts) + visitChild(part, index); + } + else if constexpr (std::is_same_v) + { + formatAppend(result, "LazyType %d", index); + finishNodeLabel(ty); + finishNode(); + } + else if constexpr (std::is_same_v) + { + formatAppend(result, "PendingExpansionType %d", index); + finishNodeLabel(ty); + finishNode(); + } + else if constexpr (std::is_same_v) + { + if (t.explicitName) + formatAppend(result, "GenericType %s", t.name.c_str()); + else + formatAppend(result, "GenericType %d", index); + finishNodeLabel(ty); + finishNode(); + } + else if constexpr (std::is_same_v) + { + formatAppend(result, "FreeType %d", index); + finishNodeLabel(ty); + finishNode(); + + if (FFlag::DebugLuauDeferredConstraintResolution) + { + if (!get(t.lowerBound)) + visitChild(t.lowerBound, index, "[lowerBound]"); + + if (!get(t.upperBound)) + visitChild(t.upperBound, index, "[upperBound]"); + } + } + else if constexpr (std::is_same_v) + { + formatAppend(result, "AnyType %d", index); + finishNodeLabel(ty); + finishNode(); + } + else if constexpr (std::is_same_v) + { + formatAppend(result, "UnknownType %d", index); + finishNodeLabel(ty); + finishNode(); + } + else if constexpr (std::is_same_v) + { + formatAppend(result, "NeverType %d", index); + finishNodeLabel(ty); + finishNode(); + } + else if constexpr (std::is_same_v) + { + formatAppend(result, "PrimitiveType %s", toString(ty).c_str()); + finishNodeLabel(ty); + finishNode(); + } + else if constexpr (std::is_same_v) + { + formatAppend(result, "ErrorType %d", index); + finishNodeLabel(ty); + finishNode(); + } + else if constexpr (std::is_same_v) + { + formatAppend(result, "ClassType %s", t.name.c_str()); + finishNodeLabel(ty); + finishNode(); + + for (const auto& [name, prop] : t.props) + visitChild(prop.type(), index, name.c_str()); + + if (t.parent) + visitChild(*t.parent, index, "[parent]"); + + if (t.metatable) + visitChild(*t.metatable, index, "[metatable]"); + + if (t.indexer) + { + visitChild(t.indexer->indexType, index, "[index]"); + visitChild(t.indexer->indexResultType, index, "[value]"); + } + } + else if constexpr (std::is_same_v) + { + std::string res; + + if (const StringSingleton* ss = get(&t)) + { + // Don't put in quotes anywhere. If it's outside of the call to escape, + // then it's invalid syntax. If it's inside, then escaping is super noisy. + res = "string: " + escape(ss->value); + } + else if (const BooleanSingleton* bs = get(&t)) + { + res = "boolean: "; + res += bs->value ? "true" : "false"; + } + else + LUAU_ASSERT(!"unknown singleton type"); + + formatAppend(result, "SingletonType %s", res.c_str()); + finishNodeLabel(ty); + finishNode(); + } + else if constexpr (std::is_same_v) + { + formatAppend(result, "NegationType %d", index); + finishNodeLabel(ty); + finishNode(); + + visitChild(t.ty, index, "[negated]"); + } + else if constexpr (std::is_same_v) + { + formatAppend(result, "TypeFamilyInstanceType %d", index); + finishNodeLabel(ty); + finishNode(); } else - LUAU_ASSERT(!"unknown singleton type"); + static_assert(always_false_v, "unknown type kind"); + }; - formatAppend(result, "SingletonType %s", res.c_str()); - finishNodeLabel(ty); - finishNode(); - } - else - { - LUAU_ASSERT(!"unknown type kind"); - finishNodeLabel(ty); - finishNode(); - } + visit(go, ty->ty); } void StateDot::visitChildren(TypePackId tp, int index) diff --git a/Analysis/src/Type.cpp b/Analysis/src/Type.cpp index 2590e4dc..86564cf5 100644 --- a/Analysis/src/Type.cpp +++ b/Analysis/src/Type.cpp @@ -27,26 +27,11 @@ LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauNormalizeBlockedTypes) LUAU_FASTFLAG(DebugLuauReadWriteProperties) +LUAU_FASTFLAGVARIABLE(LuauInitializeStringMetatableInGlobalTypes, false) namespace Luau { -std::optional> magicFunctionFormat( - TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate withPredicate); -static bool dcrMagicFunctionFormat(MagicFunctionCallContext context); - -static std::optional> magicFunctionGmatch( - TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate withPredicate); -static bool dcrMagicFunctionGmatch(MagicFunctionCallContext context); - -static std::optional> magicFunctionMatch( - TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate withPredicate); -static bool dcrMagicFunctionMatch(MagicFunctionCallContext context); - -static std::optional> magicFunctionFind( - TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate withPredicate); -static bool dcrMagicFunctionFind(MagicFunctionCallContext context); - // LUAU_NOINLINE prevents unwrapLazy from being inlined into advance below; advance is important to keep inlineable static LUAU_NOINLINE TypeId unwrapLazy(LazyType* ltv) { @@ -933,6 +918,8 @@ TypeId makeFunction(TypeArena& arena, std::optional selfType, std::initi std::initializer_list genericPacks, std::initializer_list paramTypes, std::initializer_list paramNames, std::initializer_list retTypes); +TypeId makeStringMetatable(NotNull builtinTypes); // BuiltinDefinitions.cpp + BuiltinTypes::BuiltinTypes() : arena(new TypeArena) , debugFreezeArena(FFlag::DebugLuauFreezeArena) @@ -961,9 +948,12 @@ BuiltinTypes::BuiltinTypes() , uninhabitableTypePack(arena->addTypePack(TypePackVar{TypePack{{neverType}, neverTypePack}, /*persistent*/ true})) , errorTypePack(arena->addTypePack(TypePackVar{Unifiable::Error{}, /*persistent*/ true})) { - TypeId stringMetatable = makeStringMetatable(); - asMutable(stringType)->ty = PrimitiveType{PrimitiveType::String, stringMetatable}; - persist(stringMetatable); + if (!FFlag::LuauInitializeStringMetatableInGlobalTypes) + { + TypeId stringMetatable = makeStringMetatable(NotNull{this}); + asMutable(stringType)->ty = PrimitiveType{PrimitiveType::String, stringMetatable}; + persist(stringMetatable); + } freeze(*arena); } @@ -980,82 +970,6 @@ BuiltinTypes::~BuiltinTypes() FFlag::DebugLuauFreezeArena.value = prevFlag; } -TypeId BuiltinTypes::makeStringMetatable() -{ - const TypeId optionalNumber = arena->addType(UnionType{{nilType, numberType}}); - const TypeId optionalString = arena->addType(UnionType{{nilType, stringType}}); - const TypeId optionalBoolean = arena->addType(UnionType{{nilType, booleanType}}); - - const TypePackId oneStringPack = arena->addTypePack({stringType}); - const TypePackId anyTypePack = arena->addTypePack(TypePackVar{VariadicTypePack{anyType}, true}); - - FunctionType formatFTV{arena->addTypePack(TypePack{{stringType}, anyTypePack}), oneStringPack}; - formatFTV.magicFunction = &magicFunctionFormat; - const TypeId formatFn = arena->addType(formatFTV); - attachDcrMagicFunction(formatFn, dcrMagicFunctionFormat); - - const TypePackId emptyPack = arena->addTypePack({}); - const TypePackId stringVariadicList = arena->addTypePack(TypePackVar{VariadicTypePack{stringType}}); - const TypePackId numberVariadicList = arena->addTypePack(TypePackVar{VariadicTypePack{numberType}}); - - const TypeId stringToStringType = makeFunction(*arena, std::nullopt, {}, {}, {stringType}, {}, {stringType}); - - const TypeId replArgType = - arena->addType(UnionType{{stringType, arena->addType(TableType({}, TableIndexer(stringType, stringType), TypeLevel{}, TableState::Generic)), - makeFunction(*arena, std::nullopt, {}, {}, {stringType}, {}, {stringType})}}); - const TypeId gsubFunc = makeFunction(*arena, stringType, {}, {}, {stringType, replArgType, optionalNumber}, {}, {stringType, numberType}); - const TypeId gmatchFunc = - makeFunction(*arena, stringType, {}, {}, {stringType}, {}, {arena->addType(FunctionType{emptyPack, stringVariadicList})}); - attachMagicFunction(gmatchFunc, magicFunctionGmatch); - attachDcrMagicFunction(gmatchFunc, dcrMagicFunctionGmatch); - - const TypeId matchFunc = arena->addType( - FunctionType{arena->addTypePack({stringType, stringType, optionalNumber}), arena->addTypePack(TypePackVar{VariadicTypePack{stringType}})}); - attachMagicFunction(matchFunc, magicFunctionMatch); - attachDcrMagicFunction(matchFunc, dcrMagicFunctionMatch); - - const TypeId findFunc = arena->addType(FunctionType{arena->addTypePack({stringType, stringType, optionalNumber, optionalBoolean}), - arena->addTypePack(TypePack{{optionalNumber, optionalNumber}, stringVariadicList})}); - attachMagicFunction(findFunc, magicFunctionFind); - attachDcrMagicFunction(findFunc, dcrMagicFunctionFind); - - TableType::Props stringLib = { - {"byte", {arena->addType(FunctionType{arena->addTypePack({stringType, optionalNumber, optionalNumber}), numberVariadicList})}}, - {"char", {arena->addType(FunctionType{numberVariadicList, arena->addTypePack({stringType})})}}, - {"find", {findFunc}}, - {"format", {formatFn}}, // FIXME - {"gmatch", {gmatchFunc}}, - {"gsub", {gsubFunc}}, - {"len", {makeFunction(*arena, stringType, {}, {}, {}, {}, {numberType})}}, - {"lower", {stringToStringType}}, - {"match", {matchFunc}}, - {"rep", {makeFunction(*arena, stringType, {}, {}, {numberType}, {}, {stringType})}}, - {"reverse", {stringToStringType}}, - {"sub", {makeFunction(*arena, stringType, {}, {}, {numberType, optionalNumber}, {}, {stringType})}}, - {"upper", {stringToStringType}}, - {"split", {makeFunction(*arena, stringType, {}, {}, {optionalString}, {}, - {arena->addType(TableType{{}, TableIndexer{numberType, stringType}, TypeLevel{}, TableState::Sealed})})}}, - {"pack", {arena->addType(FunctionType{ - arena->addTypePack(TypePack{{stringType}, anyTypePack}), - oneStringPack, - })}}, - {"packsize", {makeFunction(*arena, stringType, {}, {}, {}, {}, {numberType})}}, - {"unpack", {arena->addType(FunctionType{ - arena->addTypePack(TypePack{{stringType, stringType, optionalNumber}}), - anyTypePack, - })}}, - }; - - assignPropDocumentationSymbols(stringLib, "@luau/global/string"); - - TypeId tableType = arena->addType(TableType{std::move(stringLib), std::nullopt, TypeLevel{}, TableState::Sealed}); - - if (TableType* ttv = getMutable(tableType)) - ttv->name = "typeof(string)"; - - return arena->addType(TableType{{{{"__index", {tableType}}}}, std::nullopt, TypeLevel{}, TableState::Sealed}); -} - TypeId BuiltinTypes::errorRecoveryType() const { return errorType; @@ -1261,436 +1175,6 @@ IntersectionTypeIterator end(const IntersectionType* itv) return IntersectionTypeIterator{}; } -static std::vector parseFormatString(NotNull builtinTypes, const char* data, size_t size) -{ - const char* options = "cdiouxXeEfgGqs*"; - - std::vector result; - - for (size_t i = 0; i < size; ++i) - { - if (data[i] == '%') - { - i++; - - if (i < size && data[i] == '%') - continue; - - // we just ignore all characters (including flags/precision) up until first alphabetic character - while (i < size && !(data[i] > 0 && (isalpha(data[i]) || data[i] == '*'))) - i++; - - if (i == size) - break; - - if (data[i] == 'q' || data[i] == 's') - result.push_back(builtinTypes->stringType); - else if (data[i] == '*') - result.push_back(builtinTypes->unknownType); - else if (strchr(options, data[i])) - result.push_back(builtinTypes->numberType); - else - result.push_back(builtinTypes->errorRecoveryType(builtinTypes->anyType)); - } - } - - return result; -} - -std::optional> magicFunctionFormat( - TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate withPredicate) -{ - auto [paramPack, _predicates] = withPredicate; - - TypeArena& arena = typechecker.currentModule->internalTypes; - - AstExprConstantString* fmt = nullptr; - if (auto index = expr.func->as(); index && expr.self) - { - if (auto group = index->expr->as()) - fmt = group->expr->as(); - else - fmt = index->expr->as(); - } - - if (!expr.self && expr.args.size > 0) - fmt = expr.args.data[0]->as(); - - if (!fmt) - return std::nullopt; - - std::vector expected = parseFormatString(typechecker.builtinTypes, fmt->value.data, fmt->value.size); - const auto& [params, tail] = flatten(paramPack); - - size_t paramOffset = 1; - size_t dataOffset = expr.self ? 0 : 1; - - // unify the prefix one argument at a time - for (size_t i = 0; i < expected.size() && i + paramOffset < params.size(); ++i) - { - Location location = expr.args.data[std::min(i + dataOffset, expr.args.size - 1)]->location; - - typechecker.unify(params[i + paramOffset], expected[i], scope, location); - } - - // if we know the argument count or if we have too many arguments for sure, we can issue an error - size_t numActualParams = params.size(); - size_t numExpectedParams = expected.size() + 1; // + 1 for the format string - - if (numExpectedParams != numActualParams && (!tail || numExpectedParams < numActualParams)) - typechecker.reportError(TypeError{expr.location, CountMismatch{numExpectedParams, std::nullopt, numActualParams}}); - - return WithPredicate{arena.addTypePack({typechecker.stringType})}; -} - -static bool dcrMagicFunctionFormat(MagicFunctionCallContext context) -{ - TypeArena* arena = context.solver->arena; - - AstExprConstantString* fmt = nullptr; - if (auto index = context.callSite->func->as(); index && context.callSite->self) - { - if (auto group = index->expr->as()) - fmt = group->expr->as(); - else - fmt = index->expr->as(); - } - - if (!context.callSite->self && context.callSite->args.size > 0) - fmt = context.callSite->args.data[0]->as(); - - if (!fmt) - return false; - - std::vector expected = parseFormatString(context.solver->builtinTypes, fmt->value.data, fmt->value.size); - const auto& [params, tail] = flatten(context.arguments); - - size_t paramOffset = 1; - - // unify the prefix one argument at a time - for (size_t i = 0; i < expected.size() && i + paramOffset < params.size(); ++i) - { - context.solver->unify(context.solver->rootScope, context.callSite->location, params[i + paramOffset], expected[i]); - } - - // if we know the argument count or if we have too many arguments for sure, we can issue an error - size_t numActualParams = params.size(); - size_t numExpectedParams = expected.size() + 1; // + 1 for the format string - - if (numExpectedParams != numActualParams && (!tail || numExpectedParams < numActualParams)) - context.solver->reportError(TypeError{context.callSite->location, CountMismatch{numExpectedParams, std::nullopt, numActualParams}}); - - TypePackId resultPack = arena->addTypePack({context.solver->builtinTypes->stringType}); - asMutable(context.result)->ty.emplace(resultPack); - - return true; -} - -static std::vector parsePatternString(NotNull builtinTypes, const char* data, size_t size) -{ - std::vector result; - int depth = 0; - bool parsingSet = false; - - for (size_t i = 0; i < size; ++i) - { - if (data[i] == '%') - { - ++i; - if (!parsingSet && i < size && data[i] == 'b') - i += 2; - } - else if (!parsingSet && data[i] == '[') - { - parsingSet = true; - if (i + 1 < size && data[i + 1] == ']') - i += 1; - } - else if (parsingSet && data[i] == ']') - { - parsingSet = false; - } - else if (data[i] == '(') - { - if (parsingSet) - continue; - - if (i + 1 < size && data[i + 1] == ')') - { - i++; - result.push_back(builtinTypes->optionalNumberType); - continue; - } - - ++depth; - result.push_back(builtinTypes->optionalStringType); - } - else if (data[i] == ')') - { - if (parsingSet) - continue; - - --depth; - - if (depth < 0) - break; - } - } - - if (depth != 0 || parsingSet) - return std::vector(); - - if (result.empty()) - result.push_back(builtinTypes->optionalStringType); - - return result; -} - -static std::optional> magicFunctionGmatch( - TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate withPredicate) -{ - auto [paramPack, _predicates] = withPredicate; - const auto& [params, tail] = flatten(paramPack); - - if (params.size() != 2) - return std::nullopt; - - TypeArena& arena = typechecker.currentModule->internalTypes; - - AstExprConstantString* pattern = nullptr; - size_t index = expr.self ? 0 : 1; - if (expr.args.size > index) - pattern = expr.args.data[index]->as(); - - if (!pattern) - return std::nullopt; - - std::vector returnTypes = parsePatternString(typechecker.builtinTypes, pattern->value.data, pattern->value.size); - - if (returnTypes.empty()) - return std::nullopt; - - typechecker.unify(params[0], typechecker.stringType, scope, expr.args.data[0]->location); - - const TypePackId emptyPack = arena.addTypePack({}); - const TypePackId returnList = arena.addTypePack(returnTypes); - const TypeId iteratorType = arena.addType(FunctionType{emptyPack, returnList}); - return WithPredicate{arena.addTypePack({iteratorType})}; -} - -static bool dcrMagicFunctionGmatch(MagicFunctionCallContext context) -{ - const auto& [params, tail] = flatten(context.arguments); - - if (params.size() != 2) - return false; - - TypeArena* arena = context.solver->arena; - - AstExprConstantString* pattern = nullptr; - size_t index = context.callSite->self ? 0 : 1; - if (context.callSite->args.size > index) - pattern = context.callSite->args.data[index]->as(); - - if (!pattern) - return false; - - std::vector returnTypes = parsePatternString(context.solver->builtinTypes, pattern->value.data, pattern->value.size); - - if (returnTypes.empty()) - return false; - - context.solver->unify(context.solver->rootScope, context.callSite->location, params[0], context.solver->builtinTypes->stringType); - - const TypePackId emptyPack = arena->addTypePack({}); - const TypePackId returnList = arena->addTypePack(returnTypes); - const TypeId iteratorType = arena->addType(FunctionType{emptyPack, returnList}); - const TypePackId resTypePack = arena->addTypePack({iteratorType}); - asMutable(context.result)->ty.emplace(resTypePack); - - return true; -} - -static std::optional> magicFunctionMatch( - TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate withPredicate) -{ - auto [paramPack, _predicates] = withPredicate; - const auto& [params, tail] = flatten(paramPack); - - if (params.size() < 2 || params.size() > 3) - return std::nullopt; - - TypeArena& arena = typechecker.currentModule->internalTypes; - - AstExprConstantString* pattern = nullptr; - size_t patternIndex = expr.self ? 0 : 1; - if (expr.args.size > patternIndex) - pattern = expr.args.data[patternIndex]->as(); - - if (!pattern) - return std::nullopt; - - std::vector returnTypes = parsePatternString(typechecker.builtinTypes, pattern->value.data, pattern->value.size); - - if (returnTypes.empty()) - return std::nullopt; - - typechecker.unify(params[0], typechecker.stringType, scope, expr.args.data[0]->location); - - const TypeId optionalNumber = arena.addType(UnionType{{typechecker.nilType, typechecker.numberType}}); - - size_t initIndex = expr.self ? 1 : 2; - if (params.size() == 3 && expr.args.size > initIndex) - typechecker.unify(params[2], optionalNumber, scope, expr.args.data[initIndex]->location); - - const TypePackId returnList = arena.addTypePack(returnTypes); - return WithPredicate{returnList}; -} - -static bool dcrMagicFunctionMatch(MagicFunctionCallContext context) -{ - const auto& [params, tail] = flatten(context.arguments); - - if (params.size() < 2 || params.size() > 3) - return false; - - TypeArena* arena = context.solver->arena; - - AstExprConstantString* pattern = nullptr; - size_t patternIndex = context.callSite->self ? 0 : 1; - if (context.callSite->args.size > patternIndex) - pattern = context.callSite->args.data[patternIndex]->as(); - - if (!pattern) - return false; - - std::vector returnTypes = parsePatternString(context.solver->builtinTypes, pattern->value.data, pattern->value.size); - - if (returnTypes.empty()) - return false; - - context.solver->unify(context.solver->rootScope, context.callSite->location, params[0], context.solver->builtinTypes->stringType); - - const TypeId optionalNumber = arena->addType(UnionType{{context.solver->builtinTypes->nilType, context.solver->builtinTypes->numberType}}); - - size_t initIndex = context.callSite->self ? 1 : 2; - if (params.size() == 3 && context.callSite->args.size > initIndex) - context.solver->unify(context.solver->rootScope, context.callSite->location, params[2], optionalNumber); - - const TypePackId returnList = arena->addTypePack(returnTypes); - asMutable(context.result)->ty.emplace(returnList); - - return true; -} - -static std::optional> magicFunctionFind( - TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate withPredicate) -{ - auto [paramPack, _predicates] = withPredicate; - const auto& [params, tail] = flatten(paramPack); - - if (params.size() < 2 || params.size() > 4) - return std::nullopt; - - TypeArena& arena = typechecker.currentModule->internalTypes; - - AstExprConstantString* pattern = nullptr; - size_t patternIndex = expr.self ? 0 : 1; - if (expr.args.size > patternIndex) - pattern = expr.args.data[patternIndex]->as(); - - if (!pattern) - return std::nullopt; - - bool plain = false; - size_t plainIndex = expr.self ? 2 : 3; - if (expr.args.size > plainIndex) - { - AstExprConstantBool* p = expr.args.data[plainIndex]->as(); - plain = p && p->value; - } - - std::vector returnTypes; - if (!plain) - { - returnTypes = parsePatternString(typechecker.builtinTypes, pattern->value.data, pattern->value.size); - - if (returnTypes.empty()) - return std::nullopt; - } - - typechecker.unify(params[0], typechecker.stringType, scope, expr.args.data[0]->location); - - const TypeId optionalNumber = arena.addType(UnionType{{typechecker.nilType, typechecker.numberType}}); - const TypeId optionalBoolean = arena.addType(UnionType{{typechecker.nilType, typechecker.booleanType}}); - - size_t initIndex = expr.self ? 1 : 2; - if (params.size() >= 3 && expr.args.size > initIndex) - typechecker.unify(params[2], optionalNumber, scope, expr.args.data[initIndex]->location); - - if (params.size() == 4 && expr.args.size > plainIndex) - typechecker.unify(params[3], optionalBoolean, scope, expr.args.data[plainIndex]->location); - - returnTypes.insert(returnTypes.begin(), {optionalNumber, optionalNumber}); - - const TypePackId returnList = arena.addTypePack(returnTypes); - return WithPredicate{returnList}; -} - -static bool dcrMagicFunctionFind(MagicFunctionCallContext context) -{ - const auto& [params, tail] = flatten(context.arguments); - - if (params.size() < 2 || params.size() > 4) - return false; - - TypeArena* arena = context.solver->arena; - NotNull builtinTypes = context.solver->builtinTypes; - - AstExprConstantString* pattern = nullptr; - size_t patternIndex = context.callSite->self ? 0 : 1; - if (context.callSite->args.size > patternIndex) - pattern = context.callSite->args.data[patternIndex]->as(); - - if (!pattern) - return false; - - bool plain = false; - size_t plainIndex = context.callSite->self ? 2 : 3; - if (context.callSite->args.size > plainIndex) - { - AstExprConstantBool* p = context.callSite->args.data[plainIndex]->as(); - plain = p && p->value; - } - - std::vector returnTypes; - if (!plain) - { - returnTypes = parsePatternString(builtinTypes, pattern->value.data, pattern->value.size); - - if (returnTypes.empty()) - return false; - } - - context.solver->unify(context.solver->rootScope, context.callSite->location, params[0], builtinTypes->stringType); - - const TypeId optionalNumber = arena->addType(UnionType{{builtinTypes->nilType, builtinTypes->numberType}}); - const TypeId optionalBoolean = arena->addType(UnionType{{builtinTypes->nilType, builtinTypes->booleanType}}); - - size_t initIndex = context.callSite->self ? 1 : 2; - if (params.size() >= 3 && context.callSite->args.size > initIndex) - context.solver->unify(context.solver->rootScope, context.callSite->location, params[2], optionalNumber); - - if (params.size() == 4 && context.callSite->args.size > plainIndex) - context.solver->unify(context.solver->rootScope, context.callSite->location, params[3], optionalBoolean); - - returnTypes.insert(returnTypes.begin(), {optionalNumber, optionalNumber}); - - const TypePackId returnList = arena->addTypePack(returnTypes); - asMutable(context.result)->ty.emplace(returnList); - return true; -} - TypeId freshType(NotNull arena, NotNull builtinTypes, Scope* scope) { return arena->addType(FreeType{scope, builtinTypes->neverType, builtinTypes->unknownType}); diff --git a/Analysis/src/TypeInfer.cpp b/Analysis/src/TypeInfer.cpp index 5349f16a..61c90ba8 100644 --- a/Analysis/src/TypeInfer.cpp +++ b/Analysis/src/TypeInfer.cpp @@ -38,6 +38,7 @@ LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAGVARIABLE(LuauAllowIndexClassParameters, false) LUAU_FASTFLAG(LuauOccursIsntAlwaysFailure) LUAU_FASTFLAGVARIABLE(LuauTinyControlFlowAnalysis, false) +LUAU_FASTFLAGVARIABLE(LuauVariadicOverloadFix, false) LUAU_FASTFLAGVARIABLE(LuauAlwaysCommitInferencesOfFunctionCalls, false) LUAU_FASTFLAG(LuauParseDeclareClassIndexer) LUAU_FASTFLAG(LuauFloorDivision); @@ -210,21 +211,6 @@ size_t HashBoolNamePair::operator()(const std::pair& pair) const return std::hash()(pair.first) ^ std::hash()(pair.second); } -GlobalTypes::GlobalTypes(NotNull builtinTypes) - : builtinTypes(builtinTypes) -{ - globalScope = std::make_shared(globalTypes.addTypePack(TypePackVar{FreeTypePack{TypeLevel{}}})); - - globalScope->addBuiltinTypeBinding("any", TypeFun{{}, builtinTypes->anyType}); - globalScope->addBuiltinTypeBinding("nil", TypeFun{{}, builtinTypes->nilType}); - globalScope->addBuiltinTypeBinding("number", TypeFun{{}, builtinTypes->numberType}); - globalScope->addBuiltinTypeBinding("string", TypeFun{{}, builtinTypes->stringType}); - globalScope->addBuiltinTypeBinding("boolean", TypeFun{{}, builtinTypes->booleanType}); - globalScope->addBuiltinTypeBinding("thread", TypeFun{{}, builtinTypes->threadType}); - globalScope->addBuiltinTypeBinding("unknown", TypeFun{{}, builtinTypes->unknownType}); - globalScope->addBuiltinTypeBinding("never", TypeFun{{}, builtinTypes->neverType}); -} - TypeChecker::TypeChecker(const ScopePtr& globalScope, ModuleResolver* resolver, NotNull builtinTypes, InternalErrorReporter* iceHandler) : globalScope(globalScope) , resolver(resolver) @@ -4038,7 +4024,13 @@ void TypeChecker::checkArgumentList(const ScopePtr& scope, const AstExpr& funNam if (argIndex < argLocations.size()) location = argLocations[argIndex]; - unify(*argIter, vtp->ty, scope, location); + if (FFlag::LuauVariadicOverloadFix) + { + state.location = location; + state.tryUnify(*argIter, vtp->ty); + } + else + unify(*argIter, vtp->ty, scope, location); ++argIter; ++argIndex; } diff --git a/Analysis/src/Unifier.cpp b/Analysis/src/Unifier.cpp index bc8ef018..7cf05cda 100644 --- a/Analysis/src/Unifier.cpp +++ b/Analysis/src/Unifier.cpp @@ -25,7 +25,6 @@ LUAU_FASTFLAGVARIABLE(LuauOccursIsntAlwaysFailure, false) LUAU_FASTFLAG(LuauNormalizeBlockedTypes) LUAU_FASTFLAG(LuauAlwaysCommitInferencesOfFunctionCalls) LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution) -LUAU_FASTFLAGVARIABLE(LuauTableUnifyRecursionLimit, false) namespace Luau { @@ -2260,23 +2259,13 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection, if (superTable != newSuperTable || subTable != newSubTable) { - if (FFlag::LuauTableUnifyRecursionLimit) + if (errors.empty()) { - if (errors.empty()) - { - RecursionLimiter _ra(&sharedState.counters.recursionCount, sharedState.counters.recursionLimit); - tryUnifyTables(subTy, superTy, isIntersection); - } + RecursionLimiter _ra(&sharedState.counters.recursionCount, sharedState.counters.recursionLimit); + tryUnifyTables(subTy, superTy, isIntersection); + } - return; - } - else - { - if (errors.empty()) - return tryUnifyTables(subTy, superTy, isIntersection); - else - return; - } + return; } } @@ -2351,23 +2340,13 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection, if (superTable != newSuperTable || subTable != newSubTable) { - if (FFlag::LuauTableUnifyRecursionLimit) + if (errors.empty()) { - if (errors.empty()) - { - RecursionLimiter _ra(&sharedState.counters.recursionCount, sharedState.counters.recursionLimit); - tryUnifyTables(subTy, superTy, isIntersection); - } + RecursionLimiter _ra(&sharedState.counters.recursionCount, sharedState.counters.recursionLimit); + tryUnifyTables(subTy, superTy, isIntersection); + } - return; - } - else - { - if (errors.empty()) - return tryUnifyTables(subTy, superTy, isIntersection); - else - return; - } + return; } } diff --git a/Ast/src/Lexer.cpp b/Ast/src/Lexer.cpp index 894d2dd7..1795243c 100644 --- a/Ast/src/Lexer.cpp +++ b/Ast/src/Lexer.cpp @@ -7,7 +7,6 @@ #include LUAU_FASTFLAGVARIABLE(LuauFloorDivision, false) -LUAU_FASTFLAGVARIABLE(LuauLexerConsumeFast, false) LUAU_FASTFLAGVARIABLE(LuauLexerLookaheadRemembersBraceType, false) namespace Luau @@ -460,19 +459,8 @@ Position Lexer::position() const LUAU_FORCEINLINE void Lexer::consume() { - if (isNewline(buffer[offset])) - { - // TODO: When the flag is removed, remove the outer condition - if (FFlag::LuauLexerConsumeFast) - { - LUAU_ASSERT(!isNewline(buffer[offset])); - } - else - { - line++; - lineOffset = offset + 1; - } - } + // consume() assumes current character is known to not be a newline; use consumeAny if this is not guaranteed + LUAU_ASSERT(!isNewline(buffer[offset])); offset++; } diff --git a/CodeGen/include/Luau/IrBuilder.h b/CodeGen/include/Luau/IrBuilder.h index d854b400..b953c888 100644 --- a/CodeGen/include/Luau/IrBuilder.h +++ b/CodeGen/include/Luau/IrBuilder.h @@ -66,6 +66,8 @@ struct IrBuilder bool inTerminatedBlock = false; + bool interruptRequested = false; + bool activeFastcallFallback = false; IrOp fastcallFallbackReturn; int fastcallSkipTarget = -1; @@ -76,6 +78,8 @@ struct IrBuilder std::vector instIndexToBlock; // Block index at the bytecode instruction + std::vector loopStepStack; + // Similar to BytecodeBuilder, duplicate constants are removed used the same method struct ConstantKey { diff --git a/CodeGen/include/Luau/IrData.h b/CodeGen/include/Luau/IrData.h index 12465906..298258c1 100644 --- a/CodeGen/include/Luau/IrData.h +++ b/CodeGen/include/Luau/IrData.h @@ -199,24 +199,12 @@ enum class IrCmd : uint8_t // D: block (if false) JUMP_EQ_TAG, - // Jump if two int numbers are equal - // A, B: int - // C: block (if true) - // D: block (if false) - JUMP_EQ_INT, - - // Jump if A < B - // A, B: int - // C: block (if true) - // D: block (if false) - JUMP_LT_INT, - - // Jump if unsigned(A) >= unsigned(B) + // Perform a conditional jump based on the result of integer comparison // A, B: int // C: condition // D: block (if true) // E: block (if false) - JUMP_GE_UINT, + JUMP_CMP_INT, // Jump if pointers are equal // A, B: pointer (*) diff --git a/CodeGen/include/Luau/IrUtils.h b/CodeGen/include/Luau/IrUtils.h index 3def51a8..5db5f6f1 100644 --- a/CodeGen/include/Luau/IrUtils.h +++ b/CodeGen/include/Luau/IrUtils.h @@ -94,9 +94,7 @@ inline bool isBlockTerminator(IrCmd cmd) case IrCmd::JUMP_IF_TRUTHY: case IrCmd::JUMP_IF_FALSY: case IrCmd::JUMP_EQ_TAG: - case IrCmd::JUMP_EQ_INT: - case IrCmd::JUMP_LT_INT: - case IrCmd::JUMP_GE_UINT: + case IrCmd::JUMP_CMP_INT: case IrCmd::JUMP_EQ_POINTER: case IrCmd::JUMP_CMP_NUM: case IrCmd::JUMP_SLOT_MATCH: diff --git a/CodeGen/src/EmitCommonX64.cpp b/CodeGen/src/EmitCommonX64.cpp index c831c0bc..97749fbe 100644 --- a/CodeGen/src/EmitCommonX64.cpp +++ b/CodeGen/src/EmitCommonX64.cpp @@ -12,6 +12,8 @@ #include "lgc.h" #include "lstate.h" +#include + namespace Luau { namespace CodeGen @@ -22,10 +24,15 @@ namespace X64 void jumpOnNumberCmp(AssemblyBuilderX64& build, RegisterX64 tmp, OperandX64 lhs, OperandX64 rhs, IrCondition cond, Label& label) { // Refresher on comi/ucomi EFLAGS: + // all zero: greater // CF only: less // ZF only: equal // PF+CF+ZF: unordered (NaN) + // To avoid the lack of conditional jumps that check for "greater" conditions in IEEE 754 compliant way, we use "less" forms to emulate these + if (cond == IrCondition::Greater || cond == IrCondition::GreaterEqual || cond == IrCondition::NotGreater || cond == IrCondition::NotGreaterEqual) + std::swap(lhs, rhs); + if (rhs.cat == CategoryX64::reg) { build.vucomisd(rhs, lhs); @@ -41,18 +48,22 @@ void jumpOnNumberCmp(AssemblyBuilderX64& build, RegisterX64 tmp, OperandX64 lhs, switch (cond) { case IrCondition::NotLessEqual: + case IrCondition::NotGreaterEqual: // (b < a) is the same as !(a <= b). jnae checks CF=1 which means < or NaN build.jcc(ConditionX64::NotAboveEqual, label); break; case IrCondition::LessEqual: + case IrCondition::GreaterEqual: // (b >= a) is the same as (a <= b). jae checks CF=0 which means >= and not NaN build.jcc(ConditionX64::AboveEqual, label); break; case IrCondition::NotLess: + case IrCondition::NotGreater: // (b <= a) is the same as !(a < b). jna checks CF=1 or ZF=1 which means <= or NaN build.jcc(ConditionX64::NotAbove, label); break; case IrCondition::Less: + case IrCondition::Greater: // (b > a) is the same as (a < b). ja checks CF=0 and ZF=0 which means > and not NaN build.jcc(ConditionX64::Above, label); break; @@ -66,6 +77,44 @@ void jumpOnNumberCmp(AssemblyBuilderX64& build, RegisterX64 tmp, OperandX64 lhs, } } +ConditionX64 getConditionInt(IrCondition cond) +{ + switch (cond) + { + case IrCondition::Equal: + return ConditionX64::Equal; + case IrCondition::NotEqual: + return ConditionX64::NotEqual; + case IrCondition::Less: + return ConditionX64::Less; + case IrCondition::NotLess: + return ConditionX64::NotLess; + case IrCondition::LessEqual: + return ConditionX64::LessEqual; + case IrCondition::NotLessEqual: + return ConditionX64::NotLessEqual; + case IrCondition::Greater: + return ConditionX64::Greater; + case IrCondition::NotGreater: + return ConditionX64::NotGreater; + case IrCondition::GreaterEqual: + return ConditionX64::GreaterEqual; + case IrCondition::NotGreaterEqual: + return ConditionX64::NotGreaterEqual; + case IrCondition::UnsignedLess: + return ConditionX64::Below; + case IrCondition::UnsignedLessEqual: + return ConditionX64::BelowEqual; + case IrCondition::UnsignedGreater: + return ConditionX64::Above; + case IrCondition::UnsignedGreaterEqual: + return ConditionX64::AboveEqual; + default: + LUAU_ASSERT(!"Unsupported condition"); + return ConditionX64::Zero; + } +} + void getTableNodeAtCachedSlot(AssemblyBuilderX64& build, RegisterX64 tmp, RegisterX64 node, RegisterX64 table, int pcpos) { LUAU_ASSERT(tmp != node); diff --git a/CodeGen/src/EmitCommonX64.h b/CodeGen/src/EmitCommonX64.h index 3288a164..3d9a59ff 100644 --- a/CodeGen/src/EmitCommonX64.h +++ b/CodeGen/src/EmitCommonX64.h @@ -195,6 +195,8 @@ inline void jumpIfTruthy(AssemblyBuilderX64& build, int ri, Label& target, Label void jumpOnNumberCmp(AssemblyBuilderX64& build, RegisterX64 tmp, OperandX64 lhs, OperandX64 rhs, IrCondition cond, Label& label); +ConditionX64 getConditionInt(IrCondition cond); + void getTableNodeAtCachedSlot(AssemblyBuilderX64& build, RegisterX64 tmp, RegisterX64 node, RegisterX64 table, int pcpos); void convertNumberToIndexOrJump(AssemblyBuilderX64& build, RegisterX64 tmp, RegisterX64 numd, RegisterX64 numi, Label& label); diff --git a/CodeGen/src/IrBuilder.cpp b/CodeGen/src/IrBuilder.cpp index 3ee82c76..e467ca68 100644 --- a/CodeGen/src/IrBuilder.cpp +++ b/CodeGen/src/IrBuilder.cpp @@ -149,6 +149,12 @@ void IrBuilder::buildFunctionIr(Proto* proto) // We skip dead bytecode instructions when they appear after block was already terminated if (!inTerminatedBlock) { + if (interruptRequested) + { + interruptRequested = false; + inst(IrCmd::INTERRUPT, constUint(i)); + } + translateInst(op, pc, i); if (fastcallSkipTarget != -1) diff --git a/CodeGen/src/IrDump.cpp b/CodeGen/src/IrDump.cpp index 7ed1a295..21b2f702 100644 --- a/CodeGen/src/IrDump.cpp +++ b/CodeGen/src/IrDump.cpp @@ -157,12 +157,8 @@ const char* getCmdName(IrCmd cmd) return "JUMP_IF_FALSY"; case IrCmd::JUMP_EQ_TAG: return "JUMP_EQ_TAG"; - case IrCmd::JUMP_EQ_INT: - return "JUMP_EQ_INT"; - case IrCmd::JUMP_LT_INT: - return "JUMP_LT_INT"; - case IrCmd::JUMP_GE_UINT: - return "JUMP_GE_UINT"; + case IrCmd::JUMP_CMP_INT: + return "JUMP_CMP_INT"; case IrCmd::JUMP_EQ_POINTER: return "JUMP_EQ_POINTER"; case IrCmd::JUMP_CMP_NUM: diff --git a/CodeGen/src/IrLoweringA64.cpp b/CodeGen/src/IrLoweringA64.cpp index 4369d120..a030f955 100644 --- a/CodeGen/src/IrLoweringA64.cpp +++ b/CodeGen/src/IrLoweringA64.cpp @@ -58,6 +58,58 @@ inline ConditionA64 getConditionFP(IrCondition cond) } } +inline ConditionA64 getConditionInt(IrCondition cond) +{ + switch (cond) + { + case IrCondition::Equal: + return ConditionA64::Equal; + + case IrCondition::NotEqual: + return ConditionA64::NotEqual; + + case IrCondition::Less: + return ConditionA64::Minus; + + case IrCondition::NotLess: + return ConditionA64::Plus; + + case IrCondition::LessEqual: + return ConditionA64::LessEqual; + + case IrCondition::NotLessEqual: + return ConditionA64::Greater; + + case IrCondition::Greater: + return ConditionA64::Greater; + + case IrCondition::NotGreater: + return ConditionA64::LessEqual; + + case IrCondition::GreaterEqual: + return ConditionA64::GreaterEqual; + + case IrCondition::NotGreaterEqual: + return ConditionA64::Less; + + case IrCondition::UnsignedLess: + return ConditionA64::CarryClear; + + case IrCondition::UnsignedLessEqual: + return ConditionA64::UnsignedLessEqual; + + case IrCondition::UnsignedGreater: + return ConditionA64::UnsignedGreater; + + case IrCondition::UnsignedGreaterEqual: + return ConditionA64::CarrySet; + + default: + LUAU_ASSERT(!"Unexpected condition code"); + return ConditionA64::Always; + } +} + static void emitAddOffset(AssemblyBuilderA64& build, RegisterA64 dst, RegisterA64 src, size_t offset) { LUAU_ASSERT(dst != src); @@ -714,31 +766,25 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) } break; } - case IrCmd::JUMP_EQ_INT: - if (intOp(inst.b) == 0) + case IrCmd::JUMP_CMP_INT: + { + IrCondition cond = conditionOp(inst.c); + + if (cond == IrCondition::Equal && intOp(inst.b) == 0) { - build.cbz(regOp(inst.a), labelOp(inst.c)); + build.cbz(regOp(inst.a), labelOp(inst.d)); + } + else if (cond == IrCondition::NotEqual && intOp(inst.b) == 0) + { + build.cbnz(regOp(inst.a), labelOp(inst.d)); } else { LUAU_ASSERT(unsigned(intOp(inst.b)) <= AssemblyBuilderA64::kMaxImmediate); build.cmp(regOp(inst.a), uint16_t(intOp(inst.b))); - build.b(ConditionA64::Equal, labelOp(inst.c)); + build.b(getConditionInt(cond), labelOp(inst.d)); } - jumpOrFallthrough(blockOp(inst.d), next); - break; - case IrCmd::JUMP_LT_INT: - LUAU_ASSERT(unsigned(intOp(inst.b)) <= AssemblyBuilderA64::kMaxImmediate); - build.cmp(regOp(inst.a), uint16_t(intOp(inst.b))); - build.b(ConditionA64::Less, labelOp(inst.c)); - jumpOrFallthrough(blockOp(inst.d), next); - break; - case IrCmd::JUMP_GE_UINT: - { - LUAU_ASSERT(unsigned(intOp(inst.b)) <= AssemblyBuilderA64::kMaxImmediate); - build.cmp(regOp(inst.a), uint16_t(unsigned(intOp(inst.b)))); - build.b(ConditionA64::CarrySet, labelOp(inst.c)); - jumpOrFallthrough(blockOp(inst.d), next); + jumpOrFallthrough(blockOp(inst.e), next); break; } case IrCmd::JUMP_EQ_POINTER: diff --git a/CodeGen/src/IrLoweringX64.cpp b/CodeGen/src/IrLoweringX64.cpp index 261f5717..fe5127ac 100644 --- a/CodeGen/src/IrLoweringX64.cpp +++ b/CodeGen/src/IrLoweringX64.cpp @@ -655,42 +655,36 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) } break; } - case IrCmd::JUMP_EQ_INT: - if (intOp(inst.b) == 0) + case IrCmd::JUMP_CMP_INT: + { + IrCondition cond = conditionOp(inst.c); + + if ((cond == IrCondition::Equal || cond == IrCondition::NotEqual) && intOp(inst.b) == 0) { + bool invert = cond == IrCondition::NotEqual; + build.test(regOp(inst.a), regOp(inst.a)); - if (isFallthroughBlock(blockOp(inst.c), next)) + if (isFallthroughBlock(blockOp(inst.d), next)) { - build.jcc(ConditionX64::NotZero, labelOp(inst.d)); - jumpOrFallthrough(blockOp(inst.c), next); + build.jcc(invert ? ConditionX64::Zero : ConditionX64::NotZero, labelOp(inst.e)); + jumpOrFallthrough(blockOp(inst.d), next); } else { - build.jcc(ConditionX64::Zero, labelOp(inst.c)); - jumpOrFallthrough(blockOp(inst.d), next); + build.jcc(invert ? ConditionX64::NotZero : ConditionX64::Zero, labelOp(inst.d)); + jumpOrFallthrough(blockOp(inst.e), next); } } else { build.cmp(regOp(inst.a), intOp(inst.b)); - build.jcc(ConditionX64::Equal, labelOp(inst.c)); - jumpOrFallthrough(blockOp(inst.d), next); + build.jcc(getConditionInt(cond), labelOp(inst.d)); + jumpOrFallthrough(blockOp(inst.e), next); } break; - case IrCmd::JUMP_LT_INT: - build.cmp(regOp(inst.a), intOp(inst.b)); - - build.jcc(ConditionX64::Less, labelOp(inst.c)); - jumpOrFallthrough(blockOp(inst.d), next); - break; - case IrCmd::JUMP_GE_UINT: - build.cmp(regOp(inst.a), unsigned(intOp(inst.b))); - - build.jcc(ConditionX64::AboveEqual, labelOp(inst.c)); - jumpOrFallthrough(blockOp(inst.d), next); - break; + } case IrCmd::JUMP_EQ_POINTER: build.cmp(regOp(inst.a), regOp(inst.b)); @@ -703,7 +697,6 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) ScopedRegX64 tmp{regs, SizeX64::xmmword}; - // TODO: jumpOnNumberCmp should work on IrCondition directly jumpOnNumberCmp(build, tmp.reg, memRegDoubleOp(inst.a), memRegDoubleOp(inst.b), cond, labelOp(inst.d)); jumpOrFallthrough(blockOp(inst.e), next); break; diff --git a/CodeGen/src/IrTranslateBuiltins.cpp b/CodeGen/src/IrTranslateBuiltins.cpp index 8513f786..279eabea 100644 --- a/CodeGen/src/IrTranslateBuiltins.cpp +++ b/CodeGen/src/IrTranslateBuiltins.cpp @@ -411,7 +411,7 @@ static BuiltinImplResult translateBuiltinBit32BinaryOp( IrOp falsey = build.block(IrBlockKind::Internal); IrOp truthy = build.block(IrBlockKind::Internal); IrOp exit = build.block(IrBlockKind::Internal); - build.inst(IrCmd::JUMP_EQ_INT, res, build.constInt(0), falsey, truthy); + build.inst(IrCmd::JUMP_CMP_INT, res, build.constInt(0), build.cond(IrCondition::Equal), falsey, truthy); build.beginBlock(falsey); build.inst(IrCmd::STORE_INT, build.vmReg(ra), build.constInt(0)); @@ -484,7 +484,7 @@ static BuiltinImplResult translateBuiltinBit32Shift( if (!knownGoodShift) { IrOp block = build.block(IrBlockKind::Internal); - build.inst(IrCmd::JUMP_GE_UINT, vbi, build.constInt(32), fallback, block); + build.inst(IrCmd::JUMP_CMP_INT, vbi, build.constInt(32), build.cond(IrCondition::UnsignedGreaterEqual), fallback, block); build.beginBlock(block); } @@ -549,36 +549,56 @@ static BuiltinImplResult translateBuiltinBit32Extract( IrOp vb = builtinLoadDouble(build, args); IrOp n = build.inst(IrCmd::NUM_TO_UINT, va); - IrOp f = build.inst(IrCmd::NUM_TO_INT, vb); IrOp value; if (nparams == 2) { - IrOp block = build.block(IrBlockKind::Internal); - build.inst(IrCmd::JUMP_GE_UINT, f, build.constInt(32), fallback, block); - build.beginBlock(block); + if (vb.kind == IrOpKind::Constant) + { + int f = int(build.function.doubleOp(vb)); - // TODO: this can be optimized using a bit-select instruction (bt on x86) - IrOp shift = build.inst(IrCmd::BITRSHIFT_UINT, n, f); - value = build.inst(IrCmd::BITAND_UINT, shift, build.constInt(1)); + if (unsigned(f) >= 32) + build.inst(IrCmd::JUMP, fallback); + + // TODO: this pair can be optimized using a bit-select instruction (bt on x86) + if (f) + value = build.inst(IrCmd::BITRSHIFT_UINT, n, build.constInt(f)); + + if ((f + 1) < 32) + value = build.inst(IrCmd::BITAND_UINT, value, build.constInt(1)); + } + else + { + IrOp f = build.inst(IrCmd::NUM_TO_INT, vb); + + IrOp block = build.block(IrBlockKind::Internal); + build.inst(IrCmd::JUMP_CMP_INT, f, build.constInt(32), build.cond(IrCondition::UnsignedGreaterEqual), fallback, block); + build.beginBlock(block); + + // TODO: this pair can be optimized using a bit-select instruction (bt on x86) + IrOp shift = build.inst(IrCmd::BITRSHIFT_UINT, n, f); + value = build.inst(IrCmd::BITAND_UINT, shift, build.constInt(1)); + } } else { + IrOp f = build.inst(IrCmd::NUM_TO_INT, vb); + builtinCheckDouble(build, build.vmReg(args.index + 1), pcpos); IrOp vc = builtinLoadDouble(build, build.vmReg(args.index + 1)); IrOp w = build.inst(IrCmd::NUM_TO_INT, vc); IrOp block1 = build.block(IrBlockKind::Internal); - build.inst(IrCmd::JUMP_LT_INT, f, build.constInt(0), fallback, block1); + build.inst(IrCmd::JUMP_CMP_INT, f, build.constInt(0), build.cond(IrCondition::Less), fallback, block1); build.beginBlock(block1); IrOp block2 = build.block(IrBlockKind::Internal); - build.inst(IrCmd::JUMP_LT_INT, w, build.constInt(1), fallback, block2); + build.inst(IrCmd::JUMP_CMP_INT, w, build.constInt(1), build.cond(IrCondition::Less), fallback, block2); build.beginBlock(block2); IrOp block3 = build.block(IrBlockKind::Internal); IrOp fw = build.inst(IrCmd::ADD_INT, f, w); - build.inst(IrCmd::JUMP_LT_INT, fw, build.constInt(33), block3, fallback); + build.inst(IrCmd::JUMP_CMP_INT, fw, build.constInt(33), build.cond(IrCondition::Less), block3, fallback); build.beginBlock(block3); IrOp shift = build.inst(IrCmd::BITLSHIFT_UINT, build.constInt(0xfffffffe), build.inst(IrCmd::SUB_INT, w, build.constInt(1))); @@ -615,10 +635,15 @@ static BuiltinImplResult translateBuiltinBit32ExtractK( uint32_t m = ~(0xfffffffeu << w1); - IrOp nf = build.inst(IrCmd::BITRSHIFT_UINT, n, build.constInt(f)); - IrOp and_ = build.inst(IrCmd::BITAND_UINT, nf, build.constInt(m)); + IrOp result = n; - IrOp value = build.inst(IrCmd::UINT_TO_NUM, and_); + if (f) + result = build.inst(IrCmd::BITRSHIFT_UINT, result, build.constInt(f)); + + if ((f + w1 + 1) < 32) + result = build.inst(IrCmd::BITAND_UINT, result, build.constInt(m)); + + IrOp value = build.inst(IrCmd::UINT_TO_NUM, result); build.inst(IrCmd::STORE_DOUBLE, build.vmReg(ra), value); if (ra != arg) @@ -673,7 +698,7 @@ static BuiltinImplResult translateBuiltinBit32Replace( if (nparams == 3) { IrOp block = build.block(IrBlockKind::Internal); - build.inst(IrCmd::JUMP_GE_UINT, f, build.constInt(32), fallback, block); + build.inst(IrCmd::JUMP_CMP_INT, f, build.constInt(32), build.cond(IrCondition::UnsignedGreaterEqual), fallback, block); build.beginBlock(block); // TODO: this can be optimized using a bit-select instruction (btr on x86) @@ -694,16 +719,16 @@ static BuiltinImplResult translateBuiltinBit32Replace( IrOp w = build.inst(IrCmd::NUM_TO_INT, vd); IrOp block1 = build.block(IrBlockKind::Internal); - build.inst(IrCmd::JUMP_LT_INT, f, build.constInt(0), fallback, block1); + build.inst(IrCmd::JUMP_CMP_INT, f, build.constInt(0), build.cond(IrCondition::Less), fallback, block1); build.beginBlock(block1); IrOp block2 = build.block(IrBlockKind::Internal); - build.inst(IrCmd::JUMP_LT_INT, w, build.constInt(1), fallback, block2); + build.inst(IrCmd::JUMP_CMP_INT, w, build.constInt(1), build.cond(IrCondition::Less), fallback, block2); build.beginBlock(block2); IrOp block3 = build.block(IrBlockKind::Internal); IrOp fw = build.inst(IrCmd::ADD_INT, f, w); - build.inst(IrCmd::JUMP_LT_INT, fw, build.constInt(33), block3, fallback); + build.inst(IrCmd::JUMP_CMP_INT, fw, build.constInt(33), build.cond(IrCondition::Less), block3, fallback); build.beginBlock(block3); IrOp shift1 = build.inst(IrCmd::BITLSHIFT_UINT, build.constInt(0xfffffffe), build.inst(IrCmd::SUB_INT, w, build.constInt(1))); diff --git a/CodeGen/src/IrTranslation.cpp b/CodeGen/src/IrTranslation.cpp index 26ad727a..f1eea645 100644 --- a/CodeGen/src/IrTranslation.cpp +++ b/CodeGen/src/IrTranslation.cpp @@ -12,6 +12,8 @@ #include "lstate.h" #include "ltm.h" +LUAU_FASTFLAGVARIABLE(LuauImproveForN, false) + namespace Luau { namespace CodeGen @@ -170,7 +172,7 @@ void translateInstJumpIfEq(IrBuilder& build, const Instruction* pc, int pcpos, b build.inst(IrCmd::SET_SAVEDPC, build.constUint(pcpos + 1)); IrOp result = build.inst(IrCmd::CMP_ANY, build.vmReg(ra), build.vmReg(rb), build.cond(IrCondition::Equal)); - build.inst(IrCmd::JUMP_EQ_INT, result, build.constInt(0), not_ ? target : next, not_ ? next : target); + build.inst(IrCmd::JUMP_CMP_INT, result, build.constInt(0), build.cond(IrCondition::Equal), not_ ? target : next, not_ ? next : target); build.beginBlock(next); } @@ -218,7 +220,7 @@ void translateInstJumpIfCond(IrBuilder& build, const Instruction* pc, int pcpos, } IrOp result = build.inst(IrCmd::CMP_ANY, build.vmReg(ra), build.vmReg(rb), build.cond(cond)); - build.inst(IrCmd::JUMP_EQ_INT, result, build.constInt(0), reverse ? target : next, reverse ? next : target); + build.inst(IrCmd::JUMP_CMP_INT, result, build.constInt(0), build.cond(IrCondition::Equal), reverse ? target : next, reverse ? next : target); build.beginBlock(next); } @@ -262,7 +264,7 @@ void translateInstJumpxEqB(IrBuilder& build, const Instruction* pc, int pcpos) build.beginBlock(checkValue); IrOp va = build.inst(IrCmd::LOAD_INT, build.vmReg(ra)); - build.inst(IrCmd::JUMP_EQ_INT, va, build.constInt(aux & 0x1), not_ ? next : target, not_ ? target : next); + build.inst(IrCmd::JUMP_CMP_INT, va, build.constInt(aux & 0x1), build.cond(IrCondition::Equal), not_ ? next : target, not_ ? target : next); // Fallthrough in original bytecode is implicit, so we start next internal block here if (build.isInternalBlock(next)) @@ -607,6 +609,27 @@ IrOp translateFastCallN(IrBuilder& build, const Instruction* pc, int pcpos, bool return fallback; } +// numeric for loop always ends with the computation of step that targets ra+1 +// any conditionals would result in a split basic block, so we can recover the step constants by pattern matching the IR we generated for LOADN/K +static IrOp getLoopStepK(IrBuilder& build, int ra) +{ + IrBlock& active = build.function.blocks[build.activeBlockIdx]; + + if (active.start + 2 < build.function.instructions.size()) + { + IrInst& sv = build.function.instructions[build.function.instructions.size() - 2]; + IrInst& st = build.function.instructions[build.function.instructions.size() - 1]; + + // We currently expect to match IR generated from LOADN/LOADK so we match a particular sequence of opcodes + // In the future this can be extended to cover opposite STORE order as well as STORE_SPLIT_TVALUE + if (sv.cmd == IrCmd::STORE_DOUBLE && sv.a.kind == IrOpKind::VmReg && sv.a.index == ra + 1 && sv.b.kind == IrOpKind::Constant && + st.cmd == IrCmd::STORE_TAG && st.a.kind == IrOpKind::VmReg && st.a.index == ra + 1 && build.function.tagOp(st.b) == LUA_TNUMBER) + return sv.b; + } + + return build.undef(); +} + void translateInstForNPrep(IrBuilder& build, const Instruction* pc, int pcpos) { int ra = LUAU_INSN_A(*pc); @@ -614,40 +637,103 @@ void translateInstForNPrep(IrBuilder& build, const Instruction* pc, int pcpos) IrOp loopStart = build.blockAtInst(pcpos + getOpLength(LuauOpcode(LUAU_INSN_OP(*pc)))); IrOp loopExit = build.blockAtInst(getJumpTarget(*pc, pcpos)); - IrOp direct = build.block(IrBlockKind::Internal); - IrOp reverse = build.block(IrBlockKind::Internal); + if (FFlag::LuauImproveForN) + { + IrOp stepK = getLoopStepK(build, ra); + build.loopStepStack.push_back(stepK); - // When loop parameters are not numbers, VM tries to perform type coercion from string and raises an exception if that fails - // Performing that fallback in native code increases code size and complicates CFG, obscuring the values when they are constant - // To avoid that overhead for an extreemely rare case (that doesn't even typecheck), we exit to VM to handle it - IrOp tagLimit = build.inst(IrCmd::LOAD_TAG, build.vmReg(ra + 0)); - build.inst(IrCmd::CHECK_TAG, tagLimit, build.constTag(LUA_TNUMBER), build.vmExit(pcpos)); - IrOp tagStep = build.inst(IrCmd::LOAD_TAG, build.vmReg(ra + 1)); - build.inst(IrCmd::CHECK_TAG, tagStep, build.constTag(LUA_TNUMBER), build.vmExit(pcpos)); - IrOp tagIdx = build.inst(IrCmd::LOAD_TAG, build.vmReg(ra + 2)); - build.inst(IrCmd::CHECK_TAG, tagIdx, build.constTag(LUA_TNUMBER), build.vmExit(pcpos)); + // When loop parameters are not numbers, VM tries to perform type coercion from string and raises an exception if that fails + // Performing that fallback in native code increases code size and complicates CFG, obscuring the values when they are constant + // To avoid that overhead for an extremely rare case (that doesn't even typecheck), we exit to VM to handle it + IrOp tagLimit = build.inst(IrCmd::LOAD_TAG, build.vmReg(ra + 0)); + build.inst(IrCmd::CHECK_TAG, tagLimit, build.constTag(LUA_TNUMBER), build.vmExit(pcpos)); + IrOp tagIdx = build.inst(IrCmd::LOAD_TAG, build.vmReg(ra + 2)); + build.inst(IrCmd::CHECK_TAG, tagIdx, build.constTag(LUA_TNUMBER), build.vmExit(pcpos)); - IrOp zero = build.constDouble(0.0); - IrOp limit = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(ra + 0)); - IrOp step = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(ra + 1)); - IrOp idx = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(ra + 2)); + IrOp limit = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(ra + 0)); + IrOp idx = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(ra + 2)); - // step <= 0 - build.inst(IrCmd::JUMP_CMP_NUM, step, zero, build.cond(IrCondition::LessEqual), reverse, direct); + if (stepK.kind == IrOpKind::Undef) + { + IrOp tagStep = build.inst(IrCmd::LOAD_TAG, build.vmReg(ra + 1)); + build.inst(IrCmd::CHECK_TAG, tagStep, build.constTag(LUA_TNUMBER), build.vmExit(pcpos)); - // TODO: target branches can probably be arranged better, but we need tests for NaN behavior preservation + IrOp direct = build.block(IrBlockKind::Internal); + IrOp reverse = build.block(IrBlockKind::Internal); - // step <= 0 is false, check idx <= limit - build.beginBlock(direct); - build.inst(IrCmd::JUMP_CMP_NUM, idx, limit, build.cond(IrCondition::LessEqual), loopStart, loopExit); + IrOp zero = build.constDouble(0.0); + IrOp step = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(ra + 1)); - // step <= 0 is true, check limit <= idx - build.beginBlock(reverse); - build.inst(IrCmd::JUMP_CMP_NUM, limit, idx, build.cond(IrCondition::LessEqual), loopStart, loopExit); + // step > 0 + // note: equivalent to 0 < step, but lowers into one instruction on both X64 and A64 + build.inst(IrCmd::JUMP_CMP_NUM, step, zero, build.cond(IrCondition::Greater), direct, reverse); + + // Condition to start the loop: step > 0 ? idx <= limit : limit <= idx + // We invert the condition so that loopStart is the fallthrough (false) label + + // step > 0 is false, check limit <= idx + build.beginBlock(reverse); + build.inst(IrCmd::JUMP_CMP_NUM, limit, idx, build.cond(IrCondition::NotLessEqual), loopExit, loopStart); + + // step > 0 is true, check idx <= limit + build.beginBlock(direct); + build.inst(IrCmd::JUMP_CMP_NUM, idx, limit, build.cond(IrCondition::NotLessEqual), loopExit, loopStart); + } + else + { + double stepN = build.function.doubleOp(stepK); + + // Condition to start the loop: step > 0 ? idx <= limit : limit <= idx + // We invert the condition so that loopStart is the fallthrough (false) label + if (stepN > 0) + build.inst(IrCmd::JUMP_CMP_NUM, idx, limit, build.cond(IrCondition::NotLessEqual), loopExit, loopStart); + else + build.inst(IrCmd::JUMP_CMP_NUM, limit, idx, build.cond(IrCondition::NotLessEqual), loopExit, loopStart); + } + } + else + { + IrOp direct = build.block(IrBlockKind::Internal); + IrOp reverse = build.block(IrBlockKind::Internal); + + // When loop parameters are not numbers, VM tries to perform type coercion from string and raises an exception if that fails + // Performing that fallback in native code increases code size and complicates CFG, obscuring the values when they are constant + // To avoid that overhead for an extreemely rare case (that doesn't even typecheck), we exit to VM to handle it + IrOp tagLimit = build.inst(IrCmd::LOAD_TAG, build.vmReg(ra + 0)); + build.inst(IrCmd::CHECK_TAG, tagLimit, build.constTag(LUA_TNUMBER), build.vmExit(pcpos)); + IrOp tagStep = build.inst(IrCmd::LOAD_TAG, build.vmReg(ra + 1)); + build.inst(IrCmd::CHECK_TAG, tagStep, build.constTag(LUA_TNUMBER), build.vmExit(pcpos)); + IrOp tagIdx = build.inst(IrCmd::LOAD_TAG, build.vmReg(ra + 2)); + build.inst(IrCmd::CHECK_TAG, tagIdx, build.constTag(LUA_TNUMBER), build.vmExit(pcpos)); + + IrOp zero = build.constDouble(0.0); + IrOp limit = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(ra + 0)); + IrOp step = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(ra + 1)); + IrOp idx = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(ra + 2)); + + // step <= 0 + build.inst(IrCmd::JUMP_CMP_NUM, step, zero, build.cond(IrCondition::LessEqual), reverse, direct); + + // TODO: target branches can probably be arranged better, but we need tests for NaN behavior preservation + + // step <= 0 is false, check idx <= limit + build.beginBlock(direct); + build.inst(IrCmd::JUMP_CMP_NUM, idx, limit, build.cond(IrCondition::LessEqual), loopStart, loopExit); + + // step <= 0 is true, check limit <= idx + build.beginBlock(reverse); + build.inst(IrCmd::JUMP_CMP_NUM, limit, idx, build.cond(IrCondition::LessEqual), loopStart, loopExit); + } // Fallthrough in original bytecode is implicit, so we start next internal block here if (build.isInternalBlock(loopStart)) build.beginBlock(loopStart); + + // VM places interrupt in FORNLOOP, but that creates a likely spill point for short loops that use loop index as INTERRUPT always spills + // We place the interrupt at the beginning of the loop body instead; VM uses FORNLOOP because it doesn't want to waste an extra instruction. + // Because loop block may not have been started yet (as it's started when lowering the first instruction!), we need to defer INTERRUPT placement. + if (FFlag::LuauImproveForN) + build.interruptRequested = true; } void translateInstForNLoop(IrBuilder& build, const Instruction* pc, int pcpos) @@ -657,29 +743,76 @@ void translateInstForNLoop(IrBuilder& build, const Instruction* pc, int pcpos) IrOp loopRepeat = build.blockAtInst(getJumpTarget(*pc, pcpos)); IrOp loopExit = build.blockAtInst(pcpos + getOpLength(LuauOpcode(LUAU_INSN_OP(*pc)))); - build.inst(IrCmd::INTERRUPT, build.constUint(pcpos)); + if (FFlag::LuauImproveForN) + { + LUAU_ASSERT(!build.loopStepStack.empty()); + IrOp stepK = build.loopStepStack.back(); + build.loopStepStack.pop_back(); - IrOp zero = build.constDouble(0.0); - IrOp limit = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(ra + 0)); - IrOp step = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(ra + 1)); + IrOp zero = build.constDouble(0.0); + IrOp limit = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(ra + 0)); + IrOp step = stepK.kind == IrOpKind::Undef ? build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(ra + 1)) : stepK; - IrOp idx = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(ra + 2)); - idx = build.inst(IrCmd::ADD_NUM, idx, step); - build.inst(IrCmd::STORE_DOUBLE, build.vmReg(ra + 2), idx); + IrOp idx = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(ra + 2)); + idx = build.inst(IrCmd::ADD_NUM, idx, step); + build.inst(IrCmd::STORE_DOUBLE, build.vmReg(ra + 2), idx); - IrOp direct = build.block(IrBlockKind::Internal); - IrOp reverse = build.block(IrBlockKind::Internal); + if (stepK.kind == IrOpKind::Undef) + { + IrOp direct = build.block(IrBlockKind::Internal); + IrOp reverse = build.block(IrBlockKind::Internal); - // step <= 0 - build.inst(IrCmd::JUMP_CMP_NUM, step, zero, build.cond(IrCondition::LessEqual), reverse, direct); + // step > 0 + // note: equivalent to 0 < step, but lowers into one instruction on both X64 and A64 + build.inst(IrCmd::JUMP_CMP_NUM, step, zero, build.cond(IrCondition::Greater), direct, reverse); - // step <= 0 is false, check idx <= limit - build.beginBlock(direct); - build.inst(IrCmd::JUMP_CMP_NUM, idx, limit, build.cond(IrCondition::LessEqual), loopRepeat, loopExit); + // Condition to continue the loop: step > 0 ? idx <= limit : limit <= idx - // step <= 0 is true, check limit <= idx - build.beginBlock(reverse); - build.inst(IrCmd::JUMP_CMP_NUM, limit, idx, build.cond(IrCondition::LessEqual), loopRepeat, loopExit); + // step > 0 is false, check limit <= idx + build.beginBlock(reverse); + build.inst(IrCmd::JUMP_CMP_NUM, limit, idx, build.cond(IrCondition::LessEqual), loopRepeat, loopExit); + + // step > 0 is true, check idx <= limit + build.beginBlock(direct); + build.inst(IrCmd::JUMP_CMP_NUM, idx, limit, build.cond(IrCondition::LessEqual), loopRepeat, loopExit); + } + else + { + double stepN = build.function.doubleOp(stepK); + + // Condition to continue the loop: step > 0 ? idx <= limit : limit <= idx + if (stepN > 0) + build.inst(IrCmd::JUMP_CMP_NUM, idx, limit, build.cond(IrCondition::LessEqual), loopRepeat, loopExit); + else + build.inst(IrCmd::JUMP_CMP_NUM, limit, idx, build.cond(IrCondition::LessEqual), loopRepeat, loopExit); + } + } + else + { + build.inst(IrCmd::INTERRUPT, build.constUint(pcpos)); + + IrOp zero = build.constDouble(0.0); + IrOp limit = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(ra + 0)); + IrOp step = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(ra + 1)); + + IrOp idx = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(ra + 2)); + idx = build.inst(IrCmd::ADD_NUM, idx, step); + build.inst(IrCmd::STORE_DOUBLE, build.vmReg(ra + 2), idx); + + IrOp direct = build.block(IrBlockKind::Internal); + IrOp reverse = build.block(IrBlockKind::Internal); + + // step <= 0 + build.inst(IrCmd::JUMP_CMP_NUM, step, zero, build.cond(IrCondition::LessEqual), reverse, direct); + + // step <= 0 is false, check idx <= limit + build.beginBlock(direct); + build.inst(IrCmd::JUMP_CMP_NUM, idx, limit, build.cond(IrCondition::LessEqual), loopRepeat, loopExit); + + // step <= 0 is true, check limit <= idx + build.beginBlock(reverse); + build.inst(IrCmd::JUMP_CMP_NUM, limit, idx, build.cond(IrCondition::LessEqual), loopRepeat, loopExit); + } // Fallthrough in original bytecode is implicit, so we start next internal block here if (build.isInternalBlock(loopExit)) diff --git a/CodeGen/src/IrUtils.cpp b/CodeGen/src/IrUtils.cpp index 07704388..d263d3aa 100644 --- a/CodeGen/src/IrUtils.cpp +++ b/CodeGen/src/IrUtils.cpp @@ -72,9 +72,7 @@ IrValueKind getCmdValueKind(IrCmd cmd) case IrCmd::JUMP_IF_TRUTHY: case IrCmd::JUMP_IF_FALSY: case IrCmd::JUMP_EQ_TAG: - case IrCmd::JUMP_EQ_INT: - case IrCmd::JUMP_LT_INT: - case IrCmd::JUMP_GE_UINT: + case IrCmd::JUMP_CMP_INT: case IrCmd::JUMP_EQ_POINTER: case IrCmd::JUMP_CMP_NUM: case IrCmd::JUMP_SLOT_MATCH: @@ -422,6 +420,45 @@ bool compare(double a, double b, IrCondition cond) return false; } +bool compare(int a, int b, IrCondition cond) +{ + switch (cond) + { + case IrCondition::Equal: + return a == b; + case IrCondition::NotEqual: + return a != b; + case IrCondition::Less: + return a < b; + case IrCondition::NotLess: + return !(a < b); + case IrCondition::LessEqual: + return a <= b; + case IrCondition::NotLessEqual: + return !(a <= b); + case IrCondition::Greater: + return a > b; + case IrCondition::NotGreater: + return !(a > b); + case IrCondition::GreaterEqual: + return a >= b; + case IrCondition::NotGreaterEqual: + return !(a >= b); + case IrCondition::UnsignedLess: + return unsigned(a) < unsigned(b); + case IrCondition::UnsignedLessEqual: + return unsigned(a) <= unsigned(b); + case IrCondition::UnsignedGreater: + return unsigned(a) > unsigned(b); + case IrCondition::UnsignedGreaterEqual: + return unsigned(a) >= unsigned(b); + default: + LUAU_ASSERT(!"Unsupported condition"); + } + + return false; +} + void foldConstants(IrBuilder& build, IrFunction& function, IrBlock& block, uint32_t index) { IrInst& inst = function.instructions[index]; @@ -540,31 +577,13 @@ void foldConstants(IrBuilder& build, IrFunction& function, IrBlock& block, uint3 replace(function, block, index, {IrCmd::JUMP, inst.d}); } break; - case IrCmd::JUMP_EQ_INT: + case IrCmd::JUMP_CMP_INT: if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant) { - if (function.intOp(inst.a) == function.intOp(inst.b)) - replace(function, block, index, {IrCmd::JUMP, inst.c}); - else + if (compare(function.intOp(inst.a), function.intOp(inst.b), conditionOp(inst.c))) replace(function, block, index, {IrCmd::JUMP, inst.d}); - } - break; - case IrCmd::JUMP_LT_INT: - if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant) - { - if (function.intOp(inst.a) < function.intOp(inst.b)) - replace(function, block, index, {IrCmd::JUMP, inst.c}); else - replace(function, block, index, {IrCmd::JUMP, inst.d}); - } - break; - case IrCmd::JUMP_GE_UINT: - if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant) - { - if (unsigned(function.intOp(inst.a)) >= unsigned(function.intOp(inst.b))) - replace(function, block, index, {IrCmd::JUMP, inst.c}); - else - replace(function, block, index, {IrCmd::JUMP, inst.d}); + replace(function, block, index, {IrCmd::JUMP, inst.e}); } break; case IrCmd::JUMP_CMP_NUM: diff --git a/CodeGen/src/OptimizeConstProp.cpp b/CodeGen/src/OptimizeConstProp.cpp index 03c26cdd..de839061 100644 --- a/CodeGen/src/OptimizeConstProp.cpp +++ b/CodeGen/src/OptimizeConstProp.cpp @@ -17,6 +17,7 @@ LUAU_FASTINTVARIABLE(LuauCodeGenReuseSlotLimit, 64) LUAU_FASTFLAGVARIABLE(DebugLuauAbortingChecks, false) LUAU_FASTFLAGVARIABLE(LuauReuseHashSlots2, false) LUAU_FASTFLAGVARIABLE(LuauKeepVmapLinear, false) +LUAU_FASTFLAGVARIABLE(LuauMergeTagLoads, false) namespace Luau { @@ -502,9 +503,16 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& { case IrCmd::LOAD_TAG: if (uint8_t tag = state.tryGetTag(inst.a); tag != 0xff) + { substitute(function, inst, build.constTag(tag)); + } else if (inst.a.kind == IrOpKind::VmReg) - state.createRegLink(index, inst.a); + { + if (FFlag::LuauMergeTagLoads) + state.substituteOrRecordVmRegLoad(inst); + else + state.createRegLink(index, inst.a); + } break; case IrCmd::LOAD_POINTER: if (inst.a.kind == IrOpKind::VmReg) @@ -716,44 +724,20 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& else replace(function, block, index, {IrCmd::JUMP, inst.d}); } + else if (FFlag::LuauMergeTagLoads && inst.a == inst.b) + { + replace(function, block, index, {IrCmd::JUMP, inst.c}); + } break; } - case IrCmd::JUMP_EQ_INT: + case IrCmd::JUMP_CMP_INT: { std::optional valueA = function.asIntOp(inst.a.kind == IrOpKind::Constant ? inst.a : state.tryGetValue(inst.a)); std::optional valueB = function.asIntOp(inst.b.kind == IrOpKind::Constant ? inst.b : state.tryGetValue(inst.b)); if (valueA && valueB) { - if (*valueA == *valueB) - replace(function, block, index, {IrCmd::JUMP, inst.c}); - else - replace(function, block, index, {IrCmd::JUMP, inst.d}); - } - break; - } - case IrCmd::JUMP_LT_INT: - { - std::optional valueA = function.asIntOp(inst.a.kind == IrOpKind::Constant ? inst.a : state.tryGetValue(inst.a)); - std::optional valueB = function.asIntOp(inst.b.kind == IrOpKind::Constant ? inst.b : state.tryGetValue(inst.b)); - - if (valueA && valueB) - { - if (*valueA < *valueB) - replace(function, block, index, {IrCmd::JUMP, inst.c}); - else - replace(function, block, index, {IrCmd::JUMP, inst.d}); - } - break; - } - case IrCmd::JUMP_GE_UINT: - { - std::optional valueA = function.asUintOp(inst.a.kind == IrOpKind::Constant ? inst.a : state.tryGetValue(inst.a)); - std::optional valueB = function.asUintOp(inst.b.kind == IrOpKind::Constant ? inst.b : state.tryGetValue(inst.b)); - - if (valueA && valueB) - { - if (*valueA >= *valueB) + if (compare(*valueA, *valueB, conditionOp(inst.c))) replace(function, block, index, {IrCmd::JUMP, inst.c}); else replace(function, block, index, {IrCmd::JUMP, inst.d}); diff --git a/Sources.cmake b/Sources.cmake index 4776b6b8..ddd514f5 100644 --- a/Sources.cmake +++ b/Sources.cmake @@ -167,6 +167,7 @@ target_sources(Luau.Analysis PRIVATE Analysis/include/Luau/Error.h Analysis/include/Luau/FileResolver.h Analysis/include/Luau/Frontend.h + Analysis/include/Luau/GlobalTypes.h Analysis/include/Luau/InsertionOrderedMap.h Analysis/include/Luau/Instantiation.h Analysis/include/Luau/IostreamHelpers.h @@ -226,6 +227,7 @@ target_sources(Luau.Analysis PRIVATE Analysis/src/EmbeddedBuiltinDefinitions.cpp Analysis/src/Error.cpp Analysis/src/Frontend.cpp + Analysis/src/GlobalTypes.cpp Analysis/src/Instantiation.cpp Analysis/src/IostreamHelpers.cpp Analysis/src/JsonEmitter.cpp @@ -365,6 +367,8 @@ if(TARGET Luau.UnitTest) tests/AstQueryDsl.cpp tests/AstQueryDsl.h tests/AstVisitor.test.cpp + tests/RegisterCallbacks.h + tests/RegisterCallbacks.cpp tests/Autocomplete.test.cpp tests/BuiltinDefinitions.test.cpp tests/ClassFixture.cpp @@ -447,6 +451,8 @@ endif() if(TARGET Luau.Conformance) # Luau.Conformance Sources target_sources(Luau.Conformance PRIVATE + tests/RegisterCallbacks.h + tests/RegisterCallbacks.cpp tests/Conformance.test.cpp tests/main.cpp) endif() @@ -464,6 +470,8 @@ if(TARGET Luau.CLI.Test) CLI/Profiler.cpp CLI/Repl.cpp + tests/RegisterCallbacks.h + tests/RegisterCallbacks.cpp tests/Repl.test.cpp tests/main.cpp) endif() diff --git a/VM/src/lvmexecute.cpp b/VM/src/lvmexecute.cpp index 44bba9c0..68efc0e4 100644 --- a/VM/src/lvmexecute.cpp +++ b/VM/src/lvmexecute.cpp @@ -135,6 +135,8 @@ // Does VM support native execution via ExecutionCallbacks? We mostly assume it does but keep the define to make it easy to quantify the cost. #define VM_HAS_NATIVE 1 +void (*lua_iter_call_telemetry)(lua_State* L, int gtt, int stt, int itt) = NULL; + LUAU_NOINLINE void luau_callhook(lua_State* L, lua_Hook hook, void* userdata) { ptrdiff_t base = savestack(L, L->base); @@ -2289,6 +2291,10 @@ reentry: { // table or userdata with __call, will be called during FORGLOOP // TODO: we might be able to stop supporting this depending on whether it's used in practice + void (*telemetrycb)(lua_State* L, int gtt, int stt, int itt) = lua_iter_call_telemetry; + + if (telemetrycb) + telemetrycb(L, ttype(ra), ttype(ra + 1), ttype(ra + 2)); } else if (ttistable(ra)) { diff --git a/tests/Autocomplete.test.cpp b/tests/Autocomplete.test.cpp index 6bfdc0f4..dd90dd8e 100644 --- a/tests/Autocomplete.test.cpp +++ b/tests/Autocomplete.test.cpp @@ -15,7 +15,6 @@ LUAU_FASTFLAG(LuauTraceTypesInNonstrictMode2) LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel) -LUAU_FASTFLAG(LuauAutocompleteLastTypecheck) using namespace Luau; @@ -34,36 +33,27 @@ struct ACFixtureImpl : BaseType AutocompleteResult autocomplete(unsigned row, unsigned column) { - if (FFlag::LuauAutocompleteLastTypecheck) - { - FrontendOptions opts; - opts.forAutocomplete = true; - this->frontend.check("MainModule", opts); - } + FrontendOptions opts; + opts.forAutocomplete = true; + this->frontend.check("MainModule", opts); return Luau::autocomplete(this->frontend, "MainModule", Position{row, column}, nullCallback); } AutocompleteResult autocomplete(char marker, StringCompletionCallback callback = nullCallback) { - if (FFlag::LuauAutocompleteLastTypecheck) - { - FrontendOptions opts; - opts.forAutocomplete = true; - this->frontend.check("MainModule", opts); - } + FrontendOptions opts; + opts.forAutocomplete = true; + this->frontend.check("MainModule", opts); return Luau::autocomplete(this->frontend, "MainModule", getPosition(marker), callback); } AutocompleteResult autocomplete(const ModuleName& name, Position pos, StringCompletionCallback callback = nullCallback) { - if (FFlag::LuauAutocompleteLastTypecheck) - { - FrontendOptions opts; - opts.forAutocomplete = true; - this->frontend.check(name, opts); - } + FrontendOptions opts; + opts.forAutocomplete = true; + this->frontend.check(name, opts); return Luau::autocomplete(this->frontend, name, pos, callback); } @@ -3699,8 +3689,6 @@ TEST_CASE_FIXTURE(ACFixture, "string_completion_outside_quotes") TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled_empty") { - ScopedFastFlag flag{"LuauAnonymousAutofilled1", true}; - check(R"( local function foo(a: () -> ()) a() @@ -3722,8 +3710,6 @@ foo(@1) TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled_args") { - ScopedFastFlag flag{"LuauAnonymousAutofilled1", true}; - check(R"( local function foo(a: (number, string) -> ()) a() @@ -3745,8 +3731,6 @@ foo(@1) TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled_args_single_return") { - ScopedFastFlag flag{"LuauAnonymousAutofilled1", true}; - check(R"( local function foo(a: (number, string) -> (string)) a() @@ -3768,8 +3752,6 @@ foo(@1) TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled_args_multi_return") { - ScopedFastFlag flag{"LuauAnonymousAutofilled1", true}; - check(R"( local function foo(a: (number, string) -> (string, number)) a() @@ -3791,8 +3773,6 @@ foo(@1) TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled__noargs_multi_return") { - ScopedFastFlag flag{"LuauAnonymousAutofilled1", true}; - check(R"( local function foo(a: () -> (string, number)) a() @@ -3814,8 +3794,6 @@ foo(@1) TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled__varargs_multi_return") { - ScopedFastFlag flag{"LuauAnonymousAutofilled1", true}; - check(R"( local function foo(a: (...number) -> (string, number)) a() @@ -3837,8 +3815,6 @@ foo(@1) TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled_multi_varargs_multi_return") { - ScopedFastFlag flag{"LuauAnonymousAutofilled1", true}; - check(R"( local function foo(a: (string, ...number) -> (string, number)) a() @@ -3860,8 +3836,6 @@ foo(@1) TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled_multi_varargs_varargs_return") { - ScopedFastFlag flag{"LuauAnonymousAutofilled1", true}; - check(R"( local function foo(a: (string, ...number) -> ...number) a() @@ -3883,8 +3857,6 @@ foo(@1) TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled_multi_varargs_multi_varargs_return") { - ScopedFastFlag flag{"LuauAnonymousAutofilled1", true}; - check(R"( local function foo(a: (string, ...number) -> (boolean, ...number)) a() @@ -3906,8 +3878,6 @@ foo(@1) TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled_named_args") { - ScopedFastFlag flag{"LuauAnonymousAutofilled1", true}; - check(R"( local function foo(a: (foo: number, bar: string) -> (string, number)) a() @@ -3929,8 +3899,6 @@ foo(@1) TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled_partially_args") { - ScopedFastFlag flag{"LuauAnonymousAutofilled1", true}; - check(R"( local function foo(a: (number, bar: string) -> (string, number)) a() @@ -3952,8 +3920,6 @@ foo(@1) TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled_partially_args_last") { - ScopedFastFlag flag{"LuauAnonymousAutofilled1", true}; - check(R"( local function foo(a: (foo: number, string) -> (string, number)) a() @@ -3975,8 +3941,6 @@ foo(@1) TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled_typeof_args") { - ScopedFastFlag flag{"LuauAnonymousAutofilled1", true}; - check(R"( local t = { a = 1, b = 2 } @@ -4000,8 +3964,6 @@ foo(@1) TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled_table_literal_args") { - ScopedFastFlag flag{"LuauAnonymousAutofilled1", true}; - check(R"( local function foo(a: (tbl: { x: number, y: number }) -> number) return a({x=2, y = 3}) end foo(@1) @@ -4020,8 +3982,6 @@ foo(@1) TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled_typeof_returns") { - ScopedFastFlag flag{"LuauAnonymousAutofilled1", true}; - check(R"( local t = { a = 1, b = 2 } @@ -4045,8 +4005,6 @@ foo(@1) TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled_table_literal_args") { - ScopedFastFlag flag{"LuauAnonymousAutofilled1", true}; - check(R"( local function foo(a: () -> { x: number, y: number }) return {x=2, y = 3} end foo(@1) @@ -4065,8 +4023,6 @@ foo(@1) TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled_typeof_vararg") { - ScopedFastFlag flag{"LuauAnonymousAutofilled1", true}; - check(R"( local t = { a = 1, b = 2 } @@ -4090,8 +4046,6 @@ foo(@1) TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled_generic_type_pack_vararg") { - ScopedFastFlag flag{"LuauAnonymousAutofilled1", true}; - check(R"( local function foo(a: (...A) -> number, ...: A) return a(...) @@ -4113,8 +4067,6 @@ foo(@1) TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled_generic_on_argument_type_pack_vararg") { - ScopedFastFlag flag{"LuauAnonymousAutofilled1", true}; - check(R"( local function foo(a: (...: T...) -> number) return a(4, 5, 6) diff --git a/tests/Conformance.test.cpp b/tests/Conformance.test.cpp index 75c38762..c8918954 100644 --- a/tests/Conformance.test.cpp +++ b/tests/Conformance.test.cpp @@ -282,6 +282,8 @@ TEST_CASE("Assert") TEST_CASE("Basic") { ScopedFastFlag sffs{"LuauFloorDivision", true}; + ScopedFastFlag sfff{"LuauImproveForN", true}; + runConformance("basic.lua"); } diff --git a/tests/Fixture.cpp b/tests/Fixture.cpp index d4fa7178..f31727e0 100644 --- a/tests/Fixture.cpp +++ b/tests/Fixture.cpp @@ -9,6 +9,7 @@ #include "Luau/Parser.h" #include "Luau/Type.h" #include "Luau/TypeAttach.h" +#include "Luau/TypeInfer.h" #include "Luau/Transpiler.h" #include "doctest.h" @@ -144,8 +145,6 @@ Fixture::Fixture(bool freeze, bool prepareAutocomplete) configResolver.defaultConfig.enabledLint.warningMask = ~0ull; configResolver.defaultConfig.parseOptions.captureComments = true; - registerBuiltinTypes(frontend.globals); - Luau::freeze(frontend.globals.globalTypes); Luau::freeze(frontend.globalsForAutocomplete.globalTypes); diff --git a/tests/Frontend.test.cpp b/tests/Frontend.test.cpp index ec0c213a..96032fd1 100644 --- a/tests/Frontend.test.cpp +++ b/tests/Frontend.test.cpp @@ -1222,4 +1222,28 @@ TEST_CASE_FIXTURE(FrontendFixture, "parse_only") CHECK_EQ("Type 'string' could not be converted into 'number'", toString(result.errors[0])); } +TEST_CASE_FIXTURE(FrontendFixture, "markdirty_early_return") +{ + ScopedFastFlag fflag("CorrectEarlyReturnInMarkDirty", true); + + constexpr char moduleName[] = "game/Gui/Modules/A"; + fileResolver.source[moduleName] = R"( + return 1 + )"; + + { + std::vector markedDirty; + frontend.markDirty(moduleName, &markedDirty); + CHECK(markedDirty.empty()); + } + + frontend.parse(moduleName); + + { + std::vector markedDirty; + frontend.markDirty(moduleName, &markedDirty); + CHECK(!markedDirty.empty()); + } +} + TEST_SUITE_END(); diff --git a/tests/IrBuilder.test.cpp b/tests/IrBuilder.test.cpp index a736f5cf..b511156d 100644 --- a/tests/IrBuilder.test.cpp +++ b/tests/IrBuilder.test.cpp @@ -621,11 +621,11 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "ControlFlowEq") }); withTwoBlocks([this](IrOp a, IrOp b) { - build.inst(IrCmd::JUMP_EQ_INT, build.constInt(0), build.constInt(0), a, b); + build.inst(IrCmd::JUMP_CMP_INT, build.constInt(0), build.constInt(0), build.cond(IrCondition::Equal), a, b); }); withTwoBlocks([this](IrOp a, IrOp b) { - build.inst(IrCmd::JUMP_EQ_INT, build.constInt(0), build.constInt(1), a, b); + build.inst(IrCmd::JUMP_CMP_INT, build.constInt(0), build.constInt(1), build.cond(IrCondition::Equal), a, b); }); updateUseCounts(build.function); @@ -1359,7 +1359,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "IntEqRemoval") build.beginBlock(block); build.inst(IrCmd::STORE_INT, build.vmReg(1), build.constInt(5)); IrOp value = build.inst(IrCmd::LOAD_INT, build.vmReg(1)); - build.inst(IrCmd::JUMP_EQ_INT, value, build.constInt(5), trueBlock, falseBlock); + build.inst(IrCmd::JUMP_CMP_INT, value, build.constInt(5), build.cond(IrCondition::Equal), trueBlock, falseBlock); build.beginBlock(trueBlock); build.inst(IrCmd::RETURN, build.constUint(1)); @@ -1556,7 +1556,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "RecursiveSccUseRemoval2") IrOp repeat = build.block(IrBlockKind::Internal); build.beginBlock(entry); - build.inst(IrCmd::JUMP_EQ_INT, build.constInt(0), build.constInt(1), block, exit1); + build.inst(IrCmd::JUMP_CMP_INT, build.constInt(0), build.constInt(1), build.cond(IrCondition::Equal), block, exit1); build.beginBlock(exit1); build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(0)); @@ -2785,4 +2785,37 @@ bb_0: )"); } +TEST_CASE_FIXTURE(IrBuilderFixture, "TagSelfEqualityCheckRemoval") +{ + ScopedFastFlag luauMergeTagLoads{"LuauMergeTagLoads", true}; + + IrOp entry = build.block(IrBlockKind::Internal); + IrOp trueBlock = build.block(IrBlockKind::Internal); + IrOp falseBlock = build.block(IrBlockKind::Internal); + + build.beginBlock(entry); + + IrOp tag1 = build.inst(IrCmd::LOAD_TAG, build.vmReg(0)); + IrOp tag2 = build.inst(IrCmd::LOAD_TAG, build.vmReg(0)); + build.inst(IrCmd::JUMP_EQ_TAG, tag1, tag2, trueBlock, falseBlock); + + build.beginBlock(trueBlock); + build.inst(IrCmd::RETURN, build.constUint(1)); + + build.beginBlock(falseBlock); + build.inst(IrCmd::RETURN, build.constUint(2)); + + updateUseCounts(build.function); + constPropInBlockChains(build, true); + + CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( +bb_0: + JUMP bb_1 + +bb_1: + RETURN 1u + +)"); +} + TEST_SUITE_END(); diff --git a/tests/RegisterCallbacks.cpp b/tests/RegisterCallbacks.cpp new file mode 100644 index 00000000..9f471933 --- /dev/null +++ b/tests/RegisterCallbacks.cpp @@ -0,0 +1,20 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details + +#include "RegisterCallbacks.h" + +namespace Luau +{ + +std::unordered_set& getRegisterCallbacks() +{ + static std::unordered_set cbs; + return cbs; +} + +int addTestCallback(RegisterCallback cb) +{ + getRegisterCallbacks().insert(cb); + return 0; +} + +} // namespace Luau diff --git a/tests/RegisterCallbacks.h b/tests/RegisterCallbacks.h new file mode 100644 index 00000000..f62ac0e7 --- /dev/null +++ b/tests/RegisterCallbacks.h @@ -0,0 +1,22 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#pragma once + +#include +#include + +namespace Luau +{ + +using RegisterCallback = void (*)(); + +/// Gets a set of callbacks to run immediately before running tests, intended +/// for registering new tests at runtime. +std::unordered_set& getRegisterCallbacks(); + +/// Adds a new callback to be ran immediately before running tests. +/// +/// @param cb the callback to add. +/// @returns a dummy integer to satisfy a doctest internal contract. +int addTestCallback(RegisterCallback cb); + +} // namespace Luau diff --git a/tests/Subtyping.test.cpp b/tests/Subtyping.test.cpp index 23d05f91..6089f036 100644 --- a/tests/Subtyping.test.cpp +++ b/tests/Subtyping.test.cpp @@ -2,7 +2,9 @@ #include "doctest.h" #include "Fixture.h" +#include "RegisterCallbacks.h" +#include "Luau/Normalize.h" #include "Luau/Subtyping.h" #include "Luau/TypePack.h" @@ -344,14 +346,72 @@ struct SubtypeFixture : Fixture CHECK_MESSAGE(!result.isErrorSuppressing, "Expected " << leftTy << " to error-suppress " << rightTy); \ } while (0) +/// Internal macro for registering a generated test case. +/// +/// @param der the name of the derived fixture struct +/// @param reg the name of the registration callback, invoked immediately before +/// tests are ran to register the test +/// @param run the name of the run callback, invoked to actually run the test case +#define TEST_REGISTER(der, reg, run) \ + static inline DOCTEST_NOINLINE void run() \ + { \ + der fix; \ + fix.test(); \ + } \ + static inline DOCTEST_NOINLINE void reg() \ + { \ + /* we have to mark this as `static` to ensure the memory remains alive \ + for the entirety of the test process */ \ + static std::string name = der().testName; \ + doctest::detail::regTest(doctest::detail::TestCase(run, __FILE__, __LINE__, \ + doctest_detail_test_suite_ns::getCurrentTestSuite()) /* the test case's name, determined at runtime */ \ + * name.c_str() /* getCurrentTestSuite() only works at static initialization \ + time due to implementation details. To ensure that test cases \ + are grouped where they should be, manually override the suite \ + with the test_suite decorator. */ \ + * doctest::test_suite("Subtyping")); \ + } \ + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_VAR_), addTestCallback(reg)); + +/// Internal macro for deriving a test case fixture. Roughly analogous to +/// DOCTEST_IMPLEMENT_FIXTURE. +/// +/// @param op a function (or macro) to call that compares the subtype to +/// the supertype. +/// @param symbol the symbol to use in stringification +/// @param der the name of the derived fixture struct +/// @param left the subtype expression +/// @param right the supertype expression +#define TEST_DERIVE(op, symbol, der, left, right) \ + namespace \ + { \ + struct der : SubtypeFixture \ + { \ + const TypeId subTy = (left); \ + const TypeId superTy = (right); \ + const std::string testName = toString(subTy) + " " symbol " " + toString(superTy); \ + inline DOCTEST_NOINLINE void test() \ + { \ + op(subTy, superTy); \ + } \ + }; \ + TEST_REGISTER(der, DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_)); \ + } + +/// Generates a test that checks if a type is a subtype of another. +#define TEST_IS_SUBTYPE(left, right) TEST_DERIVE(CHECK_IS_SUBTYPE, "<:", DOCTEST_ANONYMOUS(DOCTEST_ANON_CLASS_), left, right) + +/// Generates a test that checks if a type is _not_ a subtype of another. +/// Uses numberType, builtinTypes->anyType); -} +TEST_IS_SUBTYPE(builtinTypes->numberType, builtinTypes->anyType); +TEST_IS_NOT_SUBTYPE(builtinTypes->numberType, builtinTypes->stringType); TEST_CASE_FIXTURE(SubtypeFixture, "any numberType, builtinTypes->numberType); } -TEST_CASE_FIXTURE(SubtypeFixture, "number numberType, builtinTypes->stringType); -} - TEST_CASE_FIXTURE(SubtypeFixture, "number <: number?") { CHECK_IS_SUBTYPE(builtinTypes->numberType, builtinTypes->optionalNumberType); @@ -895,6 +950,16 @@ TEST_CASE_FIXTURE(SubtypeFixture, "string ( CHECK_IS_NOT_SUBTYPE(builtinTypes->stringType, tableWithoutScalarProp); } +TEST_CASE_FIXTURE(SubtypeFixture, "~fun & (string) -> number <: (string) -> number") +{ + CHECK_IS_SUBTYPE(meet(negate(builtinTypes->functionType), numberToStringType), numberToStringType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "(string) -> number <: ~fun & (string) -> number") +{ + CHECK_IS_NOT_SUBTYPE(numberToStringType, meet(negate(builtinTypes->functionType), numberToStringType)); +} + /* * (A) -> A <: (X) -> X * A can be bound to X. diff --git a/tests/ToDot.test.cpp b/tests/ToDot.test.cpp index 9293bfb2..ac01b5f3 100644 --- a/tests/ToDot.test.cpp +++ b/tests/ToDot.test.cpp @@ -44,25 +44,34 @@ TEST_SUITE_BEGIN("ToDot"); TEST_CASE_FIXTURE(Fixture, "primitive") { - CheckResult result = check(R"( -local a: nil -local b: number -local c: any -)"); - LUAU_REQUIRE_NO_ERRORS(result); - - CHECK_NE("nil", toDot(requireType("a"))); + CHECK_EQ(R"(digraph graphname { +n1 [label="nil"]; +})", + toDot(builtinTypes->nilType)); CHECK_EQ(R"(digraph graphname { n1 [label="number"]; })", - toDot(requireType("b"))); + toDot(builtinTypes->numberType)); CHECK_EQ(R"(digraph graphname { n1 [label="any"]; })", - toDot(requireType("c"))); + toDot(builtinTypes->anyType)); + CHECK_EQ(R"(digraph graphname { +n1 [label="unknown"]; +})", + toDot(builtinTypes->unknownType)); + + CHECK_EQ(R"(digraph graphname { +n1 [label="never"]; +})", + toDot(builtinTypes->neverType)); +} + +TEST_CASE_FIXTURE(Fixture, "no_duplicatePrimitives") +{ ToDotOptions opts; opts.showPointers = false; opts.duplicatePrimitives = false; @@ -70,12 +79,22 @@ n1 [label="any"]; CHECK_EQ(R"(digraph graphname { n1 [label="PrimitiveType number"]; })", - toDot(requireType("b"), opts)); + toDot(builtinTypes->numberType, opts)); CHECK_EQ(R"(digraph graphname { n1 [label="AnyType 1"]; })", - toDot(requireType("c"), opts)); + toDot(builtinTypes->anyType, opts)); + + CHECK_EQ(R"(digraph graphname { +n1 [label="UnknownType 1"]; +})", + toDot(builtinTypes->unknownType, opts)); + + CHECK_EQ(R"(digraph graphname { +n1 [label="NeverType 1"]; +})", + toDot(builtinTypes->neverType, opts)); } TEST_CASE_FIXTURE(Fixture, "bound") @@ -283,6 +302,30 @@ n1 [label="FreeType 1"]; toDot(&type, opts)); } +TEST_CASE_FIXTURE(Fixture, "free_with_constraints") +{ + ScopedFastFlag sff[] = { + {"DebugLuauDeferredConstraintResolution", true}, + }; + + Type type{TypeVariant{FreeType{nullptr, builtinTypes->numberType, builtinTypes->optionalNumberType}}}; + + ToDotOptions opts; + opts.showPointers = false; + CHECK_EQ(R"(digraph graphname { +n1 [label="FreeType 1"]; +n1 -> n2 [label="[lowerBound]"]; +n2 [label="number"]; +n1 -> n3 [label="[upperBound]"]; +n3 [label="UnionType 3"]; +n3 -> n4; +n4 [label="number"]; +n3 -> n5; +n5 [label="nil"]; +})", + toDot(&type, opts)); +} + TEST_CASE_FIXTURE(Fixture, "error") { Type type{TypeVariant{ErrorType{}}}; @@ -440,4 +483,19 @@ n5 [label="SingletonType boolean: false"]; toDot(requireType("x"), opts)); } +TEST_CASE_FIXTURE(Fixture, "negation") +{ + TypeArena arena; + TypeId t = arena.addType(NegationType{builtinTypes->stringType}); + + ToDotOptions opts; + opts.showPointers = false; + + CHECK(R"(digraph graphname { +n1 [label="NegationType 1"]; +n1 -> n2 [label="[negated]"]; +n2 [label="string"]; +})" == toDot(t, opts)); +} + TEST_SUITE_END(); diff --git a/tests/TypeFamily.test.cpp b/tests/TypeFamily.test.cpp index 6e6dba09..c7ab66dd 100644 --- a/tests/TypeFamily.test.cpp +++ b/tests/TypeFamily.test.cpp @@ -1,5 +1,7 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "Luau/TypeFamily.h" + +#include "Luau/TxnLog.h" #include "Luau/Type.h" #include "Fixture.h" diff --git a/tests/TypeInfer.provisional.test.cpp b/tests/TypeInfer.provisional.test.cpp index 794171fb..d4dbfa91 100644 --- a/tests/TypeInfer.provisional.test.cpp +++ b/tests/TypeInfer.provisional.test.cpp @@ -1006,8 +1006,6 @@ end // We would prefer this unification to be able to complete, but at least it should not crash TEST_CASE_FIXTURE(BuiltinsFixture, "table_unification_infinite_recursion") { - ScopedFastFlag luauTableUnifyRecursionLimit{"LuauTableUnifyRecursionLimit", true}; - #if defined(_NOOPT) || defined(_DEBUG) ScopedFastInt LuauTypeInferRecursionLimit{"LuauTypeInferRecursionLimit", 100}; #endif diff --git a/tests/TypeInfer.test.cpp b/tests/TypeInfer.test.cpp index 8af5c684..aeabf0ac 100644 --- a/tests/TypeInfer.test.cpp +++ b/tests/TypeInfer.test.cpp @@ -1404,4 +1404,32 @@ TEST_CASE_FIXTURE(Fixture, "promote_tail_type_packs") LUAU_REQUIRE_NO_ERRORS(result); } +/* + * CLI-49876 + * + * We had a bug where we would not use the correct TxnLog when evaluating a + * variadic overload. We could therefore get into a state where the TxnLog has + * logged that a generic matches to one type, but the variadic tail has already + * been bound to another type outside of that TxnLog. + * + * This caused type checking to succeed when it should have failed. + */ +TEST_CASE_FIXTURE(BuiltinsFixture, "be_sure_to_use_active_txnlog_when_evaluating_a_variadic_overload") +{ + ScopedFastFlag sff{"LuauVariadicOverloadFix", true}; + + CheckResult result = check(R"( + local function concat(target: {T}, ...: {T} | T): {T} + return (nil :: any) :: {T} + end + + local res = concat({"alic"}, 1, 2) + )"); + + LUAU_REQUIRE_ERRORS(result); + + for (const auto& e: result.errors) + CHECK(5 == e.location.begin.line); +} + TEST_SUITE_END(); diff --git a/tests/conformance/basic.lua b/tests/conformance/basic.lua index 42030c55..17f4497a 100644 --- a/tests/conformance/basic.lua +++ b/tests/conformance/basic.lua @@ -177,6 +177,33 @@ assert((function() local a = 1 for b=1,9 do a = a * 2 if a == 128 then break els -- make sure internal index is protected against modification assert((function() local a = 1 for b=9,1,-2 do a = a * 2 b = nil end return a end)() == 32) +-- make sure that when step is 0, we treat it as backward iteration (and as such, iterate zero times or indefinitely) +-- this is consistent with Lua 5.1; future Lua versions emit an error when step is 0; LuaJIT instead treats 0 as forward iteration +-- we repeat tests twice, with and without constant folding +local zero = tonumber("0") +assert((function() local c = 0 for i=1,10,0 do c += 1 if c > 10 then break end end return c end)() == 0) +assert((function() local c = 0 for i=10,1,0 do c += 1 if c > 10 then break end end return c end)() == 11) +assert((function() local c = 0 for i=1,10,zero do c += 1 if c > 10 then break end end return c end)() == 0) +assert((function() local c = 0 for i=10,1,zero do c += 1 if c > 10 then break end end return c end)() == 11) + +-- make sure that when limit is nan, we iterate zero times (this is consistent with Lua 5.1; future Lua versions break this) +-- we repeat tests twice, with and without constant folding +local nan = tonumber("nan") +assert((function() local c = 0 for i=1,0/0 do c += 1 end return c end)() == 0) +assert((function() local c = 0 for i=1,0/0,-1 do c += 1 end return c end)() == 0) +assert((function() local c = 0 for i=1,nan do c += 1 end return c end)() == 0) +assert((function() local c = 0 for i=1,nan,-1 do c += 1 end return c end)() == 0) + +-- make sure that when step is nan, we treat it as backward iteration and as such iterate once iff start<=limit +assert((function() local c = 0 for i=1,10,0/0 do c += 1 end return c end)() == 0) +assert((function() local c = 0 for i=10,1,0/0 do c += 1 end return c end)() == 1) +assert((function() local c = 0 for i=1,10,nan do c += 1 end return c end)() == 0) +assert((function() local c = 0 for i=10,1,nan do c += 1 end return c end)() == 1) + +-- make sure that when index becomes nan mid-iteration, we correctly exit the loop (this is broken in Lua 5.1; future Lua versions fix this) +assert((function() local c = 0 for i=-math.huge,0,math.huge do c += 1 end return c end)() == 1) +assert((function() local c = 0 for i=math.huge,math.huge,-math.huge do c += 1 end return c end)() == 1) + -- generic for -- ipairs assert((function() local a = '' for k in ipairs({5, 6, 7}) do a = a .. k end return a end)() == "123") @@ -286,6 +313,10 @@ assert((function() return result end)() == "ArcticDunesCanyonsWaterMountainsHillsLavaflowPlainsMarsh") +-- table literals may contain duplicate fields; the language doesn't specify assignment order but we currently assign left to right +assert((function() local t = {data = 4, data = nil, data = 42} return t.data end)() == 42) +assert((function() local t = {data = 4, data = nil, data = 42, data = nil} return t.data end)() == nil) + -- multiple returns -- local= assert((function() function foo() return 2, 3, 4 end local a, b, c = foo() return ''..a..b..c end)() == "234") diff --git a/tests/conformance/math.lua b/tests/conformance/math.lua index 6b5bfc5f..4027bba0 100644 --- a/tests/conformance/math.lua +++ b/tests/conformance/math.lua @@ -189,6 +189,26 @@ do -- testing NaN assert(a[NaN] == nil) end +-- extra NaN tests, hidden in a function +do + function neq(a) return a ~= a end + function eq(a) return a == a end + function lt(a) return a < a end + function le(a) return a <= a end + function gt(a) return a > a end + function ge(a) return a >= a end + + local NaN -- to avoid constant folding + NaN = 10e500 - 10e400 + + assert(neq(NaN)) + assert(not eq(NaN)) + assert(not lt(NaN)) + assert(not le(NaN)) + assert(not gt(NaN)) + assert(not ge(NaN)) +end + -- require "checktable" -- stat(a) diff --git a/tests/main.cpp b/tests/main.cpp index 9435c61a..26872196 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -6,6 +6,8 @@ #define DOCTEST_CONFIG_OPTIONS_PREFIX "" #include "doctest.h" +#include "RegisterCallbacks.h" + #ifdef _WIN32 #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN @@ -327,6 +329,14 @@ int main(int argc, char** argv) } } + // These callbacks register unit tests that need runtime support to be + // correctly set up. Running them here means that all command line flags + // have been parsed, fast flags have been set, and we've potentially already + // exited. Once doctest::Context::run is invoked, the test list will be + // picked up from global state. + for (Luau::RegisterCallback cb : Luau::getRegisterCallbacks()) + cb(); + int result = context.run(); if (doctest::parseFlag(argc, argv, "--help") || doctest::parseFlag(argc, argv, "-h")) { From a35d3d458813c2d3b494784392ab724c853eefac Mon Sep 17 00:00:00 2001 From: Someon1e <142684596+Someon1e@users.noreply.github.com> Date: Mon, 11 Sep 2023 17:09:00 +0100 Subject: [PATCH 18/20] Demo site: case sensitive highlighting (#1040) Before: ![image](https://github.com/Roblox/luau/assets/142684596/6a5ec602-9946-48aa-98c7-ddebc404c5dd) After: ![image](https://github.com/Roblox/luau/assets/142684596/d7a9bf3d-2163-417c-8407-43d65d6eca01) --- docs/assets/js/luau_mode.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/assets/js/luau_mode.js b/docs/assets/js/luau_mode.js index a5dcdc97..3bd86505 100644 --- a/docs/assets/js/luau_mode.js +++ b/docs/assets/js/luau_mode.js @@ -17,10 +17,10 @@ var indentUnit = 4; function prefixRE(words) { - return new RegExp("^(?:" + words.join("|") + ")", "i"); + return new RegExp("^(?:" + words.join("|") + ")"); } function wordRE(words) { - return new RegExp("^(?:" + words.join("|") + ")$", "i"); + return new RegExp("^(?:" + words.join("|") + ")$"); } var specials = wordRE(parserConfig.specials || ["type"]); From 31a017c5c7d48841c63ac66a0a62fad06f71860e Mon Sep 17 00:00:00 2001 From: Andy Friesen Date: Fri, 15 Sep 2023 10:26:59 -0700 Subject: [PATCH 19/20] Sync to upstream/release/595 (#1044) * Rerun clang-format on the code * Fix the variance on indexer result subtyping. This fixes some issues with inconsistent error reporting. * Fix a bug in the normalization logic for intersections of strings New Type Solver * New overload selection logic * Subtype tests now correctly treat a generic as its upper bound within that generic's scope * Semantic subtyping for negation types * Semantic subtyping between strings and compatible table types like `{lower: (string) -> string}` * Further work toward finalizing our new subtype test * Correctly generalize module-scope symbols Native Codegen * Lowering statistics for assembly * Make executable allocation size/limit configurable without a rebuild. Use `FInt::LuauCodeGenBlockSize` and `FInt::LuauCodeGenMaxTotalSize`. --------- Co-authored-by: Arseny Kapoulkine Co-authored-by: Vyacheslav Egorov Co-authored-by: Lily Brown --- .../include/Luau/ConstraintGraphBuilder.h | 7 +- Analysis/include/Luau/GlobalTypes.h | 2 +- Analysis/include/Luau/Instantiation.h | 7 +- Analysis/include/Luau/Subtyping.h | 88 ++-- Analysis/include/Luau/Type.h | 12 + Analysis/include/Luau/TypeChecker2.h | 4 +- Analysis/include/Luau/TypeUtils.h | 3 +- Analysis/include/Luau/Unifier.h | 6 +- Analysis/include/Luau/Unifier2.h | 4 +- Analysis/include/Luau/VisitType.h | 17 +- Analysis/src/Autocomplete.cpp | 29 +- Analysis/src/Clone.cpp | 2 +- Analysis/src/ConstraintGraphBuilder.cpp | 22 +- Analysis/src/ConstraintSolver.cpp | 69 ++- Analysis/src/Differ.cpp | 51 +- Analysis/src/Frontend.cpp | 3 +- Analysis/src/GlobalTypes.cpp | 2 +- Analysis/src/Instantiation.cpp | 3 +- Analysis/src/Linter.cpp | 4 +- Analysis/src/Normalize.cpp | 68 ++- Analysis/src/Subtyping.cpp | 495 +++++++++++++----- Analysis/src/ToDot.cpp | 3 +- Analysis/src/TypeChecker2.cpp | 19 +- Analysis/src/Unifier.cpp | 3 +- Analysis/src/Unifier2.cpp | 92 +++- Ast/include/Luau/Ast.h | 2 +- CLI/Compile.cpp | 20 +- CodeGen/include/Luau/CodeGen.h | 13 +- CodeGen/include/Luau/IrRegAllocX64.h | 6 +- CodeGen/include/Luau/IrUtils.h | 9 + CodeGen/src/AssemblyBuilderA64.cpp | 2 +- CodeGen/src/CodeGen.cpp | 2 +- CodeGen/src/CodeGenAssembly.cpp | 16 +- CodeGen/src/CodeGenLower.h | 48 +- CodeGen/src/IrBuilder.cpp | 3 +- CodeGen/src/IrLoweringA64.cpp | 16 +- CodeGen/src/IrLoweringA64.h | 4 +- CodeGen/src/IrLoweringX64.cpp | 14 +- CodeGen/src/IrLoweringX64.h | 4 +- CodeGen/src/IrRegAllocA64.cpp | 15 +- CodeGen/src/IrRegAllocA64.h | 6 +- CodeGen/src/IrRegAllocX64.cpp | 10 +- CodeGen/src/IrTranslation.cpp | 14 +- CodeGen/src/IrUtils.cpp | 38 ++ CodeGen/src/NativeState.cpp | 8 +- Common/include/Luau/ExperimentalFlags.h | 1 + VM/src/lmathlib.cpp | 18 +- VM/src/lvmexecute.cpp | 2 +- tests/Autocomplete.test.cpp | 3 +- tests/CodeAllocator.test.cpp | 3 +- tests/Compiler.test.cpp | 11 +- tests/IrCallWrapperX64.test.cpp | 2 +- tests/IrRegAllocX64.test.cpp | 2 +- tests/Normalize.test.cpp | 65 +++ tests/Subtyping.test.cpp | 326 ++++++------ tests/TypeInfer.operators.test.cpp | 3 +- tests/TypeInfer.refinements.test.cpp | 1 - tests/TypeInfer.tables.test.cpp | 36 ++ tests/TypeInfer.test.cpp | 2 +- tests/Unifier2.test.cpp | 60 ++- tools/faillist.txt | 31 +- 61 files changed, 1250 insertions(+), 581 deletions(-) diff --git a/Analysis/include/Luau/ConstraintGraphBuilder.h b/Analysis/include/Luau/ConstraintGraphBuilder.h index 1d3f20ee..902da0d5 100644 --- a/Analysis/include/Luau/ConstraintGraphBuilder.h +++ b/Analysis/include/Luau/ConstraintGraphBuilder.h @@ -101,9 +101,10 @@ struct ConstraintGraphBuilder DcrLogger* logger; - ConstraintGraphBuilder(ModulePtr module, NotNull normalizer, NotNull moduleResolver, NotNull builtinTypes, - NotNull ice, const ScopePtr& globalScope, std::function prepareModuleScope, - DcrLogger* logger, NotNull dfg, std::vector requireCycles); + ConstraintGraphBuilder(ModulePtr module, NotNull normalizer, NotNull moduleResolver, + NotNull builtinTypes, NotNull ice, const ScopePtr& globalScope, + std::function prepareModuleScope, DcrLogger* logger, NotNull dfg, + std::vector requireCycles); /** * Fabricates a new free type belonging to a given scope. diff --git a/Analysis/include/Luau/GlobalTypes.h b/Analysis/include/Luau/GlobalTypes.h index 86bfd943..7a34f935 100644 --- a/Analysis/include/Luau/GlobalTypes.h +++ b/Analysis/include/Luau/GlobalTypes.h @@ -23,4 +23,4 @@ struct GlobalTypes ScopePtr globalScope; // shared by all modules }; -} +} // namespace Luau diff --git a/Analysis/include/Luau/Instantiation.h b/Analysis/include/Luau/Instantiation.h index 642f2b9e..1dbf6b67 100644 --- a/Analysis/include/Luau/Instantiation.h +++ b/Analysis/include/Luau/Instantiation.h @@ -17,8 +17,8 @@ struct TypeCheckLimits; // A substitution which replaces generic types in a given set by free types. struct ReplaceGenerics : Substitution { - ReplaceGenerics(const TxnLog* log, TypeArena* arena, NotNull builtinTypes, TypeLevel level, Scope* scope, const std::vector& generics, - const std::vector& genericPacks) + ReplaceGenerics(const TxnLog* log, TypeArena* arena, NotNull builtinTypes, TypeLevel level, Scope* scope, + const std::vector& generics, const std::vector& genericPacks) : Substitution(log, arena) , builtinTypes(builtinTypes) , level(level) @@ -77,6 +77,7 @@ struct Instantiation : Substitution * Instantiation fails only when processing the type causes internal recursion * limits to be exceeded. */ -std::optional instantiate(NotNull builtinTypes, NotNull arena, NotNull limits, NotNull scope, TypeId ty); +std::optional instantiate( + NotNull builtinTypes, NotNull arena, NotNull limits, NotNull scope, TypeId ty); } // namespace Luau diff --git a/Analysis/include/Luau/Subtyping.h b/Analysis/include/Luau/Subtyping.h index 70cd8bae..de702d53 100644 --- a/Analysis/include/Luau/Subtyping.h +++ b/Analysis/include/Luau/Subtyping.h @@ -19,20 +19,18 @@ class TypeIds; class Normalizer; struct NormalizedType; struct NormalizedClassType; +struct NormalizedStringType; struct NormalizedFunctionType; + struct SubtypingResult { - // Did the test succeed? bool isSubtype = false; bool isErrorSuppressing = false; bool normalizationTooComplex = false; - // If so, what constraints are implied by this relation? - // If not, what happened? - - void andAlso(const SubtypingResult& other); - void orElse(const SubtypingResult& other); + SubtypingResult& andAlso(const SubtypingResult& other); + SubtypingResult& orElse(const SubtypingResult& other); // Only negates the `isSubtype`. static SubtypingResult negate(const SubtypingResult& result); @@ -47,6 +45,8 @@ struct Subtyping NotNull normalizer; NotNull iceReporter; + NotNull scope; + enum class Variance { Covariant, @@ -72,6 +72,12 @@ struct Subtyping SeenSet seenTypes; + Subtyping(const Subtyping&) = delete; + Subtyping& operator=(const Subtyping&) = delete; + + Subtyping(Subtyping&&) = default; + Subtyping& operator=(Subtyping&&) = default; + // TODO cache // TODO cyclic types // TODO recursion limits @@ -80,43 +86,61 @@ struct Subtyping SubtypingResult isSubtype(TypePackId subTy, TypePackId superTy); private: - SubtypingResult isSubtype_(TypeId subTy, TypeId superTy); - SubtypingResult isSubtype_(TypePackId subTy, TypePackId superTy); + SubtypingResult isCovariantWith(TypeId subTy, TypeId superTy); + SubtypingResult isCovariantWith(TypePackId subTy, TypePackId superTy); template - SubtypingResult isSubtype_(const TryPair& pair); + SubtypingResult isContravariantWith(SubTy&& subTy, SuperTy&& superTy); - SubtypingResult isSubtype_(TypeId subTy, const UnionType* superUnion); - SubtypingResult isSubtype_(const UnionType* subUnion, TypeId superTy); - SubtypingResult isSubtype_(TypeId subTy, const IntersectionType* superIntersection); - SubtypingResult isSubtype_(const IntersectionType* subIntersection, TypeId superTy); - SubtypingResult isSubtype_(const PrimitiveType* subPrim, const PrimitiveType* superPrim); - SubtypingResult isSubtype_(const SingletonType* subSingleton, const PrimitiveType* superPrim); - SubtypingResult isSubtype_(const SingletonType* subSingleton, const SingletonType* superSingleton); - SubtypingResult isSubtype_(const TableType* subTable, const TableType* superTable); - SubtypingResult isSubtype_(const MetatableType* subMt, const MetatableType* superMt); - SubtypingResult isSubtype_(const MetatableType* subMt, const TableType* superTable); - SubtypingResult isSubtype_(const ClassType* subClass, const ClassType* superClass); - SubtypingResult isSubtype_(const ClassType* subClass, const TableType* superTable); // Actually a class <: shape. - SubtypingResult isSubtype_(const FunctionType* subFunction, const FunctionType* superFunction); - SubtypingResult isSubtype_(const PrimitiveType* subPrim, const TableType* superTable); - SubtypingResult isSubtype_(const SingletonType* subSingleton, const TableType* superTable); + template + SubtypingResult isInvariantWith(SubTy&& subTy, SuperTy&& superTy); - SubtypingResult isSubtype_(const NormalizedType* subNorm, const NormalizedType* superNorm); - SubtypingResult isSubtype_(const NormalizedClassType& subClass, const NormalizedClassType& superClass, const TypeIds& superTables); - SubtypingResult isSubtype_(const NormalizedFunctionType& subFunction, const NormalizedFunctionType& superFunction); - SubtypingResult isSubtype_(const TypeIds& subTypes, const TypeIds& superTypes); + template + SubtypingResult isCovariantWith(const TryPair& pair); - SubtypingResult isSubtype_(const VariadicTypePack* subVariadic, const VariadicTypePack* superVariadic); + template + SubtypingResult isContravariantWith(const TryPair& pair); + + template + SubtypingResult isInvariantWith(const TryPair& pair); + + SubtypingResult isCovariantWith(TypeId subTy, const UnionType* superUnion); + SubtypingResult isCovariantWith(const UnionType* subUnion, TypeId superTy); + SubtypingResult isCovariantWith(TypeId subTy, const IntersectionType* superIntersection); + SubtypingResult isCovariantWith(const IntersectionType* subIntersection, TypeId superTy); + + SubtypingResult isCovariantWith(const NegationType* subNegation, TypeId superTy); + SubtypingResult isCovariantWith(const TypeId subTy, const NegationType* superNegation); + + SubtypingResult isCovariantWith(const PrimitiveType* subPrim, const PrimitiveType* superPrim); + SubtypingResult isCovariantWith(const SingletonType* subSingleton, const PrimitiveType* superPrim); + SubtypingResult isCovariantWith(const SingletonType* subSingleton, const SingletonType* superSingleton); + SubtypingResult isCovariantWith(const TableType* subTable, const TableType* superTable); + SubtypingResult isCovariantWith(const MetatableType* subMt, const MetatableType* superMt); + SubtypingResult isCovariantWith(const MetatableType* subMt, const TableType* superTable); + SubtypingResult isCovariantWith(const ClassType* subClass, const ClassType* superClass); + SubtypingResult isCovariantWith(const ClassType* subClass, const TableType* superTable); + SubtypingResult isCovariantWith(const FunctionType* subFunction, const FunctionType* superFunction); + SubtypingResult isCovariantWith(const PrimitiveType* subPrim, const TableType* superTable); + SubtypingResult isCovariantWith(const SingletonType* subSingleton, const TableType* superTable); + + SubtypingResult isCovariantWith(const NormalizedType* subNorm, const NormalizedType* superNorm); + SubtypingResult isCovariantWith(const NormalizedClassType& subClass, const NormalizedClassType& superClass); + SubtypingResult isCovariantWith(const NormalizedClassType& subClass, const TypeIds& superTables); + SubtypingResult isCovariantWith(const NormalizedStringType& subString, const NormalizedStringType& superString); + SubtypingResult isCovariantWith(const NormalizedStringType& subString, const TypeIds& superTables); + SubtypingResult isCovariantWith(const NormalizedFunctionType& subFunction, const NormalizedFunctionType& superFunction); + SubtypingResult isCovariantWith(const TypeIds& subTypes, const TypeIds& superTypes); + + SubtypingResult isCovariantWith(const VariadicTypePack* subVariadic, const VariadicTypePack* superVariadic); bool bindGeneric(TypeId subTp, TypeId superTp); bool bindGeneric(TypePackId subTp, TypePackId superTp); - template + template TypeId makeAggregateType(const Container& container, TypeId orElse); - [[noreturn]] - void unexpected(TypePackId tp); + [[noreturn]] void unexpected(TypePackId tp); }; } // namespace Luau diff --git a/Analysis/include/Luau/Type.h b/Analysis/include/Luau/Type.h index ffbe3fa0..d43266cb 100644 --- a/Analysis/include/Luau/Type.h +++ b/Analysis/include/Luau/Type.h @@ -849,6 +849,18 @@ bool isSubclass(const ClassType* cls, const ClassType* parent); Type* asMutable(TypeId ty); +template +bool is(T&& tv) +{ + if (!tv) + return false; + + if constexpr (std::is_same_v && !(std::is_same_v || ...)) + LUAU_ASSERT(get_if(&tv->ty) == nullptr); + + return (get(tv) || ...); +} + template const T* get(TypeId tv) { diff --git a/Analysis/include/Luau/TypeChecker2.h b/Analysis/include/Luau/TypeChecker2.h index db81d9cf..aeeab0f8 100644 --- a/Analysis/include/Luau/TypeChecker2.h +++ b/Analysis/include/Luau/TypeChecker2.h @@ -14,7 +14,7 @@ struct DcrLogger; struct TypeCheckLimits; struct UnifierSharedState; -void check(NotNull builtinTypes, NotNull sharedState, NotNull limits, DcrLogger* logger, const SourceModule& sourceModule, - Module* module); +void check(NotNull builtinTypes, NotNull sharedState, NotNull limits, DcrLogger* logger, + const SourceModule& sourceModule, Module* module); } // namespace Luau diff --git a/Analysis/include/Luau/TypeUtils.h b/Analysis/include/Luau/TypeUtils.h index 9699c4ae..4d41926c 100644 --- a/Analysis/include/Luau/TypeUtils.h +++ b/Analysis/include/Luau/TypeUtils.h @@ -104,7 +104,8 @@ ErrorSuppression shouldSuppressErrors(NotNull normalizer, TypePackId // Similar to `std::optional>`, but whose `sizeof()` is the same as `std::pair` // and cooperates with C++'s `if (auto p = ...)` syntax without the extra fatness of `std::optional`. template -struct TryPair { +struct TryPair +{ A first; B second; diff --git a/Analysis/include/Luau/Unifier.h b/Analysis/include/Luau/Unifier.h index f7c5c94c..1260ac93 100644 --- a/Analysis/include/Luau/Unifier.h +++ b/Analysis/include/Luau/Unifier.h @@ -105,10 +105,12 @@ struct Unifier * Populate the vector errors with any type errors that may arise. * Populate the transaction log with the set of TypeIds that need to be reset to undo the unification attempt. */ - void tryUnify(TypeId subTy, TypeId superTy, bool isFunctionCall = false, bool isIntersection = false, const LiteralProperties* aliasableMap = nullptr); + void tryUnify( + TypeId subTy, TypeId superTy, bool isFunctionCall = false, bool isIntersection = false, const LiteralProperties* aliasableMap = nullptr); private: - void tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall = false, bool isIntersection = false, const LiteralProperties* aliasableMap = nullptr); + void tryUnify_( + TypeId subTy, TypeId superTy, bool isFunctionCall = false, bool isIntersection = false, const LiteralProperties* aliasableMap = nullptr); void tryUnifyUnionWithType(TypeId subTy, const UnionType* uv, TypeId superTy); // Traverse the two types provided and block on any BlockedTypes we find. diff --git a/Analysis/include/Luau/Unifier2.h b/Analysis/include/Luau/Unifier2.h index cf769da3..6d32e03f 100644 --- a/Analysis/include/Luau/Unifier2.h +++ b/Analysis/include/Luau/Unifier2.h @@ -55,8 +55,8 @@ struct Unifier2 bool unify(TypePackId subTp, TypePackId superTp); std::optional generalize(NotNull scope, TypeId ty); -private: +private: /** * @returns simplify(left | right) */ @@ -72,4 +72,4 @@ private: OccursCheckResult occursCheck(DenseHashSet& seen, TypePackId needle, TypePackId haystack); }; -} +} // namespace Luau diff --git a/Analysis/include/Luau/VisitType.h b/Analysis/include/Luau/VisitType.h index a84fb48c..28dfffbf 100644 --- a/Analysis/include/Luau/VisitType.h +++ b/Analysis/include/Luau/VisitType.h @@ -10,6 +10,7 @@ LUAU_FASTINT(LuauVisitRecursionLimit) LUAU_FASTFLAG(LuauBoundLazyTypes2) +LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution) LUAU_FASTFLAG(DebugLuauReadWriteProperties) namespace Luau @@ -220,7 +221,21 @@ struct GenericTypeVisitor traverse(btv->boundTo); } else if (auto ftv = get(ty)) - visit(ty, *ftv); + { + if (FFlag::DebugLuauDeferredConstraintResolution) + { + if (visit(ty, *ftv)) + { + LUAU_ASSERT(ftv->lowerBound); + traverse(ftv->lowerBound); + + LUAU_ASSERT(ftv->upperBound); + traverse(ftv->upperBound); + } + } + else + visit(ty, *ftv); + } else if (auto gtv = get(ty)) visit(ty, *gtv); else if (auto etv = get(ty)) diff --git a/Analysis/src/Autocomplete.cpp b/Analysis/src/Autocomplete.cpp index 4a5638b1..3eba2e12 100644 --- a/Analysis/src/Autocomplete.cpp +++ b/Analysis/src/Autocomplete.cpp @@ -282,20 +282,8 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, NotNul ParenthesesRecommendation parens = indexType == PropIndexType::Key ? ParenthesesRecommendation::None : getParenRecommendation(type, nodes, typeCorrect); - result[name] = AutocompleteEntry{ - AutocompleteEntryKind::Property, - type, - prop.deprecated, - isWrongIndexer(type), - typeCorrect, - containingClass, - &prop, - prop.documentationSymbol, - {}, - parens, - {}, - indexType == PropIndexType::Colon - }; + result[name] = AutocompleteEntry{AutocompleteEntryKind::Property, type, prop.deprecated, isWrongIndexer(type), typeCorrect, + containingClass, &prop, prop.documentationSymbol, {}, parens, {}, indexType == PropIndexType::Colon}; } } }; @@ -606,7 +594,7 @@ std::optional getLocalTypeInScopeAt(const Module& module, Position posit return {}; } -template +template static std::optional tryToStringDetailed(const ScopePtr& scope, T ty, bool functionTypeArguments) { ToStringOptions opts; @@ -1418,7 +1406,7 @@ static std::string makeAnonymous(const ScopePtr& scope, const FunctionType& func name = "a" + std::to_string(argIdx); if (std::optional type = tryGetTypeNameInScope(scope, args[argIdx], true)) - result += name + ": " + *type; + result += name + ": " + *type; else result += name; } @@ -1434,7 +1422,7 @@ static std::string makeAnonymous(const ScopePtr& scope, const FunctionType& func if (std::optional res = tryToStringDetailed(scope, pack->ty, true)) varArgType = std::move(res); } - + if (varArgType) result += "...: " + *varArgType; else @@ -1461,7 +1449,8 @@ static std::string makeAnonymous(const ScopePtr& scope, const FunctionType& func return result; } -static std::optional makeAnonymousAutofilled(const ModulePtr& module, Position position, const AstNode* node, const std::vector& ancestry) +static std::optional makeAnonymousAutofilled( + const ModulePtr& module, Position position, const AstNode* node, const std::vector& ancestry) { const AstExprCall* call = node->as(); if (!call && ancestry.size() > 1) @@ -1498,10 +1487,10 @@ static std::optional makeAnonymousAutofilled(const ModulePtr& auto [args, tail] = flatten(outerFunction->argTypes); if (argument < args.size()) argType = args[argument]; - + if (!argType) return std::nullopt; - + TypeId followed = follow(*argType); const FunctionType* type = get(followed); if (!type) diff --git a/Analysis/src/Clone.cpp b/Analysis/src/Clone.cpp index ad96527e..c28e1678 100644 --- a/Analysis/src/Clone.cpp +++ b/Analysis/src/Clone.cpp @@ -780,7 +780,7 @@ void TypeCloner::operator()(const UnionType& t) // We're just using this FreeType as a placeholder until we've finished // cloning the parts of this union so it is okay that its bounds are // nullptr. We'll never indirect them. - TypeId result = dest.addType(FreeType{nullptr, /*lowerBound*/nullptr, /*upperBound*/nullptr}); + TypeId result = dest.addType(FreeType{nullptr, /*lowerBound*/ nullptr, /*upperBound*/ nullptr}); seenTypes[typeId] = result; std::vector options; diff --git a/Analysis/src/ConstraintGraphBuilder.cpp b/Analysis/src/ConstraintGraphBuilder.cpp index d2413adb..ae143ca5 100644 --- a/Analysis/src/ConstraintGraphBuilder.cpp +++ b/Analysis/src/ConstraintGraphBuilder.cpp @@ -420,17 +420,17 @@ void ConstraintGraphBuilder::applyRefinements(const ScopePtr& scope, Location lo { switch (shouldSuppressErrors(normalizer, ty)) { - case ErrorSuppression::DoNotSuppress: - ty = simplifyIntersection(builtinTypes, arena, ty, dt).result; - break; - case ErrorSuppression::Suppress: - ty = simplifyIntersection(builtinTypes, arena, ty, dt).result; - ty = simplifyUnion(builtinTypes, arena, ty, builtinTypes->errorType).result; - break; - case ErrorSuppression::NormalizationFailed: - reportError(location, NormalizationTooComplex{}); - ty = simplifyIntersection(builtinTypes, arena, ty, dt).result; - break; + case ErrorSuppression::DoNotSuppress: + ty = simplifyIntersection(builtinTypes, arena, ty, dt).result; + break; + case ErrorSuppression::Suppress: + ty = simplifyIntersection(builtinTypes, arena, ty, dt).result; + ty = simplifyUnion(builtinTypes, arena, ty, builtinTypes->errorType).result; + break; + case ErrorSuppression::NormalizationFailed: + reportError(location, NormalizationTooComplex{}); + ty = simplifyIntersection(builtinTypes, arena, ty, dt).result; + break; } } } diff --git a/Analysis/src/ConstraintSolver.cpp b/Analysis/src/ConstraintSolver.cpp index fd0c1c00..6faea1d2 100644 --- a/Analysis/src/ConstraintSolver.cpp +++ b/Analysis/src/ConstraintSolver.cpp @@ -430,6 +430,35 @@ bool ConstraintSolver::isDone() return unsolvedConstraints.empty(); } +namespace +{ + +struct TypeAndLocation +{ + TypeId typeId; + Location location; +}; + +struct FreeTypeSearcher : TypeOnceVisitor +{ + std::deque* result; + Location location; + + FreeTypeSearcher(std::deque* result, Location location) + : result(result) + , location(location) + { + } + + bool visit(TypeId ty, const FreeType&) override + { + result->push_back({ty, location}); + return false; + } +}; + +} // namespace + void ConstraintSolver::finalizeModule() { Anyification a{arena, rootScope, builtinTypes, &iceReporter, builtinTypes->anyType, builtinTypes->anyTypePack}; @@ -446,12 +475,28 @@ void ConstraintSolver::finalizeModule() Unifier2 u2{NotNull{arena}, builtinTypes, NotNull{&iceReporter}}; + std::deque queue; for (auto& [name, binding] : rootScope->bindings) + queue.push_back({binding.typeId, binding.location}); + + DenseHashSet seen{nullptr}; + + while (!queue.empty()) { - auto generalizedTy = u2.generalize(rootScope, binding.typeId); - if (generalizedTy) - binding.typeId = *generalizedTy; - else + TypeAndLocation binding = queue.front(); + queue.pop_front(); + + TypeId ty = follow(binding.typeId); + + if (seen.find(ty)) + continue; + seen.insert(ty); + + FreeTypeSearcher fts{&queue, binding.location}; + fts.traverse(ty); + + auto result = u2.generalize(rootScope, ty); + if (!result) reportError(CodeTooComplex{}, binding.location); } } @@ -2642,20 +2687,14 @@ ErrorVec ConstraintSolver::unify(NotNull scope, Location location, TypeId ErrorVec ConstraintSolver::unify(NotNull scope, Location location, TypePackId subPack, TypePackId superPack) { - UnifierSharedState sharedState{&iceReporter}; - Unifier u{normalizer, scope, Location{}, Covariant}; - u.enableNewSolver(); + Unifier2 u{arena, builtinTypes, NotNull{&iceReporter}}; - u.tryUnify(subPack, superPack); + u.unify(subPack, superPack); - const auto [changedTypes, changedPacks] = u.log.getChanges(); + unblock(subPack, Location{}); + unblock(superPack, Location{}); - u.log.commit(); - - unblock(changedTypes, Location{}); - unblock(changedPacks, Location{}); - - return std::move(u.errors); + return {}; } NotNull ConstraintSolver::pushConstraint(NotNull scope, const Location& location, ConstraintV cv) diff --git a/Analysis/src/Differ.cpp b/Analysis/src/Differ.cpp index 84505071..5b614cef 100644 --- a/Analysis/src/Differ.cpp +++ b/Analysis/src/Differ.cpp @@ -117,9 +117,7 @@ std::string DiffError::toStringALeaf(std::string rootName, const DiffPathNodeLea case DiffError::Kind::Normal: { checkNonMissingPropertyLeavesHaveNulloptTableProperty(); - return pathStr + conditionalNewline - + "has type" + conditionalNewline - + conditionalIndent + Luau::toString(*leaf.ty); + return pathStr + conditionalNewline + "has type" + conditionalNewline + conditionalIndent + Luau::toString(*leaf.ty); } case DiffError::Kind::MissingTableProperty: { @@ -127,17 +125,14 @@ std::string DiffError::toStringALeaf(std::string rootName, const DiffPathNodeLea { if (!leaf.tableProperty.has_value()) throw InternalCompilerError{"leaf.tableProperty is nullopt"}; - return pathStr + "." + *leaf.tableProperty + conditionalNewline - + "has type" + conditionalNewline - + conditionalIndent + Luau::toString(*leaf.ty); + return pathStr + "." + *leaf.tableProperty + conditionalNewline + "has type" + conditionalNewline + conditionalIndent + + Luau::toString(*leaf.ty); } else if (otherLeaf.ty.has_value()) { if (!otherLeaf.tableProperty.has_value()) throw InternalCompilerError{"otherLeaf.tableProperty is nullopt"}; - return pathStr + conditionalNewline - + "is missing the property" + conditionalNewline - + conditionalIndent + *otherLeaf.tableProperty; + return pathStr + conditionalNewline + "is missing the property" + conditionalNewline + conditionalIndent + *otherLeaf.tableProperty; } throw InternalCompilerError{"Both leaf.ty and otherLeaf.ty is nullopt"}; } @@ -148,15 +143,11 @@ std::string DiffError::toStringALeaf(std::string rootName, const DiffPathNodeLea { if (!leaf.unionIndex.has_value()) throw InternalCompilerError{"leaf.unionIndex is nullopt"}; - return pathStr + conditionalNewline - + "is a union containing type" + conditionalNewline - + conditionalIndent + Luau::toString(*leaf.ty); + return pathStr + conditionalNewline + "is a union containing type" + conditionalNewline + conditionalIndent + Luau::toString(*leaf.ty); } else if (otherLeaf.ty.has_value()) { - return pathStr + conditionalNewline - + "is a union missing type" + conditionalNewline - + conditionalIndent + Luau::toString(*otherLeaf.ty); + return pathStr + conditionalNewline + "is a union missing type" + conditionalNewline + conditionalIndent + Luau::toString(*otherLeaf.ty); } throw InternalCompilerError{"Both leaf.ty and otherLeaf.ty is nullopt"}; } @@ -169,15 +160,13 @@ std::string DiffError::toStringALeaf(std::string rootName, const DiffPathNodeLea { if (!leaf.unionIndex.has_value()) throw InternalCompilerError{"leaf.unionIndex is nullopt"}; - return pathStr + conditionalNewline - + "is an intersection containing type" + conditionalNewline - + conditionalIndent + Luau::toString(*leaf.ty); + return pathStr + conditionalNewline + "is an intersection containing type" + conditionalNewline + conditionalIndent + + Luau::toString(*leaf.ty); } else if (otherLeaf.ty.has_value()) { - return pathStr + conditionalNewline - + "is an intersection missing type" + conditionalNewline - + conditionalIndent + Luau::toString(*otherLeaf.ty); + return pathStr + conditionalNewline + "is an intersection missing type" + conditionalNewline + conditionalIndent + + Luau::toString(*otherLeaf.ty); } throw InternalCompilerError{"Both leaf.ty and otherLeaf.ty is nullopt"}; } @@ -185,15 +174,13 @@ std::string DiffError::toStringALeaf(std::string rootName, const DiffPathNodeLea { if (!leaf.minLength.has_value()) throw InternalCompilerError{"leaf.minLength is nullopt"}; - return pathStr + conditionalNewline - + "takes " + std::to_string(*leaf.minLength) + (leaf.isVariadic ? " or more" : "") + " arguments"; + return pathStr + conditionalNewline + "takes " + std::to_string(*leaf.minLength) + (leaf.isVariadic ? " or more" : "") + " arguments"; } case DiffError::Kind::LengthMismatchInFnRets: { if (!leaf.minLength.has_value()) throw InternalCompilerError{"leaf.minLength is nullopt"}; - return pathStr + conditionalNewline - + "returns " + std::to_string(*leaf.minLength) + (leaf.isVariadic ? " or more" : "") + " values"; + return pathStr + conditionalNewline + "returns " + std::to_string(*leaf.minLength) + (leaf.isVariadic ? " or more" : "") + " values"; } default: { @@ -249,17 +236,15 @@ std::string DiffError::toString(bool multiLine) const case DiffError::Kind::IncompatibleGeneric: { std::string diffPathStr{diffPath.toString(true)}; - return "DiffError: these two types are not equal because the left generic at" + conditionalNewline - + conditionalIndent + leftRootName + diffPathStr + conditionalNewline - + "cannot be the same type parameter as the right generic at" + conditionalNewline - + conditionalIndent + rightRootName + diffPathStr; + return "DiffError: these two types are not equal because the left generic at" + conditionalNewline + conditionalIndent + leftRootName + + diffPathStr + conditionalNewline + "cannot be the same type parameter as the right generic at" + conditionalNewline + + conditionalIndent + rightRootName + diffPathStr; } default: { - return "DiffError: these two types are not equal because the left type at" + conditionalNewline - + conditionalIndent + toStringALeaf(leftRootName, left, right, multiLine) + "," + conditionalNewline + - "while the right type at" + conditionalNewline - + conditionalIndent + toStringALeaf(rightRootName, right, left, multiLine); + return "DiffError: these two types are not equal because the left type at" + conditionalNewline + conditionalIndent + + toStringALeaf(leftRootName, left, right, multiLine) + "," + conditionalNewline + "while the right type at" + conditionalNewline + + conditionalIndent + toStringALeaf(rightRootName, right, left, multiLine); } } } diff --git a/Analysis/src/Frontend.cpp b/Analysis/src/Frontend.cpp index b71a9354..677458ab 100644 --- a/Analysis/src/Frontend.cpp +++ b/Analysis/src/Frontend.cpp @@ -1289,7 +1289,8 @@ ModulePtr check(const SourceModule& sourceModule, const std::vectortimeout || result->cancelled) { - // If solver was interrupted, skip typechecking and replace all module results with error-supressing types to avoid leaking blocked/pending types + // If solver was interrupted, skip typechecking and replace all module results with error-supressing types to avoid leaking blocked/pending + // types ScopePtr moduleScope = result->getModuleScope(); moduleScope->returnType = builtinTypes->errorRecoveryTypePack(); diff --git a/Analysis/src/GlobalTypes.cpp b/Analysis/src/GlobalTypes.cpp index 9e26a2e3..654cfa5d 100644 --- a/Analysis/src/GlobalTypes.cpp +++ b/Analysis/src/GlobalTypes.cpp @@ -31,4 +31,4 @@ GlobalTypes::GlobalTypes(NotNull builtinTypes) } } -} +} // namespace Luau diff --git a/Analysis/src/Instantiation.cpp b/Analysis/src/Instantiation.cpp index 0c8bc1ec..e74ece06 100644 --- a/Analysis/src/Instantiation.cpp +++ b/Analysis/src/Instantiation.cpp @@ -174,7 +174,8 @@ struct Replacer : Substitution } }; -std::optional instantiate(NotNull builtinTypes, NotNull arena, NotNull limits, NotNull scope, TypeId ty) +std::optional instantiate( + NotNull builtinTypes, NotNull arena, NotNull limits, NotNull scope, TypeId ty) { ty = follow(ty); diff --git a/Analysis/src/Linter.cpp b/Analysis/src/Linter.cpp index e8cd7dbd..d4a16a75 100644 --- a/Analysis/src/Linter.cpp +++ b/Analysis/src/Linter.cpp @@ -2791,8 +2791,8 @@ static void lintComments(LintContext& context, const std::vector& ho else if (first == "native") { if (space != std::string::npos) - emitWarning(context, LintWarning::Code_CommentDirective, hc.location, - "native directive has extra symbols at the end of the line"); + emitWarning( + context, LintWarning::Code_CommentDirective, hc.location, "native directive has extra symbols at the end of the line"); } else { diff --git a/Analysis/src/Normalize.cpp b/Analysis/src/Normalize.cpp index bcad75b0..ac58c7f6 100644 --- a/Analysis/src/Normalize.cpp +++ b/Analysis/src/Normalize.cpp @@ -176,7 +176,7 @@ const NormalizedStringType NormalizedStringType::never; bool isSubtype(const NormalizedStringType& subStr, const NormalizedStringType& superStr) { - if (subStr.isUnion() && superStr.isUnion()) + if (subStr.isUnion() && (superStr.isUnion() && !superStr.isNever())) { for (auto [name, ty] : subStr.singletons) { @@ -1983,18 +1983,68 @@ void Normalizer::intersectClassesWithClass(NormalizedClassType& heres, TypeId th void Normalizer::intersectStrings(NormalizedStringType& here, const NormalizedStringType& there) { + /* There are 9 cases to worry about here + Normalized Left | Normalized Right + C1 string | string ===> trivial + C2 string - {u_1,..} | string ===> trivial + C3 {u_1, ..} | string ===> trivial + C4 string | string - {v_1, ..} ===> string - {v_1, ..} + C5 string - {u_1,..} | string - {v_1, ..} ===> string - ({u_s} U {v_s}) + C6 {u_1, ..} | string - {v_1, ..} ===> {u_s} - {v_s} + C7 string | {v_1, ..} ===> {v_s} + C8 string - {u_1,..} | {v_1, ..} ===> {v_s} - {u_s} + C9 {u_1, ..} | {v_1, ..} ===> {u_s} ∩ {v_s} + */ + // Case 1,2,3 if (there.isString()) return; - if (here.isString()) - here.resetToNever(); - - for (auto it = here.singletons.begin(); it != here.singletons.end();) + // Case 4, Case 7 + else if (here.isString()) { - if (there.singletons.count(it->first)) - it++; - else - it = here.singletons.erase(it); + here.singletons.clear(); + for (const auto& [key, type] : there.singletons) + here.singletons[key] = type; + here.isCofinite = here.isCofinite && there.isCofinite; } + // Case 5 + else if (here.isIntersection() && there.isIntersection()) + { + here.isCofinite = true; + for (const auto& [key, type] : there.singletons) + here.singletons[key] = type; + } + // Case 6 + else if (here.isUnion() && there.isIntersection()) + { + here.isCofinite = false; + for (const auto& [key, _] : there.singletons) + here.singletons.erase(key); + } + // Case 8 + else if (here.isIntersection() && there.isUnion()) + { + here.isCofinite = false; + std::map result(there.singletons); + for (const auto& [key, _] : here.singletons) + result.erase(key); + here.singletons = result; + } + // Case 9 + else if (here.isUnion() && there.isUnion()) + { + here.isCofinite = false; + std::map result; + result.insert(here.singletons.begin(), here.singletons.end()); + result.insert(there.singletons.begin(), there.singletons.end()); + for (auto it = result.begin(); it != result.end();) + if (!here.singletons.count(it->first) || !there.singletons.count(it->first)) + it = result.erase(it); + else + ++it; + here.singletons = result; + } + else + LUAU_ASSERT(0 && "Internal Error - unrecognized case"); } std::optional Normalizer::intersectionOfTypePacks(TypePackId here, TypePackId there) diff --git a/Analysis/src/Subtyping.cpp b/Analysis/src/Subtyping.cpp index 8012bac7..e216d623 100644 --- a/Analysis/src/Subtyping.cpp +++ b/Analysis/src/Subtyping.cpp @@ -5,6 +5,7 @@ #include "Luau/Common.h" #include "Luau/Error.h" #include "Luau/Normalize.h" +#include "Luau/Scope.h" #include "Luau/StringUtils.h" #include "Luau/ToString.h" #include "Luau/Type.h" @@ -28,12 +29,12 @@ struct VarianceFlipper { switch (oldValue) { - case Subtyping::Variance::Covariant: - *variance = Subtyping::Variance::Contravariant; - break; - case Subtyping::Variance::Contravariant: - *variance = Subtyping::Variance::Covariant; - break; + case Subtyping::Variance::Covariant: + *variance = Subtyping::Variance::Contravariant; + break; + case Subtyping::Variance::Contravariant: + *variance = Subtyping::Variance::Covariant; + break; } } @@ -43,19 +44,21 @@ struct VarianceFlipper } }; -void SubtypingResult::andAlso(const SubtypingResult& other) +SubtypingResult& SubtypingResult::andAlso(const SubtypingResult& other) { isSubtype &= other.isSubtype; // `|=` is intentional here, we want to preserve error related flags. isErrorSuppressing |= other.isErrorSuppressing; normalizationTooComplex |= other.normalizationTooComplex; + return *this; } -void SubtypingResult::orElse(const SubtypingResult& other) +SubtypingResult& SubtypingResult::orElse(const SubtypingResult& other) { isSubtype |= other.isSubtype; isErrorSuppressing |= other.isErrorSuppressing; normalizationTooComplex |= other.normalizationTooComplex; + return *this; } SubtypingResult SubtypingResult::negate(const SubtypingResult& result) @@ -88,9 +91,9 @@ SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy) mappedGenerics.clear(); mappedGenericPacks.clear(); - SubtypingResult result = isSubtype_(subTy, superTy); + SubtypingResult result = isCovariantWith(subTy, superTy); - for (const auto& [subTy, bounds]: mappedGenerics) + for (const auto& [subTy, bounds] : mappedGenerics) { const auto& lb = bounds.lowerBound; const auto& ub = bounds.upperBound; @@ -98,7 +101,7 @@ SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy) TypeId lowerBound = makeAggregateType(lb, builtinTypes->neverType); TypeId upperBound = makeAggregateType(ub, builtinTypes->unknownType); - result.andAlso(isSubtype_(lowerBound, upperBound)); + result.andAlso(isCovariantWith(lowerBound, upperBound)); } return result; @@ -106,7 +109,7 @@ SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy) SubtypingResult Subtyping::isSubtype(TypePackId subTp, TypePackId superTp) { - return isSubtype_(subTp, superTp); + return isCovariantWith(subTp, superTp); } namespace @@ -119,16 +122,17 @@ struct SeenSetPopper SeenSetPopper(Subtyping::SeenSet* seenTypes, std::pair pair) : seenTypes(seenTypes) , pair(pair) - {} + { + } ~SeenSetPopper() { seenTypes->erase(pair); } }; -} +} // namespace -SubtypingResult Subtyping::isSubtype_(TypeId subTy, TypeId superTy) +SubtypingResult Subtyping::isCovariantWith(TypeId subTy, TypeId superTy) { subTy = follow(subTy); superTy = follow(superTy); @@ -146,19 +150,27 @@ SubtypingResult Subtyping::isSubtype_(TypeId subTy, TypeId superTy) SeenSetPopper ssp{&seenTypes, typePair}; + // Within the scope to which a generic belongs, that generic should be + // tested as though it were its upper bounds. We do not yet support bounded + // generics, so the upper bound is always unknown. + if (auto subGeneric = get(subTy); subGeneric && subsumes(subGeneric->scope, scope)) + return isCovariantWith(builtinTypes->unknownType, superTy); + if (auto superGeneric = get(superTy); superGeneric && subsumes(superGeneric->scope, scope)) + return isCovariantWith(subTy, builtinTypes->unknownType); + if (auto subUnion = get(subTy)) - return isSubtype_(subUnion, superTy); + return isCovariantWith(subUnion, superTy); else if (auto superUnion = get(superTy)) - return isSubtype_(subTy, superUnion); + return isCovariantWith(subTy, superUnion); else if (auto superIntersection = get(superTy)) - return isSubtype_(subTy, superIntersection); + return isCovariantWith(subTy, superIntersection); else if (auto subIntersection = get(subTy)) { - SubtypingResult result = isSubtype_(subIntersection, superTy); + SubtypingResult result = isCovariantWith(subIntersection, superTy); if (result.isSubtype || result.isErrorSuppressing || result.normalizationTooComplex) return result; else - return isSubtype_(normalizer->normalize(subTy), normalizer->normalize(superTy)); + return isCovariantWith(normalizer->normalize(subTy), normalizer->normalize(superTy)); } else if (get(superTy)) return {true}; // This is always true. @@ -166,14 +178,12 @@ SubtypingResult Subtyping::isSubtype_(TypeId subTy, TypeId superTy) { // any = unknown | error, so we rewrite this to match. // As per TAPL: A | B <: T iff A <: T && B <: T - SubtypingResult result = isSubtype_(builtinTypes->unknownType, superTy); - result.andAlso(isSubtype_(builtinTypes->errorType, superTy)); - return result; + return isCovariantWith(builtinTypes->unknownType, superTy).andAlso(isCovariantWith(builtinTypes->errorType, superTy)); } else if (get(superTy)) { - LUAU_ASSERT(!get(subTy)); // TODO: replace with ice. - LUAU_ASSERT(!get(subTy)); // TODO: replace with ice. + LUAU_ASSERT(!get(subTy)); // TODO: replace with ice. + LUAU_ASSERT(!get(subTy)); // TODO: replace with ice. LUAU_ASSERT(!get(subTy)); // TODO: replace with ice. bool errorSuppressing = get(subTy); @@ -185,6 +195,12 @@ SubtypingResult Subtyping::isSubtype_(TypeId subTy, TypeId superTy) return {false, true}; else if (get(subTy)) return {false, true}; + else if (auto p = get2(subTy, superTy)) + return isCovariantWith(p.first->ty, p.second->ty); + else if (auto subNegation = get(subTy)) + return isCovariantWith(subNegation, superTy); + else if (auto superNegation = get(superTy)) + return isCovariantWith(subTy, superNegation); else if (auto subGeneric = get(subTy); subGeneric && variance == Variance::Covariant) { bool ok = bindGeneric(subTy, superTy); @@ -196,32 +212,32 @@ SubtypingResult Subtyping::isSubtype_(TypeId subTy, TypeId superTy) return {ok}; } else if (auto p = get2(subTy, superTy)) - return isSubtype_(p); + return isCovariantWith(p); else if (auto p = get2(subTy, superTy)) - return isSubtype_(p); + return isCovariantWith(p); else if (auto p = get2(subTy, superTy)) - return isSubtype_(p); + return isCovariantWith(p); else if (auto p = get2(subTy, superTy)) - return isSubtype_(p); + return isCovariantWith(p); else if (auto p = get2(subTy, superTy)) - return isSubtype_(p); + return isCovariantWith(p); else if (auto p = get2(subTy, superTy)) - return isSubtype_(p); + return isCovariantWith(p); else if (auto p = get2(subTy, superTy)) - return isSubtype_(p); + return isCovariantWith(p); else if (auto p = get2(subTy, superTy)) - return isSubtype_(p); + return isCovariantWith(p); else if (auto p = get2(subTy, superTy)) - return isSubtype_(p); + return isCovariantWith(p); else if (auto p = get2(subTy, superTy)) - return isSubtype_(p); + return isCovariantWith(p); else if (auto p = get2(subTy, superTy)) - return isSubtype_(p); + return isCovariantWith(p); return {false}; } -SubtypingResult Subtyping::isSubtype_(TypePackId subTp, TypePackId superTp) +SubtypingResult Subtyping::isCovariantWith(TypePackId subTp, TypePackId superTp) { subTp = follow(subTp); superTp = follow(superTp); @@ -241,7 +257,7 @@ SubtypingResult Subtyping::isSubtype_(TypePackId subTp, TypePackId superTp) for (size_t i = 0; i < headSize; ++i) { - results.push_back(isSubtype_(subHead[i], superHead[i])); + results.push_back(isCovariantWith(subHead[i], superHead[i])); if (!results.back().isSubtype) return {false}; } @@ -255,7 +271,7 @@ SubtypingResult Subtyping::isSubtype_(TypePackId subTp, TypePackId superTp) if (auto vt = get(*subTail)) { for (size_t i = headSize; i < superHead.size(); ++i) - results.push_back(isSubtype_(vt->ty, superHead[i])); + results.push_back(isCovariantWith(vt->ty, superHead[i])); } else if (auto gt = get(*subTail)) { @@ -266,11 +282,11 @@ SubtypingResult Subtyping::isSubtype_(TypePackId subTp, TypePackId superTp) // (X) -> () <: (T) -> () // Possible optimization: If headSize == 0 then we can just use subTp as-is. - std::vector headSlice(begin(superHead), end(superHead) + headSize); + std::vector headSlice(begin(superHead), begin(superHead) + headSize); TypePackId superTailPack = arena->addTypePack(std::move(headSlice), superTail); if (TypePackId* other = mappedGenericPacks.find(*subTail)) - results.push_back(isSubtype_(*other, superTailPack)); + results.push_back(isCovariantWith(*other, superTailPack)); else mappedGenericPacks.try_insert(*subTail, superTailPack); @@ -300,7 +316,7 @@ SubtypingResult Subtyping::isSubtype_(TypePackId subTp, TypePackId superTp) if (auto vt = get(*superTail)) { for (size_t i = headSize; i < subHead.size(); ++i) - results.push_back(isSubtype_(subHead[i], vt->ty)); + results.push_back(isCovariantWith(subHead[i], vt->ty)); } else if (auto gt = get(*superTail)) { @@ -311,11 +327,11 @@ SubtypingResult Subtyping::isSubtype_(TypePackId subTp, TypePackId superTp) // (X...) -> () <: (T) -> () // Possible optimization: If headSize == 0 then we can just use subTp as-is. - std::vector headSlice(begin(subHead), end(subHead) + headSize); + std::vector headSlice(begin(subHead), begin(subHead) + headSize); TypePackId subTailPack = arena->addTypePack(std::move(headSlice), subTail); if (TypePackId* other = mappedGenericPacks.find(*superTail)) - results.push_back(isSubtype_(*other, subTailPack)); + results.push_back(isCovariantWith(*other, subTailPack)); else mappedGenericPacks.try_insert(*superTail, subTailPack); @@ -344,7 +360,7 @@ SubtypingResult Subtyping::isSubtype_(TypePackId subTp, TypePackId superTp) { if (auto p = get2(*subTail, *superTail)) { - results.push_back(isSubtype_(p)); + results.push_back(isCovariantWith(p)); } else if (auto p = get2(*subTail, *superTail)) { @@ -380,7 +396,8 @@ SubtypingResult Subtyping::isSubtype_(TypePackId subTp, TypePackId superTp) } } else - iceReporter->ice(format("Subtyping::isSubtype got unexpected type packs %s and %s", toString(*subTail).c_str(), toString(*superTail).c_str())); + iceReporter->ice( + format("Subtyping::isSubtype got unexpected type packs %s and %s", toString(*subTail).c_str(), toString(*superTail).c_str())); } else if (subTail) { @@ -428,9 +445,33 @@ SubtypingResult Subtyping::isSubtype_(TypePackId subTp, TypePackId superTp) } template -SubtypingResult Subtyping::isSubtype_(const TryPair& pair) +SubtypingResult Subtyping::isContravariantWith(SubTy&& subTy, SuperTy&& superTy) { - return isSubtype_(pair.first, pair.second); + return isCovariantWith(superTy, subTy); +} + +template +SubtypingResult Subtyping::isInvariantWith(SubTy&& subTy, SuperTy&& superTy) +{ + return isCovariantWith(subTy, superTy).andAlso(isContravariantWith(subTy, superTy)); +} + +template +SubtypingResult Subtyping::isCovariantWith(const TryPair& pair) +{ + return isCovariantWith(pair.first, pair.second); +} + +template +SubtypingResult Subtyping::isContravariantWith(const TryPair& pair) +{ + return isCovariantWith(pair.second, pair.first); +} + +template +SubtypingResult Subtyping::isInvariantWith(const TryPair& pair) +{ + return isCovariantWith(pair).andAlso(isContravariantWith(pair)); } /* @@ -464,48 +505,219 @@ SubtypingResult Subtyping::isSubtype_(const TryPair subtypings; for (TypeId ty : superUnion) - subtypings.push_back(isSubtype_(subTy, ty)); + subtypings.push_back(isCovariantWith(subTy, ty)); return SubtypingResult::any(subtypings); } -SubtypingResult Subtyping::isSubtype_(const UnionType* subUnion, TypeId superTy) +SubtypingResult Subtyping::isCovariantWith(const UnionType* subUnion, TypeId superTy) { // As per TAPL: A | B <: T iff A <: T && B <: T std::vector subtypings; for (TypeId ty : subUnion) - subtypings.push_back(isSubtype_(ty, superTy)); + subtypings.push_back(isCovariantWith(ty, superTy)); return SubtypingResult::all(subtypings); } -SubtypingResult Subtyping::isSubtype_(TypeId subTy, const IntersectionType* superIntersection) +SubtypingResult Subtyping::isCovariantWith(TypeId subTy, const IntersectionType* superIntersection) { // As per TAPL: T <: A & B iff T <: A && T <: B std::vector subtypings; for (TypeId ty : superIntersection) - subtypings.push_back(isSubtype_(subTy, ty)); + subtypings.push_back(isCovariantWith(subTy, ty)); return SubtypingResult::all(subtypings); } -SubtypingResult Subtyping::isSubtype_(const IntersectionType* subIntersection, TypeId superTy) +SubtypingResult Subtyping::isCovariantWith(const IntersectionType* subIntersection, TypeId superTy) { // As per TAPL: A & B <: T iff A <: T || B <: T std::vector subtypings; for (TypeId ty : subIntersection) - subtypings.push_back(isSubtype_(ty, superTy)); + subtypings.push_back(isCovariantWith(ty, superTy)); return SubtypingResult::any(subtypings); } -SubtypingResult Subtyping::isSubtype_(const PrimitiveType* subPrim, const PrimitiveType* superPrim) +SubtypingResult Subtyping::isCovariantWith(const NegationType* subNegation, TypeId superTy) +{ + TypeId negatedTy = follow(subNegation->ty); + + // In order to follow a consistent codepath, rather than folding the + // isCovariantWith test down to its conclusion here, we test the subtyping test + // of the result of negating the type for never, unknown, any, and error. + if (is(negatedTy)) + { + // ¬never ~ unknown + return isCovariantWith(builtinTypes->unknownType, superTy); + } + else if (is(negatedTy)) + { + // ¬unknown ~ never + return isCovariantWith(builtinTypes->neverType, superTy); + } + else if (is(negatedTy)) + { + // ¬any ~ any + return isCovariantWith(negatedTy, superTy); + } + else if (auto u = get(negatedTy)) + { + // ¬(A ∪ B) ~ ¬A ∩ ¬B + // follow intersection rules: A & B <: T iff A <: T && B <: T + std::vector subtypings; + + for (TypeId ty : u) + { + NegationType negatedTmp{ty}; + subtypings.push_back(isCovariantWith(&negatedTmp, superTy)); + } + + return SubtypingResult::all(subtypings); + } + else if (auto i = get(negatedTy)) + { + // ¬(A ∩ B) ~ ¬A ∪ ¬B + // follow union rules: A | B <: T iff A <: T || B <: T + std::vector subtypings; + + for (TypeId ty : i) + { + if (auto negatedPart = get(follow(ty))) + subtypings.push_back(isCovariantWith(negatedPart->ty, superTy)); + else + { + NegationType negatedTmp{ty}; + subtypings.push_back(isCovariantWith(&negatedTmp, superTy)); + } + } + + return SubtypingResult::any(subtypings); + } + else if (is(negatedTy)) + { + iceReporter->ice("attempting to negate a non-testable type"); + } + // negating a different subtype will get you a very wide type that's not a + // subtype of other stuff. + else + { + return {false}; + } +} + +SubtypingResult Subtyping::isCovariantWith(const TypeId subTy, const NegationType* superNegation) +{ + TypeId negatedTy = follow(superNegation->ty); + + if (is(negatedTy)) + { + // ¬never ~ unknown + return isCovariantWith(subTy, builtinTypes->unknownType); + } + else if (is(negatedTy)) + { + // ¬unknown ~ never + return isCovariantWith(subTy, builtinTypes->neverType); + } + else if (is(negatedTy)) + { + // ¬any ~ any + return isSubtype(subTy, negatedTy); + } + else if (auto u = get(negatedTy)) + { + // ¬(A ∪ B) ~ ¬A ∩ ¬B + // follow intersection rules: A & B <: T iff A <: T && B <: T + std::vector subtypings; + + for (TypeId ty : u) + { + if (auto negatedPart = get(follow(ty))) + subtypings.push_back(isCovariantWith(subTy, negatedPart->ty)); + else + { + NegationType negatedTmp{ty}; + subtypings.push_back(isCovariantWith(subTy, &negatedTmp)); + } + } + + return SubtypingResult::all(subtypings); + } + else if (auto i = get(negatedTy)) + { + // ¬(A ∩ B) ~ ¬A ∪ ¬B + // follow union rules: A | B <: T iff A <: T || B <: T + std::vector subtypings; + + for (TypeId ty : i) + { + if (auto negatedPart = get(follow(ty))) + subtypings.push_back(isCovariantWith(subTy, negatedPart->ty)); + else + { + NegationType negatedTmp{ty}; + subtypings.push_back(isCovariantWith(subTy, &negatedTmp)); + } + } + + return SubtypingResult::any(subtypings); + } + else if (auto p = get2(subTy, negatedTy)) + { + // number <: ¬boolean + // number type != p.second->type}; + } + else if (auto p = get2(subTy, negatedTy)) + { + // "foo" (p.first) && p.second->type == PrimitiveType::String) + return {false}; + // false (p.first) && p.second->type == PrimitiveType::Boolean) + return {false}; + // other cases are true + else + return {true}; + } + else if (auto p = get2(subTy, negatedTy)) + { + if (p.first->type == PrimitiveType::String && get(p.second)) + return {false}; + else if (p.first->type == PrimitiveType::Boolean && get(p.second)) + return {false}; + else + return {true}; + } + // the top class type is not actually a primitive type, so the negation of + // any one of them includes the top class type. + else if (auto p = get2(subTy, negatedTy)) + return {true}; + else if (auto p = get(negatedTy); p && is(subTy)) + return {p->type != PrimitiveType::Table}; + else if (auto p = get2(subTy, negatedTy)) + return {p.second->type != PrimitiveType::Function}; + else if (auto p = get2(subTy, negatedTy)) + return {*p.first != *p.second}; + else if (auto p = get2(subTy, negatedTy)) + return SubtypingResult::negate(isCovariantWith(p.first, p.second)); + else if (get2(subTy, negatedTy)) + return {true}; + else if (is(negatedTy)) + iceReporter->ice("attempting to negate a non-testable type"); + + return {false}; +} + +SubtypingResult Subtyping::isCovariantWith(const PrimitiveType* subPrim, const PrimitiveType* superPrim) { return {subPrim->type == superPrim->type}; } -SubtypingResult Subtyping::isSubtype_(const SingletonType* subSingleton, const PrimitiveType* superPrim) +SubtypingResult Subtyping::isCovariantWith(const SingletonType* subSingleton, const PrimitiveType* superPrim) { if (get(subSingleton) && superPrim->type == PrimitiveType::String) return {true}; @@ -515,24 +727,20 @@ SubtypingResult Subtyping::isSubtype_(const SingletonType* subSingleton, const P return {false}; } -SubtypingResult Subtyping::isSubtype_(const SingletonType* subSingleton, const SingletonType* superSingleton) +SubtypingResult Subtyping::isCovariantWith(const SingletonType* subSingleton, const SingletonType* superSingleton) { return {*subSingleton == *superSingleton}; } -SubtypingResult Subtyping::isSubtype_(const TableType* subTable, const TableType* superTable) +SubtypingResult Subtyping::isCovariantWith(const TableType* subTable, const TableType* superTable) { SubtypingResult result{true}; - for (const auto& [name, prop]: superTable->props) + for (const auto& [name, prop] : superTable->props) { auto it = subTable->props.find(name); if (it != subTable->props.end()) - { - // Table properties are invariant - result.andAlso(isSubtype(it->second.type(), prop.type())); - result.andAlso(isSubtype(prop.type(), it->second.type())); - } + result.andAlso(isInvariantWith(prop.type(), it->second.type())); else return SubtypingResult{false}; } @@ -540,17 +748,18 @@ SubtypingResult Subtyping::isSubtype_(const TableType* subTable, const TableType return result; } -SubtypingResult Subtyping::isSubtype_(const MetatableType* subMt, const MetatableType* superMt) +SubtypingResult Subtyping::isCovariantWith(const MetatableType* subMt, const MetatableType* superMt) { return SubtypingResult::all({ - isSubtype_(subMt->table, superMt->table), - isSubtype_(subMt->metatable, superMt->metatable), + isCovariantWith(subMt->table, superMt->table), + isCovariantWith(subMt->metatable, superMt->metatable), }); } -SubtypingResult Subtyping::isSubtype_(const MetatableType* subMt, const TableType* superTable) +SubtypingResult Subtyping::isCovariantWith(const MetatableType* subMt, const TableType* superTable) { - if (auto subTable = get(subMt->table)) { + if (auto subTable = get(subMt->table)) + { // Metatables cannot erase properties from the table they're attached to, so // the subtyping rule for this is just if the table component is a subtype // of the supertype table. @@ -560,7 +769,7 @@ SubtypingResult Subtyping::isSubtype_(const MetatableType* subMt, const TableTyp // that the metatable isn't a subtype of the table, even though they have // compatible properties/shapes. We'll revisit this later when we have a // better understanding of how important this is. - return isSubtype_(subTable, superTable); + return isCovariantWith(subTable, superTable); } else { @@ -569,23 +778,19 @@ SubtypingResult Subtyping::isSubtype_(const MetatableType* subMt, const TableTyp } } -SubtypingResult Subtyping::isSubtype_(const ClassType* subClass, const ClassType* superClass) +SubtypingResult Subtyping::isCovariantWith(const ClassType* subClass, const ClassType* superClass) { return {isSubclass(subClass, superClass)}; } -SubtypingResult Subtyping::isSubtype_(const ClassType* subClass, const TableType* superTable) +SubtypingResult Subtyping::isCovariantWith(const ClassType* subClass, const TableType* superTable) { SubtypingResult result{true}; - for (const auto& [name, prop]: superTable->props) + for (const auto& [name, prop] : superTable->props) { if (auto classProp = lookupClassProp(subClass, name)) - { - // Table properties are invariant - result.andAlso(isSubtype_(classProp->type(), prop.type())); - result.andAlso(isSubtype_(prop.type(), classProp->type())); - } + result.andAlso(isInvariantWith(prop.type(), classProp->type())); else return SubtypingResult{false}; } @@ -593,20 +798,20 @@ SubtypingResult Subtyping::isSubtype_(const ClassType* subClass, const TableType return result; } -SubtypingResult Subtyping::isSubtype_(const FunctionType* subFunction, const FunctionType* superFunction) +SubtypingResult Subtyping::isCovariantWith(const FunctionType* subFunction, const FunctionType* superFunction) { SubtypingResult result; { VarianceFlipper vf{&variance}; - result.orElse(isSubtype_(superFunction->argTypes, subFunction->argTypes)); + result.orElse(isContravariantWith(subFunction->argTypes, superFunction->argTypes)); } - result.andAlso(isSubtype_(subFunction->retTypes, superFunction->retTypes)); + result.andAlso(isCovariantWith(subFunction->retTypes, superFunction->retTypes)); return result; } -SubtypingResult Subtyping::isSubtype_(const PrimitiveType* subPrim, const TableType* superTable) +SubtypingResult Subtyping::isCovariantWith(const PrimitiveType* subPrim, const TableType* superTable) { SubtypingResult result{false}; if (subPrim->type == PrimitiveType::String) @@ -618,7 +823,7 @@ SubtypingResult Subtyping::isSubtype_(const PrimitiveType* subPrim, const TableT if (auto it = mttv->props.find("__index"); it != mttv->props.end()) { if (auto stringTable = get(it->second.type())) - result.orElse(isSubtype_(stringTable, superTable)); + result.orElse(isCovariantWith(stringTable, superTable)); } } } @@ -627,7 +832,7 @@ SubtypingResult Subtyping::isSubtype_(const PrimitiveType* subPrim, const TableT return result; } -SubtypingResult Subtyping::isSubtype_(const SingletonType* subSingleton, const TableType* superTable) +SubtypingResult Subtyping::isCovariantWith(const SingletonType* subSingleton, const TableType* superTable) { SubtypingResult result{false}; if (auto stringleton = get(subSingleton)) @@ -639,7 +844,7 @@ SubtypingResult Subtyping::isSubtype_(const SingletonType* subSingleton, const T if (auto it = mttv->props.find("__index"); it != mttv->props.end()) { if (auto stringTable = get(it->second.type())) - result.orElse(isSubtype_(stringTable, superTable)); + result.orElse(isCovariantWith(stringTable, superTable)); } } } @@ -647,29 +852,27 @@ SubtypingResult Subtyping::isSubtype_(const SingletonType* subSingleton, const T return result; } -SubtypingResult Subtyping::isSubtype_(const NormalizedType* subNorm, const NormalizedType* superNorm) +SubtypingResult Subtyping::isCovariantWith(const NormalizedType* subNorm, const NormalizedType* superNorm) { if (!subNorm || !superNorm) return {false, true, true}; - SubtypingResult result = isSubtype_(subNorm->tops, superNorm->tops); - result.andAlso(isSubtype_(subNorm->booleans, superNorm->booleans)); - result.andAlso(isSubtype_(subNorm->classes, superNorm->classes, superNorm->tables)); - result.andAlso(isSubtype_(subNorm->errors, superNorm->errors)); - result.andAlso(isSubtype_(subNorm->nils, superNorm->nils)); - result.andAlso(isSubtype_(subNorm->numbers, superNorm->numbers)); - result.isSubtype &= Luau::isSubtype(subNorm->strings, superNorm->strings); - // isSubtype_(subNorm->strings, superNorm->tables); - result.andAlso(isSubtype_(subNorm->threads, superNorm->threads)); - result.andAlso(isSubtype_(subNorm->tables, superNorm->tables)); - // isSubtype_(subNorm->tables, superNorm->strings); - // isSubtype_(subNorm->tables, superNorm->classes); - result.andAlso(isSubtype_(subNorm->functions, superNorm->functions)); - // isSubtype_(subNorm->tyvars, superNorm->tyvars); + SubtypingResult result = isCovariantWith(subNorm->tops, superNorm->tops); + result.andAlso(isCovariantWith(subNorm->booleans, superNorm->booleans)); + result.andAlso(isCovariantWith(subNorm->classes, superNorm->classes).orElse(isCovariantWith(subNorm->classes, superNorm->tables))); + result.andAlso(isCovariantWith(subNorm->errors, superNorm->errors)); + result.andAlso(isCovariantWith(subNorm->nils, superNorm->nils)); + result.andAlso(isCovariantWith(subNorm->numbers, superNorm->numbers)); + result.andAlso(isCovariantWith(subNorm->strings, superNorm->strings)); + result.andAlso(isCovariantWith(subNorm->strings, superNorm->tables)); + result.andAlso(isCovariantWith(subNorm->threads, superNorm->threads)); + result.andAlso(isCovariantWith(subNorm->tables, superNorm->tables)); + result.andAlso(isCovariantWith(subNorm->functions, superNorm->functions)); + // isCovariantWith(subNorm->tyvars, superNorm->tyvars); return result; } -SubtypingResult Subtyping::isSubtype_(const NormalizedClassType& subClass, const NormalizedClassType& superClass, const TypeIds& superTables) +SubtypingResult Subtyping::isCovariantWith(const NormalizedClassType& subClass, const NormalizedClassType& superClass) { for (const auto& [subClassTy, _] : subClass.classes) { @@ -677,24 +880,18 @@ SubtypingResult Subtyping::isSubtype_(const NormalizedClassType& subClass, const for (const auto& [superClassTy, superNegations] : superClass.classes) { - result.orElse(isSubtype_(subClassTy, superClassTy)); + result.orElse(isCovariantWith(subClassTy, superClassTy)); if (!result.isSubtype) continue; for (TypeId negation : superNegations) { - result.andAlso(SubtypingResult::negate(isSubtype_(subClassTy, negation))); + result.andAlso(SubtypingResult::negate(isCovariantWith(subClassTy, negation))); if (result.isSubtype) break; } } - if (result.isSubtype) - continue; - - for (TypeId superTableTy : superTables) - result.orElse(isSubtype_(subClassTy, superTableTy)); - if (!result.isSubtype) return result; } @@ -702,17 +899,79 @@ SubtypingResult Subtyping::isSubtype_(const NormalizedClassType& subClass, const return {true}; } -SubtypingResult Subtyping::isSubtype_(const NormalizedFunctionType& subFunction, const NormalizedFunctionType& superFunction) +SubtypingResult Subtyping::isCovariantWith(const NormalizedClassType& subClass, const TypeIds& superTables) +{ + for (const auto& [subClassTy, _] : subClass.classes) + { + SubtypingResult result; + + for (TypeId superTableTy : superTables) + result.orElse(isCovariantWith(subClassTy, superTableTy)); + + if (!result.isSubtype) + return result; + } + + return {true}; +} + +SubtypingResult Subtyping::isCovariantWith(const NormalizedStringType& subString, const NormalizedStringType& superString) +{ + bool isSubtype = Luau::isSubtype(subString, superString); + return {isSubtype}; +} + +SubtypingResult Subtyping::isCovariantWith(const NormalizedStringType& subString, const TypeIds& superTables) +{ + if (subString.isNever()) + return {true}; + + if (subString.isCofinite) + { + SubtypingResult result; + for (const auto& superTable : superTables) + { + result.orElse(isCovariantWith(builtinTypes->stringType, superTable)); + if (result.isSubtype) + return result; + } + return result; + } + + // Finite case + // S = s1 | s2 | s3 ... sn <: t1 | t2 | ... | tn + // iff for some ti, S <: ti + // iff for all sj, sj <: ti + for (const auto& superTable : superTables) + { + SubtypingResult result{true}; + for (const auto& [_, subString] : subString.singletons) + { + result.andAlso(isCovariantWith(subString, superTable)); + if (!result.isSubtype) + break; + } + + if (!result.isSubtype) + continue; + else + return result; + } + + return {false}; +} + +SubtypingResult Subtyping::isCovariantWith(const NormalizedFunctionType& subFunction, const NormalizedFunctionType& superFunction) { if (subFunction.isNever()) return {true}; else if (superFunction.isTop) return {true}; else - return isSubtype_(subFunction.parts, superFunction.parts); + return isCovariantWith(subFunction.parts, superFunction.parts); } -SubtypingResult Subtyping::isSubtype_(const TypeIds& subTypes, const TypeIds& superTypes) +SubtypingResult Subtyping::isCovariantWith(const TypeIds& subTypes, const TypeIds& superTypes) { std::vector results; @@ -720,15 +979,15 @@ SubtypingResult Subtyping::isSubtype_(const TypeIds& subTypes, const TypeIds& su { results.emplace_back(); for (TypeId superTy : superTypes) - results.back().orElse(isSubtype_(subTy, superTy)); + results.back().orElse(isCovariantWith(subTy, superTy)); } return SubtypingResult::all(results); } -SubtypingResult Subtyping::isSubtype_(const VariadicTypePack* subVariadic, const VariadicTypePack* superVariadic) +SubtypingResult Subtyping::isCovariantWith(const VariadicTypePack* subVariadic, const VariadicTypePack* superVariadic) { - return isSubtype_(subVariadic->ty, superVariadic->ty); + return isCovariantWith(subVariadic->ty, superVariadic->ty); } bool Subtyping::bindGeneric(TypeId subTy, TypeId superTy) @@ -772,7 +1031,7 @@ bool Subtyping::bindGeneric(TypePackId subTp, TypePackId superTp) return true; } -template +template TypeId Subtyping::makeAggregateType(const Container& container, TypeId orElse) { if (container.empty()) diff --git a/Analysis/src/ToDot.cpp b/Analysis/src/ToDot.cpp index 04d04470..09851024 100644 --- a/Analysis/src/ToDot.cpp +++ b/Analysis/src/ToDot.cpp @@ -145,8 +145,7 @@ void StateDot::visitChildren(TypeId ty, int index) startNode(index); startNodeLabel(); - auto go = [&](auto&& t) - { + auto go = [&](auto&& t) { using T = std::decay_t; if constexpr (std::is_same_v) diff --git a/Analysis/src/TypeChecker2.cpp b/Analysis/src/TypeChecker2.cpp index 65da9dfa..1daabda3 100644 --- a/Analysis/src/TypeChecker2.cpp +++ b/Analysis/src/TypeChecker2.cpp @@ -242,8 +242,8 @@ struct TypeChecker2 Normalizer normalizer; - TypeChecker2(NotNull builtinTypes, NotNull unifierState, NotNull limits, DcrLogger* logger, const SourceModule* sourceModule, - Module* module) + TypeChecker2(NotNull builtinTypes, NotNull unifierState, NotNull limits, DcrLogger* logger, + const SourceModule* sourceModule, Module* module) : builtinTypes(builtinTypes) , logger(logger) , limits(limits) @@ -1295,13 +1295,8 @@ struct TypeChecker2 else if (auto assertion = expr->as()) return isLiteral(assertion->expr); - return - expr->is() || - expr->is() || - expr->is() || - expr->is() || - expr->is() || - expr->is(); + return expr->is() || expr->is() || expr->is() || + expr->is() || expr->is() || expr->is(); } static std::unique_ptr buildLiteralPropertiesSet(AstExpr* expr) @@ -1423,7 +1418,7 @@ struct TypeChecker2 LUAU_ASSERT(argOffset == args->head.size()); const Location argLoc = argExprs->empty() ? Location{} // TODO - : argExprs->at(argExprs->size() - 1)->location; + : argExprs->at(argExprs->size() - 1)->location; if (paramIter.tail() && args->tail) { @@ -2686,8 +2681,8 @@ struct TypeChecker2 } }; -void check( - NotNull builtinTypes, NotNull unifierState, NotNull limits, DcrLogger* logger, const SourceModule& sourceModule, Module* module) +void check(NotNull builtinTypes, NotNull unifierState, NotNull limits, DcrLogger* logger, + const SourceModule& sourceModule, Module* module) { TypeChecker2 typeChecker{builtinTypes, unifierState, limits, logger, &sourceModule, module}; diff --git a/Analysis/src/Unifier.cpp b/Analysis/src/Unifier.cpp index 7cf05cda..4a27bbde 100644 --- a/Analysis/src/Unifier.cpp +++ b/Analysis/src/Unifier.cpp @@ -25,6 +25,7 @@ LUAU_FASTFLAGVARIABLE(LuauOccursIsntAlwaysFailure, false) LUAU_FASTFLAG(LuauNormalizeBlockedTypes) LUAU_FASTFLAG(LuauAlwaysCommitInferencesOfFunctionCalls) LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution) +LUAU_FASTFLAGVARIABLE(LuauFixIndexerSubtypingOrdering, false) namespace Luau { @@ -2285,7 +2286,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection, variance = Invariant; Unifier innerState = makeChildUnifier(); - if (useNewSolver) + if (useNewSolver || FFlag::LuauFixIndexerSubtypingOrdering) innerState.tryUnify_(prop.type(), superTable->indexer->indexResultType); else { diff --git a/Analysis/src/Unifier2.cpp b/Analysis/src/Unifier2.cpp index 0be6941b..1f3330dc 100644 --- a/Analysis/src/Unifier2.cpp +++ b/Analysis/src/Unifier2.cpp @@ -26,7 +26,6 @@ Unifier2::Unifier2(NotNull arena, NotNull builtinTypes, , ice(ice) , recursionLimit(FInt::LuauTypeInferRecursionLimit) { - } bool Unifier2::unify(TypeId subTy, TypeId superTy) @@ -99,10 +98,7 @@ bool Unifier2::unify(TypePackId subTp, TypePackId superTp) return true; } - size_t maxLength = std::max( - flatten(subTp).first.size(), - flatten(superTp).first.size() - ); + size_t maxLength = std::max(flatten(subTp).first.size(), flatten(superTp).first.size()); auto [subTypes, subTail] = extendTypePack(*arena, builtinTypes, subTp, maxLength); auto [superTypes, superTail] = extendTypePack(*arena, builtinTypes, superTp, maxLength); @@ -123,16 +119,25 @@ struct FreeTypeSearcher : TypeVisitor explicit FreeTypeSearcher(NotNull scope) : TypeVisitor(/*skipBoundTypes*/ true) , scope(scope) - {} + { + } - enum { Positive, Negative } polarity = Positive; + enum + { + Positive, + Negative + } polarity = Positive; void flip() { switch (polarity) { - case Positive: polarity = Negative; break; - case Negative: polarity = Positive; break; + case Positive: + polarity = Negative; + break; + case Negative: + polarity = Positive; + break; } } @@ -152,8 +157,12 @@ struct FreeTypeSearcher : TypeVisitor switch (polarity) { - case Positive: positiveTypes.insert(ty); break; - case Negative: negativeTypes.insert(ty); break; + case Positive: + positiveTypes.insert(ty); + break; + case Negative: + negativeTypes.insert(ty); + break; } return true; @@ -180,13 +189,17 @@ struct MutatingGeneralizer : TypeOnceVisitor std::unordered_set negativeTypes; std::vector generics; - MutatingGeneralizer(NotNull builtinTypes, NotNull scope, std::unordered_set positiveTypes, std::unordered_set negativeTypes) + bool isWithinFunction = false; + + MutatingGeneralizer( + NotNull builtinTypes, NotNull scope, std::unordered_set positiveTypes, std::unordered_set negativeTypes) : TypeOnceVisitor(/* skipBoundTypes */ true) , builtinTypes(builtinTypes) , scope(scope) , positiveTypes(std::move(positiveTypes)) , negativeTypes(std::move(negativeTypes)) - {} + { + } static void replace(DenseHashSet& seen, TypeId haystack, TypeId needle, TypeId replacement) { @@ -211,10 +224,7 @@ struct MutatingGeneralizer : TypeOnceVisitor // FIXME: I bet this function has reentrancy problems option = follow(option); if (option == needle) - { - LUAU_ASSERT(!seen.find(option)); option = replacement; - } // TODO seen set else if (get(option)) @@ -224,7 +234,21 @@ struct MutatingGeneralizer : TypeOnceVisitor } } - bool visit (TypeId ty, const FreeType&) override + bool visit(TypeId ty, const FunctionType& ft) override + { + const bool oldValue = isWithinFunction; + + isWithinFunction = true; + + traverse(ft.argTypes); + traverse(ft.retTypes); + + isWithinFunction = oldValue; + + return false; + } + + bool visit(TypeId ty, const FreeType&) override { const FreeType* ft = get(ty); LUAU_ASSERT(ft); @@ -232,7 +256,8 @@ struct MutatingGeneralizer : TypeOnceVisitor traverse(ft->lowerBound); traverse(ft->upperBound); - // ft is potentially invalid now. + // It is possible for the above traverse() calls to cause ty to be + // transmuted. We must reaquire ft if this happens. ty = follow(ty); ft = get(ty); if (!ft) @@ -252,8 +277,13 @@ struct MutatingGeneralizer : TypeOnceVisitor if (!hasLowerBound && !hasUpperBound) { - emplaceType(asMutable(ty), scope); - generics.push_back(ty); + if (isWithinFunction) + { + emplaceType(asMutable(ty), scope); + generics.push_back(ty); + } + else + emplaceType(asMutable(ty), builtinTypes->unknownType); } // It is possible that this free type has other free types in its upper @@ -264,19 +294,27 @@ struct MutatingGeneralizer : TypeOnceVisitor // If we do not do this, we get tautological bounds like a <: a <: unknown. else if (isPositive && !hasUpperBound) { - if (FreeType* lowerFree = getMutable(ft->lowerBound); lowerFree && lowerFree->upperBound == ty) + TypeId lb = follow(ft->lowerBound); + if (FreeType* lowerFree = getMutable(lb); lowerFree && lowerFree->upperBound == ty) lowerFree->upperBound = builtinTypes->unknownType; else - replace(seen, ft->lowerBound, ty, builtinTypes->unknownType); - emplaceType(asMutable(ty), ft->lowerBound); + { + DenseHashSet replaceSeen{nullptr}; + replace(replaceSeen, lb, ty, builtinTypes->unknownType); + } + emplaceType(asMutable(ty), lb); } else { - if (FreeType* upperFree = getMutable(ft->upperBound); upperFree && upperFree->lowerBound == ty) + TypeId ub = follow(ft->upperBound); + if (FreeType* upperFree = getMutable(ub); upperFree && upperFree->lowerBound == ty) upperFree->lowerBound = builtinTypes->neverType; else - replace(seen, ft->upperBound, ty, builtinTypes->neverType); - emplaceType(asMutable(ty), ft->upperBound); + { + DenseHashSet replaceSeen{nullptr}; + replace(replaceSeen, ub, ty, builtinTypes->neverType); + } + emplaceType(asMutable(ty), ub); } return false; @@ -363,4 +401,4 @@ OccursCheckResult Unifier2::occursCheck(DenseHashSet& seen, TypePack return OccursCheckResult::Pass; } -} +} // namespace Luau diff --git a/Ast/include/Luau/Ast.h b/Ast/include/Luau/Ast.h index b60fec28..f3b5b149 100644 --- a/Ast/include/Luau/Ast.h +++ b/Ast/include/Luau/Ast.h @@ -534,7 +534,7 @@ class AstStatBlock : public AstStat public: LUAU_RTTI(AstStatBlock) - AstStatBlock(const Location& location, const AstArray& body, bool hasEnd=true); + AstStatBlock(const Location& location, const AstArray& body, bool hasEnd = true); void visit(AstVisitor* visitor) override; diff --git a/CLI/Compile.cpp b/CLI/Compile.cpp index 6197f03e..c35f9c3d 100644 --- a/CLI/Compile.cpp +++ b/CLI/Compile.cpp @@ -89,13 +89,14 @@ static void reportError(const char* name, const Luau::CompileError& error) report(name, error.getLocation(), "CompileError", error.what()); } -static std::string getCodegenAssembly(const char* name, const std::string& bytecode, Luau::CodeGen::AssemblyOptions options) +static std::string getCodegenAssembly( + const char* name, const std::string& bytecode, Luau::CodeGen::AssemblyOptions options, Luau::CodeGen::LoweringStats* stats) { std::unique_ptr globalState(luaL_newstate(), lua_close); lua_State* L = globalState.get(); if (luau_load(L, name, bytecode.data(), bytecode.size(), 0) == 0) - return Luau::CodeGen::getAssembly(L, -1, options); + return Luau::CodeGen::getAssembly(L, -1, options, stats); fprintf(stderr, "Error loading bytecode %s\n", name); return ""; @@ -119,6 +120,8 @@ struct CompileStats double parseTime; double compileTime; double codegenTime; + + Luau::CodeGen::LoweringStats lowerStats; }; static double recordDeltaTime(double& timer) @@ -213,10 +216,10 @@ static bool compileFile(const char* name, CompileFormat format, Luau::CodeGen::A case CompileFormat::CodegenAsm: case CompileFormat::CodegenIr: case CompileFormat::CodegenVerbose: - printf("%s", getCodegenAssembly(name, bcb.getBytecode(), options).c_str()); + printf("%s", getCodegenAssembly(name, bcb.getBytecode(), options, &stats.lowerStats).c_str()); break; case CompileFormat::CodegenNull: - stats.codegen += getCodegenAssembly(name, bcb.getBytecode(), options).size(); + stats.codegen += getCodegenAssembly(name, bcb.getBytecode(), options, &stats.lowerStats).size(); stats.codegenTime += recordDeltaTime(currts); break; case CompileFormat::Null: @@ -355,13 +358,22 @@ int main(int argc, char** argv) failed += !compileFile(path.c_str(), compileFormat, assemblyTarget, stats); if (compileFormat == CompileFormat::Null) + { printf("Compiled %d KLOC into %d KB bytecode (read %.2fs, parse %.2fs, compile %.2fs)\n", int(stats.lines / 1000), int(stats.bytecode / 1024), stats.readTime, stats.parseTime, stats.compileTime); + } else if (compileFormat == CompileFormat::CodegenNull) + { printf("Compiled %d KLOC into %d KB bytecode => %d KB native code (%.2fx) (read %.2fs, parse %.2fs, compile %.2fs, codegen %.2fs)\n", int(stats.lines / 1000), int(stats.bytecode / 1024), int(stats.codegen / 1024), stats.bytecode == 0 ? 0.0 : double(stats.codegen) / double(stats.bytecode), stats.readTime, stats.parseTime, stats.compileTime, stats.codegenTime); + printf("Lowering stats:\n"); + printf("- spills to stack: %d, spills to restore: %d, max spill slot %u\n", stats.lowerStats.spillsToSlot, stats.lowerStats.spillsToRestore, + stats.lowerStats.maxSpillSlotsUsed); + printf("- regalloc failed: %d, lowering failed %d\n", stats.lowerStats.regAllocErrors, stats.lowerStats.loweringErrors); + } + return failed ? 1 : 0; } diff --git a/CodeGen/include/Luau/CodeGen.h b/CodeGen/include/Luau/CodeGen.h index 85f19d01..031779f7 100644 --- a/CodeGen/include/Luau/CodeGen.h +++ b/CodeGen/include/Luau/CodeGen.h @@ -3,6 +3,7 @@ #include +#include #include struct lua_State; @@ -74,8 +75,18 @@ struct AssemblyOptions void* annotatorContext = nullptr; }; +struct LoweringStats +{ + int spillsToSlot = 0; + int spillsToRestore = 0; + unsigned maxSpillSlotsUsed = 0; + + int regAllocErrors = 0; + int loweringErrors = 0; +}; + // Generates assembly for target function and all inner functions -std::string getAssembly(lua_State* L, int idx, AssemblyOptions options = {}); +std::string getAssembly(lua_State* L, int idx, AssemblyOptions options = {}, LoweringStats* stats = nullptr); using PerfLogFn = void (*)(void* context, uintptr_t addr, unsigned size, const char* symbol); diff --git a/CodeGen/include/Luau/IrRegAllocX64.h b/CodeGen/include/Luau/IrRegAllocX64.h index 665b5229..632499a4 100644 --- a/CodeGen/include/Luau/IrRegAllocX64.h +++ b/CodeGen/include/Luau/IrRegAllocX64.h @@ -12,6 +12,9 @@ namespace Luau { namespace CodeGen { + +struct LoweringStats; + namespace X64 { @@ -33,7 +36,7 @@ struct IrSpillX64 struct IrRegAllocX64 { - IrRegAllocX64(AssemblyBuilderX64& build, IrFunction& function); + IrRegAllocX64(AssemblyBuilderX64& build, IrFunction& function, LoweringStats* stats); RegisterX64 allocReg(SizeX64 size, uint32_t instIdx); RegisterX64 allocRegOrReuse(SizeX64 size, uint32_t instIdx, std::initializer_list oprefs); @@ -70,6 +73,7 @@ struct IrRegAllocX64 AssemblyBuilderX64& build; IrFunction& function; + LoweringStats* stats = nullptr; uint32_t currInstIdx = ~0u; diff --git a/CodeGen/include/Luau/IrUtils.h b/CodeGen/include/Luau/IrUtils.h index 5db5f6f1..50b3dc7c 100644 --- a/CodeGen/include/Luau/IrUtils.h +++ b/CodeGen/include/Luau/IrUtils.h @@ -264,5 +264,14 @@ uint32_t getNativeContextOffset(int bfid); // Cleans up blocks that were created with no users void killUnusedBlocks(IrFunction& function); +// Get blocks in order that tries to maximize fallthrough between them during lowering +// We want to mostly preserve build order with fallbacks outlined +// But we also use hints from optimization passes that chain blocks together where there's only one out-in edge between them +std::vector getSortedBlockOrder(IrFunction& function); + +// Returns first non-dead block that comes after block at index 'i' in the sorted blocks array +// 'dummy' block is returned if the end of array was reached +IrBlock& getNextBlock(IrFunction& function, std::vector& sortedBlocks, IrBlock& dummy, size_t i); + } // namespace CodeGen } // namespace Luau diff --git a/CodeGen/src/AssemblyBuilderA64.cpp b/CodeGen/src/AssemblyBuilderA64.cpp index f385cd0a..15be6b88 100644 --- a/CodeGen/src/AssemblyBuilderA64.cpp +++ b/CodeGen/src/AssemblyBuilderA64.cpp @@ -1091,7 +1091,7 @@ void AssemblyBuilderA64::placeER(const char* name, RegisterA64 dst, RegisterA64 LUAU_ASSERT(shift >= 0 && shift <= 4); uint32_t sf = (dst.kind == KindA64::x) ? 0x80000000 : 0; // could be useful in the future for byte->word extends - int option = 0b010; // UXTW + int option = 0b010; // UXTW place(dst.index | (src1.index << 5) | (shift << 10) | (option << 13) | (src2.index << 16) | (1 << 21) | (op << 24) | sf); commit(); diff --git a/CodeGen/src/CodeGen.cpp b/CodeGen/src/CodeGen.cpp index 9d117b1d..a25e1046 100644 --- a/CodeGen/src/CodeGen.cpp +++ b/CodeGen/src/CodeGen.cpp @@ -106,7 +106,7 @@ static std::optional createNativeFunction(AssemblyBuilder& build, M IrBuilder ir; ir.buildFunctionIr(proto); - if (!lowerFunction(ir, build, helpers, proto, {})) + if (!lowerFunction(ir, build, helpers, proto, {}, /* stats */ nullptr)) return std::nullopt; return createNativeProto(proto, ir); diff --git a/CodeGen/src/CodeGenAssembly.cpp b/CodeGen/src/CodeGenAssembly.cpp index fed5ddd3..4fd24cd9 100644 --- a/CodeGen/src/CodeGenAssembly.cpp +++ b/CodeGen/src/CodeGenAssembly.cpp @@ -43,7 +43,7 @@ static void logFunctionHeader(AssemblyBuilder& build, Proto* proto) } template -static std::string getAssemblyImpl(AssemblyBuilder& build, const TValue* func, AssemblyOptions options) +static std::string getAssemblyImpl(AssemblyBuilder& build, const TValue* func, AssemblyOptions options, LoweringStats* stats) { std::vector protos; gatherFunctions(protos, clvalue(func)->l.p); @@ -66,7 +66,7 @@ static std::string getAssemblyImpl(AssemblyBuilder& build, const TValue* func, A if (options.includeAssembly || options.includeIr) logFunctionHeader(build, p); - if (!lowerFunction(ir, build, helpers, p, options)) + if (!lowerFunction(ir, build, helpers, p, options, stats)) { if (build.logText) build.logAppend("; skipping (can't lower)\n"); @@ -90,7 +90,7 @@ static std::string getAssemblyImpl(AssemblyBuilder& build, const TValue* func, A unsigned int getCpuFeaturesA64(); #endif -std::string getAssembly(lua_State* L, int idx, AssemblyOptions options) +std::string getAssembly(lua_State* L, int idx, AssemblyOptions options, LoweringStats* stats) { LUAU_ASSERT(lua_isLfunction(L, idx)); const TValue* func = luaA_toobject(L, idx); @@ -106,35 +106,35 @@ std::string getAssembly(lua_State* L, int idx, AssemblyOptions options) X64::AssemblyBuilderX64 build(/* logText= */ options.includeAssembly); #endif - return getAssemblyImpl(build, func, options); + return getAssemblyImpl(build, func, options, stats); } case AssemblyOptions::A64: { A64::AssemblyBuilderA64 build(/* logText= */ options.includeAssembly, /* features= */ A64::Feature_JSCVT); - return getAssemblyImpl(build, func, options); + return getAssemblyImpl(build, func, options, stats); } case AssemblyOptions::A64_NoFeatures: { A64::AssemblyBuilderA64 build(/* logText= */ options.includeAssembly, /* features= */ 0); - return getAssemblyImpl(build, func, options); + return getAssemblyImpl(build, func, options, stats); } case AssemblyOptions::X64_Windows: { X64::AssemblyBuilderX64 build(/* logText= */ options.includeAssembly, X64::ABIX64::Windows); - return getAssemblyImpl(build, func, options); + return getAssemblyImpl(build, func, options, stats); } case AssemblyOptions::X64_SystemV: { X64::AssemblyBuilderX64 build(/* logText= */ options.includeAssembly, X64::ABIX64::SystemV); - return getAssemblyImpl(build, func, options); + return getAssemblyImpl(build, func, options, stats); } default: diff --git a/CodeGen/src/CodeGenLower.h b/CodeGen/src/CodeGenLower.h index 24e1c38c..9b729a92 100644 --- a/CodeGen/src/CodeGenLower.h +++ b/CodeGen/src/CodeGenLower.h @@ -44,42 +44,10 @@ inline void gatherFunctions(std::vector& results, Proto* proto) gatherFunctions(results, proto->p[i]); } -inline IrBlock& getNextBlock(IrFunction& function, std::vector& sortedBlocks, IrBlock& dummy, size_t i) -{ - for (size_t j = i + 1; j < sortedBlocks.size(); ++j) - { - IrBlock& block = function.blocks[sortedBlocks[j]]; - if (block.kind != IrBlockKind::Dead) - return block; - } - - return dummy; -} - template inline bool lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction& function, int bytecodeid, AssemblyOptions options) { - // While we will need a better block ordering in the future, right now we want to mostly preserve build order with fallbacks outlined - std::vector sortedBlocks; - sortedBlocks.reserve(function.blocks.size()); - for (uint32_t i = 0; i < function.blocks.size(); i++) - sortedBlocks.push_back(i); - - std::sort(sortedBlocks.begin(), sortedBlocks.end(), [&](uint32_t idxA, uint32_t idxB) { - const IrBlock& a = function.blocks[idxA]; - const IrBlock& b = function.blocks[idxB]; - - // Place fallback blocks at the end - if ((a.kind == IrBlockKind::Fallback) != (b.kind == IrBlockKind::Fallback)) - return (a.kind == IrBlockKind::Fallback) < (b.kind == IrBlockKind::Fallback); - - // Try to order by instruction order - if (a.sortkey != b.sortkey) - return a.sortkey < b.sortkey; - - // Chains of blocks are merged together by having the same sort key and consecutive chain key - return a.chainkey < b.chainkey; - }); + std::vector sortedBlocks = getSortedBlockOrder(function); // For each IR instruction that begins a bytecode instruction, which bytecode instruction is it? std::vector bcLocations(function.instructions.size() + 1, ~0u); @@ -231,24 +199,26 @@ inline bool lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction& return true; } -inline bool lowerIr(X64::AssemblyBuilderX64& build, IrBuilder& ir, ModuleHelpers& helpers, Proto* proto, AssemblyOptions options) +inline bool lowerIr( + X64::AssemblyBuilderX64& build, IrBuilder& ir, ModuleHelpers& helpers, Proto* proto, AssemblyOptions options, LoweringStats* stats) { optimizeMemoryOperandsX64(ir.function); - X64::IrLoweringX64 lowering(build, helpers, ir.function); + X64::IrLoweringX64 lowering(build, helpers, ir.function, stats); return lowerImpl(build, lowering, ir.function, proto->bytecodeid, options); } -inline bool lowerIr(A64::AssemblyBuilderA64& build, IrBuilder& ir, ModuleHelpers& helpers, Proto* proto, AssemblyOptions options) +inline bool lowerIr( + A64::AssemblyBuilderA64& build, IrBuilder& ir, ModuleHelpers& helpers, Proto* proto, AssemblyOptions options, LoweringStats* stats) { - A64::IrLoweringA64 lowering(build, helpers, ir.function); + A64::IrLoweringA64 lowering(build, helpers, ir.function, stats); return lowerImpl(build, lowering, ir.function, proto->bytecodeid, options); } template -inline bool lowerFunction(IrBuilder& ir, AssemblyBuilder& build, ModuleHelpers& helpers, Proto* proto, AssemblyOptions options) +inline bool lowerFunction(IrBuilder& ir, AssemblyBuilder& build, ModuleHelpers& helpers, Proto* proto, AssemblyOptions options, LoweringStats* stats) { killUnusedBlocks(ir.function); @@ -264,7 +234,7 @@ inline bool lowerFunction(IrBuilder& ir, AssemblyBuilder& build, ModuleHelpers& createLinearBlocks(ir, useValueNumbering); } - return lowerIr(build, ir, helpers, proto, options); + return lowerIr(build, ir, helpers, proto, options, stats); } } // namespace CodeGen diff --git a/CodeGen/src/IrBuilder.cpp b/CodeGen/src/IrBuilder.cpp index e467ca68..1aca2993 100644 --- a/CodeGen/src/IrBuilder.cpp +++ b/CodeGen/src/IrBuilder.cpp @@ -385,7 +385,8 @@ void IrBuilder::translateInst(LuauOpcode op, const Instruction* pc, int i) translateInstDupTable(*this, pc, i); break; case LOP_SETLIST: - inst(IrCmd::SETLIST, constUint(i), vmReg(LUAU_INSN_A(*pc)), vmReg(LUAU_INSN_B(*pc)), constInt(LUAU_INSN_C(*pc) - 1), constUint(pc[1]), undef()); + inst(IrCmd::SETLIST, constUint(i), vmReg(LUAU_INSN_A(*pc)), vmReg(LUAU_INSN_B(*pc)), constInt(LUAU_INSN_C(*pc) - 1), constUint(pc[1]), + undef()); break; case LOP_GETUPVAL: translateInstGetUpval(*this, pc, i); diff --git a/CodeGen/src/IrLoweringA64.cpp b/CodeGen/src/IrLoweringA64.cpp index a030f955..6e51b0d5 100644 --- a/CodeGen/src/IrLoweringA64.cpp +++ b/CodeGen/src/IrLoweringA64.cpp @@ -240,11 +240,12 @@ static bool emitBuiltin( } } -IrLoweringA64::IrLoweringA64(AssemblyBuilderA64& build, ModuleHelpers& helpers, IrFunction& function) +IrLoweringA64::IrLoweringA64(AssemblyBuilderA64& build, ModuleHelpers& helpers, IrFunction& function, LoweringStats* stats) : build(build) , helpers(helpers) , function(function) - , regs(function, {{x0, x15}, {x16, x17}, {q0, q7}, {q16, q31}}) + , stats(stats) + , regs(function, stats, {{x0, x15}, {x16, x17}, {q0, q7}, {q16, q31}}) , valueTracker(function) , exitHandlerMap(~0u) { @@ -858,7 +859,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) build.ldr(x3, mem(rNativeContext, offsetof(NativeContext, luaH_setnum))); build.blr(x3); inst.regA64 = regs.takeReg(x0, index); - break; + break; } case IrCmd::NEW_TABLE: { @@ -2016,6 +2017,15 @@ void IrLoweringA64::finishFunction() build.mov(x0, handler.pcpos * sizeof(Instruction)); build.b(helpers.updatePcAndContinueInVm); } + + if (stats) + { + if (error) + stats->loweringErrors++; + + if (regs.error) + stats->regAllocErrors++; + } } bool IrLoweringA64::hasError() const diff --git a/CodeGen/src/IrLoweringA64.h b/CodeGen/src/IrLoweringA64.h index 5134ceda..46f41021 100644 --- a/CodeGen/src/IrLoweringA64.h +++ b/CodeGen/src/IrLoweringA64.h @@ -17,13 +17,14 @@ namespace CodeGen struct ModuleHelpers; struct AssemblyOptions; +struct LoweringStats; namespace A64 { struct IrLoweringA64 { - IrLoweringA64(AssemblyBuilderA64& build, ModuleHelpers& helpers, IrFunction& function); + IrLoweringA64(AssemblyBuilderA64& build, ModuleHelpers& helpers, IrFunction& function, LoweringStats* stats); void lowerInst(IrInst& inst, uint32_t index, const IrBlock& next); void finishBlock(const IrBlock& curr, const IrBlock& next); @@ -74,6 +75,7 @@ struct IrLoweringA64 ModuleHelpers& helpers; IrFunction& function; + LoweringStats* stats = nullptr; IrRegAllocA64 regs; diff --git a/CodeGen/src/IrLoweringX64.cpp b/CodeGen/src/IrLoweringX64.cpp index fe5127ac..03ae4f0c 100644 --- a/CodeGen/src/IrLoweringX64.cpp +++ b/CodeGen/src/IrLoweringX64.cpp @@ -22,11 +22,12 @@ namespace CodeGen namespace X64 { -IrLoweringX64::IrLoweringX64(AssemblyBuilderX64& build, ModuleHelpers& helpers, IrFunction& function) +IrLoweringX64::IrLoweringX64(AssemblyBuilderX64& build, ModuleHelpers& helpers, IrFunction& function, LoweringStats* stats) : build(build) , helpers(helpers) , function(function) - , regs(build, function) + , stats(stats) + , regs(build, function, stats) , valueTracker(function) , exitHandlerMap(~0u) { @@ -1646,6 +1647,15 @@ void IrLoweringX64::finishFunction() build.mov(edx, handler.pcpos * sizeof(Instruction)); build.jmp(helpers.updatePcAndContinueInVm); } + + if (stats) + { + if (regs.maxUsedSlot > kSpillSlots) + stats->regAllocErrors++; + + if (regs.maxUsedSlot > stats->maxSpillSlotsUsed) + stats->maxSpillSlotsUsed = regs.maxUsedSlot; + } } bool IrLoweringX64::hasError() const diff --git a/CodeGen/src/IrLoweringX64.h b/CodeGen/src/IrLoweringX64.h index a32e034d..920ad002 100644 --- a/CodeGen/src/IrLoweringX64.h +++ b/CodeGen/src/IrLoweringX64.h @@ -19,13 +19,14 @@ namespace CodeGen struct ModuleHelpers; struct AssemblyOptions; +struct LoweringStats; namespace X64 { struct IrLoweringX64 { - IrLoweringX64(AssemblyBuilderX64& build, ModuleHelpers& helpers, IrFunction& function); + IrLoweringX64(AssemblyBuilderX64& build, ModuleHelpers& helpers, IrFunction& function, LoweringStats* stats); void lowerInst(IrInst& inst, uint32_t index, const IrBlock& next); void finishBlock(const IrBlock& curr, const IrBlock& next); @@ -76,6 +77,7 @@ struct IrLoweringX64 ModuleHelpers& helpers; IrFunction& function; + LoweringStats* stats = nullptr; IrRegAllocX64 regs; diff --git a/CodeGen/src/IrRegAllocA64.cpp b/CodeGen/src/IrRegAllocA64.cpp index f552c17f..d2b7ff14 100644 --- a/CodeGen/src/IrRegAllocA64.cpp +++ b/CodeGen/src/IrRegAllocA64.cpp @@ -2,6 +2,7 @@ #include "IrRegAllocA64.h" #include "Luau/AssemblyBuilderA64.h" +#include "Luau/CodeGen.h" #include "Luau/IrUtils.h" #include "BitUtils.h" @@ -109,8 +110,9 @@ static void restoreInst(AssemblyBuilderA64& build, uint32_t& freeSpillSlots, IrF inst.regA64 = reg; } -IrRegAllocA64::IrRegAllocA64(IrFunction& function, std::initializer_list> regs) +IrRegAllocA64::IrRegAllocA64(IrFunction& function, LoweringStats* stats, std::initializer_list> regs) : function(function) + , stats(stats) { for (auto& p : regs) { @@ -329,6 +331,9 @@ size_t IrRegAllocA64::spill(AssemblyBuilderA64& build, uint32_t index, std::init spills.push_back(s); def.needsReload = true; + + if (stats) + stats->spillsToRestore++; } else { @@ -345,6 +350,14 @@ size_t IrRegAllocA64::spill(AssemblyBuilderA64& build, uint32_t index, std::init spills.push_back(s); def.spilled = true; + + if (stats) + { + stats->spillsToSlot++; + + if (slot != kInvalidSpill && unsigned(slot + 1) > stats->maxSpillSlotsUsed) + stats->maxSpillSlotsUsed = slot + 1; + } } def.regA64 = noreg; diff --git a/CodeGen/src/IrRegAllocA64.h b/CodeGen/src/IrRegAllocA64.h index ae3110d7..d16fa19f 100644 --- a/CodeGen/src/IrRegAllocA64.h +++ b/CodeGen/src/IrRegAllocA64.h @@ -12,6 +12,9 @@ namespace Luau { namespace CodeGen { + +struct LoweringStats; + namespace A64 { @@ -19,7 +22,7 @@ class AssemblyBuilderA64; struct IrRegAllocA64 { - IrRegAllocA64(IrFunction& function, std::initializer_list> regs); + IrRegAllocA64(IrFunction& function, LoweringStats* stats, std::initializer_list> regs); RegisterA64 allocReg(KindA64 kind, uint32_t index); RegisterA64 allocTemp(KindA64 kind); @@ -69,6 +72,7 @@ struct IrRegAllocA64 Set& getSet(KindA64 kind); IrFunction& function; + LoweringStats* stats = nullptr; Set gpr, simd; std::vector spills; diff --git a/CodeGen/src/IrRegAllocX64.cpp b/CodeGen/src/IrRegAllocX64.cpp index 7690f69a..81cf2f4c 100644 --- a/CodeGen/src/IrRegAllocX64.cpp +++ b/CodeGen/src/IrRegAllocX64.cpp @@ -1,6 +1,7 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "Luau/IrRegAllocX64.h" +#include "Luau/CodeGen.h" #include "Luau/IrUtils.h" #include "EmitCommonX64.h" @@ -14,9 +15,10 @@ namespace X64 static const RegisterX64 kGprAllocOrder[] = {rax, rdx, rcx, rbx, rsi, rdi, r8, r9, r10, r11}; -IrRegAllocX64::IrRegAllocX64(AssemblyBuilderX64& build, IrFunction& function) +IrRegAllocX64::IrRegAllocX64(AssemblyBuilderX64& build, IrFunction& function, LoweringStats* stats) : build(build) , function(function) + , stats(stats) , usableXmmRegCount(getXmmRegisterCount(build.abi)) { freeGprMap.fill(true); @@ -225,10 +227,16 @@ void IrRegAllocX64::preserve(IrInst& inst) spill.stackSlot = uint8_t(i); inst.spilled = true; + + if (stats) + stats->spillsToSlot++; } else { inst.needsReload = true; + + if (stats) + stats->spillsToRestore++; } spills.push_back(spill); diff --git a/CodeGen/src/IrTranslation.cpp b/CodeGen/src/IrTranslation.cpp index f1eea645..13256789 100644 --- a/CodeGen/src/IrTranslation.cpp +++ b/CodeGen/src/IrTranslation.cpp @@ -615,7 +615,7 @@ static IrOp getLoopStepK(IrBuilder& build, int ra) { IrBlock& active = build.function.blocks[build.activeBlockIdx]; - if (active.start + 2 < build.function.instructions.size()) + if (active.start + 2 < build.function.instructions.size()) { IrInst& sv = build.function.instructions[build.function.instructions.size() - 2]; IrInst& st = build.function.instructions[build.function.instructions.size() - 1]; @@ -665,7 +665,7 @@ void translateInstForNPrep(IrBuilder& build, const Instruction* pc, int pcpos) IrOp step = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(ra + 1)); // step > 0 - // note: equivalent to 0 < step, but lowers into one instruction on both X64 and A64 + // note: equivalent to 0 < step, but lowers into one instruction on both X64 and A64 build.inst(IrCmd::JUMP_CMP_NUM, step, zero, build.cond(IrCondition::Greater), direct, reverse); // Condition to start the loop: step > 0 ? idx <= limit : limit <= idx @@ -763,7 +763,7 @@ void translateInstForNLoop(IrBuilder& build, const Instruction* pc, int pcpos) IrOp reverse = build.block(IrBlockKind::Internal); // step > 0 - // note: equivalent to 0 < step, but lowers into one instruction on both X64 and A64 + // note: equivalent to 0 < step, but lowers into one instruction on both X64 and A64 build.inst(IrCmd::JUMP_CMP_NUM, step, zero, build.cond(IrCondition::Greater), direct, reverse); // Condition to continue the loop: step > 0 ? idx <= limit : limit <= idx @@ -776,8 +776,8 @@ void translateInstForNLoop(IrBuilder& build, const Instruction* pc, int pcpos) build.beginBlock(direct); build.inst(IrCmd::JUMP_CMP_NUM, idx, limit, build.cond(IrCondition::LessEqual), loopRepeat, loopExit); } - else - { + else + { double stepN = build.function.doubleOp(stepK); // Condition to continue the loop: step > 0 ? idx <= limit : limit <= idx @@ -785,9 +785,9 @@ void translateInstForNLoop(IrBuilder& build, const Instruction* pc, int pcpos) build.inst(IrCmd::JUMP_CMP_NUM, idx, limit, build.cond(IrCondition::LessEqual), loopRepeat, loopExit); else build.inst(IrCmd::JUMP_CMP_NUM, limit, idx, build.cond(IrCondition::LessEqual), loopRepeat, loopExit); - } + } } - else + else { build.inst(IrCmd::INTERRUPT, build.constUint(pcpos)); diff --git a/CodeGen/src/IrUtils.cpp b/CodeGen/src/IrUtils.cpp index d263d3aa..687b0c2a 100644 --- a/CodeGen/src/IrUtils.cpp +++ b/CodeGen/src/IrUtils.cpp @@ -860,5 +860,43 @@ void killUnusedBlocks(IrFunction& function) } } +std::vector getSortedBlockOrder(IrFunction& function) +{ + std::vector sortedBlocks; + sortedBlocks.reserve(function.blocks.size()); + for (uint32_t i = 0; i < function.blocks.size(); i++) + sortedBlocks.push_back(i); + + std::sort(sortedBlocks.begin(), sortedBlocks.end(), [&](uint32_t idxA, uint32_t idxB) { + const IrBlock& a = function.blocks[idxA]; + const IrBlock& b = function.blocks[idxB]; + + // Place fallback blocks at the end + if ((a.kind == IrBlockKind::Fallback) != (b.kind == IrBlockKind::Fallback)) + return (a.kind == IrBlockKind::Fallback) < (b.kind == IrBlockKind::Fallback); + + // Try to order by instruction order + if (a.sortkey != b.sortkey) + return a.sortkey < b.sortkey; + + // Chains of blocks are merged together by having the same sort key and consecutive chain key + return a.chainkey < b.chainkey; + }); + + return sortedBlocks; +} + +IrBlock& getNextBlock(IrFunction& function, std::vector& sortedBlocks, IrBlock& dummy, size_t i) +{ + for (size_t j = i + 1; j < sortedBlocks.size(); ++j) + { + IrBlock& block = function.blocks[sortedBlocks[j]]; + if (block.kind != IrBlockKind::Dead) + return block; + } + + return dummy; +} + } // namespace CodeGen } // namespace Luau diff --git a/CodeGen/src/NativeState.cpp b/CodeGen/src/NativeState.cpp index 6f567a2b..7b2f068b 100644 --- a/CodeGen/src/NativeState.cpp +++ b/CodeGen/src/NativeState.cpp @@ -14,21 +14,21 @@ #include #include +LUAU_FASTINTVARIABLE(LuauCodeGenBlockSize, 4 * 1024 * 1024) +LUAU_FASTINTVARIABLE(LuauCodeGenMaxTotalSize, 256 * 1024 * 1024) + namespace Luau { namespace CodeGen { -constexpr unsigned kBlockSize = 4 * 1024 * 1024; -constexpr unsigned kMaxTotalSize = 256 * 1024 * 1024; - NativeState::NativeState() : NativeState(nullptr, nullptr) { } NativeState::NativeState(AllocationCallback* allocationCallback, void* allocationCallbackContext) - : codeAllocator{kBlockSize, kMaxTotalSize, allocationCallback, allocationCallbackContext} + : codeAllocator{size_t(FInt::LuauCodeGenBlockSize), size_t(FInt::LuauCodeGenMaxTotalSize), allocationCallback, allocationCallbackContext} { } diff --git a/Common/include/Luau/ExperimentalFlags.h b/Common/include/Luau/ExperimentalFlags.h index fd074be1..94d13ea8 100644 --- a/Common/include/Luau/ExperimentalFlags.h +++ b/Common/include/Luau/ExperimentalFlags.h @@ -13,6 +13,7 @@ inline bool isFlagExperimental(const char* flag) static const char* const kList[] = { "LuauInstantiateInSubtyping", // requires some fixes to lua-apps code "LuauTinyControlFlowAnalysis", // waiting for updates to packages depended by internal builtin plugins + "LuauFixIndexerSubtypingOrdering", // requires some small fixes to lua-apps code since this fixes a false negative // makes sure we always have at least one entry nullptr, }; diff --git a/VM/src/lmathlib.cpp b/VM/src/lmathlib.cpp index 8a140780..5a817f25 100644 --- a/VM/src/lmathlib.cpp +++ b/VM/src/lmathlib.cpp @@ -275,15 +275,15 @@ static int math_randomseed(lua_State* L) return 0; } -static const unsigned char kPerlinHash[257] = {151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, - 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, - 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, - 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, - 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, - 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, - 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, - 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24, - 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180, 151}; +static const unsigned char kPerlinHash[257] = {151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, + 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, + 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, + 41, 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, + 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, + 17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, + 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, + 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, + 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180, 151}; const float kPerlinGrad[16][3] = {{1, 1, 0}, {-1, 1, 0}, {1, -1, 0}, {-1, -1, 0}, {1, 0, 1}, {-1, 0, 1}, {1, 0, -1}, {-1, 0, -1}, {0, 1, 1}, {0, -1, 1}, {0, 1, -1}, {0, -1, -1}, {1, 1, 0}, {0, -1, 1}, {-1, 1, 0}, {0, -1, -1}}; diff --git a/VM/src/lvmexecute.cpp b/VM/src/lvmexecute.cpp index 68efc0e4..086ed649 100644 --- a/VM/src/lvmexecute.cpp +++ b/VM/src/lvmexecute.cpp @@ -2291,7 +2291,7 @@ reentry: { // table or userdata with __call, will be called during FORGLOOP // TODO: we might be able to stop supporting this depending on whether it's used in practice - void (*telemetrycb)(lua_State* L, int gtt, int stt, int itt) = lua_iter_call_telemetry; + void (*telemetrycb)(lua_State * L, int gtt, int stt, int itt) = lua_iter_call_telemetry; if (telemetrycb) telemetrycb(L, ttype(ra), ttype(ra + 1), ttype(ra + 2)); diff --git a/tests/Autocomplete.test.cpp b/tests/Autocomplete.test.cpp index dd90dd8e..84dee432 100644 --- a/tests/Autocomplete.test.cpp +++ b/tests/Autocomplete.test.cpp @@ -3667,8 +3667,7 @@ TEST_CASE_FIXTURE(ACFixture, "string_completion_outside_quotes") )"); StringCompletionCallback callback = [](std::string, std::optional, - std::optional contents) -> std::optional - { + std::optional contents) -> std::optional { Luau::AutocompleteEntryMap results = {{"test", Luau::AutocompleteEntry{Luau::AutocompleteEntryKind::String, std::nullopt, false, false}}}; return results; }; diff --git a/tests/CodeAllocator.test.cpp b/tests/CodeAllocator.test.cpp index 298035c2..9c99862d 100644 --- a/tests/CodeAllocator.test.cpp +++ b/tests/CodeAllocator.test.cpp @@ -57,8 +57,7 @@ TEST_CASE("CodeAllocationCallbacks") AllocationData allocationData{}; - const auto allocationCallback = [](void* context, void* oldPointer, size_t oldSize, void* newPointer, size_t newSize) - { + const auto allocationCallback = [](void* context, void* oldPointer, size_t oldSize, void* newPointer, size_t newSize) { AllocationData& allocationData = *static_cast(context); if (oldPointer != nullptr) { diff --git a/tests/Compiler.test.cpp b/tests/Compiler.test.cpp index 93290567..49498744 100644 --- a/tests/Compiler.test.cpp +++ b/tests/Compiler.test.cpp @@ -74,7 +74,7 @@ TEST_CASE("BytecodeIsStable") // Bytecode ops (serialized & in-memory) CHECK(LOP_FASTCALL2K == 75); // bytecode v1 - CHECK(LOP_JUMPXEQKS == 80); // bytecode v3 + CHECK(LOP_JUMPXEQKS == 80); // bytecode v3 // Bytecode fastcall ids (serialized & in-memory) // Note: these aren't strictly bound to specific bytecode versions, but must monotonically increase to keep backwards compat @@ -7371,7 +7371,8 @@ TEST_CASE("BuiltinFoldMathK") function test() return math.pi * 2 end -)", 0, 2), +)", + 0, 2), R"( LOADK R0 K0 [6.2831853071795862] RETURN R0 1 @@ -7382,7 +7383,8 @@ RETURN R0 1 function test() return math.pi * 2 end -)", 0, 1), +)", + 0, 1), R"( GETIMPORT R1 3 [math.pi] MULK R0 R1 K0 [2] @@ -7396,7 +7398,8 @@ function test() end math = { pi = 4 } -)", 0, 2), +)", + 0, 2), R"( GETGLOBAL R2 K1 ['math'] GETTABLEKS R1 R2 K2 ['pi'] diff --git a/tests/IrCallWrapperX64.test.cpp b/tests/IrCallWrapperX64.test.cpp index ec04e531..1ff22a32 100644 --- a/tests/IrCallWrapperX64.test.cpp +++ b/tests/IrCallWrapperX64.test.cpp @@ -12,7 +12,7 @@ class IrCallWrapperX64Fixture public: IrCallWrapperX64Fixture(ABIX64 abi = ABIX64::Windows) : build(/* logText */ true, abi) - , regs(build, function) + , regs(build, function, nullptr) , callWrap(regs, build, ~0u) { } diff --git a/tests/IrRegAllocX64.test.cpp b/tests/IrRegAllocX64.test.cpp index bbf9c154..b4b63f4b 100644 --- a/tests/IrRegAllocX64.test.cpp +++ b/tests/IrRegAllocX64.test.cpp @@ -11,7 +11,7 @@ class IrRegAllocX64Fixture public: IrRegAllocX64Fixture() : build(/* logText */ true, ABIX64::Windows) - , regs(build, function) + , regs(build, function, nullptr) { } diff --git a/tests/Normalize.test.cpp b/tests/Normalize.test.cpp index afcc08f2..7bed110b 100644 --- a/tests/Normalize.test.cpp +++ b/tests/Normalize.test.cpp @@ -516,6 +516,71 @@ struct NormalizeFixture : Fixture TEST_SUITE_BEGIN("Normalize"); +TEST_CASE_FIXTURE(NormalizeFixture, "string_intersection_is_commutative") +{ + auto c4 = toString(normal(R"( + string & (string & Not<"a"> & Not<"b">) +)")); + auto c4Reverse = toString(normal(R"( + (string & Not<"a"> & Not<"b">) & string +)")); + CHECK(c4 == c4Reverse); + CHECK_EQ("string & ~\"a\" & ~\"b\"", c4); + + auto c5 = toString(normal(R"( + (string & Not<"a"> & Not<"b">) & (string & Not<"b"> & Not<"c">) +)")); + auto c5Reverse = toString(normal(R"( + (string & Not<"b"> & Not<"c">) & (string & Not<"a"> & Not<"c">) +)")); + CHECK(c5 == c5Reverse); + CHECK_EQ("string & ~\"a\" & ~\"b\" & ~\"c\"", c5); + + auto c6 = toString(normal(R"( + ("a" | "b") & (string & Not<"b"> & Not<"c">) +)")); + auto c6Reverse = toString(normal(R"( + (string & Not<"b"> & Not<"c">) & ("a" | "b") +)")); + CHECK(c6 == c6Reverse); + CHECK_EQ("\"a\"", c6); + + auto c7 = toString(normal(R"( + string & ("b" | "c") +)")); + auto c7Reverse = toString(normal(R"( + ("b" | "c") & string +)")); + CHECK(c7 == c7Reverse); + CHECK_EQ("\"b\" | \"c\"", c7); + + auto c8 = toString(normal(R"( +(string & Not<"a"> & Not<"b">) & ("b" | "c") +)")); + auto c8Reverse = toString(normal(R"( + ("b" | "c") & (string & Not<"a"> & Not<"b">) +)")); + CHECK(c8 == c8Reverse); + CHECK_EQ("\"c\"", c8); + auto c9 = toString(normal(R"( + ("a" | "b") & ("b" | "c") + )")); + auto c9Reverse = toString(normal(R"( + ("b" | "c") & ("a" | "b") + )")); + CHECK(c9 == c9Reverse); + CHECK_EQ("\"b\"", c9); + + auto l = toString(normal(R"( + (string | number) & ("a" | true) + )")); + auto r = toString(normal(R"( + ("a" | true) & (string | number) + )")); + CHECK(l == r); + CHECK_EQ("\"a\"", l); +} + TEST_CASE_FIXTURE(NormalizeFixture, "negate_string") { CHECK("number" == toString(normal(R"( diff --git a/tests/Subtyping.test.cpp b/tests/Subtyping.test.cpp index 6089f036..ce418b71 100644 --- a/tests/Subtyping.test.cpp +++ b/tests/Subtyping.test.cpp @@ -6,6 +6,7 @@ #include "Luau/Normalize.h" #include "Luau/Subtyping.h" +#include "Luau/Type.h" #include "Luau/TypePack.h" using namespace Luau; @@ -17,7 +18,15 @@ struct SubtypeFixture : Fixture UnifierSharedState sharedState{&ice}; Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}}; - Subtyping subtyping{builtinTypes, NotNull{&arena}, NotNull{&normalizer}, NotNull{&iceReporter}}; + ScopePtr rootScope{new Scope(builtinTypes->emptyTypePack)}; + ScopePtr moduleScope{new Scope(rootScope)}; + + Subtyping subtyping = mkSubtyping(rootScope); + + Subtyping mkSubtyping(const ScopePtr& scope) + { + return Subtyping{builtinTypes, NotNull{&arena}, NotNull{&normalizer}, NotNull{&iceReporter}, NotNull{scope.get()}}; + } TypePackId pack(std::initializer_list tys) { @@ -66,11 +75,18 @@ struct SubtypeFixture : Fixture return arena.addType(UnionType{{a, b}}); } + // `~` TypeId negate(TypeId ty) { return arena.addType(NegationType{ty}); } + // "literal" + TypeId str(const char* literal) + { + return arena.addType(SingletonType{StringSingleton{literal}}); + } + TypeId cls(const std::string& name, std::optional parent = std::nullopt) { return arena.addType(ClassType{name, {}, parent.value_or(builtinTypes->classType), {}, {}, nullptr, ""}); @@ -97,8 +113,8 @@ struct SubtypeFixture : Fixture return arena.addType(MetatableType{tbl(std::move(tableProps)), tbl(std::move(metaProps))}); } - TypeId genericT = arena.addType(GenericType{"T"}); - TypeId genericU = arena.addType(GenericType{"U"}); + TypeId genericT = arena.addType(GenericType{moduleScope.get(), "T"}); + TypeId genericU = arena.addType(GenericType{moduleScope.get(), "U"}); TypePackId genericAs = arena.addTypePack(GenericTypePack{"A"}); TypePackId genericBs = arena.addTypePack(GenericTypePack{"B"}); @@ -113,6 +129,10 @@ struct SubtypeFixture : Fixture TypeId helloType2 = arena.addType(SingletonType{StringSingleton{"hello"}}); TypeId worldType = arena.addType(SingletonType{StringSingleton{"world"}}); + TypeId aType = arena.addType(SingletonType{StringSingleton{"a"}}); + TypeId bType = arena.addType(SingletonType{StringSingleton{"b"}}); + TypeId trueSingleton = arena.addType(SingletonType{BooleanSingleton{true}}); + TypeId falseSingleton = arena.addType(SingletonType{BooleanSingleton{false}}); TypeId helloOrWorldType = join(helloType, worldType); TypeId trueOrFalseType = join(builtinTypes->trueType, builtinTypes->falseType); @@ -128,7 +148,7 @@ struct SubtypeFixture : Fixture * \- AnotherChild * |- AnotherGrandchildOne * \- AnotherGrandchildTwo - */ + */ TypeId rootClass = cls("Root"); TypeId childClass = cls("Child", rootClass); TypeId grandchildOneClass = cls("GrandchildOne", childClass); @@ -138,9 +158,9 @@ struct SubtypeFixture : Fixture TypeId anotherGrandchildTwoClass = cls("AnotherGrandchildTwo", anotherChildClass); TypeId vec2Class = cls("Vec2", { - {"X", builtinTypes->numberType}, - {"Y", builtinTypes->numberType}, - }); + {"X", builtinTypes->numberType}, + {"Y", builtinTypes->numberType}, + }); // "hello" | "hello" TypeId helloOrHelloType = arena.addType(UnionType{{helloType, helloType}}); @@ -149,160 +169,76 @@ struct SubtypeFixture : Fixture const TypeId nothingToNothingType = fn({}, {}); // (number) -> string - const TypeId numberToStringType = fn( - {builtinTypes->numberType}, - {builtinTypes->stringType} - ); + const TypeId numberToStringType = fn({builtinTypes->numberType}, {builtinTypes->stringType}); // (unknown) -> string - const TypeId unknownToStringType = fn( - {builtinTypes->unknownType}, - {builtinTypes->stringType} - ); + const TypeId unknownToStringType = fn({builtinTypes->unknownType}, {builtinTypes->stringType}); // (number) -> () - const TypeId numberToNothingType = fn( - {builtinTypes->numberType}, - {} - ); + const TypeId numberToNothingType = fn({builtinTypes->numberType}, {}); // () -> number - const TypeId nothingToNumberType = fn( - {}, - {builtinTypes->numberType} - ); + const TypeId nothingToNumberType = fn({}, {builtinTypes->numberType}); // (number) -> number - const TypeId numberToNumberType = fn( - {builtinTypes->numberType}, - {builtinTypes->numberType} - ); + const TypeId numberToNumberType = fn({builtinTypes->numberType}, {builtinTypes->numberType}); // (number) -> unknown - const TypeId numberToUnknownType = fn( - {builtinTypes->numberType}, - {builtinTypes->unknownType} - ); + const TypeId numberToUnknownType = fn({builtinTypes->numberType}, {builtinTypes->unknownType}); // (number) -> (string, string) - const TypeId numberToTwoStringsType = fn( - {builtinTypes->numberType}, - {builtinTypes->stringType, builtinTypes->stringType} - ); + const TypeId numberToTwoStringsType = fn({builtinTypes->numberType}, {builtinTypes->stringType, builtinTypes->stringType}); // (number) -> (string, unknown) - const TypeId numberToStringAndUnknownType = fn( - {builtinTypes->numberType}, - {builtinTypes->stringType, builtinTypes->unknownType} - ); + const TypeId numberToStringAndUnknownType = fn({builtinTypes->numberType}, {builtinTypes->stringType, builtinTypes->unknownType}); // (number, number) -> string - const TypeId numberNumberToStringType = fn( - {builtinTypes->numberType, builtinTypes->numberType}, - {builtinTypes->stringType} - ); + const TypeId numberNumberToStringType = fn({builtinTypes->numberType, builtinTypes->numberType}, {builtinTypes->stringType}); // (unknown, number) -> string - const TypeId unknownNumberToStringType = fn( - {builtinTypes->unknownType, builtinTypes->numberType}, - {builtinTypes->stringType} - ); + const TypeId unknownNumberToStringType = fn({builtinTypes->unknownType, builtinTypes->numberType}, {builtinTypes->stringType}); // (number, string) -> string - const TypeId numberAndStringToStringType = fn( - {builtinTypes->numberType, builtinTypes->stringType}, - {builtinTypes->stringType} - ); + const TypeId numberAndStringToStringType = fn({builtinTypes->numberType, builtinTypes->stringType}, {builtinTypes->stringType}); // (number, ...string) -> string - const TypeId numberAndStringsToStringType = fn( - {builtinTypes->numberType}, VariadicTypePack{builtinTypes->stringType}, - {builtinTypes->stringType} - ); + const TypeId numberAndStringsToStringType = + fn({builtinTypes->numberType}, VariadicTypePack{builtinTypes->stringType}, {builtinTypes->stringType}); // (number, ...string?) -> string - const TypeId numberAndOptionalStringsToStringType = fn( - {builtinTypes->numberType}, VariadicTypePack{builtinTypes->optionalStringType}, - {builtinTypes->stringType} - ); + const TypeId numberAndOptionalStringsToStringType = + fn({builtinTypes->numberType}, VariadicTypePack{builtinTypes->optionalStringType}, {builtinTypes->stringType}); // (...number) -> number - const TypeId numbersToNumberType = arena.addType(FunctionType{ - arena.addTypePack(VariadicTypePack{builtinTypes->numberType}), - arena.addTypePack({builtinTypes->numberType}) - }); + const TypeId numbersToNumberType = + arena.addType(FunctionType{arena.addTypePack(VariadicTypePack{builtinTypes->numberType}), arena.addTypePack({builtinTypes->numberType})}); // (T) -> () - const TypeId genericTToNothingType = arena.addType(FunctionType{ - {genericT}, - {}, - arena.addTypePack({genericT}), - builtinTypes->emptyTypePack - }); + const TypeId genericTToNothingType = arena.addType(FunctionType{{genericT}, {}, arena.addTypePack({genericT}), builtinTypes->emptyTypePack}); // (T) -> T - const TypeId genericTToTType = arena.addType(FunctionType{ - {genericT}, - {}, - arena.addTypePack({genericT}), - arena.addTypePack({genericT}) - }); + const TypeId genericTToTType = arena.addType(FunctionType{{genericT}, {}, arena.addTypePack({genericT}), arena.addTypePack({genericT})}); // (U) -> () - const TypeId genericUToNothingType = arena.addType(FunctionType{ - {genericU}, - {}, - arena.addTypePack({genericU}), - builtinTypes->emptyTypePack - }); + const TypeId genericUToNothingType = arena.addType(FunctionType{{genericU}, {}, arena.addTypePack({genericU}), builtinTypes->emptyTypePack}); // () -> T - const TypeId genericNothingToTType = arena.addType(FunctionType{ - {genericT}, - {}, - builtinTypes->emptyTypePack, - arena.addTypePack({genericT}) - }); + const TypeId genericNothingToTType = arena.addType(FunctionType{{genericT}, {}, builtinTypes->emptyTypePack, arena.addTypePack({genericT})}); // (A...) -> A... - const TypeId genericAsToAsType = arena.addType(FunctionType{ - {}, - {genericAs}, - genericAs, - genericAs - }); + const TypeId genericAsToAsType = arena.addType(FunctionType{{}, {genericAs}, genericAs, genericAs}); // (A...) -> number - const TypeId genericAsToNumberType = arena.addType(FunctionType{ - {}, - {genericAs}, - genericAs, - arena.addTypePack({builtinTypes->numberType}) - }); + const TypeId genericAsToNumberType = arena.addType(FunctionType{{}, {genericAs}, genericAs, arena.addTypePack({builtinTypes->numberType})}); // (B...) -> B... - const TypeId genericBsToBsType = arena.addType(FunctionType{ - {}, - {genericBs}, - genericBs, - genericBs - }); + const TypeId genericBsToBsType = arena.addType(FunctionType{{}, {genericBs}, genericBs, genericBs}); // (B...) -> C... - const TypeId genericBsToCsType = arena.addType(FunctionType{ - {}, - {genericBs, genericCs}, - genericBs, - genericCs - }); + const TypeId genericBsToCsType = arena.addType(FunctionType{{}, {genericBs, genericCs}, genericBs, genericCs}); // () -> A... - const TypeId genericNothingToAsType = arena.addType(FunctionType{ - {}, - {genericAs}, - builtinTypes->emptyTypePack, - genericAs - }); + const TypeId genericNothingToAsType = arena.addType(FunctionType{{}, {genericAs}, builtinTypes->emptyTypePack, genericAs}); // { lower : string -> string } TypeId tableWithLower = tbl(TableType::Props{{"lower", fn({builtinTypes->stringType}, {builtinTypes->stringType})}}); @@ -728,60 +664,98 @@ TEST_CASE_FIXTURE(SubtypeFixture, "{x: number?} (T) -> ()} <: {x: (U) -> ()}") { - CHECK_IS_SUBTYPE( - tbl({{"x", genericTToNothingType}}), - tbl({{"x", genericUToNothingType}}) - ); + CHECK_IS_SUBTYPE(tbl({{"x", genericTToNothingType}}), tbl({{"x", genericUToNothingType}})); } TEST_CASE_FIXTURE(SubtypeFixture, "{ @metatable { x: number } } <: { @metatable {} }") { - CHECK_IS_SUBTYPE( - meta({{"x", builtinTypes->numberType}}), - meta({}) - ); + CHECK_IS_SUBTYPE(meta({{"x", builtinTypes->numberType}}), meta({})); } TEST_CASE_FIXTURE(SubtypeFixture, "{ @metatable { x: number } } numberType}}), - meta({{"x", builtinTypes->booleanType}}) - ); + CHECK_IS_NOT_SUBTYPE(meta({{"x", builtinTypes->numberType}}), meta({{"x", builtinTypes->booleanType}})); } TEST_CASE_FIXTURE(SubtypeFixture, "{ @metatable {} } booleanType}}) - ); + CHECK_IS_NOT_SUBTYPE(meta({}), meta({{"x", builtinTypes->booleanType}})); } TEST_CASE_FIXTURE(SubtypeFixture, "{ @metatable {} } <: {}") { - CHECK_IS_SUBTYPE( - meta({}), - tbl({}) - ); + CHECK_IS_SUBTYPE(meta({}), tbl({})); } TEST_CASE_FIXTURE(SubtypeFixture, "{ @metatable { u: boolean }, x: number } <: { x: number }") { - CHECK_IS_SUBTYPE( - meta({{"u", builtinTypes->booleanType}}, {{"x", builtinTypes->numberType}}), - tbl({{"x", builtinTypes->numberType}}) - ); + CHECK_IS_SUBTYPE(meta({{"u", builtinTypes->booleanType}}, {{"x", builtinTypes->numberType}}), tbl({{"x", builtinTypes->numberType}})); } TEST_CASE_FIXTURE(SubtypeFixture, "{ @metatable { x: number } } numberType}}), - tbl({{"x", builtinTypes->numberType}}) - ); + CHECK_IS_NOT_SUBTYPE(meta({{"x", builtinTypes->numberType}}), tbl({{"x", builtinTypes->numberType}})); } +// Negated subtypes +TEST_IS_NOT_SUBTYPE(negate(builtinTypes->neverType), builtinTypes->stringType); +TEST_IS_SUBTYPE(negate(builtinTypes->unknownType), builtinTypes->stringType); +TEST_IS_NOT_SUBTYPE(negate(builtinTypes->anyType), builtinTypes->stringType); +TEST_IS_SUBTYPE(negate(meet(builtinTypes->neverType, builtinTypes->unknownType)), builtinTypes->stringType); +TEST_IS_NOT_SUBTYPE(negate(join(builtinTypes->neverType, builtinTypes->unknownType)), builtinTypes->stringType); + +// Negated supertypes: never/unknown/any/error +TEST_IS_SUBTYPE(builtinTypes->stringType, negate(builtinTypes->neverType)); +TEST_IS_SUBTYPE(builtinTypes->neverType, negate(builtinTypes->unknownType)); +TEST_IS_NOT_SUBTYPE(builtinTypes->stringType, negate(builtinTypes->unknownType)); +TEST_IS_SUBTYPE(builtinTypes->numberType, negate(builtinTypes->anyType)); +TEST_IS_SUBTYPE(builtinTypes->unknownType, negate(builtinTypes->anyType)); + +// Negated supertypes: unions +TEST_IS_SUBTYPE(builtinTypes->booleanType, negate(join(builtinTypes->stringType, builtinTypes->numberType))); +TEST_IS_SUBTYPE(rootClass, negate(join(childClass, builtinTypes->numberType))); +TEST_IS_SUBTYPE(str("foo"), negate(join(builtinTypes->numberType, builtinTypes->booleanType))); +TEST_IS_NOT_SUBTYPE(str("foo"), negate(join(builtinTypes->stringType, builtinTypes->numberType))); +TEST_IS_NOT_SUBTYPE(childClass, negate(join(rootClass, builtinTypes->numberType))); +TEST_IS_NOT_SUBTYPE(numbersToNumberType, negate(join(builtinTypes->functionType, rootClass))); + +// Negated supertypes: intersections +TEST_IS_SUBTYPE(builtinTypes->booleanType, negate(meet(builtinTypes->stringType, str("foo")))); +TEST_IS_SUBTYPE(builtinTypes->trueType, negate(meet(builtinTypes->booleanType, builtinTypes->numberType))); +TEST_IS_SUBTYPE(rootClass, negate(meet(builtinTypes->classType, childClass))); +TEST_IS_SUBTYPE(childClass, negate(meet(builtinTypes->classType, builtinTypes->numberType))); +TEST_IS_NOT_SUBTYPE(builtinTypes->unknownType, negate(meet(builtinTypes->classType, builtinTypes->numberType))); +TEST_IS_NOT_SUBTYPE(str("foo"), negate(meet(builtinTypes->stringType, negate(str("bar"))))); + +// Negated supertypes: tables and metatables +TEST_IS_SUBTYPE(tbl({}), negate(builtinTypes->numberType)); +TEST_IS_NOT_SUBTYPE(tbl({}), negate(builtinTypes->tableType)); +TEST_IS_SUBTYPE(meta({}), negate(builtinTypes->numberType)); +TEST_IS_NOT_SUBTYPE(meta({}), negate(builtinTypes->tableType)); + +// Negated supertypes: Functions +TEST_IS_SUBTYPE(numberToNumberType, negate(builtinTypes->classType)); +TEST_IS_NOT_SUBTYPE(numberToNumberType, negate(builtinTypes->functionType)); + +// Negated supertypes: Primitives and singletons +TEST_IS_SUBTYPE(builtinTypes->stringType, negate(builtinTypes->numberType)); +TEST_IS_SUBTYPE(str("foo"), meet(builtinTypes->stringType, negate(str("bar")))); +TEST_IS_NOT_SUBTYPE(builtinTypes->trueType, negate(builtinTypes->booleanType)); +TEST_IS_NOT_SUBTYPE(str("foo"), negate(str("foo"))); +TEST_IS_NOT_SUBTYPE(str("foo"), negate(builtinTypes->stringType)); +TEST_IS_SUBTYPE(builtinTypes->falseType, negate(builtinTypes->trueType)); +TEST_IS_SUBTYPE(builtinTypes->falseType, meet(builtinTypes->booleanType, negate(builtinTypes->trueType))); +TEST_IS_NOT_SUBTYPE(builtinTypes->stringType, meet(builtinTypes->booleanType, negate(builtinTypes->trueType))); +TEST_IS_NOT_SUBTYPE(builtinTypes->stringType, negate(str("foo"))); +TEST_IS_NOT_SUBTYPE(builtinTypes->booleanType, negate(builtinTypes->falseType)); + +// Negated supertypes: Classes +TEST_IS_SUBTYPE(rootClass, negate(builtinTypes->tableType)); +TEST_IS_NOT_SUBTYPE(rootClass, negate(builtinTypes->classType)); +TEST_IS_NOT_SUBTYPE(childClass, negate(rootClass)); +TEST_IS_NOT_SUBTYPE(childClass, meet(builtinTypes->classType, negate(rootClass))); +TEST_IS_SUBTYPE(anotherChildClass, meet(builtinTypes->classType, negate(childClass))); + TEST_CASE_FIXTURE(SubtypeFixture, "Root <: class") { CHECK_IS_SUBTYPE(rootClass, builtinTypes->classType); @@ -829,13 +803,11 @@ TEST_CASE_FIXTURE(SubtypeFixture, "Child & ~GrandchildOne string} <: t2 where t2 = {trim: (t2) -> string}") { - TypeId t1 = cyclicTable([&](TypeId ty, TableType* tt) - { + TypeId t1 = cyclicTable([&](TypeId ty, TableType* tt) { tt->props["trim"] = fn({ty}, {builtinTypes->stringType}); }); - TypeId t2 = cyclicTable([&](TypeId ty, TableType* tt) - { + TypeId t2 = cyclicTable([&](TypeId ty, TableType* tt) { tt->props["trim"] = fn({ty}, {builtinTypes->stringType}); }); @@ -844,13 +816,11 @@ TEST_CASE_FIXTURE(SubtypeFixture, "t1 where t1 = {trim: (t1) -> string} <: t2 wh TEST_CASE_FIXTURE(SubtypeFixture, "t1 where t1 = {trim: (t1) -> string} t2}") { - TypeId t1 = cyclicTable([&](TypeId ty, TableType* tt) - { + TypeId t1 = cyclicTable([&](TypeId ty, TableType* tt) { tt->props["trim"] = fn({ty}, {builtinTypes->stringType}); }); - TypeId t2 = cyclicTable([&](TypeId ty, TableType* tt) - { + TypeId t2 = cyclicTable([&](TypeId ty, TableType* tt) { tt->props["trim"] = fn({ty}, {ty}); }); @@ -859,13 +829,11 @@ TEST_CASE_FIXTURE(SubtypeFixture, "t1 where t1 = {trim: (t1) -> string} t1} string}") { - TypeId t1 = cyclicTable([&](TypeId ty, TableType* tt) - { + TypeId t1 = cyclicTable([&](TypeId ty, TableType* tt) { tt->props["trim"] = fn({ty}, {ty}); }); - TypeId t2 = cyclicTable([&](TypeId ty, TableType* tt) - { + TypeId t2 = cyclicTable([&](TypeId ty, TableType* tt) { tt->props["trim"] = fn({ty}, {builtinTypes->stringType}); }); @@ -960,6 +928,50 @@ TEST_CASE_FIXTURE(SubtypeFixture, "(string) -> number <: ~fun & (string) -> numb CHECK_IS_NOT_SUBTYPE(numberToStringType, meet(negate(builtinTypes->functionType), numberToStringType)); } +TEST_CASE_FIXTURE(SubtypeFixture, "~\"a\" & ~\"b\" & string <: { lower : (string) -> ()}") +{ + CHECK_IS_SUBTYPE(meet(meet(negate(aType), negate(bType)), builtinTypes->stringType), tableWithLower); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "\"a\" | (~\"b\" & string) <: { lower : (string) -> ()}") +{ + CHECK_IS_SUBTYPE(join(aType, meet(negate(bType), builtinTypes->stringType)), tableWithLower); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "(string | number) & (\"a\" | true) <: { lower: (string) -> string }") +{ + auto base = meet(join(builtinTypes->stringType, builtinTypes->numberType), join(aType, trueSingleton)); + CHECK_IS_SUBTYPE(base, tableWithLower); +} + +/* + * Within the scope to which a generic belongs, that generic ought to be treated + * as its bounds. + * + * We do not yet support bounded generics, so all generics are considered to be + * bounded by unknown. + */ +TEST_CASE_FIXTURE(SubtypeFixture, "unknown <: X") +{ + ScopePtr childScope{new Scope(rootScope)}; + ScopePtr grandChildScope{new Scope(childScope)}; + + TypeId genericX = arena.addType(GenericType(childScope.get(), "X")); + + SubtypingResult usingGlobalScope = subtyping.isSubtype(builtinTypes->unknownType, genericX); + CHECK_MESSAGE(!usingGlobalScope.isSubtype, "Expected " << builtinTypes->unknownType << " unknownType, genericX); + CHECK_MESSAGE(usingChildScope.isSubtype, "Expected " << builtinTypes->unknownType << " <: " << genericX); + + Subtyping grandChildSubtyping{mkSubtyping(grandChildScope)}; + + SubtypingResult usingGrandChildScope = grandChildSubtyping.isSubtype(builtinTypes->unknownType, genericX); + CHECK_MESSAGE(usingGrandChildScope.isSubtype, "Expected " << builtinTypes->unknownType << " <: " << genericX); +} + /* * (A) -> A <: (X) -> X * A can be bound to X. diff --git a/tests/TypeInfer.operators.test.cpp b/tests/TypeInfer.operators.test.cpp index 6a551811..c588eaba 100644 --- a/tests/TypeInfer.operators.test.cpp +++ b/tests/TypeInfer.operators.test.cpp @@ -1067,8 +1067,9 @@ local w = c and 1 CHECK("false | number" == toString(requireType("z"))); else CHECK("boolean | number" == toString(requireType("z"))); // 'false' widened to boolean + if (FFlag::DebugLuauDeferredConstraintResolution) - CHECK("((false?) & a) | number" == toString(requireType("w"))); + CHECK("((false?) & unknown) | number" == toString(requireType("w"))); else CHECK("(boolean | number)?" == toString(requireType("w"))); } diff --git a/tests/TypeInfer.refinements.test.cpp b/tests/TypeInfer.refinements.test.cpp index c0dbfce8..c42830aa 100644 --- a/tests/TypeInfer.refinements.test.cpp +++ b/tests/TypeInfer.refinements.test.cpp @@ -308,7 +308,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "typeguard_in_if_condition_position") else CHECK_EQ("number", toString(requireTypeAtPosition({3, 26}))); CHECK_EQ("number", toString(requireTypeAtPosition({6, 26}))); - } TEST_CASE_FIXTURE(BuiltinsFixture, "typeguard_in_assert_position") diff --git a/tests/TypeInfer.tables.test.cpp b/tests/TypeInfer.tables.test.cpp index 315798c2..aea38253 100644 --- a/tests/TypeInfer.tables.test.cpp +++ b/tests/TypeInfer.tables.test.cpp @@ -3788,4 +3788,40 @@ TEST_CASE_FIXTURE(Fixture, "cyclic_shifted_tables") LUAU_REQUIRE_NO_ERRORS(result); } + +TEST_CASE_FIXTURE(Fixture, "cli_84607_missing_prop_in_array_or_dict") +{ + ScopedFastFlag sff{"LuauFixIndexerSubtypingOrdering", true}; + + CheckResult result = check(R"( + type Thing = { name: string, prop: boolean } + + local arrayOfThings : {Thing} = { + { name = "a" } + } + + local dictOfThings : {[string]: Thing} = { + a = { name = "a" } + } + )"); + + LUAU_REQUIRE_ERROR_COUNT(2, result); + + TypeError& err1 = result.errors[0]; + MissingProperties* error1 = get(err1); + REQUIRE(error1); + REQUIRE(error1->properties.size() == 1); + + CHECK_EQ("prop", error1->properties[0]); + + TypeError& err2 = result.errors[1]; + TypeMismatch* mismatch = get(err2); + REQUIRE(mismatch); + MissingProperties* error2 = get(*mismatch->error); + REQUIRE(error2); + REQUIRE(error2->properties.size() == 1); + + CHECK_EQ("prop", error2->properties[0]); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.test.cpp b/tests/TypeInfer.test.cpp index aeabf0ac..2d34fc7f 100644 --- a/tests/TypeInfer.test.cpp +++ b/tests/TypeInfer.test.cpp @@ -1428,7 +1428,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "be_sure_to_use_active_txnlog_when_evaluating LUAU_REQUIRE_ERRORS(result); - for (const auto& e: result.errors) + for (const auto& e : result.errors) CHECK(5 == e.location.begin.line); } diff --git a/tests/Unifier2.test.cpp b/tests/Unifier2.test.cpp index 363c8109..2e6cf3b6 100644 --- a/tests/Unifier2.test.cpp +++ b/tests/Unifier2.test.cpp @@ -81,18 +81,12 @@ TEST_CASE_FIXTURE(Unifier2Fixture, "T <: U") TEST_CASE_FIXTURE(Unifier2Fixture, "(string) -> () <: (X) -> Y...") { - TypeId stringToUnit = arena.addType(FunctionType{ - arena.addTypePack({builtinTypes.stringType}), - arena.addTypePack({}) - }); + TypeId stringToUnit = arena.addType(FunctionType{arena.addTypePack({builtinTypes.stringType}), arena.addTypePack({})}); auto [x, xFree] = freshType(); TypePackId y = arena.freshTypePack(&scope); - TypeId xToY = arena.addType(FunctionType{ - arena.addTypePack({x}), - y - }); + TypeId xToY = arena.addType(FunctionType{arena.addTypePack({x}), y}); u2.unify(stringToUnit, xToY); @@ -105,4 +99,54 @@ TEST_CASE_FIXTURE(Unifier2Fixture, "(string) -> () <: (X) -> Y...") CHECK(!yPack->tail); } +TEST_CASE_FIXTURE(Unifier2Fixture, "generalize_a_type_that_is_bounded_by_another_generalizable_type") +{ + auto [t1, ft1] = freshType(); + auto [t2, ft2] = freshType(); + + // t2 <: t1 <: unknown + // unknown <: t2 <: t1 + + ft1->lowerBound = t2; + ft2->upperBound = t1; + ft2->lowerBound = builtinTypes.unknownType; + + auto t2generalized = u2.generalize(NotNull{&scope}, t2); + REQUIRE(t2generalized); + + CHECK(follow(t1) == follow(t2)); + + auto t1generalized = u2.generalize(NotNull{&scope}, t1); + REQUIRE(t1generalized); + + CHECK(builtinTypes.unknownType == follow(t1)); + CHECK(builtinTypes.unknownType == follow(t2)); +} + +// Same as generalize_a_type_that_is_bounded_by_another_generalizable_type +// except that we generalize the types in the opposite order +TEST_CASE_FIXTURE(Unifier2Fixture, "generalize_a_type_that_is_bounded_by_another_generalizable_type_in_reverse_order") +{ + auto [t1, ft1] = freshType(); + auto [t2, ft2] = freshType(); + + // t2 <: t1 <: unknown + // unknown <: t2 <: t1 + + ft1->lowerBound = t2; + ft2->upperBound = t1; + ft2->lowerBound = builtinTypes.unknownType; + + auto t1generalized = u2.generalize(NotNull{&scope}, t1); + REQUIRE(t1generalized); + + CHECK(follow(t1) == follow(t2)); + + auto t2generalized = u2.generalize(NotNull{&scope}, t2); + REQUIRE(t2generalized); + + CHECK(builtinTypes.unknownType == follow(t1)); + CHECK(builtinTypes.unknownType == follow(t2)); +} + TEST_SUITE_END(); diff --git a/tools/faillist.txt b/tools/faillist.txt index f179a63f..69790ba5 100644 --- a/tools/faillist.txt +++ b/tools/faillist.txt @@ -3,7 +3,6 @@ AnnotationTests.two_type_params AstQuery.last_argument_function_call_type AutocompleteTest.anonymous_autofilled_generic_on_argument_type_pack_vararg AutocompleteTest.anonymous_autofilled_generic_type_pack_vararg -AutocompleteTest.autocomplete_if_else_regression AutocompleteTest.autocomplete_interpolated_string_as_singleton AutocompleteTest.autocomplete_oop_implicit_self AutocompleteTest.autocomplete_response_perf1 @@ -92,8 +91,6 @@ IntersectionTypes.table_intersection_write_sealed_indirect IntersectionTypes.table_write_sealed_indirect Normalize.negations_of_tables Normalize.specific_functions_cannot_be_negated -ParserTests.parse_nesting_based_end_detection -ParserTests.parse_nesting_based_end_detection_single_line ProvisionalTests.assign_table_with_refined_property_with_a_similar_type_is_illegal ProvisionalTests.do_not_ice_when_trying_to_pick_first_of_generic_type_pack ProvisionalTests.error_on_eq_metamethod_returning_a_type_other_than_boolean @@ -124,13 +121,13 @@ RefinementTest.x_as_any_if_x_is_instance_elseif_x_is_table TableTests.a_free_shape_can_turn_into_a_scalar_if_it_is_compatible TableTests.a_free_shape_cannot_turn_into_a_scalar_if_it_is_not_compatible TableTests.call_method -TableTests.call_method_with_explicit_self_argument TableTests.cannot_augment_sealed_table TableTests.cannot_change_type_of_unsealed_table_prop TableTests.casting_sealed_tables_with_props_into_table_with_indexer TableTests.casting_tables_with_props_into_table_with_indexer4 TableTests.casting_unsealed_tables_with_props_into_table_with_indexer TableTests.checked_prop_too_early +TableTests.cli_84607_missing_prop_in_array_or_dict TableTests.cyclic_shifted_tables TableTests.defining_a_method_for_a_local_sealed_table_must_fail TableTests.defining_a_self_method_for_a_local_sealed_table_must_fail @@ -139,6 +136,7 @@ TableTests.dont_crash_when_setmetatable_does_not_produce_a_metatabletypevar TableTests.dont_extend_unsealed_tables_in_rvalue_position TableTests.dont_hang_when_trying_to_look_up_in_cyclic_metatable_index TableTests.dont_leak_free_table_props +TableTests.dont_quantify_table_that_belongs_to_outer_scope TableTests.dont_suggest_exact_match_keys TableTests.error_detailed_metatable_prop TableTests.explicitly_typed_table @@ -154,19 +152,21 @@ TableTests.inequality_operators_imply_exactly_matching_types TableTests.infer_array_2 TableTests.infer_indexer_from_value_property_in_literal TableTests.infer_type_when_indexing_from_a_table_indexer -TableTests.inferred_properties_of_a_table_should_start_with_the_same_TypeLevel_of_that_table TableTests.inferred_return_type_of_free_table TableTests.instantiate_table_cloning_3 TableTests.leaking_bad_metatable_errors TableTests.less_exponential_blowup_please TableTests.missing_metatable_for_sealed_tables_do_not_get_inferred TableTests.mixed_tables_with_implicit_numbered_keys +TableTests.ok_to_add_property_to_free_table TableTests.ok_to_provide_a_subtype_during_construction TableTests.okay_to_add_property_to_unsealed_tables_by_assignment -TableTests.okay_to_add_property_to_unsealed_tables_by_function_call -TableTests.only_ascribe_synthetic_names_at_module_scope TableTests.oop_indexer_works TableTests.oop_polymorphic +TableTests.pass_a_union_of_tables_to_a_function_that_requires_a_table +TableTests.pass_a_union_of_tables_to_a_function_that_requires_a_table_2 +TableTests.pass_incompatible_union_to_a_generic_table_without_crashing +TableTests.passing_compatible_unions_to_a_generic_table_without_crashing TableTests.quantify_even_that_table_was_never_exported_at_all TableTests.quantify_metatables_of_metatables_of_table TableTests.quantifying_a_bound_var_works @@ -185,7 +185,6 @@ TableTests.table_unification_4 TableTests.type_mismatch_on_massive_table_is_cut_short TableTests.used_colon_instead_of_dot TableTests.used_dot_instead_of_colon -TableTests.used_dot_instead_of_colon_but_correctly TableTests.when_augmenting_an_unsealed_table_with_an_indexer_apply_the_correct_scope_to_the_indexer_type TableTests.wrong_assign_does_hit_indexer ToDot.function @@ -215,6 +214,7 @@ TypeAliases.type_alias_of_an_imported_recursive_generic_type TypeFamilyTests.family_as_fn_arg TypeFamilyTests.table_internal_families TypeFamilyTests.unsolvable_family +TypeInfer.be_sure_to_use_active_txnlog_when_evaluating_a_variadic_overload TypeInfer.bidirectional_checking_of_higher_order_function TypeInfer.check_type_infer_recursion_count TypeInfer.cli_39932_use_unifier_in_ensure_methods @@ -226,21 +226,24 @@ TypeInfer.fuzz_free_table_type_change_during_index_check TypeInfer.infer_assignment_value_types_mutable_lval TypeInfer.infer_locals_via_assignment_from_its_call_site TypeInfer.no_stack_overflow_from_isoptional -TypeInfer.recursive_function_that_invokes_itself_with_a_refinement_of_its_parameter TypeInfer.recursive_function_that_invokes_itself_with_a_refinement_of_its_parameter_2 TypeInfer.tc_after_error_recovery_no_replacement_name_in_error TypeInfer.type_infer_cache_limit_normalizer TypeInfer.type_infer_recursion_limit_no_ice TypeInfer.type_infer_recursion_limit_normalizer TypeInferAnyError.can_subscript_any +TypeInferAnyError.for_in_loop_iterator_is_any TypeInferAnyError.for_in_loop_iterator_is_any2 +TypeInferAnyError.for_in_loop_iterator_is_error +TypeInferAnyError.for_in_loop_iterator_is_error2 TypeInferAnyError.for_in_loop_iterator_returns_any +TypeInferAnyError.for_in_loop_iterator_returns_any2 TypeInferAnyError.intersection_of_any_can_have_props TypeInferAnyError.replace_every_free_type_when_unifying_a_complex_function_with_any TypeInferAnyError.union_of_types_regression_test -TypeInferClasses.can_read_prop_of_base_class_using_string TypeInferClasses.class_type_mismatch_with_name_conflict TypeInferClasses.index_instance_property +TypeInferClasses.table_class_unification_reports_sane_errors_for_missing_properties TypeInferFunctions.cannot_hoist_interior_defns_into_signature TypeInferFunctions.dont_assert_when_the_tarjan_limit_is_exceeded_during_generalization TypeInferFunctions.dont_infer_parameter_types_for_functions_from_their_call_site @@ -254,10 +257,10 @@ TypeInferFunctions.higher_order_function_2 TypeInferFunctions.higher_order_function_4 TypeInferFunctions.improved_function_arg_mismatch_errors TypeInferFunctions.infer_anonymous_function_arguments +TypeInferFunctions.infer_anonymous_function_arguments_outside_call TypeInferFunctions.infer_generic_function_function_argument TypeInferFunctions.infer_generic_function_function_argument_overloaded TypeInferFunctions.infer_generic_lib_function_function_argument -TypeInferFunctions.infer_anonymous_function_arguments_outside_call TypeInferFunctions.infer_that_function_does_not_return_a_table TypeInferFunctions.luau_subtyping_is_np_hard TypeInferFunctions.no_lossy_function_type @@ -269,21 +272,22 @@ TypeInferFunctions.too_few_arguments_variadic_generic2 TypeInferFunctions.too_many_arguments_error_location TypeInferFunctions.too_many_return_values_in_parentheses TypeInferFunctions.too_many_return_values_no_function -TypeInferFunctions.toposort_doesnt_break_mutual_recursion -TypeInferFunctions.vararg_function_is_quantified TypeInferLoops.cli_68448_iterators_need_not_accept_nil TypeInferLoops.dcr_iteration_on_never_gives_never TypeInferLoops.for_in_loop TypeInferLoops.for_in_loop_error_on_factory_not_returning_the_right_amount_of_values +TypeInferLoops.for_in_loop_on_error TypeInferLoops.for_in_loop_with_custom_iterator TypeInferLoops.for_in_loop_with_incompatible_args_to_iterator TypeInferLoops.for_in_loop_with_next TypeInferLoops.ipairs_produces_integral_indices TypeInferLoops.iteration_regression_issue_69967_alt +TypeInferLoops.loop_iter_basic TypeInferLoops.loop_iter_metamethod_nil TypeInferLoops.loop_iter_metamethod_ok_with_inference TypeInferLoops.loop_iter_trailing_nil TypeInferLoops.unreachable_code_after_infinite_loop +TypeInferLoops.varlist_declared_by_for_in_loop_should_be_free TypeInferModules.do_not_modify_imported_types_5 TypeInferModules.module_type_conflict TypeInferModules.module_type_conflict_instantiated @@ -294,7 +298,6 @@ TypeInferOOP.inferring_hundreds_of_self_calls_should_not_suffocate_memory TypeInferOOP.methods_are_topologically_sorted TypeInferOperators.and_binexps_dont_unify TypeInferOperators.cli_38355_recursive_union -TypeInferOperators.compound_assign_mismatch_metatable TypeInferOperators.concat_op_on_string_lhs_and_free_rhs TypeInferOperators.disallow_string_and_types_without_metatables_from_arithmetic_binary_ops TypeInferOperators.luau_polyfill_is_array From 309001020a784a9f18acc3136146b0ac4fd57b52 Mon Sep 17 00:00:00 2001 From: Arseny Kapoulkine Date: Sat, 16 Sep 2023 12:21:09 +0200 Subject: [PATCH 20/20] Update benchmark.yml Update apt-get cache before installing valgrind as it looks like the default cache got out of date. --- .github/workflows/benchmark.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index c7531608..7a11fbe1 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -25,6 +25,7 @@ jobs: - name: Install valgrind run: | + sudo apt-get update sudo apt-get install valgrind - name: Build Luau (gcc)