From 47ad768c69ca1995fa9c2fb3b18fab6d0f02527b Mon Sep 17 00:00:00 2001 From: Lily Brown Date: Sat, 30 Mar 2024 16:14:44 -0700 Subject: [PATCH] Sync to upstream/release/619 (#1218) # What's Changed ## New Type Solver - Many fixes to crashes, assertions, and hangs - Binary type family aliases now have a default parameter - Added a debug check for unsolved types escaping the constraint solver - Overloaded functions are no longer inferred - Unification creates additional subtyping constraints for blocked types - Attempt to guess the result type for type families that are too large to resolve timely ## Native Code Generation - Fixed `IrCmd::CHECK_TRUTHY` lowering in a specific case - Detailed compilation errors are now supported - More work on the new allocator --- # Internal Contributors Co-authored-by: Aaron Weiss Co-authored-by: Alexander McCord Co-authored-by: Andy Friesen Co-authored-by: James McNellis Co-authored-by: Lily Brown Co-authored-by: Vyacheslav Egorov Co-authored-by: Vighnesh Vijay --- Analysis/include/Luau/Constraint.h | 19 +- Analysis/include/Luau/ConstraintGenerator.h | 28 +- Analysis/include/Luau/ConstraintSolver.h | 35 +- Analysis/include/Luau/Type.h | 1 + .../include/Luau/TypeFamilyReductionGuesser.h | 6 +- Analysis/include/Luau/Unifier2.h | 6 +- Analysis/include/Luau/VisitType.h | 8 + Analysis/src/BuiltinDefinitions.cpp | 14 +- Analysis/src/ConstraintGenerator.cpp | 240 +++++++------ Analysis/src/ConstraintSolver.cpp | 321 +++++++++--------- Analysis/src/Def.cpp | 4 + Analysis/src/Frontend.cpp | 96 +++++- Analysis/src/Module.cpp | 3 - Analysis/src/Normalize.cpp | 9 + Analysis/src/OverloadResolution.cpp | 42 ++- Analysis/src/Subtyping.cpp | 2 +- Analysis/src/ToString.cpp | 52 ++- Analysis/src/TypeChecker2.cpp | 2 +- Analysis/src/TypeFamily.cpp | 78 ++++- Analysis/src/TypeFamilyReductionGuesser.cpp | 44 ++- Analysis/src/Unifier2.cpp | 80 ++++- CodeGen/include/Luau/CodeGen.h | 23 +- CodeGen/include/Luau/NativeProtoExecData.h | 1 + CodeGen/src/CodeBlockUnwind.cpp | 8 + CodeGen/src/CodeGen.cpp | 168 ++++++++- CodeGen/src/CodeGenA64.cpp | 34 ++ CodeGen/src/CodeGenA64.h | 2 + CodeGen/src/CodeGenContext.cpp | 226 ++++++++++++ CodeGen/src/CodeGenContext.h | 114 +++++++ CodeGen/src/CodeGenX64.cpp | 34 ++ CodeGen/src/CodeGenX64.h | 2 + CodeGen/src/IrLoweringA64.cpp | 145 +------- CodeGen/src/IrLoweringX64.cpp | 49 +-- CodeGen/src/IrLoweringX64.h | 1 - CodeGen/src/IrTranslation.cpp | 13 +- CodeGen/src/NativeProtoExecData.cpp | 12 +- CodeGen/src/NativeState.cpp | 78 +++++ CodeGen/src/NativeState.h | 1 + CodeGen/src/OptimizeConstProp.cpp | 82 +++-- CodeGen/src/OptimizeDeadStore.cpp | 14 +- CodeGen/src/lcodegen.cpp | 7 +- Common/include/Luau/Common.h | 2 +- Sources.cmake | 22 +- VM/src/ldebug.cpp | 12 +- tests/Conformance.test.cpp | 40 ++- tests/DataFlowGraph.test.cpp | 27 ++ tests/IrBuilder.test.cpp | 28 +- tests/IrLowering.test.cpp | 11 - tests/Subtyping.test.cpp | 20 ++ tests/ToString.test.cpp | 37 +- tests/TypeFamily.test.cpp | 15 + tests/TypeInfer.functions.test.cpp | 24 +- tests/TypeInfer.generics.test.cpp | 10 +- tests/TypeInfer.loops.test.cpp | 16 + tests/TypeInfer.refinements.test.cpp | 18 + tests/TypeInfer.tables.test.cpp | 40 ++- tests/TypeInfer.typestates.test.cpp | 1 - tests/conformance/native.lua | 35 ++ tools/faillist.txt | 17 +- 59 files changed, 1853 insertions(+), 626 deletions(-) create mode 100644 CodeGen/src/CodeGenContext.cpp create mode 100644 CodeGen/src/CodeGenContext.h diff --git a/Analysis/include/Luau/Constraint.h b/Analysis/include/Luau/Constraint.h index 3ab54954..74160c33 100644 --- a/Analysis/include/Luau/Constraint.h +++ b/Analysis/include/Luau/Constraint.h @@ -224,7 +224,6 @@ struct HasIndexerConstraint // If the table is a free or unsealed table, we augment it with a new indexer. struct SetIndexerConstraint { - TypeId resultType; TypeId subjectType; TypeId indexType; TypeId propType; @@ -256,6 +255,20 @@ struct UnpackConstraint bool resultIsLValue = false; }; +// resultType ~ unpack sourceType +// +// The same as UnpackConstraint, but specialized for a pair of types as opposed to packs. +struct Unpack1Constraint +{ + TypeId resultType; + TypeId sourceType; + + // UnpackConstraint is sometimes used to resolve the types of assignments. + // When this is the case, any LocalTypes in resultPack can have their + // domains extended by the corresponding type from sourcePack. + bool resultIsLValue = false; +}; + // resultType ~ T0 op T1 op ... op TN // // op is either union or intersection. If any of the input types are blocked, @@ -290,8 +303,8 @@ struct ReducePackConstraint using ConstraintV = Variant; + SetPropConstraint, HasIndexerConstraint, SetIndexerConstraint, SingletonOrTopTypeConstraint, UnpackConstraint, Unpack1Constraint, + SetOpConstraint, ReduceConstraint, ReducePackConstraint, EqualityConstraint>; struct Constraint { diff --git a/Analysis/include/Luau/ConstraintGenerator.h b/Analysis/include/Luau/ConstraintGenerator.h index dec8f113..48a1b77a 100644 --- a/Analysis/include/Luau/ConstraintGenerator.h +++ b/Analysis/include/Luau/ConstraintGenerator.h @@ -95,6 +95,10 @@ struct ConstraintGenerator // will enqueue them during solving. std::vector unqueuedConstraints; + // Type family instances created by the generator. This is used to ensure + // that these instances are reduced fully by the solver. + std::vector familyInstances; + // The private scope of type aliases for which the type parameters belong to. DenseHashMap astTypeAliasDefiningScopes{nullptr}; @@ -254,16 +258,18 @@ private: Inference check(const ScopePtr& scope, AstExprTable* expr, std::optional expectedType); std::tuple checkBinary(const ScopePtr& scope, AstExprBinary* binary, std::optional expectedType); - /** - * Generate constraints to assign assignedTy to the expression expr - * @returns the type of the expression. This may or may not be assignedTy itself. - */ - std::optional checkLValue(const ScopePtr& scope, AstExpr* expr, TypeId assignedTy, bool transform); - std::optional checkLValue(const ScopePtr& scope, AstExprLocal* local, TypeId assignedTy, bool transform); - std::optional checkLValue(const ScopePtr& scope, AstExprGlobal* global, TypeId assignedTy); - std::optional checkLValue(const ScopePtr& scope, AstExprIndexName* indexName, TypeId assignedTy); - std::optional checkLValue(const ScopePtr& scope, AstExprIndexExpr* indexExpr, TypeId assignedTy); - TypeId updateProperty(const ScopePtr& scope, AstExpr* expr, TypeId assignedTy); + struct LValueBounds + { + std::optional annotationTy; + std::optional assignedTy; + }; + + LValueBounds checkLValue(const ScopePtr& scope, AstExpr* expr, bool transform); + LValueBounds checkLValue(const ScopePtr& scope, AstExprLocal* local, bool transform); + LValueBounds checkLValue(const ScopePtr& scope, AstExprGlobal* global); + LValueBounds checkLValue(const ScopePtr& scope, AstExprIndexName* indexName); + LValueBounds checkLValue(const ScopePtr& scope, AstExprIndexExpr* indexExpr); + LValueBounds updateProperty(const ScopePtr& scope, AstExpr* expr); struct FunctionSignature { @@ -370,6 +376,8 @@ private: * yields a vector of size 1, with value: [number | string] */ std::vector> getExpectedCallTypesForFunctionOverloads(const TypeId fnType); + + TypeId createFamilyInstance(TypeFamilyInstanceType instance, const ScopePtr& scope, Location location); }; /** Borrow a vector of pointers from a vector of owning pointers to constraints. diff --git a/Analysis/include/Luau/ConstraintSolver.h b/Analysis/include/Luau/ConstraintSolver.h index 4eb6bcc7..3d00ec06 100644 --- a/Analysis/include/Luau/ConstraintSolver.h +++ b/Analysis/include/Luau/ConstraintSolver.h @@ -25,6 +25,8 @@ enum class ValueContext; struct DcrLogger; +class AstExpr; + // TypeId, TypePackId, or Constraint*. It is impossible to know which, but we // never dereference this pointer. using BlockedConstraintId = Variant; @@ -73,6 +75,9 @@ struct ConstraintSolver // A constraint can be both blocked and unsolved, for instance. std::vector> unsolvedConstraints; + // This is a set of type families that need to be reduced after all constraints have been dispatched. + DenseHashSet familyInstances{nullptr}; + // A mapping of constraint pointer to how many things the constraint is // blocked on. Can be empty or 0 for constraints that are not blocked on // anything. @@ -130,14 +135,23 @@ struct ConstraintSolver bool tryDispatch(const FunctionCheckConstraint& c, NotNull constraint); bool tryDispatch(const PrimitiveTypeConstraint& c, NotNull constraint); bool tryDispatch(const HasPropConstraint& c, NotNull constraint); - bool tryDispatch(const SetPropConstraint& c, NotNull constraint, bool force); + bool tryDispatch(const SetPropConstraint& c, NotNull constraint); bool tryDispatchHasIndexer(int& recursionDepth, NotNull constraint, TypeId subjectType, TypeId indexType, TypeId resultType); bool tryDispatch(const HasIndexerConstraint& c, NotNull constraint); + /// (dispatched, found) where + /// - dispatched: this constraint can be considered having dispatched. + /// - found: true if adding an indexer for a particular type was allowed. + std::pair tryDispatchSetIndexer(NotNull constraint, TypeId subjectType, TypeId indexType, TypeId propType, bool expandFreeTypeBounds); bool tryDispatch(const SetIndexerConstraint& c, NotNull constraint, bool force); + bool tryDispatch(const SingletonOrTopTypeConstraint& c, NotNull constraint); + + bool tryDispatchUnpack1(NotNull constraint, TypeId resultType, TypeId sourceType, bool resultIsLValue); bool tryDispatch(const UnpackConstraint& c, NotNull constraint); + bool tryDispatch(const Unpack1Constraint& c, NotNull constraint); + bool tryDispatch(const SetOpConstraint& c, NotNull constraint, bool force); bool tryDispatch(const ReduceConstraint& c, NotNull constraint, bool force); bool tryDispatch(const ReducePackConstraint& c, NotNull constraint, bool force); @@ -151,10 +165,10 @@ struct ConstraintSolver bool tryDispatchIterableFunction( TypeId nextTy, TypeId tableTy, TypeId firstIndexTy, const IterableConstraint& c, NotNull constraint, bool force); - std::pair, std::optional> lookupTableProp( - TypeId subjectType, const std::string& propName, ValueContext context, bool inConditional = false, bool suppressSimplification = false); - std::pair, std::optional> lookupTableProp( - TypeId subjectType, const std::string& propName, ValueContext context, bool inConditional, bool suppressSimplification, DenseHashSet& seen); + std::pair, std::optional> lookupTableProp(NotNull constraint, TypeId subjectType, + const std::string& propName, ValueContext context, bool inConditional = false, bool suppressSimplification = false); + std::pair, std::optional> lookupTableProp(NotNull constraint, TypeId subjectType, + const std::string& propName, ValueContext context, bool inConditional, bool suppressSimplification, DenseHashSet& seen); void block(NotNull target, NotNull constraint); /** @@ -242,17 +256,6 @@ struct ConstraintSolver */ bool hasUnresolvedConstraints(TypeId ty); - /** - * Creates a new Unifier and performs a single unification operation. - * - * @param subType the sub-type to unify. - * @param superType the super-type to unify. - * @returns true if the unification succeeded. False if the unification was - * too complex. - */ - template - bool unify(NotNull scope, Location location, TID subType, TID superType); - /** Attempts to unify subTy with superTy. If doing so would require unifying * BlockedTypes, fail and block the constraint on those BlockedTypes. * diff --git a/Analysis/include/Luau/Type.h b/Analysis/include/Luau/Type.h index 8e82ee8f..d03e1669 100644 --- a/Analysis/include/Luau/Type.h +++ b/Analysis/include/Luau/Type.h @@ -299,6 +299,7 @@ using MagicFunction = std::function>( struct MagicFunctionCallContext { NotNull solver; + NotNull constraint; const class AstExprCall* callSite; TypePackId arguments; TypePackId result; diff --git a/Analysis/include/Luau/TypeFamilyReductionGuesser.h b/Analysis/include/Luau/TypeFamilyReductionGuesser.h index 0092c317..9903e381 100644 --- a/Analysis/include/Luau/TypeFamilyReductionGuesser.h +++ b/Analysis/include/Luau/TypeFamilyReductionGuesser.h @@ -13,6 +13,7 @@ #include "Luau/TypeFwd.h" #include "Luau/VisitType.h" #include "Luau/NotNull.h" +#include "TypeArena.h" namespace Luau { @@ -42,11 +43,14 @@ struct TypeFamilyReductionGuesser DenseHashSet cyclicInstances{nullptr}; // Utilities + NotNull arena; NotNull builtins; NotNull normalizer; - TypeFamilyReductionGuesser(NotNull builtins, NotNull normalizer); + TypeFamilyReductionGuesser(NotNull arena, NotNull builtins, NotNull normalizer); + std::optional guess(TypeId typ); + std::optional guess(TypePackId typ); TypeFamilyReductionGuessResult guessTypeFamilyReductionForFunction(const AstExprFunction& expr, const FunctionType* ftv, TypeId retTy); private: diff --git a/Analysis/include/Luau/Unifier2.h b/Analysis/include/Luau/Unifier2.h index 6728c0f0..3ea590e6 100644 --- a/Analysis/include/Luau/Unifier2.h +++ b/Analysis/include/Luau/Unifier2.h @@ -2,11 +2,12 @@ #pragma once +#include "Luau/Constraint.h" #include "Luau/DenseHash.h" #include "Luau/NotNull.h" -#include "Luau/TypePairHash.h" #include "Luau/TypeCheckLimits.h" #include "Luau/TypeFwd.h" +#include "Luau/TypePairHash.h" #include #include @@ -46,6 +47,8 @@ struct Unifier2 int recursionCount = 0; int recursionLimit = 0; + std::vector incompleteSubtypes; + Unifier2(NotNull arena, NotNull builtinTypes, NotNull scope, NotNull ice); /** Attempt to commit the subtype relation subTy <: superTy to the type @@ -61,6 +64,7 @@ struct Unifier2 * free TypePack to another and encounter an occurs check violation. */ bool unify(TypeId subTy, TypeId superTy); + bool unifyFreeWithType(TypeId subTy, TypeId superTy); bool unify(const LocalType* subTy, TypeId superFn); bool unify(TypeId subTy, const FunctionType* superFn); bool unify(const UnionType* subUnion, TypeId superTy); diff --git a/Analysis/include/Luau/VisitType.h b/Analysis/include/Luau/VisitType.h index 94510e32..40dccbd2 100644 --- a/Analysis/include/Luau/VisitType.h +++ b/Analysis/include/Luau/VisitType.h @@ -64,6 +64,9 @@ inline void unsee(DenseHashSet& seen, const void* tv) } // namespace visit_detail +// recursion counter is equivalent here, but we'd like a better name to express the intent. +using TypeFamilyDepthCounter = RecursionCounter; + template struct GenericTypeVisitor { @@ -72,6 +75,7 @@ struct GenericTypeVisitor Set seen; bool skipBoundTypes = false; int recursionCounter = 0; + int typeFamilyDepth = 0; GenericTypeVisitor() = default; @@ -400,6 +404,8 @@ struct GenericTypeVisitor } else if (auto tfit = get(ty)) { + TypeFamilyDepthCounter tfdc{&typeFamilyDepth}; + if (visit(ty, *tfit)) { for (TypeId p : tfit->typeArguments) @@ -460,6 +466,8 @@ struct GenericTypeVisitor visit(tp, *btp); else if (auto tfitp = get(tp)) { + TypeFamilyDepthCounter tfdc{&typeFamilyDepth}; + if (visit(tp, *tfitp)) { for (TypeId t : tfitp->typeArguments) diff --git a/Analysis/src/BuiltinDefinitions.cpp b/Analysis/src/BuiltinDefinitions.cpp index 3a7fd724..253f851b 100644 --- a/Analysis/src/BuiltinDefinitions.cpp +++ b/Analysis/src/BuiltinDefinitions.cpp @@ -431,7 +431,7 @@ static bool dcrMagicFunctionFormat(MagicFunctionCallContext context) // 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]); + context.solver->unify(context.constraint, 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 @@ -561,7 +561,7 @@ static bool dcrMagicFunctionGmatch(MagicFunctionCallContext context) if (returnTypes.empty()) return false; - context.solver->unify(context.solver->rootScope, context.callSite->location, params[0], context.solver->builtinTypes->stringType); + context.solver->unify(context.constraint, params[0], context.solver->builtinTypes->stringType); const TypePackId emptyPack = arena->addTypePack({}); const TypePackId returnList = arena->addTypePack(returnTypes); @@ -630,13 +630,13 @@ static bool dcrMagicFunctionMatch(MagicFunctionCallContext context) if (returnTypes.empty()) return false; - context.solver->unify(context.solver->rootScope, context.callSite->location, params[0], context.solver->builtinTypes->stringType); + context.solver->unify(context.constraint, 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); + context.solver->unify(context.constraint, params[2], optionalNumber); const TypePackId returnList = arena->addTypePack(returnTypes); asMutable(context.result)->ty.emplace(returnList); @@ -733,17 +733,17 @@ static bool dcrMagicFunctionFind(MagicFunctionCallContext context) return false; } - context.solver->unify(context.solver->rootScope, context.callSite->location, params[0], builtinTypes->stringType); + context.solver->unify(context.constraint, 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); + context.solver->unify(context.constraint, params[2], optionalNumber); if (params.size() == 4 && context.callSite->args.size > plainIndex) - context.solver->unify(context.solver->rootScope, context.callSite->location, params[3], optionalBoolean); + context.solver->unify(context.constraint, params[3], optionalBoolean); returnTypes.insert(returnTypes.begin(), {optionalNumber, optionalNumber}); diff --git a/Analysis/src/ConstraintGenerator.cpp b/Analysis/src/ConstraintGenerator.cpp index 308f6c78..8b8ca062 100644 --- a/Analysis/src/ConstraintGenerator.cpp +++ b/Analysis/src/ConstraintGenerator.cpp @@ -525,12 +525,11 @@ void ConstraintGenerator::applyRefinements(const ScopePtr& scope, Location locat { if (mustDeferIntersection(ty) || mustDeferIntersection(dt)) { - TypeId resultType = arena->addType(TypeFamilyInstanceType{ + TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ NotNull{&kBuiltinTypeFamilies.refineFamily}, {ty, dt}, {}, - }); - addConstraint(scope, location, ReduceConstraint{resultType}); + }, scope, location); ty = resultType; } @@ -1005,8 +1004,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f else if (AstExprIndexName* indexName = function->name->as()) { Checkpoint check1 = checkpoint(this); - std::optional lvalueType = checkLValue(scope, indexName, generalizedType); - LUAU_ASSERT(lvalueType); + auto [_, lvalueType] = checkLValue(scope, indexName); Checkpoint check2 = checkpoint(this); forEachConstraint(check1, check2, this, [&excludeList](const ConstraintPtr& c) { @@ -1017,7 +1015,8 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f if (lvalueType && *lvalueType != generalizedType) { - addConstraint(scope, indexName->location, SubtypeConstraint{*lvalueType, generalizedType}); + LUAU_ASSERT(get(lvalueType)); + emplaceType(asMutable(*lvalueType), generalizedType); } } else if (AstExprError* err = function->name->as()) @@ -1110,14 +1109,17 @@ static void bindFreeType(TypeId a, TypeId b) ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatAssign* assign) { - std::vector assignees; - assignees.reserve(assign->vars.size); + std::vector upperBounds; + upperBounds.reserve(assign->vars.size); + + std::vector typeStates; + typeStates.reserve(assign->vars.size); + + Checkpoint lvalueBeginCheckpoint = checkpoint(this); size_t i = 0; for (AstExpr* lvalue : assign->vars) { - TypeId assignee = arena->addType(BlockedType{}); - // This is a really weird thing to do, but it's critically important for some kinds of // assignments with the current type state behavior. Consider this code: // local function f(l, r) @@ -1158,18 +1160,28 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatAssign* ass } } - checkLValue(scope, lvalue, assignee, transform); - assignees.push_back(assignee); + auto [upperBound, typeState] = checkLValue(scope, lvalue, transform); + upperBounds.push_back(upperBound.value_or(builtinTypes->unknownType)); + typeStates.push_back(typeState.value_or(builtinTypes->unknownType)); ++i; } + Checkpoint lvalueEndCheckpoint = checkpoint(this); + TypePackId resultPack = checkPack(scope, assign->values).tp; - auto c = addConstraint(scope, assign->location, UnpackConstraint{arena->addTypePack(assignees), resultPack, /*resultIsLValue*/ true}); - for (TypeId assignee : assignees) + auto uc = addConstraint(scope, assign->location, UnpackConstraint{arena->addTypePack(typeStates), resultPack, /*resultIsLValue*/ true}); + forEachConstraint(lvalueBeginCheckpoint, lvalueEndCheckpoint, this, [uc](const ConstraintPtr& constraint) { + uc->dependencies.push_back(NotNull{constraint.get()}); + }); + + auto psc = addConstraint(scope, assign->location, PackSubtypeConstraint{resultPack, arena->addTypePack(std::move(upperBounds))}); + psc->dependencies.push_back(uc); + + for (TypeId assignee : typeStates) { auto blocked = getMutable(assignee); - LUAU_ASSERT(blocked); - blocked->setOwner(c); + if (blocked && !blocked->getOwner()) + blocked->setOwner(uc); } return ControlFlow::None; @@ -1180,7 +1192,21 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatCompoundAss AstExprBinary binop = AstExprBinary{assign->location, assign->op, assign->var, assign->value}; TypeId resultTy = check(scope, &binop).ty; - checkLValue(scope, assign->var, resultTy, true); + auto [upperBound, typeState] = checkLValue(scope, assign->var, true); + + Constraint* sc = nullptr; + if (upperBound) + sc = addConstraint(scope, assign->location, SubtypeConstraint{resultTy, *upperBound}); + + if (typeState) + { + NotNull uc = addConstraint(scope, assign->location, Unpack1Constraint{*typeState, resultTy, /*resultIsLValue=*/ true}); + if (auto blocked = getMutable(*typeState); blocked && !blocked->getOwner()) + blocked->setOwner(uc); + + if (sc) + uc->dependencies.push_back(NotNull{sc}); + } DefId def = dfg->getDef(assign->var); scope->lvalueTypes[def] = resultTy; @@ -2024,32 +2050,29 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprUnary* unary) { case AstExprUnary::Op::Not: { - TypeId resultType = arena->addType(TypeFamilyInstanceType{ + TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ NotNull{&kBuiltinTypeFamilies.notFamily}, {operandType}, {}, - }); - addConstraint(scope, unary->location, ReduceConstraint{resultType}); + }, scope, unary->location); return Inference{resultType, refinementArena.negation(refinement)}; } case AstExprUnary::Op::Len: { - TypeId resultType = arena->addType(TypeFamilyInstanceType{ + TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ NotNull{&kBuiltinTypeFamilies.lenFamily}, {operandType}, {}, - }); - addConstraint(scope, unary->location, ReduceConstraint{resultType}); + }, scope, unary->location); return Inference{resultType, refinementArena.negation(refinement)}; } case AstExprUnary::Op::Minus: { - TypeId resultType = arena->addType(TypeFamilyInstanceType{ + TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ NotNull{&kBuiltinTypeFamilies.unmFamily}, {operandType}, {}, - }); - addConstraint(scope, unary->location, ReduceConstraint{resultType}); + }, scope, unary->location); return Inference{resultType, refinementArena.negation(refinement)}; } default: // msvc can't prove that this is exhaustive. @@ -2065,153 +2088,138 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprBinary* binar { case AstExprBinary::Op::Add: { - TypeId resultType = arena->addType(TypeFamilyInstanceType{ + TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ NotNull{&kBuiltinTypeFamilies.addFamily}, {leftType, rightType}, {}, - }); - addConstraint(scope, binary->location, ReduceConstraint{resultType}); + }, scope, binary->location); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::Sub: { - TypeId resultType = arena->addType(TypeFamilyInstanceType{ + TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ NotNull{&kBuiltinTypeFamilies.subFamily}, {leftType, rightType}, {}, - }); - addConstraint(scope, binary->location, ReduceConstraint{resultType}); + }, scope, binary->location); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::Mul: { - TypeId resultType = arena->addType(TypeFamilyInstanceType{ + TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ NotNull{&kBuiltinTypeFamilies.mulFamily}, {leftType, rightType}, {}, - }); - addConstraint(scope, binary->location, ReduceConstraint{resultType}); + }, scope, binary->location); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::Div: { - TypeId resultType = arena->addType(TypeFamilyInstanceType{ + TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ NotNull{&kBuiltinTypeFamilies.divFamily}, {leftType, rightType}, {}, - }); - addConstraint(scope, binary->location, ReduceConstraint{resultType}); + }, scope, binary->location); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::FloorDiv: { - TypeId resultType = arena->addType(TypeFamilyInstanceType{ + TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ NotNull{&kBuiltinTypeFamilies.idivFamily}, {leftType, rightType}, {}, - }); - addConstraint(scope, binary->location, ReduceConstraint{resultType}); + }, scope, binary->location); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::Pow: { - TypeId resultType = arena->addType(TypeFamilyInstanceType{ + TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ NotNull{&kBuiltinTypeFamilies.powFamily}, {leftType, rightType}, {}, - }); - addConstraint(scope, binary->location, ReduceConstraint{resultType}); + }, scope, binary->location); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::Mod: { - TypeId resultType = arena->addType(TypeFamilyInstanceType{ + TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ NotNull{&kBuiltinTypeFamilies.modFamily}, {leftType, rightType}, {}, - }); - addConstraint(scope, binary->location, ReduceConstraint{resultType}); + }, scope, binary->location); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::Concat: { - TypeId resultType = arena->addType(TypeFamilyInstanceType{ + TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ NotNull{&kBuiltinTypeFamilies.concatFamily}, {leftType, rightType}, {}, - }); - addConstraint(scope, binary->location, ReduceConstraint{resultType}); + }, scope, binary->location); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::And: { - TypeId resultType = arena->addType(TypeFamilyInstanceType{ + TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ NotNull{&kBuiltinTypeFamilies.andFamily}, {leftType, rightType}, {}, - }); - addConstraint(scope, binary->location, ReduceConstraint{resultType}); + }, scope, binary->location); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::Or: { - TypeId resultType = arena->addType(TypeFamilyInstanceType{ + TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ NotNull{&kBuiltinTypeFamilies.orFamily}, {leftType, rightType}, {}, - }); - addConstraint(scope, binary->location, ReduceConstraint{resultType}); + }, scope, binary->location); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::CompareLt: { - TypeId resultType = arena->addType(TypeFamilyInstanceType{ + TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ NotNull{&kBuiltinTypeFamilies.ltFamily}, {leftType, rightType}, {}, - }); - addConstraint(scope, binary->location, ReduceConstraint{resultType}); + }, scope, binary->location); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::CompareGe: { - TypeId resultType = arena->addType(TypeFamilyInstanceType{ + TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ NotNull{&kBuiltinTypeFamilies.ltFamily}, {rightType, leftType}, // lua decided that `__ge(a, b)` is instead just `__lt(b, a)` {}, - }); - addConstraint(scope, binary->location, ReduceConstraint{resultType}); + }, scope, binary->location); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::CompareLe: { - TypeId resultType = arena->addType(TypeFamilyInstanceType{ + TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ NotNull{&kBuiltinTypeFamilies.leFamily}, {leftType, rightType}, {}, - }); - addConstraint(scope, binary->location, ReduceConstraint{resultType}); + }, scope, binary->location); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::CompareGt: { - TypeId resultType = arena->addType(TypeFamilyInstanceType{ + TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ NotNull{&kBuiltinTypeFamilies.leFamily}, {rightType, leftType}, // lua decided that `__gt(a, b)` is instead just `__le(b, a)` {}, - }); - addConstraint(scope, binary->location, ReduceConstraint{resultType}); + }, scope, binary->location); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::CompareEq: case AstExprBinary::Op::CompareNe: { - TypeId resultType = arena->addType(TypeFamilyInstanceType{ + TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ NotNull{&kBuiltinTypeFamilies.eqFamily}, {leftType, rightType}, {}, - }); - addConstraint(scope, binary->location, ReduceConstraint{resultType}); + }, scope, binary->location); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::Op__Count: @@ -2365,31 +2373,29 @@ std::tuple ConstraintGenerator::checkBinary( } } -std::optional ConstraintGenerator::checkLValue(const ScopePtr& scope, AstExpr* expr, TypeId assignedTy, bool transform) +ConstraintGenerator::LValueBounds ConstraintGenerator::checkLValue(const ScopePtr& scope, AstExpr* expr, bool transform) { if (auto local = expr->as()) - return checkLValue(scope, local, assignedTy, transform); + return checkLValue(scope, local, transform); else if (auto global = expr->as()) - return checkLValue(scope, global, assignedTy); + return checkLValue(scope, global); else if (auto indexName = expr->as()) - return checkLValue(scope, indexName, assignedTy); + return checkLValue(scope, indexName); else if (auto indexExpr = expr->as()) - return checkLValue(scope, indexExpr, assignedTy); + return checkLValue(scope, indexExpr); else if (auto error = expr->as()) { check(scope, error); - return builtinTypes->errorRecoveryType(); + return {builtinTypes->errorRecoveryType(), builtinTypes->errorRecoveryType()}; } else ice->ice("checkLValue is inexhaustive"); } -std::optional ConstraintGenerator::checkLValue(const ScopePtr& scope, AstExprLocal* local, TypeId assignedTy, bool transform) +ConstraintGenerator::LValueBounds ConstraintGenerator::checkLValue(const ScopePtr& scope, AstExprLocal* local, bool transform) { std::optional annotatedTy = scope->lookup(local->local); LUAU_ASSERT(annotatedTy); - if (annotatedTy) - addConstraint(scope, local->location, SubtypeConstraint{assignedTy, *annotatedTy}); const DefId defId = dfg->getDef(local); std::optional ty = scope->lookupUnrefinedType(defId); @@ -2430,41 +2436,48 @@ std::optional ConstraintGenerator::checkLValue(const ScopePtr& scope, As scope->lvalueTypes[defId] = *ty; } + // TODO: Need to clip this, but this requires more code to be reworked first before we can clip this. + std::optional assignedTy; + if (transform) { - Constraint* owner = nullptr; - if (auto blocked = get(*ty)) - owner = blocked->getOwner(); + assignedTy = arena->addType(BlockedType{}); auto unpackC = addConstraint(scope, local->location, - UnpackConstraint{arena->addTypePack({*ty}), arena->addTypePack({assignedTy}), + UnpackConstraint{arena->addTypePack({*ty}), arena->addTypePack({*assignedTy}), /*resultIsLValue*/ true}); - if (owner) - unpackC->dependencies.push_back(NotNull{owner}); - else if (auto blocked = getMutable(*ty)) - blocked->setOwner(unpackC); + if (auto blocked = get(*ty)) + { + if (blocked->getOwner()) + unpackC->dependencies.push_back(NotNull{blocked->getOwner()}); + else if (auto blocked = getMutable(*ty)) + blocked->setOwner(unpackC); + } recordInferredBinding(local->local, *ty); } - - return ty; + return {annotatedTy, assignedTy}; } -std::optional ConstraintGenerator::checkLValue(const ScopePtr& scope, AstExprGlobal* global, TypeId assignedTy) +ConstraintGenerator::LValueBounds ConstraintGenerator::checkLValue(const ScopePtr& scope, AstExprGlobal* global) { - return scope->lookup(Symbol{global->name}); + std::optional annotatedTy = scope->lookup(Symbol{global->name}); + if (annotatedTy) + return {annotatedTy, arena->addType(BlockedType{})}; + else + return {annotatedTy, std::nullopt}; } -std::optional ConstraintGenerator::checkLValue(const ScopePtr& scope, AstExprIndexName* indexName, TypeId assignedTy) +ConstraintGenerator::LValueBounds ConstraintGenerator::checkLValue(const ScopePtr& scope, AstExprIndexName* indexName) { - return updateProperty(scope, indexName, assignedTy); + return updateProperty(scope, indexName); } -std::optional ConstraintGenerator::checkLValue(const ScopePtr& scope, AstExprIndexExpr* indexExpr, TypeId assignedTy) +ConstraintGenerator::LValueBounds ConstraintGenerator::checkLValue(const ScopePtr& scope, AstExprIndexExpr* indexExpr) { - return updateProperty(scope, indexExpr, assignedTy); + return updateProperty(scope, indexExpr); } /** @@ -2472,15 +2485,14 @@ std::optional ConstraintGenerator::checkLValue(const ScopePtr& scope, As * * If expr has the form name.a.b.c */ -TypeId ConstraintGenerator::updateProperty(const ScopePtr& scope, AstExpr* expr, TypeId assignedTy) +ConstraintGenerator::LValueBounds ConstraintGenerator::updateProperty(const ScopePtr& scope, AstExpr* expr) { // There are a bunch of cases where we realize that this is not the kind of // assignment that potentially changes the shape of a table. When we // encounter them, we call this to fall back and do the "usual thing." - auto fallback = [&]() { + auto fallback = [&]() -> LValueBounds { TypeId resTy = check(scope, expr).ty; - addConstraint(scope, expr->location, SubtypeConstraint{assignedTy, resTy}); - return resTy; + return {resTy, std::nullopt}; }; LUAU_ASSERT(expr->is() || expr->is()); @@ -2499,15 +2511,14 @@ TypeId ConstraintGenerator::updateProperty(const ScopePtr& scope, AstExpr* expr, // a.b.c[1] = 44 -- lvalue // a.b[4].c = 2 -- rvalue - TypeId resultType = arena->addType(BlockedType{}); TypeId subjectType = check(scope, indexExpr->expr).ty; TypeId indexType = check(scope, indexExpr->index).ty; - auto c = addConstraint(scope, expr->location, SetIndexerConstraint{resultType, subjectType, indexType, assignedTy}); - getMutable(resultType)->setOwner(c); + TypeId assignedTy = arena->addType(BlockedType{}); + addConstraint(scope, expr->location, SetIndexerConstraint{subjectType, indexType, assignedTy}); module->astTypes[expr] = assignedTy; - return assignedTy; + return {assignedTy, assignedTy}; } Symbol sym; @@ -2573,6 +2584,7 @@ TypeId ConstraintGenerator::updateProperty(const ScopePtr& scope, AstExpr* expr, std::vector segmentStrings(begin(segments), end(segments)); TypeId updatedType = arena->addType(BlockedType{}); + TypeId assignedTy = arena->addType(BlockedType{}); auto setC = addConstraint(scope, expr->location, SetPropConstraint{updatedType, subjectType, std::move(segmentStrings), assignedTy}); getMutable(updatedType)->setOwner(setC); @@ -2604,7 +2616,7 @@ TypeId ConstraintGenerator::updateProperty(const ScopePtr& scope, AstExpr* expr, } } - return assignedTy; + return {assignedTy, assignedTy}; } Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr, std::optional expectedType) @@ -3287,24 +3299,22 @@ void ConstraintGenerator::reportCodeTooComplex(Location location) TypeId ConstraintGenerator::makeUnion(const ScopePtr& scope, Location location, TypeId lhs, TypeId rhs) { - TypeId resultType = arena->addType(TypeFamilyInstanceType{ + TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ NotNull{&kBuiltinTypeFamilies.unionFamily}, {lhs, rhs}, {}, - }); - addConstraint(scope, location, ReduceConstraint{resultType}); + }, scope, location); return resultType; } TypeId ConstraintGenerator::makeIntersect(const ScopePtr& scope, Location location, TypeId lhs, TypeId rhs) { - TypeId resultType = arena->addType(TypeFamilyInstanceType{ + TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ NotNull{&kBuiltinTypeFamilies.intersectFamily}, {lhs, rhs}, {}, - }); - addConstraint(scope, location, ReduceConstraint{resultType}); + }, scope, location); return resultType; } @@ -3454,6 +3464,14 @@ std::vector> ConstraintGenerator::getExpectedCallTypesForF return expectedTypes; } +TypeId ConstraintGenerator::createFamilyInstance(TypeFamilyInstanceType instance, const ScopePtr& scope, Location location) +{ + TypeId result = arena->addType(std::move(instance)); + addConstraint(scope, location, ReduceConstraint{result}); + familyInstances.push_back(result); + return result; +} + std::vector> borrowConstraints(const std::vector& constraints) { std::vector> result; diff --git a/Analysis/src/ConstraintSolver.cpp b/Analysis/src/ConstraintSolver.cpp index 702b299d..7cbe88cd 100644 --- a/Analysis/src/ConstraintSolver.cpp +++ b/Analysis/src/ConstraintSolver.cpp @@ -273,9 +273,10 @@ struct InstantiationQueuer : TypeOnceVisitor return false; } - bool visit(TypeId ty, const TypeFamilyInstanceType& tfit) override + bool visit(TypeId ty, const TypeFamilyInstanceType&) override { solver->pushConstraint(scope, location, ReduceConstraint{ty}); + solver->familyInstances.insert(ty); return true; } @@ -454,6 +455,16 @@ void ConstraintSolver::run() progress |= runSolverPass(true); } while (progress); + for (TypeId instance : familyInstances) + { + if (FFlag::DebugLuauLogSolver) + printf("Post-solve family reduction of %s\n", toString(instance).c_str()); + + TypeCheckLimits limits{}; + FamilyGraphReductionResult result = + reduceFamilies(instance, Location{}, TypeFamilyContext{arena, builtinTypes, rootScope, normalizer, NotNull{&iceReporter}, NotNull{&limits}}, false); + } + if (FFlag::DebugLuauLogSolver) { dumpBindings(rootScope, opts); @@ -479,29 +490,6 @@ struct TypeAndLocation Location location; }; -struct FreeTypeSearcher : TypeOnceVisitor -{ - VecDeque* result; - Location location; - - FreeTypeSearcher(VecDeque* result, Location location) - : result(result) - , location(location) - { - } - - bool visit(TypeId ty, const FreeType&) override - { - result->push_back({ty, location}); - return false; - } - - bool visit(TypeId, const ClassType&) override - { - return false; - } -}; - } // namespace bool ConstraintSolver::tryDispatch(NotNull constraint, bool force) @@ -534,7 +522,7 @@ bool ConstraintSolver::tryDispatch(NotNull constraint, bool fo else if (auto hpc = get(*constraint)) success = tryDispatch(*hpc, constraint); else if (auto spc = get(*constraint)) - success = tryDispatch(*spc, constraint, force); + success = tryDispatch(*spc, constraint); else if (auto spc = get(*constraint)) success = tryDispatch(*spc, constraint); else if (auto spc = get(*constraint)) @@ -543,6 +531,8 @@ bool ConstraintSolver::tryDispatch(NotNull constraint, bool fo success = tryDispatch(*sottc, constraint); else if (auto uc = get(*constraint)) success = tryDispatch(*uc, constraint); + else if (auto uc = get(*constraint)) + success = tryDispatch(*uc, constraint); else if (auto soc = get(*constraint)) success = tryDispatch(*soc, constraint, force); else if (auto rc = get(*constraint)) @@ -626,7 +616,10 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNulllocation); for (TypeId ty : c.interiorTypes) + { u2.generalize(ty); + unblock(ty, constraint->location); + } return true; } @@ -1075,7 +1068,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNulldcrMagicFunction) - usedMagic = ftv->dcrMagicFunction(MagicFunctionCallContext{NotNull(this), c.callSite, c.argsPack, result}); + usedMagic = ftv->dcrMagicFunction(MagicFunctionCallContext{NotNull{this}, constraint, c.callSite, c.argsPack, result}); if (ftv->dcrMagicRefinement) ftv->dcrMagicRefinement(MagicRefinementContext{constraint->scope, c.callSite, c.discriminantTypes}); @@ -1292,7 +1285,7 @@ bool ConstraintSolver::tryDispatch(const HasPropConstraint& c, NotNull(subjectType) || get(subjectType)) return block(subjectType, constraint); - auto [blocked, result] = lookupTableProp(subjectType, c.prop, c.context, c.inConditional, c.suppressSimplification); + auto [blocked, result] = lookupTableProp(constraint, subjectType, c.prop, c.context, c.inConditional, c.suppressSimplification); if (!blocked.empty()) { for (TypeId blocked : blocked) @@ -1381,7 +1374,7 @@ static void updateTheTableType( tt->props[lastSegment].setType(replaceTy); } -bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull constraint, bool force) +bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull constraint) { TypeId subjectType = follow(c.subjectType); const TypeId propType = follow(c.propType); @@ -1403,7 +1396,7 @@ bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNullscope, constraint->location, propType, *existingPropType); - unify(constraint->scope, constraint->location, *existingPropType, propType); + unify(constraint, propType, *existingPropType); + unify(constraint, *existingPropType, propType); bind(c.resultType, c.subjectType); unblock(c.resultType, constraint->location); return true; @@ -1640,63 +1633,79 @@ bool ConstraintSolver::tryDispatch(const HasIndexerConstraint& c, NotNull ConstraintSolver::tryDispatchSetIndexer(NotNull constraint, TypeId subjectType, TypeId indexType, TypeId propType, bool expandFreeTypeBounds) +{ + if (isBlocked(subjectType)) + return {block(subjectType, constraint), false}; + + if (auto tt = getMutable(subjectType)) + { + if (tt->indexer) + { + unify(constraint, indexType, tt->indexer->indexType); + + // We have a `BoundType` check here because we must mutate only our owning `BlockedType`, not some other constraint's `BlockedType`. + // TODO: We should rather have a `bool mutateProp` parameter that is set to false if we're traversing a union or intersection type. + // The union or intersection type themselves should be the one to mutate the `propType`, not each or first `TableType` in a union/intersection type. + // + // Fixing this requires fixing other ones first. + if (!get(propType) && get(propType)) + emplaceType(asMutable(propType), tt->indexer->indexResultType); + + return {true, true}; + } + else if (tt->state == TableState::Free || tt->state == TableState::Unsealed) + { + tt->indexer = TableIndexer{indexType, propType}; + return {true, true}; + } + else + return {true, false}; + } + else if (auto ft = getMutable(subjectType); ft && expandFreeTypeBounds) + { + // Setting an indexer on some fresh type means we use that fresh type in a negative position. + // Therefore, we only care about the upper bound. + // + // We'll extend the upper bound if we could dispatch, but could not find a table type to update the indexer. + auto [dispatched, found] = tryDispatchSetIndexer(constraint, ft->upperBound, indexType, propType, /*expandFreeTypeBounds=*/ false); + if (dispatched && !found) + { + TypeId tableTy = arena->addType(TableType{TableState::Sealed, TypeLevel{}, constraint->scope.get()}); + TableType* tt2 = getMutable(tableTy); + tt2->indexer = TableIndexer{indexType, propType}; + + ft->upperBound = simplifyIntersection(builtinTypes, arena, ft->upperBound, tableTy).result; // TODO: intersect type family or a constraint. + } + + return {dispatched, found}; + } + else if (auto it = get(subjectType)) + { + std::pair result{true, true}; + for (TypeId part : it) + { + auto [dispatched, found] = tryDispatchSetIndexer(constraint, part, indexType, propType, expandFreeTypeBounds); + result.first &= dispatched; + result.second &= found; + } + + return result; + } + + return {true, false}; +} + bool ConstraintSolver::tryDispatch(const SetIndexerConstraint& c, NotNull constraint, bool force) { TypeId subjectType = follow(c.subjectType); if (isBlocked(subjectType)) return block(subjectType, constraint); - if (auto ft = get(subjectType)) - { - Scope* scope = ft->scope; - TableType* tt = &asMutable(subjectType)->ty.emplace(TableState::Free, TypeLevel{}, scope); - tt->indexer = TableIndexer{c.indexType, c.propType}; - - asMutable(c.resultType)->ty.emplace(subjectType); - TypeId propType = freshType(arena, builtinTypes, scope); - asMutable(c.propType)->ty.emplace(propType); + auto [dispatched, found] = tryDispatchSetIndexer(constraint, subjectType, c.indexType, c.propType, /*expandFreeTypeBounds=*/ true); + if (dispatched && found) unblock(c.propType, constraint->location); - unblock(c.resultType, constraint->location); - - return true; - } - else if (auto tt = get(subjectType)) - { - if (tt->indexer) - { - if (isBlocked(tt->indexer->indexResultType)) - return block(tt->indexer->indexResultType, constraint); - - // TODO This probably has to be invariant. - unify(constraint, c.indexType, tt->indexer->indexType); - asMutable(c.propType)->ty.emplace(tt->indexer->indexResultType); - asMutable(c.resultType)->ty.emplace(subjectType); - unblock(c.propType, constraint->location); - unblock(c.resultType, constraint->location); - return true; - } - else if (tt->state == TableState::Free || tt->state == TableState::Unsealed) - { - TypeId promotedIndexTy = freshType(arena, builtinTypes, tt->scope); - unify(constraint, c.indexType, promotedIndexTy); - - auto mtt = getMutable(subjectType); - mtt->indexer = TableIndexer{promotedIndexTy, c.propType}; - TypeId propType = freshType(arena, builtinTypes, tt->scope); - asMutable(c.propType)->ty.emplace(propType); - asMutable(c.resultType)->ty.emplace(subjectType); - unblock(c.propType, constraint->location); - unblock(c.resultType, constraint->location); - return true; - } - // Do not augment sealed or generic tables that lack indexers - } - - asMutable(c.propType)->ty.emplace(builtinTypes->errorRecoveryType()); - asMutable(c.resultType)->ty.emplace(builtinTypes->errorRecoveryType()); - unblock(c.propType, constraint->location); - unblock(c.resultType, constraint->location); - return true; + return dispatched; } bool ConstraintSolver::tryDispatch(const SingletonOrTopTypeConstraint& c, NotNull constraint) @@ -1719,6 +1728,46 @@ bool ConstraintSolver::tryDispatch(const SingletonOrTopTypeConstraint& c, NotNul return true; } +bool ConstraintSolver::tryDispatchUnpack1(NotNull constraint, TypeId resultTy, TypeId srcTy, bool resultIsLValue) +{ + resultTy = follow(resultTy); + LUAU_ASSERT(canMutate(resultTy, constraint)); + + if (auto lt = getMutable(resultTy); resultIsLValue && lt) + { + lt->domain = simplifyUnion(builtinTypes, arena, lt->domain, srcTy).result; + LUAU_ASSERT(lt->blockCount > 0); + --lt->blockCount; + + LUAU_ASSERT(0 <= lt->blockCount); + + if (0 == lt->blockCount) + asMutable(resultTy)->ty.emplace(lt->domain); + } + else if (get(resultTy)) + { + if (follow(srcTy) == resultTy) + { + // It is sometimes the case that we find that a blocked type + // is only blocked on itself. This doesn't actually + // constitute any meaningful constraint, so we replace it + // with a free type. + TypeId f = freshType(arena, builtinTypes, constraint->scope); + asMutable(resultTy)->ty.emplace(f); + } + else + bindBlockedType(resultTy, srcTy, srcTy, constraint); + } + else + { + LUAU_ASSERT(resultIsLValue); + unify(constraint, resultTy, srcTy); + } + + unblock(resultTy, constraint->location); + return true; +} + bool ConstraintSolver::tryDispatch(const UnpackConstraint& c, NotNull constraint) { TypePackId sourcePack = follow(c.sourcePack); @@ -1741,44 +1790,6 @@ bool ConstraintSolver::tryDispatch(const UnpackConstraint& c, NotNull(resultTy); c.resultIsLValue && lt) - { - lt->domain = simplifyUnion(builtinTypes, arena, lt->domain, srcTy).result; - LUAU_ASSERT(lt->blockCount > 0); - --lt->blockCount; - - LUAU_ASSERT(0 <= lt->blockCount); - - if (0 == lt->blockCount) - asMutable(resultTy)->ty.emplace(lt->domain); - } - else if (get(resultTy)) - { - if (follow(srcTy) == resultTy) - { - // It is sometimes the case that we find that a blocked type - // is only blocked on itself. This doesn't actually - // constitute any meaningful constraint, so we replace it - // with a free type. - TypeId f = freshType(arena, builtinTypes, constraint->scope); - asMutable(resultTy)->ty.emplace(f); - } - else - bindBlockedType(resultTy, srcTy, srcTy, constraint); - } - else - { - LUAU_ASSERT(c.resultIsLValue); - unify(constraint, resultTy, srcTy); - } - - unblock(resultTy, constraint->location); - }; - size_t i = 0; while (resultIter != resultEnd) { @@ -1795,10 +1806,10 @@ bool ConstraintSolver::tryDispatch(const UnpackConstraint& c, NotNull(resultTy)) { for (auto opt : ut->options) - apply(opt, srcTy); + tryDispatchUnpack1(constraint, opt, srcTy, c.resultIsLValue); } else - apply(resultTy, srcTy); + tryDispatchUnpack1(constraint, resultTy, srcTy, c.resultIsLValue); } else unify(constraint, resultTy, srcTy); @@ -1836,6 +1847,11 @@ bool ConstraintSolver::tryDispatch(const UnpackConstraint& c, NotNull constraint) +{ + return tryDispatchUnpack1(constraint, c.resultType, c.sourceType, c.resultIsLValue); +} + bool ConstraintSolver::tryDispatch(const SetOpConstraint& c, NotNull constraint, bool force) { bool blocked = false; @@ -1914,8 +1930,8 @@ bool ConstraintSolver::tryDispatch(const ReducePackConstraint& c, NotNull constraint, bool force) { - unify(constraint->scope, constraint->location, c.resultType, c.assignmentType); - unify(constraint->scope, constraint->location, c.assignmentType, c.resultType); + unify(constraint, c.resultType, c.assignmentType); + unify(constraint, c.assignmentType, c.resultType); return true; } @@ -2034,7 +2050,7 @@ bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const Iterabl LUAU_ASSERT(nextFn); const TypePackId nextRetPack = nextFn->retTypes; - pushConstraint(constraint->scope, constraint->location, UnpackConstraint{c.variables, nextRetPack}); + pushConstraint(constraint->scope, constraint->location, UnpackConstraint{c.variables, nextRetPack, /* resultIsLValue=*/ true}); return true; } else @@ -2115,15 +2131,15 @@ bool ConstraintSolver::tryDispatchIterableFunction( return true; } -std::pair, std::optional> ConstraintSolver::lookupTableProp( - TypeId subjectType, const std::string& propName, ValueContext context, bool inConditional, bool suppressSimplification) +std::pair, std::optional> ConstraintSolver::lookupTableProp(NotNull constraint, TypeId subjectType, + const std::string& propName, ValueContext context, bool inConditional, bool suppressSimplification) { DenseHashSet seen{nullptr}; - return lookupTableProp(subjectType, propName, context, inConditional, suppressSimplification, seen); + return lookupTableProp(constraint, subjectType, propName, context, inConditional, suppressSimplification, seen); } -std::pair, std::optional> ConstraintSolver::lookupTableProp( - TypeId subjectType, const std::string& propName, ValueContext context, bool inConditional, bool suppressSimplification, DenseHashSet& seen) +std::pair, std::optional> ConstraintSolver::lookupTableProp(NotNull constraint, TypeId subjectType, + const std::string& propName, ValueContext context, bool inConditional, bool suppressSimplification, DenseHashSet& seen) { if (seen.contains(subjectType)) return {}; @@ -2197,7 +2213,7 @@ std::pair, std::optional> ConstraintSolver::lookupTa } else if (auto mt = get(subjectType); mt && context == ValueContext::RValue) { - auto [blocked, result] = lookupTableProp(mt->table, propName, context, inConditional, suppressSimplification, seen); + auto [blocked, result] = lookupTableProp(constraint, mt->table, propName, context, inConditional, suppressSimplification, seen); if (!blocked.empty() || result) return {blocked, result}; @@ -2228,10 +2244,10 @@ std::pair, std::optional> ConstraintSolver::lookupTa } } else - return lookupTableProp(indexType, propName, context, inConditional, suppressSimplification, seen); + return lookupTableProp(constraint, indexType, propName, context, inConditional, suppressSimplification, seen); } else if (get(mtt)) - return lookupTableProp(mtt, propName, context, inConditional, suppressSimplification, seen); + return lookupTableProp(constraint, mtt, propName, context, inConditional, suppressSimplification, seen); } else if (auto ct = get(subjectType)) { @@ -2251,14 +2267,14 @@ std::pair, std::optional> ConstraintSolver::lookupTa if (indexProp == metatable->props.end()) return {{}, std::nullopt}; - return lookupTableProp(indexProp->second.type(), propName, context, inConditional, suppressSimplification, seen); + return lookupTableProp(constraint, indexProp->second.type(), propName, context, inConditional, suppressSimplification, seen); } else if (auto ft = get(subjectType)) { const TypeId upperBound = follow(ft->upperBound); if (get(upperBound) || get(upperBound)) - return lookupTableProp(upperBound, propName, context, inConditional, suppressSimplification, seen); + return lookupTableProp(constraint, upperBound, propName, context, inConditional, suppressSimplification, seen); // TODO: The upper bound could be an intersection that contains suitable tables or classes. @@ -2279,7 +2295,7 @@ std::pair, std::optional> ConstraintSolver::lookupTa break; } - unify(scope, Location{}, subjectType, newUpperBound); + unify(constraint, subjectType, newUpperBound); return {{}, propType}; } @@ -2290,7 +2306,7 @@ std::pair, std::optional> ConstraintSolver::lookupTa for (TypeId ty : utv) { - auto [innerBlocked, innerResult] = lookupTableProp(ty, propName, context, inConditional, suppressSimplification, seen); + auto [innerBlocked, innerResult] = lookupTableProp(constraint, ty, propName, context, inConditional, suppressSimplification, seen); blocked.insert(blocked.end(), innerBlocked.begin(), innerBlocked.end()); if (innerResult) options.insert(*innerResult); @@ -2319,7 +2335,7 @@ std::pair, std::optional> ConstraintSolver::lookupTa for (TypeId ty : itv) { - auto [innerBlocked, innerResult] = lookupTableProp(ty, propName, context, inConditional, suppressSimplification, seen); + auto [innerBlocked, innerResult] = lookupTableProp(constraint, ty, propName, context, inConditional, suppressSimplification, seen); blocked.insert(blocked.end(), innerBlocked.begin(), innerBlocked.end()); if (innerResult) options.insert(*innerResult); @@ -2353,39 +2369,39 @@ std::pair, std::optional> ConstraintSolver::lookupTa return {{}, std::nullopt}; } -template -bool ConstraintSolver::unify(NotNull scope, Location location, TID subType, TID superType) +template +bool ConstraintSolver::unify(NotNull constraint, TID subTy, TID superTy) { - Unifier2 u2{NotNull{arena}, builtinTypes, scope, NotNull{&iceReporter}}; + Unifier2 u2{NotNull{arena}, builtinTypes, constraint->scope, NotNull{&iceReporter}}; - const bool ok = u2.unify(subType, superType); + const bool ok = u2.unify(subTy, superTy); + + for (ConstraintV& c : u2.incompleteSubtypes) + { + NotNull addition = pushConstraint(constraint->scope, constraint->location, std::move(c)); + inheritBlocks(constraint, addition); + } if (ok) { for (const auto& [expanded, additions] : u2.expandedFreeTypes) { for (TypeId addition : additions) - upperBoundContributors[expanded].push_back(std::make_pair(location, addition)); + upperBoundContributors[expanded].push_back(std::make_pair(constraint->location, addition)); } } else { - reportError(OccursCheckFailed{}, location); + reportError(OccursCheckFailed{}, constraint->location); return false; } - unblock(subType, location); - unblock(superType, location); + unblock(subTy, constraint->location); + unblock(superTy, constraint->location); return true; } -template -bool ConstraintSolver::unify(NotNull constraint, TID subTy, TID superTy) -{ - return unify(constraint->scope, constraint->location, subTy, superTy); -} - void ConstraintSolver::bindBlockedType(TypeId blockedTy, TypeId resultTy, TypeId rootTy, NotNull constraint) { resultTy = follow(resultTy); @@ -2761,9 +2777,6 @@ LUAU_NOINLINE void ConstraintSolver::throwUserCancelError() } // Instantiate private template implementations for external callers -template bool ConstraintSolver::unify(NotNull scope, Location location, TypeId subType, TypeId superType); -template bool ConstraintSolver::unify(NotNull scope, Location location, TypePackId subType, TypePackId superType); - template bool ConstraintSolver::unify(NotNull constraint, TypeId subTy, TypeId superTy); template bool ConstraintSolver::unify(NotNull constraint, TypePackId subTy, TypePackId superTy); diff --git a/Analysis/src/Def.cpp b/Analysis/src/Def.cpp index 09dc2bb1..6d58b28f 100644 --- a/Analysis/src/Def.cpp +++ b/Analysis/src/Def.cpp @@ -27,6 +27,10 @@ void collectOperands(DefId def, std::vector* operands) operands->push_back(def); else if (auto phi = get(def)) { + // A trivial phi node has no operands to populate, so we push this definition in directly. + if (phi->operands.empty()) + return operands->push_back(def); + for (const Def* operand : phi->operands) collectOperands(NotNull{operand}, operands); } diff --git a/Analysis/src/Frontend.cpp b/Analysis/src/Frontend.cpp index b608e28a..758acef3 100644 --- a/Analysis/src/Frontend.cpp +++ b/Analysis/src/Frontend.cpp @@ -14,10 +14,12 @@ #include "Luau/Scope.h" #include "Luau/StringUtils.h" #include "Luau/TimeTrace.h" +#include "Luau/TypeArena.h" #include "Luau/TypeChecker2.h" #include "Luau/NonStrictTypeChecker.h" #include "Luau/TypeInfer.h" #include "Luau/Variant.h" +#include "Luau/VisitType.h" #include #include @@ -35,6 +37,7 @@ LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3, false) LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution) LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson, false) LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJsonFile, false) +LUAU_FASTFLAGVARIABLE(DebugLuauForbidInternalTypes, false) namespace Luau { @@ -1160,6 +1163,56 @@ ModulePtr check(const SourceModule& sourceModule, Mode mode, const std::vector& requireCycles, NotNull builtinTypes, NotNull iceHandler, NotNull moduleResolver, NotNull fileResolver, const ScopePtr& parentScope, std::function prepareModuleScope, FrontendOptions options, @@ -1201,12 +1254,15 @@ ModulePtr check(const SourceModule& sourceModule, Mode mode, const std::vectorerrors = std::move(cg.errors); - ConstraintSolver cs{NotNull{&normalizer}, NotNull(cg.rootScope), borrowConstraints(cg.constraints), result->name, moduleResolver, - requireCycles, logger.get(), limits}; + ConstraintSolver cs{NotNull{&normalizer}, NotNull(cg.rootScope), borrowConstraints(cg.constraints), result->name, moduleResolver, requireCycles, + logger.get(), limits}; if (options.randomizeConstraintResolutionSeed) cs.randomize(*options.randomizeConstraintResolutionSeed); + for (TypeId ty : cg.familyInstances) + cs.familyInstances.insert(ty); + try { cs.run(); @@ -1236,7 +1292,33 @@ ModulePtr check(const SourceModule& sourceModule, Mode mode, const std::vectortype = sourceModule.type; result->upperBoundContributors = std::move(cs.upperBoundContributors); - result->clonePublicInterface(builtinTypes, *iceHandler); + if (FFlag::DebugLuauForbidInternalTypes) + { + InternalTypeFinder finder; + + finder.traverse(result->returnType); + + for (const auto& [_, binding] : result->exportedTypeBindings) + finder.traverse(binding.type); + + for (const auto& [_, ty] : result->astTypes) + finder.traverse(ty); + + for (const auto& [_, ty] : result->astExpectedTypes) + finder.traverse(ty); + + for (const auto& [_, tp] : result->astTypePacks) + finder.traverse(tp); + + for (const auto& [_, ty] : result->astResolvedTypes) + finder.traverse(ty); + + for (const auto& [_, ty] : result->astOverloadResolvedTypes) + finder.traverse(ty); + + for (const auto& [_, tp] : result->astResolvedTypePacks) + finder.traverse(tp); + } if (result->timeout || result->cancelled) { @@ -1259,6 +1341,9 @@ ModulePtr check(const SourceModule& sourceModule, Mode mode, const std::vectorinterfaceTypes); + result->clonePublicInterface(builtinTypes, *iceHandler); + // It would be nice if we could freeze the arenas before doing type // checking, but we'll have to do some work to get there. // @@ -1295,9 +1380,8 @@ ModulePtr Frontend::check(const SourceModule& sourceModule, Mode mode, std::vect } catch (const InternalCompilerError& err) { - InternalCompilerError augmented = err.location.has_value() - ? InternalCompilerError{err.message, sourceModule.name, *err.location} - : InternalCompilerError{err.message, sourceModule.name}; + InternalCompilerError augmented = err.location.has_value() ? InternalCompilerError{err.message, sourceModule.name, *err.location} + : InternalCompilerError{err.message, sourceModule.name}; throw augmented; } } diff --git a/Analysis/src/Module.cpp b/Analysis/src/Module.cpp index d50719a9..440b510b 100644 --- a/Analysis/src/Module.cpp +++ b/Analysis/src/Module.cpp @@ -196,9 +196,6 @@ Module::~Module() void Module::clonePublicInterface(NotNull builtinTypes, InternalErrorReporter& ice) { - LUAU_ASSERT(interfaceTypes.types.empty()); - LUAU_ASSERT(interfaceTypes.typePacks.empty()); - CloneState cloneState{builtinTypes}; ScopePtr moduleScope = getModuleScope(); diff --git a/Analysis/src/Normalize.cpp b/Analysis/src/Normalize.cpp index 096ade6f..d749c697 100644 --- a/Analysis/src/Normalize.cpp +++ b/Analysis/src/Normalize.cpp @@ -430,6 +430,10 @@ bool Normalizer::isInhabited(const NormalizedType* norm) bool Normalizer::isInhabited(const NormalizedType* norm, Set& seen) { + RecursionCounter _rc(&sharedState->counters.recursionCount); + if (!withinResourceLimits()) + return false; + // If normalization failed, the type is complex, and so is more likely than not to be inhabited. if (!norm) return true; @@ -473,6 +477,10 @@ bool Normalizer::isInhabited(TypeId ty) bool Normalizer::isInhabited(TypeId ty, Set& seen) { + RecursionCounter _rc(&sharedState->counters.recursionCount); + if (!withinResourceLimits()) + return false; + // TODO: use log.follow(ty), CLI-64291 ty = follow(ty); @@ -2999,6 +3007,7 @@ bool Normalizer::intersectNormalWithTy(NormalizedType& here, TypeId there, Set(ty)) { for (auto& [_, prop] : tableTy->props) diff --git a/Analysis/src/OverloadResolution.cpp b/Analysis/src/OverloadResolution.cpp index 55694124..d2bfd0e1 100644 --- a/Analysis/src/OverloadResolution.cpp +++ b/Analysis/src/OverloadResolution.cpp @@ -363,6 +363,31 @@ void OverloadResolver::add(Analysis analysis, TypeId ty, ErrorVec&& errors) } } +// we wrap calling the overload resolver in a separate function to reduce overall stack pressure in `solveFunctionCall`. +// this limits the lifetime of `OverloadResolver`, a large type, to only as long as it is actually needed. +std::optional selectOverload( + NotNull builtinTypes, + NotNull arena, + NotNull normalizer, + NotNull scope, + NotNull iceReporter, + NotNull limits, + const Location& location, + TypeId fn, + TypePackId argsPack +) +{ + OverloadResolver resolver{builtinTypes, arena, normalizer, scope, iceReporter, limits, location}; + auto [status, overload] = resolver.selectOverload(fn, argsPack); + + if (status == OverloadResolver::Analysis::Ok) + return overload; + + if (get(fn) || get(fn)) + return fn; + + return {}; +} SolveResult solveFunctionCall( NotNull arena, @@ -376,17 +401,8 @@ SolveResult solveFunctionCall( TypePackId argsPack ) { - OverloadResolver resolver{ - builtinTypes, NotNull{arena}, normalizer, scope, iceReporter, limits, location}; - auto [status, overload] = resolver.selectOverload(fn, argsPack); - TypeId overloadToUse = fn; - if (status == OverloadResolver::Analysis::Ok) - overloadToUse = overload; - else if (get(fn) || get(fn)) - { - // Nothing. Let's keep going - } - else + std::optional overloadToUse = selectOverload(builtinTypes, arena, normalizer, scope, iceReporter, limits, location, fn, argsPack); + if (!overloadToUse) return {SolveResult::NoMatchingOverload}; TypePackId resultPack = arena->freshTypePack(scope); @@ -394,7 +410,7 @@ SolveResult solveFunctionCall( TypeId inferredTy = arena->addType(FunctionType{TypeLevel{}, scope.get(), argsPack, resultPack}); Unifier2 u2{NotNull{arena}, builtinTypes, scope, iceReporter}; - const bool occursCheckPassed = u2.unify(overloadToUse, inferredTy); + const bool occursCheckPassed = u2.unify(*overloadToUse, inferredTy); if (!u2.genericSubstitutions.empty() || !u2.genericPackSubstitutions.empty()) { @@ -416,7 +432,7 @@ SolveResult solveFunctionCall( result.typePackId = resultPack; LUAU_ASSERT(overloadToUse); - result.overloadToUse = overloadToUse; + result.overloadToUse = *overloadToUse; result.inferredTy = inferredTy; result.expandedFreeTypes = std::move(u2.expandedFreeTypes); diff --git a/Analysis/src/Subtyping.cpp b/Analysis/src/Subtyping.cpp index a894d97b..9c93e9c5 100644 --- a/Analysis/src/Subtyping.cpp +++ b/Analysis/src/Subtyping.cpp @@ -654,7 +654,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId { for (size_t i = headSize; i < superHead.size(); ++i) results.push_back(isCovariantWith(env, vt->ty, superHead[i]) - .withSubComponent(TypePath::TypeField::Variadic) + .withSubPath(TypePath::PathBuilder().tail().variadic().build()) .withSuperComponent(TypePath::Index{i})); } else if (auto gt = get(*subTail)) diff --git a/Analysis/src/ToString.cpp b/Analysis/src/ToString.cpp index 9bb2d065..393765e1 100644 --- a/Analysis/src/ToString.cpp +++ b/Analysis/src/ToString.cpp @@ -21,6 +21,7 @@ LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution) LUAU_FASTFLAGVARIABLE(LuauToStringSimpleCompositeTypesSingleLine, false) +LUAU_FASTFLAGVARIABLE(LuauStringifyCyclesRootedAtPacks, false) /* * Enables increasing levels of verbosity for Luau type names when stringifying. @@ -1491,10 +1492,21 @@ ToStringResult toStringDetailed(TypePackId tp, ToStringOptions& opts) else tvs.stringify(tp); - if (!cycles.empty()) + if (FFlag::LuauStringifyCyclesRootedAtPacks) { - result.cycle = true; - state.emit(" where "); + if (!cycles.empty() || !cycleTPs.empty()) + { + result.cycle = true; + state.emit(" where "); + } + } + else + { + if (!cycles.empty()) + { + result.cycle = true; + state.emit(" where "); + } } state.exhaustive = true; @@ -1521,6 +1533,32 @@ ToStringResult toStringDetailed(TypePackId tp, ToStringOptions& opts) semi = true; } + if (FFlag::LuauStringifyCyclesRootedAtPacks) + { + std::vector> sortedCycleTpNames{state.cycleTpNames.begin(), state.cycleTpNames.end()}; + std::sort(sortedCycleTpNames.begin(), sortedCycleTpNames.end(), [](const auto& a, const auto& b) { + return a.second < b.second; + }); + + TypePackStringifier tps{tvs.state}; + + for (const auto& [cycleTp, name] : sortedCycleTpNames) + { + if (semi) + state.emit(" ; "); + + state.emit(name); + state.emit(" = "); + Luau::visit( + [&tps, cycleTp = cycleTp](auto t) { + return tps(cycleTp, t); + }, + cycleTp->ty); + + semi = true; + } + } + if (opts.maxTypeLength > 0 && result.name.length() > opts.maxTypeLength) { result.name += "... *TRUNCATED*"; @@ -1721,7 +1759,7 @@ std::string toString(const Constraint& constraint, ToStringOptions& opts) { std::string subStr = tos(c.subPack); std::string superStr = tos(c.superPack); - return subStr + " <: " + superStr; + return subStr + " <...: " + superStr; } else if constexpr (std::is_same_v) { @@ -1782,7 +1820,7 @@ std::string toString(const Constraint& constraint, ToStringOptions& opts) } else if constexpr (std::is_same_v) { - return tos(c.resultType) + " ~ setIndexer " + tos(c.subjectType) + " [ " + tos(c.indexType) + " ] " + tos(c.propType); + return "setIndexer " + tos(c.subjectType) + " [ " + tos(c.indexType) + " ] " + tos(c.propType); } else if constexpr (std::is_same_v) { @@ -1795,7 +1833,9 @@ std::string toString(const Constraint& constraint, ToStringOptions& opts) return result + " ~ if isSingleton D then D else unknown where D = " + discriminant; } else if constexpr (std::is_same_v) - return tos(c.resultPack) + " ~ unpack " + tos(c.sourcePack); + return tos(c.resultPack) + " ~ ...unpack " + tos(c.sourcePack); + else if constexpr (std::is_same_v) + return tos(c.resultType) + " ~ unpack " + tos(c.sourceType); else if constexpr (std::is_same_v) { const char* op = c.mode == SetOpConstraint::Union ? " | " : " & "; diff --git a/Analysis/src/TypeChecker2.cpp b/Analysis/src/TypeChecker2.cpp index bd46e29c..922a5671 100644 --- a/Analysis/src/TypeChecker2.cpp +++ b/Analysis/src/TypeChecker2.cpp @@ -1648,7 +1648,7 @@ struct TypeChecker2 visit(*fn->returnAnnotation); // If the function type has a family annotation, we need to see if we can suggest an annotation - TypeFamilyReductionGuesser guesser{builtinTypes, NotNull{&normalizer}}; + TypeFamilyReductionGuesser guesser{NotNull{&module->internalTypes}, builtinTypes, NotNull{&normalizer}}; for (TypeId retTy : inferredFtv->retTypes) { if (get(follow(retTy))) diff --git a/Analysis/src/TypeFamily.cpp b/Analysis/src/TypeFamily.cpp index a61fb88a..c120eeb1 100644 --- a/Analysis/src/TypeFamily.cpp +++ b/Analysis/src/TypeFamily.cpp @@ -17,23 +17,32 @@ #include "Luau/TxnLog.h" #include "Luau/Type.h" #include "Luau/TypeCheckLimits.h" +#include "Luau/TypeFamilyReductionGuesser.h" #include "Luau/TypeFwd.h" #include "Luau/TypeUtils.h" #include "Luau/Unifier2.h" #include "Luau/VecDeque.h" #include "Luau/VisitType.h" +// used to control emitting CodeTooComplex warnings on type family reduction LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyGraphReductionMaximumSteps, 1'000'000); +// used to control falling back to a more conservative reduction based on guessing +// when this value is set to a negative value, guessing will be totally disabled. +LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyUseGuesserDepth, -1); + LUAU_FASTFLAG(DebugLuauLogSolver); namespace Luau { +using TypeOrTypePackIdSet = DenseHashSet; + struct InstanceCollector : TypeOnceVisitor { VecDeque tys; VecDeque tps; + TypeOrTypePackIdSet shouldGuess{nullptr}; std::vector cyclicInstance; bool visit(TypeId ty, const TypeFamilyInstanceType&) override @@ -44,7 +53,12 @@ struct InstanceCollector : TypeOnceVisitor // in the queue. Consider Add, number>, number>: // we want to reduce the innermost Add instantiation // first. + + if (DFInt::LuauTypeFamilyUseGuesserDepth >= 0 && typeFamilyDepth > DFInt::LuauTypeFamilyUseGuesserDepth) + shouldGuess.insert(ty); + tys.push_front(ty); + return true; } @@ -69,7 +83,12 @@ struct InstanceCollector : TypeOnceVisitor // in the queue. Consider Add, number>, number>: // we want to reduce the innermost Add instantiation // first. + + if (DFInt::LuauTypeFamilyUseGuesserDepth >= 0 && typeFamilyDepth > DFInt::LuauTypeFamilyUseGuesserDepth) + shouldGuess.insert(tp); + tps.push_front(tp); + return true; } }; @@ -80,19 +99,21 @@ struct FamilyReducer VecDeque queuedTys; VecDeque queuedTps; + TypeOrTypePackIdSet shouldGuess; std::vector cyclicTypeFamilies; - DenseHashSet irreducible{nullptr}; + TypeOrTypePackIdSet irreducible{nullptr}; FamilyGraphReductionResult result; bool force = false; // Local to the constraint being reduced. Location location; - FamilyReducer(VecDeque queuedTys, VecDeque queuedTps, std::vector cyclicTypes, Location location, - TypeFamilyContext ctx, bool force = false) + FamilyReducer(VecDeque queuedTys, VecDeque queuedTps, TypeOrTypePackIdSet shouldGuess, std::vector cyclicTypes, + Location location, TypeFamilyContext ctx, bool force = false) : ctx(ctx) , queuedTys(std::move(queuedTys)) , queuedTps(std::move(queuedTps)) + , shouldGuess(std::move(shouldGuess)) , cyclicTypeFamilies(std::move(cyclicTypes)) , force(force) , location(location) @@ -154,13 +175,12 @@ struct FamilyReducer template void replace(T subject, T replacement) { - if (FFlag::DebugLuauLogSolver) - printf("%s -> %s\n", toString(subject, {true}).c_str(), toString(replacement, {true}).c_str()); - - // TODO: This should be an ICE (CLI-100942) if (subject->owningArena != ctx.arena.get()) ctx.ice->ice("Attempting to modify a type family instance from another arena", location); + if (FFlag::DebugLuauLogSolver) + printf("%s -> %s\n", toString(subject, {true}).c_str(), toString(replacement, {true}).c_str()); + asMutable(subject)->ty.template emplace>(replacement); if constexpr (std::is_same_v) @@ -265,6 +285,34 @@ struct FamilyReducer return true; } + template + inline bool tryGuessing(TID subject) + { + if (shouldGuess.contains(subject)) + { + if (FFlag::DebugLuauLogSolver) + printf("Flagged %s for reduction with guesser.\n", toString(subject, {true}).c_str()); + + TypeFamilyReductionGuesser guesser{ctx.arena, ctx.builtins, ctx.normalizer}; + auto guessed = guesser.guess(subject); + + if (guessed) + { + if (FFlag::DebugLuauLogSolver) + printf("Selected %s as the guessed result type.\n", toString(*guessed, {true}).c_str()); + + replace(subject, *guessed); + return true; + } + + if (FFlag::DebugLuauLogSolver) + printf("Failed to produce a guess for the result of %s.\n", toString(subject, {true}).c_str()); + } + + return false; + } + + void stepType() { TypeId subject = follow(queuedTys.front()); @@ -288,6 +336,9 @@ struct FamilyReducer return; } + if (tryGuessing(subject)) + return; + TypeFamilyReductionResult result = tfit->family->reducer(subject, tfit->typeArguments, tfit->packArguments, NotNull{&ctx}); handleFamilyReduction(subject, result); } @@ -309,6 +360,9 @@ struct FamilyReducer if (!testParameters(subject, tfit)) return; + if (tryGuessing(subject)) + return; + TypeFamilyReductionResult result = tfit->family->reducer(subject, tfit->typeArguments, tfit->packArguments, NotNull{&ctx}); handleFamilyReduction(subject, result); } @@ -324,9 +378,9 @@ struct FamilyReducer }; static FamilyGraphReductionResult reduceFamiliesInternal( - VecDeque queuedTys, VecDeque queuedTps, std::vector cyclics, Location location, TypeFamilyContext ctx, bool force) + VecDeque queuedTys, VecDeque queuedTps, TypeOrTypePackIdSet shouldGuess, std::vector cyclics, Location location, TypeFamilyContext ctx, bool force) { - FamilyReducer reducer{std::move(queuedTys), std::move(queuedTps), std::move(cyclics), location, ctx, force}; + FamilyReducer reducer{std::move(queuedTys), std::move(queuedTps), std::move(shouldGuess), std::move(cyclics), location, ctx, force}; int iterationCount = 0; while (!reducer.done()) @@ -360,7 +414,7 @@ FamilyGraphReductionResult reduceFamilies(TypeId entrypoint, Location location, if (collector.tys.empty() && collector.tps.empty()) return {}; - return reduceFamiliesInternal(std::move(collector.tys), std::move(collector.tps), std::move(collector.cyclicInstance), location, ctx, force); + return reduceFamiliesInternal(std::move(collector.tys), std::move(collector.tps), std::move(collector.shouldGuess), std::move(collector.cyclicInstance), location, ctx, force); } FamilyGraphReductionResult reduceFamilies(TypePackId entrypoint, Location location, TypeFamilyContext ctx, bool force) @@ -379,7 +433,7 @@ FamilyGraphReductionResult reduceFamilies(TypePackId entrypoint, Location locati if (collector.tys.empty() && collector.tps.empty()) return {}; - return reduceFamiliesInternal(std::move(collector.tys), std::move(collector.tps), {}, location, ctx, force); + return reduceFamiliesInternal(std::move(collector.tys), std::move(collector.tps), std::move(collector.shouldGuess), std::move(collector.cyclicInstance), location, ctx, force); } bool isPending(TypeId ty, ConstraintSolver* solver) @@ -1495,7 +1549,7 @@ void BuiltinTypeFamilies::addToScope(NotNull arena, NotNull sc TypeId t = arena->addType(GenericType{"T"}); TypeId u = arena->addType(GenericType{"U"}); GenericTypeDefinition genericT{t}; - GenericTypeDefinition genericU{u}; + GenericTypeDefinition genericU{u, {t}}; return TypeFun{{genericT, genericU}, arena->addType(TypeFamilyInstanceType{NotNull{family}, {t, u}, {}})}; }; diff --git a/Analysis/src/TypeFamilyReductionGuesser.cpp b/Analysis/src/TypeFamilyReductionGuesser.cpp index 50c9b4f5..f1130f82 100644 --- a/Analysis/src/TypeFamilyReductionGuesser.cpp +++ b/Analysis/src/TypeFamilyReductionGuesser.cpp @@ -11,6 +11,7 @@ #include "Luau/VisitType.h" #include +#include #include namespace Luau @@ -64,8 +65,9 @@ struct InstanceCollector2 : TypeOnceVisitor -TypeFamilyReductionGuesser::TypeFamilyReductionGuesser(NotNull builtins, NotNull normalizer) - : builtins(builtins) +TypeFamilyReductionGuesser::TypeFamilyReductionGuesser(NotNull arena, NotNull builtins, NotNull normalizer) + : arena(arena) + , builtins(builtins) , normalizer(normalizer) { } @@ -87,6 +89,44 @@ void TypeFamilyReductionGuesser::dumpGuesses() printf("Substitute %s for %s\n", toString(t).c_str(), toString(t_).c_str()); } +std::optional TypeFamilyReductionGuesser::guess(TypeId typ) +{ + std::optional guessedType = guessType(typ); + + if (!guessedType.has_value()) + return {}; + + TypeId guess = follow(*guessedType); + if (get(guess)) + return {}; + + return guess; +} + +std::optional TypeFamilyReductionGuesser::guess(TypePackId tp) +{ + auto [head, tail] = flatten(tp); + + std::vector guessedHead; + guessedHead.reserve(head.size()); + + for (auto typ : head) + { + std::optional guessedType = guessType(typ); + + if (!guessedType.has_value()) + return {}; + + TypeId guess = follow(*guessedType); + if (get(guess)) + return {}; + + guessedHead.push_back(*guessedType); + } + + return arena->addTypePack(TypePack{guessedHead, tail}); +} + TypeFamilyReductionGuessResult TypeFamilyReductionGuesser::guessTypeFamilyReductionForFunction( const AstExprFunction& expr, const FunctionType* ftv, TypeId retTy) { diff --git a/Analysis/src/Unifier2.cpp b/Analysis/src/Unifier2.cpp index 51922602..42dabae0 100644 --- a/Analysis/src/Unifier2.cpp +++ b/Analysis/src/Unifier2.cpp @@ -9,6 +9,8 @@ #include "Luau/TypeArena.h" #include "Luau/TypeCheckLimits.h" #include "Luau/TypeFamily.h" +#include "Luau/TypeFwd.h" +#include "Luau/TypePack.h" #include "Luau/TypeUtils.h" #include "Luau/VisitType.h" @@ -101,6 +103,19 @@ bool Unifier2::unify(TypeId subTy, TypeId superTy) if (subTy == superTy) return true; + // We have potentially done some unifications while dispatching either `SubtypeConstraint` or `PackSubtypeConstraint`, + // so rather than implementing backtracking or traversing the entire type graph multiple times, we could push + // additional constraints as we discover blocked types along with their proper bounds. + // + // But we exclude these two subtyping patterns, they are tautological: + // - never <: *blocked* + // - *blocked* <: unknown + if ((get(subTy) || get(superTy)) && !get(subTy) && !get(superTy)) + { + incompleteSubtypes.push_back(SubtypeConstraint{subTy, superTy}); + return true; + } + FreeType* subFree = getMutable(subTy); FreeType* superFree = getMutable(superTy); @@ -124,8 +139,7 @@ bool Unifier2::unify(TypeId subTy, TypeId superTy) } else if (subFree) { - subFree->upperBound = mkIntersection(subFree->upperBound, superTy); - expandedFreeTypes[subTy].push_back(superTy); + return unifyFreeWithType(subTy, superTy); } else if (superFree) { @@ -229,6 +243,62 @@ bool Unifier2::unify(TypeId subTy, TypeId superTy) return true; } +// If superTy is a function and subTy already has a +// potentially-compatible function in its upper bound, we assume that +// the function is not overloaded and attempt to combine superTy into +// subTy's existing function bound. +bool Unifier2::unifyFreeWithType(TypeId subTy, TypeId superTy) +{ + FreeType* subFree = getMutable(subTy); + LUAU_ASSERT(subFree); + + auto doDefault = [&]() { + subFree->upperBound = mkIntersection(subFree->upperBound, superTy); + expandedFreeTypes[subTy].push_back(superTy); + return true; + }; + + TypeId upperBound = follow(subFree->upperBound); + + if (get(upperBound)) + return unify(subFree->upperBound, superTy); + + const FunctionType* superFunction = get(superTy); + if (!superFunction) + return doDefault(); + + const auto [superArgHead, superArgTail] = flatten(superFunction->argTypes); + if (superArgTail) + return doDefault(); + + const IntersectionType* upperBoundIntersection = get(subFree->upperBound); + if (!upperBoundIntersection) + return doDefault(); + + bool ok = true; + bool foundOne = false; + + for (TypeId part : upperBoundIntersection->parts) + { + const FunctionType* ft = get(follow(part)); + if (!ft) + continue; + + const auto [subArgHead, subArgTail] = flatten(ft->argTypes); + + if (!subArgTail && subArgHead.size() == superArgHead.size()) + { + foundOne = true; + ok &= unify(part, superTy); + } + } + + if (foundOne) + return ok; + else + return doDefault(); +} + bool Unifier2::unify(TypeId subTy, const FunctionType* superFn) { const FunctionType* subFn = get(subTy); @@ -403,6 +473,12 @@ bool Unifier2::unify(TypePackId subTp, TypePackId superTp) if (subTp == superTp) return true; + if (get(subTp) || get(superTp)) + { + incompleteSubtypes.push_back(PackSubtypeConstraint{subTp, superTp}); + return true; + } + const FreeTypePack* subFree = get(subTp); const FreeTypePack* superFree = get(superTp); diff --git a/CodeGen/include/Luau/CodeGen.h b/CodeGen/include/Luau/CodeGen.h index 9e51c54a..64522386 100644 --- a/CodeGen/include/Luau/CodeGen.h +++ b/CodeGen/include/Luau/CodeGen.h @@ -40,6 +40,26 @@ enum class CodeGenCompilationResult AllocationFailed = 9, // Native codegen failed due to an allocation error }; +struct ProtoCompilationFailure +{ + CodeGenCompilationResult result = CodeGenCompilationResult::Success; + + std::string debugname; + int line = -1; +}; + +struct CompilationResult +{ + CodeGenCompilationResult result = CodeGenCompilationResult::Success; + + std::vector protoFailures; + + [[nodiscard]] bool hasErrors() const + { + return result != CodeGenCompilationResult::Success || !protoFailures.empty(); + } +}; + struct CompilationStats { size_t bytecodeSizeBytes = 0; @@ -65,7 +85,8 @@ void create(lua_State* L); void setNativeExecutionEnabled(lua_State* L, bool enabled); // Builds target function and all inner functions -CodeGenCompilationResult compile(lua_State* L, int idx, unsigned int flags = 0, CompilationStats* stats = nullptr); +CodeGenCompilationResult compile_DEPRECATED(lua_State* L, int idx, unsigned int flags = 0, CompilationStats* stats = nullptr); +CompilationResult 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/NativeProtoExecData.h b/CodeGen/include/Luau/NativeProtoExecData.h index 0033c276..3c72cc3d 100644 --- a/CodeGen/include/Luau/NativeProtoExecData.h +++ b/CodeGen/include/Luau/NativeProtoExecData.h @@ -41,6 +41,7 @@ struct NativeProtoExecDataDeleter using NativeProtoExecDataPtr = std::unique_ptr; [[nodiscard]] NativeProtoExecDataPtr createNativeProtoExecData(uint32_t bytecodeInstructionCount); +void destroyNativeProtoExecData(const uint32_t* instructionOffsets) noexcept; [[nodiscard]] NativeProtoExecDataHeader& getNativeProtoExecDataHeader(uint32_t* instructionOffsets) noexcept; [[nodiscard]] const NativeProtoExecDataHeader& getNativeProtoExecDataHeader(const uint32_t* instructionOffsets) noexcept; diff --git a/CodeGen/src/CodeBlockUnwind.cpp b/CodeGen/src/CodeBlockUnwind.cpp index 486aee2f..e0d4629f 100644 --- a/CodeGen/src/CodeBlockUnwind.cpp +++ b/CodeGen/src/CodeBlockUnwind.cpp @@ -110,11 +110,15 @@ void* createBlockUnwindInfo(void* context, uint8_t* block, size_t blockSize, siz unwind->finalize(unwindData, unwindSize, block, blockSize); #if defined(_WIN32) && defined(_M_X64) + +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM) if (!RtlAddFunctionTable((RUNTIME_FUNCTION*)block, uint32_t(unwind->getFunctionCount()), uintptr_t(block))) { CODEGEN_ASSERT(!"Failed to allocate function table"); return nullptr; } +#endif + #elif defined(__linux__) || defined(__APPLE__) if (!__register_frame) return nullptr; @@ -138,8 +142,12 @@ void* createBlockUnwindInfo(void* context, uint8_t* block, size_t blockSize, siz void destroyBlockUnwindInfo(void* context, void* unwindData) { #if defined(_WIN32) && defined(_M_X64) + +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM) if (!RtlDeleteFunctionTable((RUNTIME_FUNCTION*)unwindData)) CODEGEN_ASSERT(!"Failed to deallocate function table"); +#endif + #elif defined(__linux__) || defined(__APPLE__) if (!__deregister_frame) { diff --git a/CodeGen/src/CodeGen.cpp b/CodeGen/src/CodeGen.cpp index 07004ae3..24bd07c5 100644 --- a/CodeGen/src/CodeGen.cpp +++ b/CodeGen/src/CodeGen.cpp @@ -43,6 +43,7 @@ LUAU_FASTFLAGVARIABLE(DebugCodegenNoOpt, false) LUAU_FASTFLAGVARIABLE(DebugCodegenOptSize, false) LUAU_FASTFLAGVARIABLE(DebugCodegenSkipNumbering, false) +LUAU_FASTFLAGVARIABLE(LuauCodegenDetailedCompilationResult, false) // Per-module IR instruction count limit LUAU_FASTINTVARIABLE(CodegenHeuristicsInstructionLimit, 1'048'576) // 1 M @@ -284,7 +285,7 @@ void onDisable(lua_State* L, Proto* proto) }); } -size_t getMemorySize(lua_State* L, Proto* proto) +static size_t getMemorySize(lua_State* L, Proto* proto) { CODEGEN_ASSERT(FFlag::LuauCodegenHeapSizeReport); ExtraExecData* extra = getExtraExecData(proto, proto->execdata); @@ -410,8 +411,10 @@ void setNativeExecutionEnabled(lua_State* L, bool enabled) L->global->ecb.enter = enabled ? onEnter : onEnterDisabled; } -CodeGenCompilationResult compile(lua_State* L, int idx, unsigned int flags, CompilationStats* stats) +CodeGenCompilationResult compile_DEPRECATED(lua_State* L, int idx, unsigned int flags, CompilationStats* stats) { + CODEGEN_ASSERT(!FFlag::LuauCodegenDetailedCompilationResult); + CODEGEN_ASSERT(lua_isLfunction(L, idx)); const TValue* func = luaA_toobject(L, idx); @@ -564,6 +567,167 @@ CodeGenCompilationResult compile(lua_State* L, int idx, unsigned int flags, Comp return codeGenCompilationResult; } +CompilationResult compile(lua_State* L, int idx, unsigned int flags, CompilationStats* stats) +{ + CODEGEN_ASSERT(FFlag::LuauCodegenDetailedCompilationResult); + + CompilationResult compilationResult; + + CODEGEN_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) + { + compilationResult.result = CodeGenCompilationResult::NotNativeModule; + return compilationResult; + } + + // If initialization has failed, do not compile any functions + NativeState* data = getNativeState(L); + if (!data) + { + compilationResult.result = CodeGenCompilationResult::CodeGenNotInitialized; + return compilationResult; + } + + std::vector protos; + gatherFunctions(protos, root, flags); + + // 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()) + { + compilationResult.result = CodeGenCompilationResult::NothingToCompile; + return compilationResult; + } + + if (stats != nullptr) + stats->functionsTotal = uint32_t(protos.size()); + +#if defined(__aarch64__) + static unsigned int cpuFeatures = getCpuFeaturesA64(); + A64::AssemblyBuilderA64 build(/* logText= */ false, cpuFeatures); +#else + X64::AssemblyBuilderX64 build(/* logText= */ false); +#endif + + ModuleHelpers helpers; +#if defined(__aarch64__) + A64::assembleHelpers(build, helpers); +#else + X64::assembleHelpers(build, helpers); +#endif + + std::vector results; + results.reserve(protos.size()); + + uint32_t totalIrInstCount = 0; + + for (Proto* p : protos) + { + CodeGenCompilationResult protoResult = CodeGenCompilationResult::Success; + + if (std::optional np = createNativeFunction(build, helpers, p, totalIrInstCount, protoResult)) + results.push_back(*np); + else + compilationResult.protoFailures.push_back({protoResult, p->debugname ? getstr(p->debugname) : "", p->linedefined}); + } + + // Very large modules might result in overflowing a jump offset; in this case we currently abandon the entire module + if (!build.finalize()) + { + for (OldNativeProto result : results) + destroyExecData(result.execdata); + + compilationResult.result = CodeGenCompilationResult::CodeGenAssemblerFinalizationFailure; + return compilationResult; + } + + // If no functions were assembled, we don't need to allocate/copy executable pages for helpers + if (results.empty()) + return compilationResult; + + uint8_t* nativeData = nullptr; + size_t sizeNativeData = 0; + uint8_t* codeStart = nullptr; + if (!data->codeAllocator.allocate(build.data.data(), int(build.data.size()), reinterpret_cast(build.code.data()), + int(build.code.size() * sizeof(build.code[0])), nativeData, sizeNativeData, codeStart)) + { + for (OldNativeProto result : results) + destroyExecData(result.execdata); + + compilationResult.result = CodeGenCompilationResult::AllocationFailed; + return compilationResult; + } + + if (FFlag::LuauCodegenHeapSizeReport) + { + if (gPerfLogFn && results.size() > 0) + gPerfLogFn(gPerfLogContext, uintptr_t(codeStart), uint32_t(results[0].exectarget), ""); + + for (size_t i = 0; i < results.size(); ++i) + { + uint32_t begin = uint32_t(results[i].exectarget); + uint32_t end = i + 1 < results.size() ? uint32_t(results[i + 1].exectarget) : uint32_t(build.code.size() * sizeof(build.code[0])); + CODEGEN_ASSERT(begin < end); + + if (gPerfLogFn) + logPerfFunction(results[i].p, uintptr_t(codeStart) + begin, end - begin); + + ExtraExecData* extra = getExtraExecData(results[i].p, results[i].execdata); + extra->codeSize = end - begin; + } + } + else + { + if (gPerfLogFn && results.size() > 0) + { + gPerfLogFn(gPerfLogContext, uintptr_t(codeStart), uint32_t(results[0].exectarget), ""); + + for (size_t i = 0; i < results.size(); ++i) + { + uint32_t begin = uint32_t(results[i].exectarget); + uint32_t end = i + 1 < results.size() ? uint32_t(results[i + 1].exectarget) : uint32_t(build.code.size() * sizeof(build.code[0])); + CODEGEN_ASSERT(begin < end); + + logPerfFunction(results[i].p, uintptr_t(codeStart) + begin, end - begin); + } + } + } + + for (const OldNativeProto& 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 OldNativeProto& 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(); + } + + return compilationResult; +} + void setPerfLog(void* context, PerfLogFn logFn) { gPerfLogContext = context; diff --git a/CodeGen/src/CodeGenA64.cpp b/CodeGen/src/CodeGenA64.cpp index c0cf7e04..a18278c9 100644 --- a/CodeGen/src/CodeGenA64.cpp +++ b/CodeGen/src/CodeGenA64.cpp @@ -5,6 +5,7 @@ #include "Luau/UnwindBuilder.h" #include "BitUtils.h" +#include "CodeGenContext.h" #include "CodeGenUtils.h" #include "NativeState.h" #include "EmitCommonA64.h" @@ -290,6 +291,39 @@ bool initHeaderFunctions(NativeState& data) return true; } +bool initHeaderFunctions(BaseCodeGenContext& codeGenContext) +{ + AssemblyBuilderA64 build(/* logText= */ false); + UnwindBuilder& unwind = *codeGenContext.unwindBuilder.get(); + + unwind.startInfo(UnwindBuilder::A64); + + EntryLocations entryLocations = buildEntryFunction(build, unwind); + + build.finalize(); + + unwind.finishInfo(); + + CODEGEN_ASSERT(build.data.empty()); + + uint8_t* codeStart = nullptr; + if (!codeGenContext.codeAllocator.allocate(build.data.data(), int(build.data.size()), reinterpret_cast(build.code.data()), + int(build.code.size() * sizeof(build.code[0])), codeGenContext.gateData, codeGenContext.gateDataSize, codeStart)) + { + CODEGEN_ASSERT(!"Failed to create entry function"); + return false; + } + + // Set the offset at the begining so that functions in new blocks will not overlay the locations + // specified by the unwind information of the entry function + unwind.setBeginOffset(build.getLabelOffset(entryLocations.prologueEnd)); + + codeGenContext.context.gateEntry = codeStart + build.getLabelOffset(entryLocations.start); + codeGenContext.context.gateExit = codeStart + build.getLabelOffset(entryLocations.epilogueStart); + + return true; +} + void assembleHelpers(AssemblyBuilderA64& build, ModuleHelpers& helpers) { if (build.logText) diff --git a/CodeGen/src/CodeGenA64.h b/CodeGen/src/CodeGenA64.h index f6fda726..24fedd9a 100644 --- a/CodeGen/src/CodeGenA64.h +++ b/CodeGen/src/CodeGenA64.h @@ -6,6 +6,7 @@ namespace Luau namespace CodeGen { +class BaseCodeGenContext; struct NativeState; struct ModuleHelpers; @@ -15,6 +16,7 @@ namespace A64 class AssemblyBuilderA64; bool initHeaderFunctions(NativeState& data); +bool initHeaderFunctions(BaseCodeGenContext& codeGenContext); void assembleHelpers(AssemblyBuilderA64& build, ModuleHelpers& helpers); } // namespace A64 diff --git a/CodeGen/src/CodeGenContext.cpp b/CodeGen/src/CodeGenContext.cpp new file mode 100644 index 00000000..493ca7c2 --- /dev/null +++ b/CodeGen/src/CodeGenContext.cpp @@ -0,0 +1,226 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#include "CodeGenContext.h" + +#include "CodeGenA64.h" +#include "CodeGenX64.h" + +#include "Luau/CodeBlockUnwind.h" +#include "Luau/UnwindBuilder.h" +#include "Luau/UnwindBuilderDwarf2.h" +#include "Luau/UnwindBuilderWin.h" + +LUAU_FASTFLAG(LuauCodegenHeapSizeReport) + +LUAU_FASTINT(LuauCodeGenBlockSize) +LUAU_FASTINT(LuauCodeGenMaxTotalSize) + +namespace Luau +{ +namespace CodeGen +{ + +// From CodeGen.cpp +extern void* gPerfLogContext; +extern PerfLogFn gPerfLogFn; + +BaseCodeGenContext::BaseCodeGenContext(size_t blockSize, size_t maxTotalSize, AllocationCallback* allocationCallback, void* allocationCallbackContext) + : codeAllocator{blockSize, maxTotalSize, allocationCallback, allocationCallbackContext} +{ + CODEGEN_ASSERT(isSupported()); + +#if defined(_WIN32) + unwindBuilder = std::make_unique(); +#else + unwindBuilder = std::make_unique(); +#endif + + codeAllocator.context = unwindBuilder.get(); + codeAllocator.createBlockUnwindInfo = createBlockUnwindInfo; + codeAllocator.destroyBlockUnwindInfo = destroyBlockUnwindInfo; + + initFunctions(context); +} + +[[nodiscard]] bool BaseCodeGenContext::initHeaderFunctions() +{ +#if defined(__x86_64__) || defined(_M_X64) + if (!X64::initHeaderFunctions(*this)) + return false; +#elif defined(__aarch64__) + if (!A64::initHeaderFunctions(*this)) + return false; +#endif + + if (gPerfLogFn) + gPerfLogFn(gPerfLogContext, uintptr_t(context.gateEntry), 4096, ""); + + return true; +} + + +StandaloneCodeGenContext::StandaloneCodeGenContext( + size_t blockSize, size_t maxTotalSize, AllocationCallback* allocationCallback, void* allocationCallbackContext) + : BaseCodeGenContext{blockSize, maxTotalSize, allocationCallback, allocationCallbackContext} +{ +} + +void StandaloneCodeGenContext::compileOrBindModule(const ModuleId&, lua_State*, int, unsigned int, CompilationStats*) {} + +void StandaloneCodeGenContext::onCloseState() noexcept +{ + // The StandaloneCodeGenContext is owned by the one VM that owns it, so when + // that VM is destroyed, we destroy *this as well: + delete this; +} + +void StandaloneCodeGenContext::onDestroyFunction(void* execdata) noexcept +{ + destroyNativeProtoExecData(static_cast(execdata)); +} + + +SharedCodeGenContext::SharedCodeGenContext( + size_t blockSize, size_t maxTotalSize, AllocationCallback* allocationCallback, void* allocationCallbackContext) + : BaseCodeGenContext{blockSize, maxTotalSize, allocationCallback, allocationCallbackContext} +{ +} + +void SharedCodeGenContext::compileOrBindModule(const ModuleId&, lua_State*, int, unsigned int, CompilationStats*) {} + +void SharedCodeGenContext::onCloseState() noexcept +{ + // The lifetime of the SharedCodeGenContext is managed separately from the + // VMs that use it. When a VM is destroyed, we don't need to do anything + // here. +} + +void SharedCodeGenContext::onDestroyFunction(void* execdata) noexcept +{ + getNativeProtoExecDataHeader(static_cast(execdata)).nativeModule->release(); +} + + +[[nodiscard]] UniqueSharedCodeGenContext createSharedCodeGenContext() +{ + return createSharedCodeGenContext(size_t(FInt::LuauCodeGenBlockSize), size_t(FInt::LuauCodeGenMaxTotalSize), nullptr, nullptr); +} + +[[nodiscard]] UniqueSharedCodeGenContext createSharedCodeGenContext(AllocationCallback* allocationCallback, void* allocationCallbackContext) +{ + return createSharedCodeGenContext( + size_t(FInt::LuauCodeGenBlockSize), size_t(FInt::LuauCodeGenMaxTotalSize), allocationCallback, allocationCallbackContext); +} + +[[nodiscard]] UniqueSharedCodeGenContext createSharedCodeGenContext( + size_t blockSize, size_t maxTotalSize, AllocationCallback* allocationCallback, void* allocationCallbackContext) +{ + UniqueSharedCodeGenContext codeGenContext{new SharedCodeGenContext{blockSize, maxTotalSize, nullptr, nullptr}}; + + if (!codeGenContext->initHeaderFunctions()) + return {}; + + return codeGenContext; +} + +void destroySharedCodeGenContext(const SharedCodeGenContext* codeGenContext) noexcept +{ + delete codeGenContext; +} + +void SharedCodeGenContextDeleter::operator()(const SharedCodeGenContext* codeGenContext) const noexcept +{ + destroySharedCodeGenContext(codeGenContext); +} + + +[[nodiscard]] static BaseCodeGenContext* getCodeGenContext(lua_State* L) noexcept +{ + return static_cast(L->global->ecb.context); +} + +static void onCloseState(lua_State* L) noexcept +{ + getCodeGenContext(L)->onCloseState(); + L->global->ecb = lua_ExecutionCallbacks{}; +} + +static void onDestroyFunction(lua_State* L, Proto* proto) noexcept +{ + getCodeGenContext(L)->onDestroyFunction(proto->execdata); + proto->execdata = nullptr; + proto->exectarget = 0; + proto->codeentry = proto->code; +} + +static int onEnter(lua_State* L, Proto* proto) +{ + BaseCodeGenContext* codeGenContext = getCodeGenContext(L); + + CODEGEN_ASSERT(proto->execdata); + CODEGEN_ASSERT(L->ci->savedpc >= proto->code && L->ci->savedpc < proto->code + proto->sizecode); + + uintptr_t target = proto->exectarget + static_cast(proto->execdata)[L->ci->savedpc - proto->code]; + + // Returns 1 to finish the function in the VM + return GateFn(codeGenContext->context.gateEntry)(L, proto, target, &codeGenContext->context); +} + +// Defined in CodeGen.cpp +void onDisable(lua_State* L, Proto* proto); + +static size_t getMemorySize(lua_State* L, Proto* proto) +{ + CODEGEN_ASSERT(FFlag::LuauCodegenHeapSizeReport); + + const NativeProtoExecDataHeader& execDataHeader = getNativeProtoExecDataHeader(static_cast(proto->execdata)); + + const size_t execDataSize = sizeof(NativeProtoExecDataHeader) + execDataHeader.bytecodeInstructionCount * sizeof(Instruction); + + // While execDataSize is exactly the size of the allocation we made and hold for 'execdata' field, the code size is approximate + // This is because code+data page is shared and owned by all Proto from a single module and each one can keep the whole region alive + // So individual Proto being freed by GC will not reflect memory use by native code correctly + return execDataSize + execDataHeader.nativeCodeSize; +} + +static void initializeExecutionCallbacks(lua_State* L, BaseCodeGenContext* codeGenContext) noexcept +{ + lua_ExecutionCallbacks* ecb = &L->global->ecb; + + ecb->context = codeGenContext; + ecb->close = onCloseState; + ecb->destroy = onDestroyFunction; + ecb->enter = onEnter; + ecb->disable = onDisable; + + if (FFlag::LuauCodegenHeapSizeReport) + ecb->getmemorysize = getMemorySize; +} + +void create_NEW(lua_State* L) +{ + return create_NEW(L, size_t(FInt::LuauCodeGenBlockSize), size_t(FInt::LuauCodeGenMaxTotalSize), nullptr, nullptr); +} + +void create_NEW(lua_State* L, AllocationCallback* allocationCallback, void* allocationCallbackContext) +{ + return create_NEW(L, size_t(FInt::LuauCodeGenBlockSize), size_t(FInt::LuauCodeGenMaxTotalSize), allocationCallback, allocationCallbackContext); +} + +void create_NEW(lua_State* L, size_t blockSize, size_t maxTotalSize, AllocationCallback* allocationCallback, void* allocationCallbackContext) +{ + std::unique_ptr codeGenContext = + std::make_unique(blockSize, maxTotalSize, allocationCallback, allocationCallbackContext); + + if (!codeGenContext->initHeaderFunctions()) + return; + + initializeExecutionCallbacks(L, codeGenContext.release()); +} + +void create_NEW(lua_State* L, SharedCodeGenContext* codeGenContext) +{ + initializeExecutionCallbacks(L, codeGenContext); +} + +} // namespace CodeGen +} // namespace Luau diff --git a/CodeGen/src/CodeGenContext.h b/CodeGen/src/CodeGenContext.h new file mode 100644 index 00000000..f6ad9ce9 --- /dev/null +++ b/CodeGen/src/CodeGenContext.h @@ -0,0 +1,114 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#pragma once + +#include "Luau/SharedCodeAllocator.h" + +#include "NativeState.h" + +#include +#include + +namespace Luau +{ +namespace CodeGen +{ + +// The "code-gen context" maintains the native code-gen state. There are two +// implementations. The StandaloneCodeGenContext is a VM-specific context type. +// It is the "simple" implementation that can be used when native code-gen is +// used with a single Luau VM. The SharedCodeGenContext supports use from +// multiple Luau VMs concurrently, and allows for sharing of executable native +// code and related metadata. + +class BaseCodeGenContext +{ +public: + BaseCodeGenContext(size_t blockSize, size_t maxTotalSize, AllocationCallback* allocationCallback, void* allocationCallbackContext); + + [[nodiscard]] bool initHeaderFunctions(); + + virtual void compileOrBindModule(const ModuleId& moduleId, lua_State* L, int idx, unsigned int flags, CompilationStats* stats) = 0; + + virtual void onCloseState() noexcept = 0; + virtual void onDestroyFunction(void* execdata) noexcept = 0; + + CodeAllocator codeAllocator; + std::unique_ptr unwindBuilder; + + uint8_t* gateData = nullptr; + size_t gateDataSize = 0; + + NativeContext context; +}; + +class StandaloneCodeGenContext final : public BaseCodeGenContext +{ +public: + StandaloneCodeGenContext(size_t blockSize, size_t maxTotalSize, AllocationCallback* allocationCallback, void* allocationCallbackContext); + + virtual void compileOrBindModule(const ModuleId& moduleId, lua_State* L, int idx, unsigned int flags, CompilationStats* stats) override; + + virtual void onCloseState() noexcept override; + virtual void onDestroyFunction(void* execdata) noexcept override; + +private: +}; + +class SharedCodeGenContext final : public BaseCodeGenContext +{ +public: + SharedCodeGenContext(size_t blockSize, size_t maxTotalSize, AllocationCallback* allocationCallback, void* allocationCallbackContext); + + virtual void compileOrBindModule(const ModuleId& moduleId, lua_State* L, int idx, unsigned int flags, CompilationStats* stats) override; + + virtual void onCloseState() noexcept override; + virtual void onDestroyFunction(void* execdata) noexcept override; + +private: + SharedCodeAllocator sharedAllocator; +}; + + +// The following will become the public interface, and can be moved into +// CodeGen.h after the shared allocator work is complete. When the old +// implementation is removed, the _NEW suffix can be dropped from these +// functions. + +class SharedCodeGenContext; + +struct SharedCodeGenContextDeleter +{ + void operator()(const SharedCodeGenContext* context) const noexcept; +}; + +using UniqueSharedCodeGenContext = std::unique_ptr; + +// Creates a new SharedCodeGenContext that can be used by multiple Luau VMs +// concurrently, using either the default allocator parameters or custom +// allocator parameters. +[[nodiscard]] UniqueSharedCodeGenContext createSharedCodeGenContext(); + +[[nodiscard]] UniqueSharedCodeGenContext createSharedCodeGenContext(AllocationCallback* allocationCallback, void* allocationCallbackContext); + +[[nodiscard]] UniqueSharedCodeGenContext createSharedCodeGenContext( + size_t blockSize, size_t maxTotalSize, AllocationCallback* allocationCallback, void* allocationCallbackContext); + +// Destroys the provided SharedCodeGenContext. All Luau VMs using the +// SharedCodeGenContext must be destroyed before this function is called. +void destroySharedCodeGenContext(const SharedCodeGenContext* codeGenContext) noexcept; + +// Initializes native code-gen on the provided Luau VM, using a VM-specific +// code-gen context and either the default allocator parameters or custom +// allocator parameters. +void create_NEW(lua_State* L); +void create_NEW(lua_State* L, AllocationCallback* allocationCallback, void* allocationCallbackContext); +void create_NEW(lua_State* L, size_t blockSize, size_t maxTotalSize, AllocationCallback* allocationCallback, void* allocationCallbackContext); + +// Initializes native code-gen on the provided Luau VM, using the provided +// SharedCodeGenContext. Note that after this function is called, the +// SharedCodeGenContext must not be destroyed until after the Luau VM L is +// destroyed via lua_close. +void create_NEW(lua_State* L, SharedCodeGenContext* codeGenContext); + +} // namespace CodeGen +} // namespace Luau diff --git a/CodeGen/src/CodeGenX64.cpp b/CodeGen/src/CodeGenX64.cpp index d992f0f1..5e450c9a 100644 --- a/CodeGen/src/CodeGenX64.cpp +++ b/CodeGen/src/CodeGenX64.cpp @@ -4,6 +4,7 @@ #include "Luau/AssemblyBuilderX64.h" #include "Luau/UnwindBuilder.h" +#include "CodeGenContext.h" #include "NativeState.h" #include "EmitCommonX64.h" @@ -218,6 +219,39 @@ bool initHeaderFunctions(NativeState& data) return true; } +bool initHeaderFunctions(BaseCodeGenContext& codeGenContext) +{ + AssemblyBuilderX64 build(/* logText= */ false); + UnwindBuilder& unwind = *codeGenContext.unwindBuilder.get(); + + unwind.startInfo(UnwindBuilder::X64); + + EntryLocations entryLocations = buildEntryFunction(build, unwind); + + build.finalize(); + + unwind.finishInfo(); + + CODEGEN_ASSERT(build.data.empty()); + + uint8_t* codeStart = nullptr; + if (!codeGenContext.codeAllocator.allocate(build.data.data(), int(build.data.size()), build.code.data(), int(build.code.size()), + codeGenContext.gateData, codeGenContext.gateDataSize, codeStart)) + { + CODEGEN_ASSERT(!"Failed to create entry function"); + return false; + } + + // Set the offset at the begining so that functions in new blocks will not overlay the locations + // specified by the unwind information of the entry function + unwind.setBeginOffset(build.getLabelOffset(entryLocations.prologueEnd)); + + codeGenContext.context.gateEntry = codeStart + build.getLabelOffset(entryLocations.start); + codeGenContext.context.gateExit = codeStart + build.getLabelOffset(entryLocations.epilogueStart); + + return true; +} + void assembleHelpers(X64::AssemblyBuilderX64& build, ModuleHelpers& helpers) { if (build.logText) diff --git a/CodeGen/src/CodeGenX64.h b/CodeGen/src/CodeGenX64.h index 1f0f27d9..eb6ab81c 100644 --- a/CodeGen/src/CodeGenX64.h +++ b/CodeGen/src/CodeGenX64.h @@ -6,6 +6,7 @@ namespace Luau namespace CodeGen { +class BaseCodeGenContext; struct NativeState; struct ModuleHelpers; @@ -15,6 +16,7 @@ namespace X64 class AssemblyBuilderX64; bool initHeaderFunctions(NativeState& data); +bool initHeaderFunctions(BaseCodeGenContext& codeGenContext); void assembleHelpers(AssemblyBuilderX64& build, ModuleHelpers& helpers); } // namespace X64 diff --git a/CodeGen/src/IrLoweringA64.cpp b/CodeGen/src/IrLoweringA64.cpp index 6d4bb350..060912d1 100644 --- a/CodeGen/src/IrLoweringA64.cpp +++ b/CodeGen/src/IrLoweringA64.cpp @@ -11,11 +11,10 @@ #include "lstate.h" #include "lgc.h" -LUAU_FASTFLAGVARIABLE(LuauCodeGenVectorA64, false) LUAU_FASTFLAGVARIABLE(LuauCodeGenOptVecA64, false) -LUAU_FASTFLAG(LuauCodegenVectorTag2) LUAU_FASTFLAG(LuauCodegenRemoveDeadStores4) +LUAU_FASTFLAG(LuauCodegenCheckTruthyFormB) namespace Luau { @@ -731,148 +730,35 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) { inst.regA64 = regs.allocReuse(KindA64::q, index, {inst.a, inst.b}); - if (FFlag::LuauCodeGenVectorA64) - { - build.fadd(inst.regA64, regOp(inst.a), regOp(inst.b)); - - if (!FFlag::LuauCodegenVectorTag2) - { - RegisterA64 tempw = regs.allocTemp(KindA64::w); - build.mov(tempw, LUA_TVECTOR); - build.ins_4s(inst.regA64, tempw, 3); - } - } - else - { - RegisterA64 tempa = regs.allocTemp(KindA64::s); - RegisterA64 tempb = regs.allocTemp(KindA64::s); - - for (uint8_t i = 0; i < 3; i++) - { - build.dup_4s(tempa, regOp(inst.a), i); - build.dup_4s(tempb, regOp(inst.b), i); - build.fadd(tempa, tempa, tempb); - build.ins_4s(inst.regA64, i, castReg(KindA64::q, tempa), 0); - } - } + build.fadd(inst.regA64, regOp(inst.a), regOp(inst.b)); break; } case IrCmd::SUB_VEC: { inst.regA64 = regs.allocReuse(KindA64::q, index, {inst.a, inst.b}); - if (FFlag::LuauCodeGenVectorA64) - { - build.fsub(inst.regA64, regOp(inst.a), regOp(inst.b)); - - if (!FFlag::LuauCodegenVectorTag2) - { - RegisterA64 tempw = regs.allocTemp(KindA64::w); - build.mov(tempw, LUA_TVECTOR); - build.ins_4s(inst.regA64, tempw, 3); - } - } - else - { - RegisterA64 tempa = regs.allocTemp(KindA64::s); - RegisterA64 tempb = regs.allocTemp(KindA64::s); - - for (uint8_t i = 0; i < 3; i++) - { - build.dup_4s(tempa, regOp(inst.a), i); - build.dup_4s(tempb, regOp(inst.b), i); - build.fsub(tempa, tempa, tempb); - build.ins_4s(inst.regA64, i, castReg(KindA64::q, tempa), 0); - } - } + build.fsub(inst.regA64, regOp(inst.a), regOp(inst.b)); break; } case IrCmd::MUL_VEC: { inst.regA64 = regs.allocReuse(KindA64::q, index, {inst.a, inst.b}); - if (FFlag::LuauCodeGenVectorA64) - { - build.fmul(inst.regA64, regOp(inst.a), regOp(inst.b)); - - if (!FFlag::LuauCodegenVectorTag2) - { - RegisterA64 tempw = regs.allocTemp(KindA64::w); - build.mov(tempw, LUA_TVECTOR); - build.ins_4s(inst.regA64, tempw, 3); - } - } - else - { - RegisterA64 tempa = regs.allocTemp(KindA64::s); - RegisterA64 tempb = regs.allocTemp(KindA64::s); - - for (uint8_t i = 0; i < 3; i++) - { - build.dup_4s(tempa, regOp(inst.a), i); - build.dup_4s(tempb, regOp(inst.b), i); - build.fmul(tempa, tempa, tempb); - build.ins_4s(inst.regA64, i, castReg(KindA64::q, tempa), 0); - } - } + build.fmul(inst.regA64, regOp(inst.a), regOp(inst.b)); break; } case IrCmd::DIV_VEC: { inst.regA64 = regs.allocReuse(KindA64::q, index, {inst.a, inst.b}); - if (FFlag::LuauCodeGenVectorA64) - { - build.fdiv(inst.regA64, regOp(inst.a), regOp(inst.b)); - - if (!FFlag::LuauCodegenVectorTag2) - { - RegisterA64 tempw = regs.allocTemp(KindA64::w); - build.mov(tempw, LUA_TVECTOR); - build.ins_4s(inst.regA64, tempw, 3); - } - } - else - { - RegisterA64 tempa = regs.allocTemp(KindA64::s); - RegisterA64 tempb = regs.allocTemp(KindA64::s); - - for (uint8_t i = 0; i < 3; i++) - { - build.dup_4s(tempa, regOp(inst.a), i); - build.dup_4s(tempb, regOp(inst.b), i); - build.fdiv(tempa, tempa, tempb); - build.ins_4s(inst.regA64, i, castReg(KindA64::q, tempa), 0); - } - } + build.fdiv(inst.regA64, regOp(inst.a), regOp(inst.b)); break; } case IrCmd::UNM_VEC: { inst.regA64 = regs.allocReuse(KindA64::q, index, {inst.a}); - if (FFlag::LuauCodeGenVectorA64) - { - build.fneg(inst.regA64, regOp(inst.a)); - - if (!FFlag::LuauCodegenVectorTag2) - { - RegisterA64 tempw = regs.allocTemp(KindA64::w); - build.mov(tempw, LUA_TVECTOR); - build.ins_4s(inst.regA64, tempw, 3); - } - } - else - { - RegisterA64 tempa = regs.allocTemp(KindA64::s); - - for (uint8_t i = 0; i < 3; i++) - { - build.dup_4s(tempa, regOp(inst.a), i); - build.fneg(tempa, tempa); - build.ins_4s(inst.regA64, i, castReg(KindA64::q, tempa), 0); - } - } + build.fneg(inst.regA64, regOp(inst.a)); break; } case IrCmd::NOT_ANY: @@ -1232,7 +1118,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) { inst.regA64 = regs.allocReg(KindA64::q, index); - if (FFlag::LuauCodeGenOptVecA64 && FFlag::LuauCodegenVectorTag2 && inst.a.kind == IrOpKind::Constant) + if (FFlag::LuauCodeGenOptVecA64 && inst.a.kind == IrOpKind::Constant) { float value = float(doubleOp(inst.a)); uint32_t asU32; @@ -1256,16 +1142,9 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) { RegisterA64 tempd = tempDouble(inst.a); RegisterA64 temps = castReg(KindA64::s, tempd); - RegisterA64 tempw = regs.allocTemp(KindA64::w); build.fcvt(temps, tempd); build.dup_4s(inst.regA64, castReg(KindA64::q, temps), 0); - - if (!FFlag::LuauCodegenVectorTag2) - { - build.mov(tempw, LUA_TVECTOR); - build.ins_4s(inst.regA64, tempw, 3); - } } break; } @@ -1568,7 +1447,15 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) } // fail to fallback on 'false' boolean value (falsy) - build.cbz(regOp(inst.b), target); + if (!FFlag::LuauCodegenCheckTruthyFormB || inst.b.kind != IrOpKind::Constant) + { + build.cbz(regOp(inst.b), target); + } + else + { + if (intOp(inst.b) == 0) + build.b(target); + } if (inst.a.kind != IrOpKind::Constant) build.setLabel(skip); diff --git a/CodeGen/src/IrLoweringX64.cpp b/CodeGen/src/IrLoweringX64.cpp index cfa9ba98..d417f19f 100644 --- a/CodeGen/src/IrLoweringX64.cpp +++ b/CodeGen/src/IrLoweringX64.cpp @@ -15,9 +15,9 @@ #include "lstate.h" #include "lgc.h" -LUAU_FASTFLAG(LuauCodegenVectorTag2) LUAU_FASTFLAGVARIABLE(LuauCodegenVectorOptAnd, false) LUAU_FASTFLAGVARIABLE(LuauCodegenSmallerUnm, false) +LUAU_FASTFLAGVARIABLE(LuauCodegenCheckTruthyFormB, false) namespace Luau { @@ -631,9 +631,6 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) build.vandps(tmp2.reg, regOp(inst.b), vectorAndMaskOp()); build.vaddps(inst.regX64, tmp1.reg, tmp2.reg); } - - if (!FFlag::LuauCodegenVectorTag2) - build.vorps(inst.regX64, inst.regX64, vectorOrMaskOp()); break; } case IrCmd::SUB_VEC: @@ -660,9 +657,6 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) build.vandps(tmp2.reg, regOp(inst.b), vectorAndMaskOp()); build.vsubps(inst.regX64, tmp1.reg, tmp2.reg); } - - if (!FFlag::LuauCodegenVectorTag2) - build.vorps(inst.regX64, inst.regX64, vectorOrMaskOp()); break; } case IrCmd::MUL_VEC: @@ -689,9 +683,6 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) build.vandps(tmp2.reg, regOp(inst.b), vectorAndMaskOp()); build.vmulps(inst.regX64, tmp1.reg, tmp2.reg); } - - if (!FFlag::LuauCodegenVectorTag2) - build.vorps(inst.regX64, inst.regX64, vectorOrMaskOp()); break; } case IrCmd::DIV_VEC: @@ -718,9 +709,6 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) build.vandps(tmp2.reg, regOp(inst.b), vectorAndMaskOp()); build.vdivps(inst.regX64, tmp1.reg, tmp2.reg); } - - if (!FFlag::LuauCodegenVectorTag2) - build.vpinsrd(inst.regX64, inst.regX64, build.i32(LUA_TVECTOR), 3); break; } case IrCmd::UNM_VEC: @@ -745,9 +733,6 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) build.vxorpd(inst.regX64, inst.regX64, build.f32x4(-0.0, -0.0, -0.0, -0.0)); } } - - if (!FFlag::LuauCodegenVectorTag2) - build.vpinsrd(inst.regX64, inst.regX64, build.i32(LUA_TVECTOR), 3); break; } case IrCmd::NOT_ANY: @@ -1052,18 +1037,12 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) static_assert(sizeof(asU32) == sizeof(value), "Expecting float to be 32-bit"); memcpy(&asU32, &value, sizeof(value)); - if (FFlag::LuauCodegenVectorTag2) - build.vmovaps(inst.regX64, build.u32x4(asU32, asU32, asU32, 0)); - else - build.vmovaps(inst.regX64, build.u32x4(asU32, asU32, asU32, LUA_TVECTOR)); + build.vmovaps(inst.regX64, build.u32x4(asU32, asU32, asU32, 0)); } else { build.vcvtsd2ss(inst.regX64, inst.regX64, memRegDoubleOp(inst.a)); build.vpshufps(inst.regX64, inst.regX64, inst.regX64, 0b00'00'00'00); - - if (!FFlag::LuauCodegenVectorTag2) - build.vpinsrd(inst.regX64, inst.regX64, build.i32(LUA_TVECTOR), 3); } break; case IrCmd::TAG_VECTOR: @@ -1306,8 +1285,16 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) } // fail to fallback on 'false' boolean value (falsy) - build.cmp(memRegUintOp(inst.b), 0); - jumpOrAbortOnUndef(ConditionX64::Equal, inst.c, next); + if (!FFlag::LuauCodegenCheckTruthyFormB || inst.b.kind != IrOpKind::Constant) + { + build.cmp(memRegUintOp(inst.b), 0); + jumpOrAbortOnUndef(ConditionX64::Equal, inst.c, next); + } + else + { + if (intOp(inst.b) == 0) + jumpOrAbortOnUndef(inst.c, next); + } if (inst.a.kind != IrOpKind::Constant) build.setLabel(skip); @@ -2306,7 +2293,7 @@ OperandX64 IrLoweringX64::bufferAddrOp(IrOp bufferOp, IrOp indexOp) RegisterX64 IrLoweringX64::vecOp(IrOp op, ScopedRegX64& tmp) { - if (FFlag::LuauCodegenVectorOptAnd && FFlag::LuauCodegenVectorTag2) + if (FFlag::LuauCodegenVectorOptAnd) { IrInst source = function.instOp(op); CODEGEN_ASSERT(source.cmd != IrCmd::SUBSTITUTE); // we don't process substitutions @@ -2365,16 +2352,6 @@ OperandX64 IrLoweringX64::vectorAndMaskOp() return vectorAndMask; } -OperandX64 IrLoweringX64::vectorOrMaskOp() -{ - CODEGEN_ASSERT(!FFlag::LuauCodegenVectorTag2); - - if (vectorOrMask.base == noreg) - vectorOrMask = build.u32x4(0, 0, 0, LUA_TVECTOR); - - return vectorOrMask; -} - } // namespace X64 } // namespace CodeGen } // namespace Luau diff --git a/CodeGen/src/IrLoweringX64.h b/CodeGen/src/IrLoweringX64.h index 7ec4079e..5fb7b0fa 100644 --- a/CodeGen/src/IrLoweringX64.h +++ b/CodeGen/src/IrLoweringX64.h @@ -63,7 +63,6 @@ struct IrLoweringX64 Label& labelOp(IrOp op) const; OperandX64 vectorAndMaskOp(); - OperandX64 vectorOrMaskOp(); struct InterruptHandler { diff --git a/CodeGen/src/IrTranslation.cpp b/CodeGen/src/IrTranslation.cpp index b3471573..362c4368 100644 --- a/CodeGen/src/IrTranslation.cpp +++ b/CodeGen/src/IrTranslation.cpp @@ -12,7 +12,6 @@ #include "lstate.h" #include "ltm.h" -LUAU_FASTFLAGVARIABLE(LuauCodegenVectorTag2, false) LUAU_FASTFLAGVARIABLE(LuauCodegenLoadTVTag, false) namespace Luau @@ -388,8 +387,7 @@ static void translateInstBinaryNumeric(IrBuilder& build, int ra, int rb, int rc, CODEGEN_ASSERT(!"Unknown TM op"); } - if (FFlag::LuauCodegenVectorTag2) - result = build.inst(IrCmd::TAG_VECTOR, result); + result = build.inst(IrCmd::TAG_VECTOR, result); build.inst(IrCmd::STORE_TVALUE, build.vmReg(ra), result); return; @@ -417,8 +415,7 @@ static void translateInstBinaryNumeric(IrBuilder& build, int ra, int rb, int rc, CODEGEN_ASSERT(!"Unknown TM op"); } - if (FFlag::LuauCodegenVectorTag2) - result = build.inst(IrCmd::TAG_VECTOR, result); + result = build.inst(IrCmd::TAG_VECTOR, result); build.inst(IrCmd::STORE_TVALUE, build.vmReg(ra), result); return; @@ -446,8 +443,7 @@ static void translateInstBinaryNumeric(IrBuilder& build, int ra, int rb, int rc, CODEGEN_ASSERT(!"Unknown TM op"); } - if (FFlag::LuauCodegenVectorTag2) - result = build.inst(IrCmd::TAG_VECTOR, result); + result = build.inst(IrCmd::TAG_VECTOR, result); build.inst(IrCmd::STORE_TVALUE, build.vmReg(ra), result); return; @@ -589,8 +585,7 @@ void translateInstMinus(IrBuilder& build, const Instruction* pc, int pcpos) IrOp vb = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(rb)); IrOp va = build.inst(IrCmd::UNM_VEC, vb); - if (FFlag::LuauCodegenVectorTag2) - va = build.inst(IrCmd::TAG_VECTOR, va); + va = build.inst(IrCmd::TAG_VECTOR, va); build.inst(IrCmd::STORE_TVALUE, build.vmReg(ra), va); return; } diff --git a/CodeGen/src/NativeProtoExecData.cpp b/CodeGen/src/NativeProtoExecData.cpp index 1b2ca379..33a1db76 100644 --- a/CodeGen/src/NativeProtoExecData.cpp +++ b/CodeGen/src/NativeProtoExecData.cpp @@ -17,9 +17,7 @@ namespace CodeGen void NativeProtoExecDataDeleter::operator()(const uint32_t* instructionOffsets) const noexcept { - const NativeProtoExecDataHeader* header = &getNativeProtoExecDataHeader(instructionOffsets); - header->~NativeProtoExecDataHeader(); - delete[] reinterpret_cast(header); + destroyNativeProtoExecData(instructionOffsets); } [[nodiscard]] NativeProtoExecDataPtr createNativeProtoExecData(uint32_t bytecodeInstructionCount) @@ -29,6 +27,13 @@ void NativeProtoExecDataDeleter::operator()(const uint32_t* instructionOffsets) return NativeProtoExecDataPtr{reinterpret_cast(bytes.release() + sizeof(NativeProtoExecDataHeader))}; } +void destroyNativeProtoExecData(const uint32_t* instructionOffsets) noexcept +{ + const NativeProtoExecDataHeader* header = &getNativeProtoExecDataHeader(instructionOffsets); + header->~NativeProtoExecDataHeader(); + delete[] reinterpret_cast(header); +} + [[nodiscard]] NativeProtoExecDataHeader& getNativeProtoExecDataHeader(uint32_t* instructionOffsets) noexcept { return *reinterpret_cast(reinterpret_cast(instructionOffsets) - sizeof(NativeProtoExecDataHeader)); @@ -36,7 +41,6 @@ void NativeProtoExecDataDeleter::operator()(const uint32_t* instructionOffsets) [[nodiscard]] const NativeProtoExecDataHeader& getNativeProtoExecDataHeader(const uint32_t* instructionOffsets) noexcept { - return *reinterpret_cast( reinterpret_cast(instructionOffsets) - sizeof(NativeProtoExecDataHeader)); } diff --git a/CodeGen/src/NativeState.cpp b/CodeGen/src/NativeState.cpp index a161987d..5f6df4b6 100644 --- a/CodeGen/src/NativeState.cpp +++ b/CodeGen/src/NativeState.cpp @@ -112,5 +112,83 @@ void initFunctions(NativeState& data) data.context.executeSETLIST = executeSETLIST; } +void initFunctions(NativeContext& context) +{ + static_assert(sizeof(context.luauF_table) == sizeof(luauF_table), "fastcall tables are not of the same length"); + memcpy(context.luauF_table, luauF_table, sizeof(luauF_table)); + + context.luaV_lessthan = luaV_lessthan; + context.luaV_lessequal = luaV_lessequal; + context.luaV_equalval = luaV_equalval; + context.luaV_doarith = luaV_doarith; + context.luaV_dolen = luaV_dolen; + context.luaV_gettable = luaV_gettable; + context.luaV_settable = luaV_settable; + context.luaV_getimport = luaV_getimport; + context.luaV_concat = luaV_concat; + + context.luaH_getn = luaH_getn; + context.luaH_new = luaH_new; + context.luaH_clone = luaH_clone; + context.luaH_resizearray = luaH_resizearray; + context.luaH_setnum = luaH_setnum; + + context.luaC_barriertable = luaC_barriertable; + context.luaC_barrierf = luaC_barrierf; + context.luaC_barrierback = luaC_barrierback; + context.luaC_step = luaC_step; + + context.luaF_close = luaF_close; + context.luaF_findupval = luaF_findupval; + context.luaF_newLclosure = luaF_newLclosure; + + context.luaT_gettm = luaT_gettm; + context.luaT_objtypenamestr = luaT_objtypenamestr; + + context.libm_exp = exp; + context.libm_pow = pow; + context.libm_fmod = fmod; + context.libm_log = log; + context.libm_log2 = log2; + context.libm_log10 = log10; + context.libm_ldexp = ldexp; + context.libm_round = round; + context.libm_frexp = frexp; + context.libm_modf = modf; + + context.libm_asin = asin; + context.libm_sin = sin; + context.libm_sinh = sinh; + context.libm_acos = acos; + context.libm_cos = cos; + context.libm_cosh = cosh; + context.libm_atan = atan; + context.libm_atan2 = atan2; + context.libm_tan = tan; + context.libm_tanh = tanh; + + context.forgLoopTableIter = forgLoopTableIter; + context.forgLoopNodeIter = forgLoopNodeIter; + context.forgLoopNonTableFallback = forgLoopNonTableFallback; + context.forgPrepXnextFallback = forgPrepXnextFallback; + context.callProlog = callProlog; + context.callEpilogC = callEpilogC; + + context.callFallback = callFallback; + + context.executeGETGLOBAL = executeGETGLOBAL; + context.executeSETGLOBAL = executeSETGLOBAL; + context.executeGETTABLEKS = executeGETTABLEKS; + context.executeSETTABLEKS = executeSETTABLEKS; + + context.executeNAMECALL = executeNAMECALL; + context.executeFORGPREP = executeFORGPREP; + context.executeGETVARARGSMultRet = executeGETVARARGSMultRet; + context.executeGETVARARGSConst = executeGETVARARGSConst; + context.executeDUPCLOSURE = executeDUPCLOSURE; + context.executePREPVARARGS = executePREPVARARGS; + context.executeSETLIST = executeSETLIST; +} + } // namespace CodeGen } // namespace Luau diff --git a/CodeGen/src/NativeState.h b/CodeGen/src/NativeState.h index 7670482d..3e7c85e9 100644 --- a/CodeGen/src/NativeState.h +++ b/CodeGen/src/NativeState.h @@ -124,6 +124,7 @@ struct NativeState }; void initFunctions(NativeState& data); +void initFunctions(NativeContext& context); } // namespace CodeGen } // namespace Luau diff --git a/CodeGen/src/OptimizeConstProp.cpp b/CodeGen/src/OptimizeConstProp.cpp index 8bffb3e0..01e81d0f 100644 --- a/CodeGen/src/OptimizeConstProp.cpp +++ b/CodeGen/src/OptimizeConstProp.cpp @@ -17,10 +17,10 @@ LUAU_FASTINTVARIABLE(LuauCodeGenMinLinearBlockPath, 3) LUAU_FASTINTVARIABLE(LuauCodeGenReuseSlotLimit, 64) LUAU_FASTFLAGVARIABLE(DebugLuauAbortingChecks, false) -LUAU_FASTFLAG(LuauCodegenVectorTag2) LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauCodeGenCoverForgprepEffect, false) LUAU_FASTFLAG(LuauCodegenRemoveDeadStores4) LUAU_FASTFLAG(LuauCodegenLoadTVTag) +LUAU_FASTFLAGVARIABLE(LuauCodegenInferNumTag, false) namespace Luau { @@ -717,17 +717,8 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& { if (IrInst* arg = function.asInstOp(inst.b)) { - if (FFlag::LuauCodegenVectorTag2) - { - if (arg->cmd == IrCmd::TAG_VECTOR) - tag = LUA_TVECTOR; - } - else - { - if (arg->cmd == IrCmd::ADD_VEC || arg->cmd == IrCmd::SUB_VEC || arg->cmd == IrCmd::MUL_VEC || arg->cmd == IrCmd::DIV_VEC || - arg->cmd == IrCmd::UNM_VEC) - tag = LUA_TVECTOR; - } + if (arg->cmd == IrCmd::TAG_VECTOR) + tag = LUA_TVECTOR; if (FFlag::LuauCodegenLoadTVTag && arg->cmd == IrCmd::LOAD_TVALUE && arg->c.kind != IrOpKind::None) tag = function.tagOp(arg->c); @@ -906,23 +897,58 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& { uint8_t b = function.tagOp(inst.b); - if (uint8_t tag = state.tryGetTag(inst.a); tag != 0xff) + if (FFlag::LuauCodegenInferNumTag) { - if (tag == b) + uint8_t tag = state.tryGetTag(inst.a); + + if (tag == 0xff) { - if (FFlag::DebugLuauAbortingChecks) - replace(function, inst.c, build.undef()); + if (IrOp value = state.tryGetValue(inst.a); value.kind == IrOpKind::Constant) + { + if (function.constOp(value).kind == IrConstKind::Double) + tag = LUA_TNUMBER; + } + } + + if (tag != 0xff) + { + if (tag == b) + { + if (FFlag::DebugLuauAbortingChecks) + replace(function, inst.c, build.undef()); + else + kill(function, inst); + } else - kill(function, inst); + { + replace(function, block, index, {IrCmd::JUMP, inst.c}); // Shows a conflict in assumptions on this path + } } else { - replace(function, block, index, {IrCmd::JUMP, inst.c}); // Shows a conflict in assumptions on this path + state.updateTag(inst.a, b); // We can assume the tag value going forward } } else { - state.updateTag(inst.a, b); // We can assume the tag value going forward + if (uint8_t tag = state.tryGetTag(inst.a); tag != 0xff) + { + if (tag == b) + { + if (FFlag::DebugLuauAbortingChecks) + replace(function, inst.c, build.undef()); + else + kill(function, inst); + } + else + { + replace(function, block, index, {IrCmd::JUMP, inst.c}); // Shows a conflict in assumptions on this path + } + } + else + { + state.updateTag(inst.a, b); // We can assume the tag value going forward + } } break; } @@ -1296,22 +1322,16 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& case IrCmd::SUB_VEC: case IrCmd::MUL_VEC: case IrCmd::DIV_VEC: - if (FFlag::LuauCodegenVectorTag2) - { - if (IrInst* a = function.asInstOp(inst.a); a && a->cmd == IrCmd::TAG_VECTOR) - replace(function, inst.a, a->a); + if (IrInst* a = function.asInstOp(inst.a); a && a->cmd == IrCmd::TAG_VECTOR) + replace(function, inst.a, a->a); - if (IrInst* b = function.asInstOp(inst.b); b && b->cmd == IrCmd::TAG_VECTOR) - replace(function, inst.b, b->a); - } + if (IrInst* b = function.asInstOp(inst.b); b && b->cmd == IrCmd::TAG_VECTOR) + replace(function, inst.b, b->a); break; case IrCmd::UNM_VEC: - if (FFlag::LuauCodegenVectorTag2) - { - if (IrInst* a = function.asInstOp(inst.a); a && a->cmd == IrCmd::TAG_VECTOR) - replace(function, inst.a, a->a); - } + if (IrInst* a = function.asInstOp(inst.a); a && a->cmd == IrCmd::TAG_VECTOR) + replace(function, inst.a, a->a); break; case IrCmd::CHECK_NODE_NO_NEXT: diff --git a/CodeGen/src/OptimizeDeadStore.cpp b/CodeGen/src/OptimizeDeadStore.cpp index 0b6550fd..a1a0d91d 100644 --- a/CodeGen/src/OptimizeDeadStore.cpp +++ b/CodeGen/src/OptimizeDeadStore.cpp @@ -10,7 +10,6 @@ #include "lobject.h" LUAU_FASTFLAGVARIABLE(LuauCodegenRemoveDeadStores4, false) -LUAU_FASTFLAG(LuauCodegenVectorTag2) LUAU_FASTFLAG(LuauCodegenLoadTVTag) // TODO: optimization can be improved by knowing which registers are live in at each VM exit @@ -340,17 +339,8 @@ static void markDeadStoresInInst(RemoveDeadStoreState& state, IrBuilder& build, // TODO (CLI-101027): similar code is used in constant propagation optimization and should be shared in utilities if (IrInst* arg = function.asInstOp(inst.b)) { - if (FFlag::LuauCodegenVectorTag2) - { - if (arg->cmd == IrCmd::TAG_VECTOR) - regInfo.maybeGco = false; - } - else - { - if (arg->cmd == IrCmd::ADD_VEC || arg->cmd == IrCmd::SUB_VEC || arg->cmd == IrCmd::MUL_VEC || arg->cmd == IrCmd::DIV_VEC || - arg->cmd == IrCmd::UNM_VEC) - regInfo.maybeGco = false; - } + if (arg->cmd == IrCmd::TAG_VECTOR) + regInfo.maybeGco = false; if (FFlag::LuauCodegenLoadTVTag && arg->cmd == IrCmd::LOAD_TVALUE && arg->c.kind != IrOpKind::None) regInfo.maybeGco = isGCO(function.tagOp(arg->c)); diff --git a/CodeGen/src/lcodegen.cpp b/CodeGen/src/lcodegen.cpp index 0795cd48..de84f527 100644 --- a/CodeGen/src/lcodegen.cpp +++ b/CodeGen/src/lcodegen.cpp @@ -5,6 +5,8 @@ #include "lapi.h" +LUAU_FASTFLAG(LuauCodegenDetailedCompilationResult) + int luau_codegen_supported() { return Luau::CodeGen::isSupported(); @@ -17,5 +19,8 @@ void luau_codegen_create(lua_State* L) void luau_codegen_compile(lua_State* L, int idx) { - Luau::CodeGen::compile(L, idx); + if (FFlag::LuauCodegenDetailedCompilationResult) + Luau::CodeGen::compile(L, idx); + else + Luau::CodeGen::compile_DEPRECATED(L, idx); } diff --git a/Common/include/Luau/Common.h b/Common/include/Luau/Common.h index 31b416fb..406f6553 100644 --- a/Common/include/Luau/Common.h +++ b/Common/include/Luau/Common.h @@ -77,7 +77,7 @@ struct FValue list = this; } - operator T() const + LUAU_FORCEINLINE operator T() const { return value; } diff --git a/Sources.cmake b/Sources.cmake index a1711fe0..5a536762 100644 --- a/Sources.cmake +++ b/Sources.cmake @@ -107,6 +107,7 @@ target_sources(Luau.CodeGen PRIVATE CodeGen/src/CodeBlockUnwind.cpp CodeGen/src/CodeGen.cpp CodeGen/src/CodeGenAssembly.cpp + CodeGen/src/CodeGenContext.cpp CodeGen/src/CodeGenUtils.cpp CodeGen/src/CodeGenA64.cpp CodeGen/src/CodeGenX64.cpp @@ -139,6 +140,7 @@ target_sources(Luau.CodeGen PRIVATE CodeGen/src/BitUtils.h CodeGen/src/ByteUtils.h + CodeGen/src/CodeGenContext.h CodeGen/src/CodeGenLower.h CodeGen/src/CodeGenUtils.h CodeGen/src/CodeGenA64.h @@ -356,13 +358,19 @@ target_sources(isocline PRIVATE extern/isocline/src/isocline.c ) -# Common sources shared between all CLI apps -target_sources(Luau.CLI.lib PRIVATE - CLI/FileUtils.cpp - CLI/Flags.cpp - CLI/Flags.h - CLI/FileUtils.h -) + +if (TARGET Luau.Repl.CLI OR TARGET Luau.Analyze.CLI OR + TARGET Luau.Ast.CLI OR TARGET Luau.CLI.Test OR + TARGET Luau.Reduce.CLI OR TARGET Luau.Compile.CLI OR + TARGET Luau.Bytecode.CLI) + # Common sources shared between all CLI apps. + target_sources(Luau.CLI.lib PRIVATE + CLI/FileUtils.cpp + CLI/Flags.cpp + CLI/Flags.h + CLI/FileUtils.h + ) +endif() if(TARGET Luau.Repl.CLI) # Luau.Repl.CLI Sources diff --git a/VM/src/ldebug.cpp b/VM/src/ldebug.cpp index 78ccd422..7122b035 100644 --- a/VM/src/ldebug.cpp +++ b/VM/src/ldebug.cpp @@ -186,8 +186,16 @@ int lua_getinfo(lua_State* L, int level, const char* what, lua_Debug* ar) CallInfo* ci = NULL; if (level < 0) { - const TValue* func = luaA_toobject(L, level); - api_check(L, ttisfunction(func)); + // element has to be within stack + if (-level > L->top - L->base) + return 0; + + StkId func = L->top + level; + + // and it has to be a function + if (!ttisfunction(func)) + return 0; + f = clvalue(func); } else if (unsigned(level) < unsigned(L->ci - L->base_ci)) diff --git a/tests/Conformance.test.cpp b/tests/Conformance.test.cpp index 095809d5..68904e37 100644 --- a/tests/Conformance.test.cpp +++ b/tests/Conformance.test.cpp @@ -35,6 +35,9 @@ LUAU_FASTINT(CodegenHeuristicsInstructionLimit) LUAU_FASTFLAG(LuauLoadExceptionSafe) LUAU_DYNAMIC_FASTFLAG(LuauDebugInfoDupArgLeftovers) LUAU_FASTFLAG(LuauCompileRepeatUntilSkippedLocals) +LUAU_FASTFLAG(LuauCodegenInferNumTag) +LUAU_FASTFLAG(LuauCodegenDetailedCompilationResult) +LUAU_FASTFLAG(LuauCodegenCheckTruthyFormB) static lua_CompileOptions defaultOptions() { @@ -238,7 +241,12 @@ static StateRef runConformance(const char* name, void (*setup)(lua_State* L) = n free(bytecode); if (result == 0 && codegen && !skipCodegen && luau_codegen_supported()) - Luau::CodeGen::compile(L, -1, Luau::CodeGen::CodeGen_ColdFunctions); + { + if (FFlag::LuauCodegenDetailedCompilationResult) + Luau::CodeGen::compile(L, -1, Luau::CodeGen::CodeGen_ColdFunctions); + else + Luau::CodeGen::compile_DEPRECATED(L, -1, Luau::CodeGen::CodeGen_ColdFunctions); + } int status = (result == 0) ? lua_resume(L, nullptr, 0) : LUA_ERRSYNTAX; @@ -1825,6 +1833,18 @@ TEST_CASE("LightuserdataApi") globalState.reset(); } +TEST_CASE("DebugApi") +{ + StateRef globalState(luaL_newstate(), lua_close); + lua_State* L = globalState.get(); + + lua_pushnumber(L, 10); + + lua_Debug ar; + CHECK(lua_getinfo(L, -1, "f", &ar) == 0); // number is not a function + CHECK(lua_getinfo(L, -10, "f", &ar) == 0); // not on stack +} + TEST_CASE("Iter") { runConformance("iter.lua"); @@ -2051,6 +2071,9 @@ TEST_CASE("SafeEnv") TEST_CASE("Native") { + ScopedFastFlag luauCodegenRemoveDeadStores{FFlag::LuauCodegenInferNumTag, true}; + ScopedFastFlag luauCodegenCheckTruthyFormB{FFlag::LuauCodegenCheckTruthyFormB, true}; + // This tests requires code to run natively, otherwise all 'is_native' checks will fail if (!codegen || !luau_codegen_supported()) return; @@ -2108,6 +2131,8 @@ TEST_CASE("NativeTypeAnnotations") TEST_CASE("HugeFunction") { + ScopedFastFlag luauCodegenDetailedCompilationResult{FFlag::LuauCodegenDetailedCompilationResult, true}; + std::string source = makeHugeFunctionSource(); StateRef globalState(luaL_newstate(), lua_close); @@ -2214,6 +2239,7 @@ TEST_CASE("IrInstructionLimit") return; ScopedFastInt codegenHeuristicsInstructionLimit{FInt::CodegenHeuristicsInstructionLimit, 50'000}; + ScopedFastFlag luauCodegenDetailedCompilationResult{FFlag::LuauCodegenDetailedCompilationResult, true}; std::string source; @@ -2254,10 +2280,18 @@ TEST_CASE("IrInstructionLimit") REQUIRE(result == 0); Luau::CodeGen::CompilationStats nativeStats = {}; - Luau::CodeGen::CodeGenCompilationResult nativeResult = Luau::CodeGen::compile(L, -1, Luau::CodeGen::CodeGen_ColdFunctions, &nativeStats); + Luau::CodeGen::CompilationResult nativeResult = Luau::CodeGen::compile(L, -1, Luau::CodeGen::CodeGen_ColdFunctions, &nativeStats); // Limit is not hit immediately, so with some functions compiled it should be a success - CHECK(nativeResult == Luau::CodeGen::CodeGenCompilationResult::CodeGenOverflowInstructionLimit); + CHECK(nativeResult.result == Luau::CodeGen::CodeGenCompilationResult::Success); + + // But it has some failed functions + CHECK(nativeResult.hasErrors()); + REQUIRE(!nativeResult.protoFailures.empty()); + + CHECK(nativeResult.protoFailures.front().result == Luau::CodeGen::CodeGenCompilationResult::CodeGenOverflowInstructionLimit); + CHECK(nativeResult.protoFailures.front().line != -1); + CHECK(nativeResult.protoFailures.front().debugname != ""); // We should be able to compile at least one of our functions CHECK(nativeStats.functionsCompiled > 0); diff --git a/tests/DataFlowGraph.test.cpp b/tests/DataFlowGraph.test.cpp index 96c53f85..c3a6a464 100644 --- a/tests/DataFlowGraph.test.cpp +++ b/tests/DataFlowGraph.test.cpp @@ -631,4 +631,31 @@ print(t.x) CHECK(phi->operands.at(1) == x2); } +TEST_CASE_FIXTURE(DataFlowGraphFixture, "insert_trivial_phi_nodes_inside_of_phi_nodes") +{ + dfg(R"( + local t = {} + + local function f(k: string) + if t[k] ~= nil then + return + end + + t[k] = 5 + end + )"); + + DefId t1 = graph->getDef(query(module)->vars.data[0]); // local t = {} + DefId t2 = getDef(); // t[k] ~= nil + DefId t3 = getDef(); // t[k] = 5 + + CHECK(t1 != t2); + CHECK(t2 == t3); + + const Phi* t2phi = get(t2); + REQUIRE(t2phi); + CHECK(t2phi->operands.size() == 1); + CHECK(t2phi->operands.at(0) == t1); +} + TEST_SUITE_END(); diff --git a/tests/IrBuilder.test.cpp b/tests/IrBuilder.test.cpp index 5fbfb8bc..00e9d312 100644 --- a/tests/IrBuilder.test.cpp +++ b/tests/IrBuilder.test.cpp @@ -12,9 +12,9 @@ #include -LUAU_FASTFLAG(LuauCodegenVectorTag2) LUAU_FASTFLAG(LuauCodegenRemoveDeadStores4) LUAU_FASTFLAG(DebugLuauAbortingChecks) +LUAU_FASTFLAG(LuauCodegenInferNumTag) using namespace Luau::CodeGen; @@ -2501,8 +2501,6 @@ bb_fallback_1: TEST_CASE_FIXTURE(IrBuilderFixture, "TagVectorSkipErrorFix") { - ScopedFastFlag luauCodegenVectorTag2{FFlag::LuauCodegenVectorTag2, true}; - IrOp block = build.block(IrBlockKind::Internal); build.beginBlock(block); @@ -2631,6 +2629,30 @@ bb_0: )"); } +TEST_CASE_FIXTURE(IrBuilderFixture, "InferNumberTagFromLimitedContext") +{ + ScopedFastFlag luauCodegenRemoveDeadStores{FFlag::LuauCodegenInferNumTag, true}; + + IrOp entry = build.block(IrBlockKind::Internal); + + build.beginBlock(entry); + build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(2.0)); + build.inst(IrCmd::CHECK_TAG, build.vmReg(0), build.constTag(ttable), build.vmExit(1)); + build.inst(IrCmd::STORE_TVALUE, build.vmReg(1), build.inst(IrCmd::LOAD_TVALUE, build.vmReg(0))); + build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(1)); + + updateUseCounts(build.function); + computeCfgInfo(build.function); + constPropInBlockChains(build, true); + + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( +bb_0: + STORE_DOUBLE R0, 2 + JUMP exit(1) + +)"); +} + TEST_SUITE_END(); TEST_SUITE_BEGIN("Analysis"); diff --git a/tests/IrLowering.test.cpp b/tests/IrLowering.test.cpp index baafdb01..f4b85a33 100644 --- a/tests/IrLowering.test.cpp +++ b/tests/IrLowering.test.cpp @@ -12,7 +12,6 @@ #include -LUAU_FASTFLAG(LuauCodegenVectorTag2) LUAU_FASTFLAG(LuauCodegenRemoveDeadStores4) LUAU_FASTFLAG(LuauCodegenLoadTVTag) @@ -65,8 +64,6 @@ TEST_SUITE_BEGIN("IrLowering"); TEST_CASE("VectorReciprocal") { - ScopedFastFlag luauCodegenVectorTag2{FFlag::LuauCodegenVectorTag2, true}; - CHECK_EQ("\n" + getCodegenAssembly(R"( local function vecrcp(a: vector) return 1 / a @@ -121,8 +118,6 @@ bb_bytecode_1: TEST_CASE("VectorAdd") { - ScopedFastFlag luauCodegenVectorTag2{FFlag::LuauCodegenVectorTag2, true}; - CHECK_EQ("\n" + getCodegenAssembly(R"( local function vec3add(a: vector, b: vector) return a + b @@ -149,8 +144,6 @@ bb_bytecode_1: TEST_CASE("VectorMinus") { - ScopedFastFlag luauCodegenVectorTag2{FFlag::LuauCodegenVectorTag2, true}; - CHECK_EQ("\n" + getCodegenAssembly(R"( local function vec3minus(a: vector) return -a @@ -175,7 +168,6 @@ bb_bytecode_1: TEST_CASE("VectorSubMulDiv") { - ScopedFastFlag luauCodegenVectorTag2{FFlag::LuauCodegenVectorTag2, true}; ScopedFastFlag luauCodegenRemoveDeadStores{FFlag::LuauCodegenRemoveDeadStores4, true}; CHECK_EQ("\n" + getCodegenAssembly(R"( @@ -210,7 +202,6 @@ bb_bytecode_1: TEST_CASE("VectorSubMulDiv2") { - ScopedFastFlag luauCodegenVectorTag2{FFlag::LuauCodegenVectorTag2, true}; ScopedFastFlag luauCodegenRemoveDeadStores{FFlag::LuauCodegenRemoveDeadStores4, true}; CHECK_EQ("\n" + getCodegenAssembly(R"( @@ -241,7 +232,6 @@ bb_bytecode_1: TEST_CASE("VectorMulDivMixed") { - ScopedFastFlag luauCodegenVectorTag2{FFlag::LuauCodegenVectorTag2, true}; ScopedFastFlag luauCodegenRemoveDeadStores{FFlag::LuauCodegenRemoveDeadStores4, true}; CHECK_EQ("\n" + getCodegenAssembly(R"( @@ -405,7 +395,6 @@ bb_bytecode_0: TEST_CASE("VectorConstantTag") { ScopedFastFlag luauCodegenRemoveDeadStores{FFlag::LuauCodegenRemoveDeadStores4, true}; - ScopedFastFlag luauCodegenVectorTag2{FFlag::LuauCodegenVectorTag2, true}; ScopedFastFlag luauCodegenLoadTVTag{FFlag::LuauCodegenLoadTVTag, true}; CHECK_EQ("\n" + getCodegenAssembly(R"( diff --git a/tests/Subtyping.test.cpp b/tests/Subtyping.test.cpp index d42856c2..36ec4e8b 100644 --- a/tests/Subtyping.test.cpp +++ b/tests/Subtyping.test.cpp @@ -450,6 +450,26 @@ TEST_CASE_FIXTURE(SubtypeFixture, "basic_typefamily_with_generics") CHECK(result.isSubtype); } +TEST_CASE_FIXTURE(SubtypeFixture, "variadic_subpath_in_pack") +{ + TypePackId subTArgs = arena.addTypePack(TypePack{{builtinTypes->stringType, builtinTypes->stringType}, builtinTypes->anyTypePack}); + TypePackId superTArgs = arena.addTypePack(TypePack{{builtinTypes->numberType}, builtinTypes->anyTypePack}); + // (string, string, ...any) -> number + TypeId functionSub = arena.addType(FunctionType{subTArgs, arena.addTypePack({builtinTypes->numberType})}); + // (number, ...any) -> string + TypeId functionSuper = arena.addType(FunctionType{superTArgs, arena.addTypePack({builtinTypes->stringType})}); + + + SubtypingResult result = isSubtype(functionSub, functionSuper); + CHECK(result.reasoning == std::vector{SubtypingReasoning{TypePath::PathBuilder().rets().index(0).build(), + TypePath::PathBuilder().rets().index(0).build(), SubtypingVariance::Covariant}, + SubtypingReasoning{TypePath::PathBuilder().args().index(0).build(), TypePath::PathBuilder().args().index(0).build(), + SubtypingVariance::Contravariant}, + SubtypingReasoning{TypePath::PathBuilder().args().index(1).build(), + TypePath::PathBuilder().args().tail().variadic().build(), SubtypingVariance::Contravariant}}); + CHECK(!result.isSubtype); +} + TEST_CASE_FIXTURE(SubtypeFixture, "any anyType, builtinTypes->unknownType); diff --git a/tests/ToString.test.cpp b/tests/ToString.test.cpp index da21d5cb..24dc6520 100644 --- a/tests/ToString.test.cpp +++ b/tests/ToString.test.cpp @@ -14,6 +14,7 @@ LUAU_FASTFLAG(LuauRecursiveTypeParameterRestriction); LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution); LUAU_FASTFLAG(LuauCheckedFunctionSyntax); LUAU_FASTFLAG(DebugLuauSharedSelf); +LUAU_FASTFLAG(LuauStringifyCyclesRootedAtPacks); TEST_SUITE_BEGIN("ToString"); @@ -878,7 +879,11 @@ TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_include_self_param") TypeId parentTy = requireType("foo"); auto ttv = get(follow(parentTy)); - auto ftv = get(follow(ttv->props.at("method").type())); + REQUIRE(ttv); + + TypeId methodTy = ttv->props.at("method").type(); + auto ftv = get(follow(methodTy)); + REQUIRE_MESSAGE(ftv, methodTy); if (FFlag::DebugLuauDeferredConstraintResolution) CHECK_EQ("foo:method(self: unknown, arg: string): ()", toStringNamedFunction("foo:method", *ftv)); @@ -998,4 +1003,34 @@ TEST_CASE_FIXTURE(Fixture, "read_only_properties") CHECK("{ read x: string }" == toString(requireTypeAlias("B"), {true})); } +TEST_CASE_FIXTURE(Fixture, "cycle_rooted_in_a_pack") +{ + ScopedFastFlag sff{FFlag::LuauStringifyCyclesRootedAtPacks, true}; + + TypeArena arena; + + TypePackId thePack = arena.addTypePack({builtinTypes->numberType, builtinTypes->numberType}); + TypePack* packPtr = getMutable(thePack); + REQUIRE(packPtr); + + const TableType::Props theProps = { + {"BaseField", Property::readonly(builtinTypes->unknownType)}, + {"BaseMethod", Property::readonly(arena.addType( + FunctionType{ + thePack, + arena.addTypePack({}) + } + ))} + }; + + TypeId theTable = arena.addType(TableType{theProps, {}, TypeLevel{}, TableState::Sealed}); + + packPtr->head[0] = theTable; + + if (FFlag::DebugLuauDeferredConstraintResolution) + CHECK("tp1 where tp1 = { read BaseField: unknown, read BaseMethod: (tp1) -> () }, number" == toString(thePack)); + else + CHECK("tp1 where tp1 = {| BaseField: unknown, BaseMethod: (tp1) -> () |}, number" == toString(thePack)); +} + TEST_SUITE_END(); diff --git a/tests/TypeFamily.test.cpp b/tests/TypeFamily.test.cpp index 8ce5d28d..07ef9f1e 100644 --- a/tests/TypeFamily.test.cpp +++ b/tests/TypeFamily.test.cpp @@ -552,6 +552,21 @@ TEST_CASE_FIXTURE(ClassFixture, "keyof_type_family_common_subset_if_union_of_dif LUAU_REQUIRE_NO_ERRORS(result); } +TEST_CASE_FIXTURE(ClassFixture, "binary_type_family_works_with_default_argument") +{ + if (!FFlag::DebugLuauDeferredConstraintResolution) + return; + + CheckResult result = check(R"( + type result = mul + + local function thunk(): result return 5 * 4 end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + CHECK("() -> number" == toString(requireType("thunk"))); +} + TEST_CASE_FIXTURE(ClassFixture, "vector2_multiply_is_overloaded") { if (!FFlag::DebugLuauDeferredConstraintResolution) diff --git a/tests/TypeInfer.functions.test.cpp b/tests/TypeInfer.functions.test.cpp index f3b63538..44eff77d 100644 --- a/tests/TypeInfer.functions.test.cpp +++ b/tests/TypeInfer.functions.test.cpp @@ -687,7 +687,7 @@ TEST_CASE_FIXTURE(Fixture, "higher_order_function_3") REQUIRE_EQ(1, argVec.size()); const TableType* argType = get(follow(argVec[0])); - REQUIRE(argType != nullptr); + REQUIRE_MESSAGE(argType != nullptr, argVec[0]); CHECK(bool(argType->indexer)); } @@ -2422,4 +2422,26 @@ TEST_CASE_FIXTURE(Fixture, "pass_table_literal_to_function_expecting_optional_pr LUAU_REQUIRE_NO_ERRORS(result); } +TEST_CASE_FIXTURE(Fixture, "dont_infer_overloaded_functions") +{ + CheckResult result = check(R"( + function getR6Attachments(model) + model:FindFirstChild("Right Leg") + model:FindFirstChild("Left Leg") + model:FindFirstChild("Torso") + model:FindFirstChild("Torso") + model:FindFirstChild("Head") + model:FindFirstChild("Left Arm") + model:FindFirstChild("Right Arm") + end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + + if (FFlag::DebugLuauDeferredConstraintResolution) + CHECK("(t1) -> () where t1 = { read FindFirstChild: (t1, string) -> (a...) }" == toString(requireType("getR6Attachments"))); + else + CHECK("(t1) -> () where t1 = {+ FindFirstChild: (t1, string) -> (a...) +}" == toString(requireType("getR6Attachments"))); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.generics.test.cpp b/tests/TypeInfer.generics.test.cpp index a5f17f77..be6e15dc 100644 --- a/tests/TypeInfer.generics.test.cpp +++ b/tests/TypeInfer.generics.test.cpp @@ -402,15 +402,7 @@ TEST_CASE_FIXTURE(Fixture, "calling_self_generic_methods") end )"); - if (FFlag::DebugLuauDeferredConstraintResolution) - { - LUAU_REQUIRE_NO_ERRORS(result); - - CHECK_EQ("{ f: (t1) -> (), id: (unknown, a) -> a } where t1 = { read id: ((t1, number) -> number) & ((t1, string) -> string) }", - toString(requireType("x"), {true})); - } - else - LUAU_REQUIRE_ERRORS(result); + LUAU_REQUIRE_ERRORS(result); } TEST_CASE_FIXTURE(Fixture, "infer_generic_property") diff --git a/tests/TypeInfer.loops.test.cpp b/tests/TypeInfer.loops.test.cpp index f1260fcd..6e9e9327 100644 --- a/tests/TypeInfer.loops.test.cpp +++ b/tests/TypeInfer.loops.test.cpp @@ -1054,4 +1054,20 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "iterate_array_of_singletons") LUAU_REQUIRE_ERRORS(result); } +TEST_CASE_FIXTURE(BuiltinsFixture, "iter_mm_results_are_lvalue") +{ + CheckResult result = check(R"( + local foo = setmetatable({}, { + __iter = function() + return pairs({1, 2, 3}) + end, + }) + + for k, v in foo do + end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.refinements.test.cpp b/tests/TypeInfer.refinements.test.cpp index 7408c799..a94c649d 100644 --- a/tests/TypeInfer.refinements.test.cpp +++ b/tests/TypeInfer.refinements.test.cpp @@ -2150,5 +2150,23 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "mutate_prop_of_some_refined_symbol_2" LUAU_REQUIRE_NO_ERRORS(result); } +TEST_CASE_FIXTURE(BuiltinsFixture, "ensure_t_after_return_references_all_reachable_points") +{ + CheckResult result = check(R"( + local t = {} + + local function f(k: string) + if t[k] ~= nil then + return + end + + t[k] = 5 + end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + + CHECK_EQ("{ [string]: number }", toString(requireTypeAtPosition({8, 12}), {true})); +} TEST_SUITE_END(); diff --git a/tests/TypeInfer.tables.test.cpp b/tests/TypeInfer.tables.test.cpp index 7c770ecb..8bc2a541 100644 --- a/tests/TypeInfer.tables.test.cpp +++ b/tests/TypeInfer.tables.test.cpp @@ -40,7 +40,6 @@ function FadeValue:destroy() self.finalCallback = nil end )"); - } TEST_CASE_FIXTURE(Fixture, "basic") @@ -4247,6 +4246,43 @@ TEST_CASE_FIXTURE(Fixture, "refined_thing_can_be_an_array") CHECK("({a}, a) -> a" == toString(requireType("foo"))); } +TEST_CASE_FIXTURE(Fixture, "parameter_was_set_an_indexer_and_bounded_by_string") +{ + if (!FFlag::DebugLuauDeferredConstraintResolution) + return; + + CheckResult result = check(R"( + function f(t) + local s: string = t + t[5] = 7 + end + )"); + + LUAU_REQUIRE_ERROR_COUNT(3, result); + + CHECK_EQ("Parameter 't' has been reduced to never. This function is not callable with any possible value.", toString(result.errors[0])); + CHECK_EQ("Parameter 't' is required to be a subtype of 'string' here.", toString(result.errors[1])); + CHECK_EQ("Parameter 't' is required to be a subtype of '{number}' here.", toString(result.errors[2])); +} + +TEST_CASE_FIXTURE(Fixture, "parameter_was_set_an_indexer_and_bounded_by_another_parameter") +{ + if (!FFlag::DebugLuauDeferredConstraintResolution) + return; + + CheckResult result = check(R"( + function f(t1, t2) + t1[5] = 7 -- 't1 <: {number} + t2 = t1 -- 't1 <: 't2 + t1[5] = 7 -- 't1 <: {number} + end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + + CHECK_EQ("({number}, unknown) -> ()", toString(requireType("f"))); +} + TEST_CASE_FIXTURE(Fixture, "mymovie_read_write_tables_bug") { CheckResult result = check(R"( @@ -4286,6 +4322,4 @@ TEST_CASE_FIXTURE(Fixture, "mymovie_read_write_tables_bug_2") LUAU_REQUIRE_ERRORS(result); } - - TEST_SUITE_END(); diff --git a/tests/TypeInfer.typestates.test.cpp b/tests/TypeInfer.typestates.test.cpp index 61d25189..bee1b687 100644 --- a/tests/TypeInfer.typestates.test.cpp +++ b/tests/TypeInfer.typestates.test.cpp @@ -451,5 +451,4 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "typestates_preserve_error_suppression_proper CHECK("*error-type* | string" == toString(requireTypeAtPosition({3, 16}), {true})); } - TEST_SUITE_END(); diff --git a/tests/conformance/native.lua b/tests/conformance/native.lua index 78692d39..094e6b83 100644 --- a/tests/conformance/native.lua +++ b/tests/conformance/native.lua @@ -173,6 +173,41 @@ end assert(pcall(fuzzfail18) == true) assert(fuzzfail18() == 0) +local function fuzzfail19() + local _ = 2 + _ += _ + _ = _,_ >= _,{_ >= _,_ >= _,_(),} + + local _ = 2 + do + _ = assert({n0=_,_,n0=_,}),{_={_[_()],},_,} + end +end + +assert(pcall(fuzzfail19) == false) + +local function fuzzfail20() + assert(true) + assert(false,(_),true) + _ = nil +end + +assert(pcall(fuzzfail20) == false) + +local function fuzzfail21(...) + local _ = assert,_ + if _ then else return _ / _ end + _(_) + _(_,_) + assert(...,_) + _((not _),_) + _(true,_ / _) + _(_,_()) + return _ +end + +assert(pcall(fuzzfail21) == false) + local function arraySizeInv1() local t = {1, 2, nil, nil, nil, nil, nil, nil, nil, true} diff --git a/tools/faillist.txt b/tools/faillist.txt index a5349e48..89e27f1f 100644 --- a/tools/faillist.txt +++ b/tools/faillist.txt @@ -39,9 +39,6 @@ DefinitionTests.class_definition_overload_metamethods DefinitionTests.class_definition_string_props DefinitionTests.declaring_generic_functions DefinitionTests.definition_file_classes -Differ.equal_generictp_cyclic -Differ.generictp_normal -Differ.generictp_normal_2 Differ.metatable_metamissing_left Differ.metatable_metamissing_right Differ.metatable_metanormal @@ -79,7 +76,6 @@ GenericsTests.infer_generic_function_function_argument_2 GenericsTests.infer_generic_function_function_argument_3 GenericsTests.infer_generic_function_function_argument_overloaded GenericsTests.instantiated_function_argument_names -GenericsTests.mutable_state_polymorphism GenericsTests.no_stack_overflow_from_quantifying GenericsTests.properties_can_be_instantiated_polytypes GenericsTests.quantify_functions_even_if_they_have_an_explicit_generic @@ -115,7 +111,6 @@ IntersectionTypes.union_saturate_overloaded_functions Linter.FormatStringTyped Linter.TableOperationsIndexer ModuleTests.clone_self_property -Negations.cofinite_strings_can_be_compared_for_equality Negations.negated_string_is_a_subtype_of_string NonstrictModeTests.inconsistent_module_return_types_are_ok NonstrictModeTests.infer_nullary_function @@ -202,6 +197,7 @@ TableTests.explicitly_typed_table_with_indexer TableTests.generalize_table_argument TableTests.generic_table_instantiation_potential_regression TableTests.indexer_mismatch +TableTests.indexer_on_sealed_table_must_unify_with_free_table TableTests.indexers_get_quantified_too TableTests.infer_indexer_from_array_like_table TableTests.infer_indexer_from_its_variable_type_and_unifiable @@ -218,12 +214,15 @@ TableTests.meta_add_inferred TableTests.metatable_mismatch_should_fail TableTests.missing_metatable_for_sealed_tables_do_not_get_inferred TableTests.mixed_tables_with_implicit_numbered_keys +TableTests.nil_assign_doesnt_hit_indexer TableTests.ok_to_provide_a_subtype_during_construction TableTests.ok_to_set_nil_even_on_non_lvalue_base_expr 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.open_table_unification_2 +TableTests.parameter_was_set_an_indexer_and_bounded_by_another_parameter +TableTests.parameter_was_set_an_indexer_and_bounded_by_string 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.persistent_sealed_table_is_immutable @@ -256,7 +255,6 @@ TableTests.unification_of_unions_in_a_self_referential_type TableTests.used_colon_instead_of_dot TableTests.used_dot_instead_of_colon 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 ToString.exhaustive_toString_of_cyclic_table ToString.free_types @@ -267,7 +265,6 @@ ToString.primitive ToString.tostring_unsee_ttv_if_array ToString.toStringDetailed2 ToString.toStringErrorPack -ToString.toStringNamedFunction_map TryUnifyTests.members_of_failed_typepack_unification_are_unified_with_errorType TryUnifyTests.result_of_failed_typepack_unification_is_constrained TryUnifyTests.uninhabited_table_sub_anything @@ -302,10 +299,10 @@ TypeInfer.cli_50041_committing_txnlog_in_apollo_client_error TypeInfer.dont_ice_when_failing_the_occurs_check TypeInfer.dont_report_type_errors_within_an_AstExprError TypeInfer.dont_report_type_errors_within_an_AstStatError +TypeInfer.follow_on_new_types_in_substitution TypeInfer.globals TypeInfer.globals2 TypeInfer.globals_are_banned_in_strict_mode -TypeInfer.infer_locals_via_assignment_from_its_call_site TypeInfer.infer_through_group_expr TypeInfer.no_stack_overflow_from_isoptional TypeInfer.promote_tail_type_packs @@ -320,6 +317,8 @@ TypeInferAnyError.any_type_propagates TypeInferAnyError.assign_prop_to_table_by_calling_any_yields_any TypeInferAnyError.call_to_any_yields_any TypeInferAnyError.can_subscript_any +TypeInferAnyError.for_in_loop_iterator_is_error +TypeInferAnyError.for_in_loop_iterator_is_error2 TypeInferAnyError.metatable_of_any_can_be_a_table TypeInferAnyError.quantify_any_does_not_bind_to_itself TypeInferAnyError.replace_every_free_type_when_unifying_a_complex_function_with_any @@ -357,6 +356,7 @@ TypeInferFunctions.function_exprs_are_generalized_at_signature_scope_not_enclosi TypeInferFunctions.function_is_supertype_of_concrete_functions TypeInferFunctions.function_statement_sealed_table_assignment_through_indexer TypeInferFunctions.generic_packs_are_not_variadic +TypeInferFunctions.higher_order_function_3 TypeInferFunctions.higher_order_function_4 TypeInferFunctions.improved_function_arg_mismatch_error_nonstrict TypeInferFunctions.improved_function_arg_mismatch_errors @@ -419,7 +419,6 @@ TypeInferModules.do_not_modify_imported_types TypeInferModules.do_not_modify_imported_types_5 TypeInferModules.require TypeInferOOP.CheckMethodsOfSealed -TypeInferOOP.cycle_between_object_constructor_and_alias TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_another_overload_works TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_it_wont_help_2 TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_not_defined_with_colon