diff --git a/Analysis/include/Luau/Constraint.h b/Analysis/include/Luau/Constraint.h index e69346bd..692e4a14 100644 --- a/Analysis/include/Luau/Constraint.h +++ b/Analysis/include/Luau/Constraint.h @@ -90,18 +90,42 @@ struct FunctionCallConstraint DenseHashMap<const AstNode*, TypeId>* astOverloadResolvedTypes = nullptr; }; -// result ~ prim ExpectedType SomeSingletonType MultitonType +// function_check fn argsPack // -// If ExpectedType is potentially a singleton (an actual singleton or a union -// that contains a singleton), then result ~ SomeSingletonType +// If fn is a function type and argsPack is a partially solved +// pack of arguments to be supplied to the function, propagate the argument +// types of fn into the types of argsPack. This is used to implement +// bidirectional inference of lambda arguments. +struct FunctionCheckConstraint +{ + TypeId fn; + TypePackId argsPack; + + class AstExprCall* callSite = nullptr; +}; + +// prim FreeType ExpectedType PrimitiveType // -// else result ~ MultitonType +// FreeType is bounded below by the singleton type and above by PrimitiveType +// initially. When this constraint is resolved, it will check that the bounds +// of the free type are well-formed by subtyping. +// +// If they are not well-formed, then FreeType is replaced by its lower bound +// +// If they are well-formed and ExpectedType is potentially a singleton (an +// actual singleton or a union that contains a singleton), +// then FreeType is replaced by its lower bound +// +// else FreeType is replaced by PrimitiveType struct PrimitiveTypeConstraint { - TypeId resultType; - TypeId expectedType; - TypeId singletonType; - TypeId multitonType; + TypeId freeType; + + // potentially gets used to force the lower bound? + std::optional<TypeId> expectedType; + + // the primitive type to check against + TypeId primitiveType; }; // result ~ hasProp type "prop_name" @@ -230,7 +254,7 @@ struct ReducePackConstraint }; using ConstraintV = Variant<SubtypeConstraint, PackSubtypeConstraint, GeneralizationConstraint, InstantiationConstraint, IterableConstraint, - NameConstraint, TypeAliasExpansionConstraint, FunctionCallConstraint, PrimitiveTypeConstraint, HasPropConstraint, SetPropConstraint, + NameConstraint, TypeAliasExpansionConstraint, FunctionCallConstraint, FunctionCheckConstraint, PrimitiveTypeConstraint, HasPropConstraint, SetPropConstraint, SetIndexerConstraint, SingletonOrTopTypeConstraint, UnpackConstraint, SetOpConstraint, ReduceConstraint, ReducePackConstraint>; struct Constraint diff --git a/Analysis/include/Luau/ConstraintGenerator.h b/Analysis/include/Luau/ConstraintGenerator.h index ebd237f6..2f746a74 100644 --- a/Analysis/include/Luau/ConstraintGenerator.h +++ b/Analysis/include/Luau/ConstraintGenerator.h @@ -150,7 +150,7 @@ private: */ ScopePtr childScope(AstNode* node, const ScopePtr& parent); - std::optional<TypeId> lookup(Scope* scope, DefId def, bool prototype = true); + std::optional<TypeId> lookup(const ScopePtr& scope, DefId def, bool prototype = true); /** * Adds a new constraint with no dependencies to a given scope. @@ -178,8 +178,8 @@ private: }; using RefinementContext = InsertionOrderedMap<DefId, RefinementPartition>; - void unionRefinements(const RefinementContext& lhs, const RefinementContext& rhs, RefinementContext& dest, std::vector<ConstraintV>* constraints); - void computeRefinement(const ScopePtr& scope, RefinementId refinement, RefinementContext* refis, bool sense, bool eq, std::vector<ConstraintV>* constraints); + void unionRefinements(const ScopePtr& scope, Location location, const RefinementContext& lhs, const RefinementContext& rhs, RefinementContext& dest, std::vector<ConstraintV>* constraints); + void computeRefinement(const ScopePtr& scope, Location location, RefinementId refinement, RefinementContext* refis, bool sense, bool eq, std::vector<ConstraintV>* constraints); void applyRefinements(const ScopePtr& scope, Location location, RefinementId refinement); ControlFlow visitBlockWithoutChildScope(const ScopePtr& scope, AstStatBlock* block); @@ -329,6 +329,11 @@ private: void reportError(Location location, TypeErrorData err); void reportCodeTooComplex(Location location); + // make a union type family of these two types + TypeId makeUnion(const ScopePtr& scope, Location location, TypeId lhs, TypeId rhs); + // make an intersect type family of these two types + TypeId makeIntersect(const ScopePtr& scope, Location location, TypeId lhs, TypeId rhs); + /** Scan the program for global definitions. * * ConstraintGenerator needs to differentiate between globals and accesses to undefined symbols. Doing this "for diff --git a/Analysis/include/Luau/ConstraintSolver.h b/Analysis/include/Luau/ConstraintSolver.h index f258b28b..e962f343 100644 --- a/Analysis/include/Luau/ConstraintSolver.h +++ b/Analysis/include/Luau/ConstraintSolver.h @@ -75,7 +75,7 @@ struct ConstraintSolver // anything. std::unordered_map<NotNull<const Constraint>, size_t> blockedConstraints; // A mapping of type/pack pointers to the constraints they block. - std::unordered_map<BlockedConstraintId, std::vector<NotNull<const Constraint>>, HashBlockedConstraintId> blocked; + std::unordered_map<BlockedConstraintId, DenseHashSet<const Constraint*>, HashBlockedConstraintId> blocked; // Memoized instantiations of type aliases. DenseHashMap<InstantiationSignature, TypeId, HashInstantiationSignature> instantiatedAliases{{}}; // Breadcrumbs for where a free type's upper bound was expanded. We use @@ -126,6 +126,7 @@ struct ConstraintSolver bool tryDispatch(const NameConstraint& c, NotNull<const Constraint> constraint); bool tryDispatch(const TypeAliasExpansionConstraint& c, NotNull<const Constraint> constraint); bool tryDispatch(const FunctionCallConstraint& c, NotNull<const Constraint> constraint); + bool tryDispatch(const FunctionCheckConstraint& c, NotNull<const Constraint> constraint); bool tryDispatch(const PrimitiveTypeConstraint& c, NotNull<const Constraint> constraint); bool tryDispatch(const HasPropConstraint& c, NotNull<const Constraint> constraint); bool tryDispatch(const SetPropConstraint& c, NotNull<const Constraint> constraint, bool force); @@ -285,7 +286,7 @@ private: * @param target the type or type pack pointer that the constraint is blocked on. * @param constraint the constraint to block. **/ - void block_(BlockedConstraintId target, NotNull<const Constraint> constraint); + bool block_(BlockedConstraintId target, NotNull<const Constraint> constraint); /** * Informs the solver that progress has been made on a type or type pack. The diff --git a/Analysis/include/Luau/TypeFamily.h b/Analysis/include/Luau/TypeFamily.h index 49c652ee..77fd6e8a 100644 --- a/Analysis/include/Luau/TypeFamily.h +++ b/Analysis/include/Luau/TypeFamily.h @@ -163,6 +163,9 @@ struct BuiltinTypeFamilies TypeFamily eqFamily; TypeFamily refineFamily; + TypeFamily unionFamily; + TypeFamily intersectFamily; + TypeFamily keyofFamily; TypeFamily rawkeyofFamily; diff --git a/Analysis/include/Luau/Unifier2.h b/Analysis/include/Luau/Unifier2.h index 4930df6f..f9a3fdc9 100644 --- a/Analysis/include/Luau/Unifier2.h +++ b/Analysis/include/Luau/Unifier2.h @@ -56,6 +56,7 @@ struct Unifier2 * free TypePack to another and encounter an occurs check violation. */ bool unify(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); bool unify(TypeId subTy, const UnionType* superUnion); diff --git a/Analysis/src/AstJsonEncoder.cpp b/Analysis/src/AstJsonEncoder.cpp index dcee3492..470d69b3 100644 --- a/Analysis/src/AstJsonEncoder.cpp +++ b/Analysis/src/AstJsonEncoder.cpp @@ -8,8 +8,6 @@ #include <math.h> -LUAU_FASTFLAG(LuauClipExtraHasEndProps); - namespace Luau { @@ -393,8 +391,6 @@ struct AstJsonEncoder : public AstVisitor PROP(body); PROP(functionDepth); PROP(debugname); - if (!FFlag::LuauClipExtraHasEndProps) - write("hasEnd", node->DEPRECATED_hasEnd); }); } @@ -591,11 +587,8 @@ struct AstJsonEncoder : public AstVisitor void write(class AstStatBlock* node) { writeNode(node, "AstStatBlock", [&]() { - if (FFlag::LuauClipExtraHasEndProps) - { - writeRaw(",\"hasEnd\":"); - write(node->hasEnd); - } + writeRaw(",\"hasEnd\":"); + write(node->hasEnd); writeRaw(",\"body\":["); bool comma = false; for (AstStat* stat : node->body) @@ -619,8 +612,6 @@ struct AstJsonEncoder : public AstVisitor if (node->elsebody) PROP(elsebody); write("hasThen", node->thenLocation.has_value()); - if (!FFlag::LuauClipExtraHasEndProps) - write("hasEnd", node->DEPRECATED_hasEnd); }); } @@ -630,8 +621,6 @@ struct AstJsonEncoder : public AstVisitor PROP(condition); PROP(body); PROP(hasDo); - if (!FFlag::LuauClipExtraHasEndProps) - write("hasEnd", node->DEPRECATED_hasEnd); }); } @@ -640,8 +629,6 @@ struct AstJsonEncoder : public AstVisitor writeNode(node, "AstStatRepeat", [&]() { PROP(condition); PROP(body); - if (!FFlag::LuauClipExtraHasEndProps) - write("hasUntil", node->DEPRECATED_hasUntil); }); } @@ -687,8 +674,6 @@ struct AstJsonEncoder : public AstVisitor PROP(step); PROP(body); PROP(hasDo); - if (!FFlag::LuauClipExtraHasEndProps) - write("hasEnd", node->DEPRECATED_hasEnd); }); } @@ -700,8 +685,6 @@ struct AstJsonEncoder : public AstVisitor PROP(body); PROP(hasIn); PROP(hasDo); - if (!FFlag::LuauClipExtraHasEndProps) - write("hasEnd", node->DEPRECATED_hasEnd); }); } diff --git a/Analysis/src/Autocomplete.cpp b/Analysis/src/Autocomplete.cpp index 55e6d8f0..75fd9f58 100644 --- a/Analysis/src/Autocomplete.cpp +++ b/Analysis/src/Autocomplete.cpp @@ -15,7 +15,6 @@ LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution); LUAU_FASTFLAG(DebugLuauReadWriteProperties); -LUAU_FASTFLAG(LuauClipExtraHasEndProps); LUAU_FASTFLAGVARIABLE(LuauAutocompleteStringLiteralBounds, false); static const std::unordered_set<std::string> kStatementStartingKeywords = { @@ -1068,51 +1067,30 @@ static AutocompleteEntryMap autocompleteStatement( for (const auto& kw : kStatementStartingKeywords) result.emplace(kw, AutocompleteEntry{AutocompleteEntryKind::Keyword}); - if (FFlag::LuauClipExtraHasEndProps) + for (auto it = ancestry.rbegin(); it != ancestry.rend(); ++it) { - for (auto it = ancestry.rbegin(); it != ancestry.rend(); ++it) + if (AstStatForIn* statForIn = (*it)->as<AstStatForIn>(); statForIn && !statForIn->body->hasEnd) + result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword}); + else if (AstStatFor* statFor = (*it)->as<AstStatFor>(); statFor && !statFor->body->hasEnd) + result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword}); + else if (AstStatIf* statIf = (*it)->as<AstStatIf>()) { - if (AstStatForIn* statForIn = (*it)->as<AstStatForIn>(); statForIn && !statForIn->body->hasEnd) - result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword}); - else if (AstStatFor* statFor = (*it)->as<AstStatFor>(); statFor && !statFor->body->hasEnd) - result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword}); - else if (AstStatIf* statIf = (*it)->as<AstStatIf>()) + bool hasEnd = statIf->thenbody->hasEnd; + if (statIf->elsebody) { - bool hasEnd = statIf->thenbody->hasEnd; - if (statIf->elsebody) - { - if (AstStatBlock* elseBlock = statIf->elsebody->as<AstStatBlock>()) - hasEnd = elseBlock->hasEnd; - } - - if (!hasEnd) - result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword}); + if (AstStatBlock* elseBlock = statIf->elsebody->as<AstStatBlock>()) + hasEnd = elseBlock->hasEnd; } - else if (AstStatWhile* statWhile = (*it)->as<AstStatWhile>(); statWhile && !statWhile->body->hasEnd) - result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword}); - else if (AstExprFunction* exprFunction = (*it)->as<AstExprFunction>(); exprFunction && !exprFunction->body->hasEnd) - result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword}); - if (AstStatBlock* exprBlock = (*it)->as<AstStatBlock>(); exprBlock && !exprBlock->hasEnd) - result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword}); - } - } - else - { - for (auto it = ancestry.rbegin(); it != ancestry.rend(); ++it) - { - if (AstStatForIn* statForIn = (*it)->as<AstStatForIn>(); statForIn && !statForIn->DEPRECATED_hasEnd) - result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword}); - else if (AstStatFor* statFor = (*it)->as<AstStatFor>(); statFor && !statFor->DEPRECATED_hasEnd) - result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword}); - else if (AstStatIf* statIf = (*it)->as<AstStatIf>(); statIf && !statIf->DEPRECATED_hasEnd) - result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword}); - else if (AstStatWhile* statWhile = (*it)->as<AstStatWhile>(); statWhile && !statWhile->DEPRECATED_hasEnd) - result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword}); - else if (AstExprFunction* exprFunction = (*it)->as<AstExprFunction>(); exprFunction && !exprFunction->DEPRECATED_hasEnd) - result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword}); - if (AstStatBlock* exprBlock = (*it)->as<AstStatBlock>(); exprBlock && !exprBlock->hasEnd) + + if (!hasEnd) result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword}); } + else if (AstStatWhile* statWhile = (*it)->as<AstStatWhile>(); statWhile && !statWhile->body->hasEnd) + result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword}); + else if (AstExprFunction* exprFunction = (*it)->as<AstExprFunction>(); exprFunction && !exprFunction->body->hasEnd) + result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword}); + if (AstStatBlock* exprBlock = (*it)->as<AstStatBlock>(); exprBlock && !exprBlock->hasEnd) + result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword}); } if (ancestry.size() >= 2) @@ -1127,16 +1105,8 @@ static AutocompleteEntryMap autocompleteStatement( } } - if (FFlag::LuauClipExtraHasEndProps) - { - if (AstStatRepeat* statRepeat = parent->as<AstStatRepeat>(); statRepeat && !statRepeat->body->hasEnd) - result.emplace("until", AutocompleteEntry{AutocompleteEntryKind::Keyword}); - } - else - { - if (AstStatRepeat* statRepeat = parent->as<AstStatRepeat>(); statRepeat && !statRepeat->DEPRECATED_hasUntil) - result.emplace("until", AutocompleteEntry{AutocompleteEntryKind::Keyword}); - } + if (AstStatRepeat* statRepeat = parent->as<AstStatRepeat>(); statRepeat && !statRepeat->body->hasEnd) + result.emplace("until", AutocompleteEntry{AutocompleteEntryKind::Keyword}); } if (ancestry.size() >= 4) @@ -1150,16 +1120,8 @@ static AutocompleteEntryMap autocompleteStatement( } } - if (FFlag::LuauClipExtraHasEndProps) - { - if (AstStatRepeat* statRepeat = extractStat<AstStatRepeat>(ancestry); statRepeat && !statRepeat->body->hasEnd) - result.emplace("until", AutocompleteEntry{AutocompleteEntryKind::Keyword}); - } - else - { - if (AstStatRepeat* statRepeat = extractStat<AstStatRepeat>(ancestry); statRepeat && !statRepeat->DEPRECATED_hasUntil) - result.emplace("until", AutocompleteEntry{AutocompleteEntryKind::Keyword}); - } + if (AstStatRepeat* statRepeat = extractStat<AstStatRepeat>(ancestry); statRepeat && !statRepeat->body->hasEnd) + result.emplace("until", AutocompleteEntry{AutocompleteEntryKind::Keyword}); return result; } diff --git a/Analysis/src/Constraint.cpp b/Analysis/src/Constraint.cpp index 3035d480..7d3f9e31 100644 --- a/Analysis/src/Constraint.cpp +++ b/Analysis/src/Constraint.cpp @@ -45,6 +45,12 @@ DenseHashSet<TypeId> Constraint::getFreeTypes() const ftc.traverse(psc->subPack); ftc.traverse(psc->superPack); } + else if (auto ptc = get<PrimitiveTypeConstraint>(*this)) + { + // we need to take into account primitive type constraints to prevent type families from reducing on + // primitive whose types we have not yet selected to be singleton or not. + ftc.traverse(ptc->freeType); + } return types; } diff --git a/Analysis/src/ConstraintGenerator.cpp b/Analysis/src/ConstraintGenerator.cpp index 91f2ab7e..bc412c0e 100644 --- a/Analysis/src/ConstraintGenerator.cpp +++ b/Analysis/src/ConstraintGenerator.cpp @@ -20,6 +20,7 @@ #include "Luau/InsertionOrderedMap.h" #include <algorithm> +#include <memory> LUAU_FASTINT(LuauCheckRecursionLimit); LUAU_FASTFLAG(DebugLuauLogSolverToJson); @@ -205,7 +206,7 @@ ScopePtr ConstraintGenerator::childScope(AstNode* node, const ScopePtr& parent) return scope; } -std::optional<TypeId> ConstraintGenerator::lookup(Scope* scope, DefId def, bool prototype) +std::optional<TypeId> ConstraintGenerator::lookup(const ScopePtr& scope, DefId def, bool prototype) { if (get<Cell>(def)) return scope->lookup(def); @@ -230,7 +231,7 @@ std::optional<TypeId> ConstraintGenerator::lookup(Scope* scope, DefId def, bool rootScope->lvalueTypes[operand] = *ty; } - res = simplifyUnion(builtinTypes, arena, res, *ty).result; + res = makeUnion(scope, Location{} /* TODO: can we provide a real location here? */, res, *ty); } scope->lvalueTypes[def] = res; @@ -250,18 +251,13 @@ NotNull<Constraint> ConstraintGenerator::addConstraint(const ScopePtr& scope, st return NotNull{constraints.emplace_back(std::move(c)).get()}; } -void ConstraintGenerator::unionRefinements(const RefinementContext& lhs, const RefinementContext& rhs, RefinementContext& dest, std::vector<ConstraintV>* constraints) +void ConstraintGenerator::unionRefinements(const ScopePtr& scope, Location location, const RefinementContext& lhs, const RefinementContext& rhs, RefinementContext& dest, std::vector<ConstraintV>* constraints) { const auto intersect = [&](const std::vector<TypeId>& types) { if (1 == types.size()) return types[0]; else if (2 == types.size()) - { - // TODO: It may be advantageous to introduce a refine type family here when there are blockedTypes. - SimplifyResult sr = simplifyIntersection(builtinTypes, arena, types[0], types[1]); - if (sr.blockedTypes.empty()) - return sr.result; - } + return makeIntersect(scope, location, types[0], types[1]); return arena->addType(IntersectionType{types}); }; @@ -281,48 +277,48 @@ void ConstraintGenerator::unionRefinements(const RefinementContext& lhs, const R rhsIt->second.discriminantTypes.size() == 1 ? rhsIt->second.discriminantTypes[0] : intersect(rhsIt->second.discriminantTypes); dest.insert(def, {}); - dest.get(def)->discriminantTypes.push_back(simplifyUnion(builtinTypes, arena, leftDiscriminantTy, rightDiscriminantTy).result); + dest.get(def)->discriminantTypes.push_back(makeUnion(scope, location, leftDiscriminantTy, rightDiscriminantTy)); dest.get(def)->shouldAppendNilType |= partition.shouldAppendNilType || rhsIt->second.shouldAppendNilType; } } -void ConstraintGenerator::computeRefinement(const ScopePtr& scope, RefinementId refinement, RefinementContext* refis, bool sense, bool eq, std::vector<ConstraintV>* constraints) +void ConstraintGenerator::computeRefinement(const ScopePtr& scope, Location location, RefinementId refinement, RefinementContext* refis, bool sense, bool eq, std::vector<ConstraintV>* constraints) { if (!refinement) return; else if (auto variadic = get<Variadic>(refinement)) { for (RefinementId refi : variadic->refinements) - computeRefinement(scope, refi, refis, sense, eq, constraints); + computeRefinement(scope, location, refi, refis, sense, eq, constraints); } else if (auto negation = get<Negation>(refinement)) - return computeRefinement(scope, negation->refinement, refis, !sense, eq, constraints); + return computeRefinement(scope, location, negation->refinement, refis, !sense, eq, constraints); else if (auto conjunction = get<Conjunction>(refinement)) { RefinementContext lhsRefis; RefinementContext rhsRefis; - computeRefinement(scope, conjunction->lhs, sense ? refis : &lhsRefis, sense, eq, constraints); - computeRefinement(scope, conjunction->rhs, sense ? refis : &rhsRefis, sense, eq, constraints); + computeRefinement(scope, location, conjunction->lhs, sense ? refis : &lhsRefis, sense, eq, constraints); + computeRefinement(scope, location, conjunction->rhs, sense ? refis : &rhsRefis, sense, eq, constraints); if (!sense) - unionRefinements(lhsRefis, rhsRefis, *refis, constraints); + unionRefinements(scope, location, lhsRefis, rhsRefis, *refis, constraints); } else if (auto disjunction = get<Disjunction>(refinement)) { RefinementContext lhsRefis; RefinementContext rhsRefis; - computeRefinement(scope, disjunction->lhs, sense ? &lhsRefis : refis, sense, eq, constraints); - computeRefinement(scope, disjunction->rhs, sense ? &rhsRefis : refis, sense, eq, constraints); + computeRefinement(scope, location, disjunction->lhs, sense ? &lhsRefis : refis, sense, eq, constraints); + computeRefinement(scope, location, disjunction->rhs, sense ? &rhsRefis : refis, sense, eq, constraints); if (sense) - unionRefinements(lhsRefis, rhsRefis, *refis, constraints); + unionRefinements(scope, location, lhsRefis, rhsRefis, *refis, constraints); } else if (auto equivalence = get<Equivalence>(refinement)) { - computeRefinement(scope, equivalence->lhs, refis, sense, true, constraints); - computeRefinement(scope, equivalence->rhs, refis, sense, true, constraints); + computeRefinement(scope, location, equivalence->lhs, refis, sense, true, constraints); + computeRefinement(scope, location, equivalence->rhs, refis, sense, true, constraints); } else if (auto proposition = get<Proposition>(refinement)) { @@ -423,11 +419,11 @@ void ConstraintGenerator::applyRefinements(const ScopePtr& scope, Location locat RefinementContext refinements; std::vector<ConstraintV> constraints; - computeRefinement(scope, refinement, &refinements, /*sense*/ true, /*eq*/ false, &constraints); + computeRefinement(scope, location, refinement, &refinements, /*sense*/ true, /*eq*/ false, &constraints); for (auto& [def, partition] : refinements) { - if (std::optional<TypeId> defTy = lookup(scope.get(), def)) + if (std::optional<TypeId> defTy = lookup(scope, def)) { TypeId ty = *defTy; if (partition.shouldAppendNilType) @@ -455,15 +451,15 @@ void ConstraintGenerator::applyRefinements(const ScopePtr& scope, Location locat switch (shouldSuppressErrors(normalizer, ty)) { case ErrorSuppression::DoNotSuppress: - ty = simplifyIntersection(builtinTypes, arena, ty, dt).result; + ty = makeIntersect(scope, location, ty, dt); break; case ErrorSuppression::Suppress: - ty = simplifyIntersection(builtinTypes, arena, ty, dt).result; - ty = simplifyUnion(builtinTypes, arena, ty, builtinTypes->errorType).result; + ty = makeIntersect(scope, location, ty, dt); + ty = makeUnion(scope, location, ty, builtinTypes->errorType); break; case ErrorSuppression::NormalizationFailed: reportError(location, NormalizationTooComplex{}); - ty = simplifyIntersection(builtinTypes, arena, ty, dt).result; + ty = makeIntersect(scope, location, ty, dt); break; } } @@ -761,7 +757,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatForIn* forI for (AstLocal* var : forIn->vars) { - TypeId assignee = arena->addType(BlockedType{}); + TypeId assignee = arena->addType(LocalType{builtinTypes->neverType, /* blockCount */ 1, var->name.value}); variableTypes.push_back(assignee); if (var->annotation) @@ -872,7 +868,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f DenseHashSet<Constraint*> excludeList{nullptr}; DefId def = dfg->getDef(function->name); - std::optional<TypeId> existingFunctionTy = lookup(scope.get(), def); + std::optional<TypeId> existingFunctionTy = lookup(scope, def); if (AstExprLocal* localName = function->name->as<AstExprLocal>()) { @@ -1492,8 +1488,12 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall* discriminantTypes.push_back(std::nullopt); } + Checkpoint funcBeginCheckpoint = checkpoint(this); + TypeId fnType = check(scope, call->func).ty; + Checkpoint funcEndCheckpoint = checkpoint(this); + std::vector<std::optional<TypeId>> expectedTypesForCall = getExpectedCallTypesForFunctionOverloads(fnType); module->astOriginalCallTypes[call->func] = fnType; @@ -1624,13 +1624,31 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall* &module->astOverloadResolvedTypes, }); - // We force constraints produced by checking function arguments to wait - // until after we have resolved the constraint on the function itself. - // This ensures, for instance, that we start inferring the contents of - // lambdas under the assumption that their arguments and return types - // will be compatible with the enclosing function call. - forEachConstraint(argBeginCheckpoint, argEndCheckpoint, this, [fcc](const ConstraintPtr& constraint) { - constraint->dependencies.emplace_back(fcc); + NotNull<Constraint> foo = addConstraint(scope, call->func->location, + FunctionCheckConstraint{ + fnType, + argPack, + call + } + ); + + /* + * To make bidirectional type checking work, we need to solve these constraints in a particular order: + * + * 1. Solve the function type + * 2. Propagate type information from the function type to the argument types + * 3. Solve the argument types + * 4. Solve the call + */ + + forEachConstraint(funcBeginCheckpoint, funcEndCheckpoint, this, [foo](const ConstraintPtr& constraint) { + foo->dependencies.emplace_back(constraint.get()); + }); + + forEachConstraint(argBeginCheckpoint, argEndCheckpoint, this, [foo, fcc](const ConstraintPtr& constraint) { + constraint->dependencies.emplace_back(foo); + + fcc->dependencies.emplace_back(constraint.get()); }); return InferencePack{rets, {refinementArena.variadic(returnRefinements)}}; @@ -1712,23 +1730,12 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprConstantStrin if (forceSingleton) return Inference{arena->addType(SingletonType{StringSingleton{std::string{string->value.data, string->value.size}}})}; - if (expectedType) - { - const TypeId expectedTy = follow(*expectedType); - if (get<BlockedType>(expectedTy) || get<PendingExpansionType>(expectedTy) || get<FreeType>(expectedTy)) - { - TypeId ty = arena->addType(BlockedType{}); - TypeId singletonType = arena->addType(SingletonType(StringSingleton{std::string(string->value.data, string->value.size)})); - addConstraint(scope, string->location, PrimitiveTypeConstraint{ty, expectedTy, singletonType, builtinTypes->stringType}); - return Inference{ty}; - } - else if (maybeSingleton(expectedTy)) - return Inference{arena->addType(SingletonType{StringSingleton{std::string{string->value.data, string->value.size}}})}; - - return Inference{builtinTypes->stringType}; - } - - return Inference{builtinTypes->stringType}; + FreeType ft = FreeType{scope.get()}; + ft.lowerBound = arena->addType(SingletonType{StringSingleton{std::string{string->value.data, string->value.size}}}); + ft.upperBound = builtinTypes->stringType; + const TypeId freeTy = arena->addType(ft); + addConstraint(scope, string->location, PrimitiveTypeConstraint{freeTy, expectedType, builtinTypes->stringType}); + return Inference{freeTy}; } Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprConstantBool* boolExpr, std::optional<TypeId> expectedType, bool forceSingleton) @@ -1737,23 +1744,12 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprConstantBool* if (forceSingleton) return Inference{singletonType}; - if (expectedType) - { - const TypeId expectedTy = follow(*expectedType); - - if (get<BlockedType>(expectedTy) || get<PendingExpansionType>(expectedTy)) - { - TypeId ty = arena->addType(BlockedType{}); - addConstraint(scope, boolExpr->location, PrimitiveTypeConstraint{ty, expectedTy, singletonType, builtinTypes->booleanType}); - return Inference{ty}; - } - else if (maybeSingleton(expectedTy)) - return Inference{singletonType}; - - return Inference{builtinTypes->booleanType}; - } - - return Inference{builtinTypes->booleanType}; + FreeType ft = FreeType{scope.get()}; + ft.lowerBound = singletonType; + ft.upperBound = builtinTypes->booleanType; + const TypeId freeTy = arena->addType(ft); + addConstraint(scope, boolExpr->location, PrimitiveTypeConstraint{freeTy, expectedType, builtinTypes->booleanType}); + return Inference{freeTy}; } Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprLocal* local) @@ -1766,12 +1762,12 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprLocal* local) // if we have a refinement key, we can look up its type. if (key) - maybeTy = lookup(scope.get(), key->def); + maybeTy = lookup(scope, key->def); // if the current def doesn't have a type, we might be doing a compound assignment // and therefore might need to look at the rvalue def instead. if (!maybeTy && rvalueDef) - maybeTy = lookup(scope.get(), *rvalueDef); + maybeTy = lookup(scope, *rvalueDef); if (maybeTy) { @@ -1797,7 +1793,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprGlobal* globa /* prepopulateGlobalScope() has already added all global functions to the environment by this point, so any * global that is not already in-scope is definitely an unknown symbol. */ - if (auto ty = lookup(scope.get(), def, /*prototype=*/false)) + if (auto ty = lookup(scope, def, /*prototype=*/false)) { rootScope->lvalueTypes[def] = *ty; return Inference{*ty, refinementArena.proposition(key, builtinTypes->truthyType)}; @@ -1816,7 +1812,7 @@ Inference ConstraintGenerator::checkIndexName(const ScopePtr& scope, const Refin if (key) { - if (auto ty = lookup(scope.get(), key->def)) + if (auto ty = lookup(scope, key->def)) return Inference{*ty, refinementArena.proposition(key, builtinTypes->truthyType)}; scope->rvalueRefinements[key->def] = result; @@ -1852,7 +1848,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprIndexExpr* in const RefinementKey* key = dfg->getRefinementKey(indexExpr); if (key) { - if (auto ty = lookup(scope.get(), key->def)) + if (auto ty = lookup(scope, key->def)) return Inference{*ty, refinementArena.proposition(key, builtinTypes->truthyType)}; scope->rvalueRefinements[key->def] = result; @@ -2120,7 +2116,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprIfElse* ifEls applyRefinements(elseScope, ifElse->falseExpr->location, refinementArena.negation(refinement)); TypeId elseType = check(elseScope, ifElse->falseExpr, expectedType).ty; - return Inference{expectedType ? *expectedType : simplifyUnion(builtinTypes, arena, thenType, elseType).result}; + return Inference{expectedType ? *expectedType : makeUnion(scope, ifElse->location, thenType, elseType)}; } Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTypeAssertion* typeAssert) @@ -3172,6 +3168,30 @@ void ConstraintGenerator::reportCodeTooComplex(Location location) logger->captureGenerationError(errors.back()); } +TypeId ConstraintGenerator::makeUnion(const ScopePtr& scope, Location location, TypeId lhs, TypeId rhs) +{ + TypeId resultType = arena->addType(TypeFamilyInstanceType{ + NotNull{&kBuiltinTypeFamilies.unionFamily}, + {lhs, rhs}, + {}, + }); + addConstraint(scope, location, ReduceConstraint{resultType}); + + return resultType; +} + +TypeId ConstraintGenerator::makeIntersect(const ScopePtr& scope, Location location, TypeId lhs, TypeId rhs) +{ + TypeId resultType = arena->addType(TypeFamilyInstanceType{ + NotNull{&kBuiltinTypeFamilies.intersectFamily}, + {lhs, rhs}, + {}, + }); + addConstraint(scope, location, ReduceConstraint{resultType}); + + return resultType; +} + struct GlobalPrepopulator : AstVisitor { const NotNull<Scope> globalScope; diff --git a/Analysis/src/ConstraintSolver.cpp b/Analysis/src/ConstraintSolver.cpp index 7430dbf3..ec0d6c8a 100644 --- a/Analysis/src/ConstraintSolver.cpp +++ b/Analysis/src/ConstraintSolver.cpp @@ -283,7 +283,8 @@ ConstraintSolver::ConstraintSolver(NotNull<Normalizer> normalizer, NotNull<Scope for (auto ty : c->getFreeTypes()) { // increment the reference count for `ty` - unresolvedConstraints[ty] += 1; + auto [refCount, _] = unresolvedConstraints.try_insert(ty, 0); + refCount += 1; } for (NotNull<const Constraint> dep : c->dependencies) @@ -368,7 +369,13 @@ void ConstraintSolver::run() // decrement the referenced free types for this constraint if we dispatched successfully! for (auto ty : c->getFreeTypes()) - unresolvedConstraints[ty] -= 1; + { + // this is a little weird, but because we're only counting free types in subtyping constraints, + // some constraints (like unpack) might actually produce _more_ references to a free type. + size_t& refCount = unresolvedConstraints[ty]; + if (refCount > 0) + refCount -= 1; + } if (logger) { @@ -534,6 +541,8 @@ bool ConstraintSolver::tryDispatch(NotNull<const Constraint> constraint, bool fo success = tryDispatch(*taec, constraint); else if (auto fcc = get<FunctionCallConstraint>(*constraint)) success = tryDispatch(*fcc, constraint); + else if (auto fcc = get<FunctionCheckConstraint>(*constraint)) + success = tryDispatch(*fcc, constraint); else if (auto fcc = get<PrimitiveTypeConstraint>(*constraint)) success = tryDispatch(*fcc, constraint); else if (auto hpc = get<HasPropConstraint>(*constraint)) @@ -992,6 +1001,15 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons return block(c.fn, constraint); } + // if we're calling an error type, the result is an error type, and that's that. + if (get<ErrorType>(fn)) + { + asMutable(c.result)->ty.emplace<BoundTypePack>(builtinTypes->errorTypePack); + unblock(c.result, constraint->location); + + return true; + } + auto [argsHead, argsTail] = flatten(argsPack); bool blocked = false; @@ -1080,44 +1098,6 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons *asMutable(follow(*ty)) = BoundType{builtinTypes->anyType}; } - // We know the type of the function and the arguments it expects to receive. - // We also know the TypeIds of the actual arguments that will be passed. - // - // Bidirectional type checking: Force those TypeIds to be the expected - // arguments. If something is incoherent, we'll spot it in type checking. - // - // Most important detail: If a function argument is a lambda, we also want - // to force unannotated argument types of that lambda to be the expected - // types. - - // FIXME: Bidirectional type checking of overloaded functions is not yet supported. - if (auto ftv = get<FunctionType>(fn)) - { - const std::vector<TypeId> expectedArgs = flatten(ftv->argTypes).first; - const std::vector<TypeId> argPackHead = flatten(argsPack).first; - - for (size_t i = 0; i < c.callSite->args.size && i < expectedArgs.size() && i < argPackHead.size(); ++i) - { - const FunctionType* expectedLambdaTy = get<FunctionType>(follow(expectedArgs[i])); - const FunctionType* lambdaTy = get<FunctionType>(follow(argPackHead[i])); - const AstExprFunction* lambdaExpr = c.callSite->args.data[i]->as<AstExprFunction>(); - - if (expectedLambdaTy && lambdaTy && lambdaExpr) - { - const std::vector<TypeId> expectedLambdaArgTys = flatten(expectedLambdaTy->argTypes).first; - const std::vector<TypeId> lambdaArgTys = flatten(lambdaTy->argTypes).first; - - for (size_t j = 0; j < expectedLambdaArgTys.size() && j < lambdaArgTys.size() && j < lambdaExpr->args.size; ++j) - { - if (!lambdaExpr->args.data[j]->annotation && get<FreeType>(follow(lambdaArgTys[j]))) - { - asMutable(lambdaArgTys[j])->ty.emplace<BoundType>(expectedLambdaArgTys[j]); - } - } - } - } - } - TypeId inferredTy = arena->addType(FunctionType{TypeLevel{}, constraint->scope.get(), argsPack, c.result}); Unifier2 u2{NotNull{arena}, builtinTypes, constraint->scope, NotNull{&iceReporter}}; @@ -1141,17 +1121,94 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons return true; } +bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<const Constraint> constraint) +{ + const TypeId fn = follow(c.fn); + const TypePackId argsPack = follow(c.argsPack); + + if (isBlocked(fn)) + return block(fn, constraint); + + if (isBlocked(argsPack)) + return block(argsPack, constraint); + + // We know the type of the function and the arguments it expects to receive. + // We also know the TypeIds of the actual arguments that will be passed. + // + // Bidirectional type checking: Force those TypeIds to be the expected + // arguments. If something is incoherent, we'll spot it in type checking. + // + // Most important detail: If a function argument is a lambda, we also want + // to force unannotated argument types of that lambda to be the expected + // types. + + // FIXME: Bidirectional type checking of overloaded functions is not yet supported. + if (auto ftv = get<FunctionType>(fn)) + { + const std::vector<TypeId> expectedArgs = flatten(ftv->argTypes).first; + const std::vector<TypeId> argPackHead = flatten(argsPack).first; + + for (size_t i = 0; i < c.callSite->args.size && i < expectedArgs.size() && i < argPackHead.size(); ++i) + { + const TypeId expectedArgTy = follow(expectedArgs[i]); + const TypeId actualArgTy = follow(argPackHead[i]); + + const FunctionType* expectedLambdaTy = get<FunctionType>(expectedArgTy); + const FunctionType* lambdaTy = get<FunctionType>(actualArgTy); + const AstExprFunction* lambdaExpr = c.callSite->args.data[i]->as<AstExprFunction>(); + + if (expectedLambdaTy && lambdaTy && lambdaExpr) + { + const std::vector<TypeId> expectedLambdaArgTys = flatten(expectedLambdaTy->argTypes).first; + const std::vector<TypeId> lambdaArgTys = flatten(lambdaTy->argTypes).first; + + for (size_t j = 0; j < expectedLambdaArgTys.size() && j < lambdaArgTys.size() && j < lambdaExpr->args.size; ++j) + { + if (!lambdaExpr->args.data[j]->annotation && get<FreeType>(follow(lambdaArgTys[j]))) + { + asMutable(lambdaArgTys[j])->ty.emplace<BoundType>(expectedLambdaArgTys[j]); + } + } + } + else + { + Unifier2 u2{arena, builtinTypes, constraint->scope, NotNull{&iceReporter}}; + u2.unify(actualArgTy, expectedArgTy); + } + } + } + + return true; +} + bool ConstraintSolver::tryDispatch(const PrimitiveTypeConstraint& c, NotNull<const Constraint> constraint) { - TypeId expectedType = follow(c.expectedType); - if (isBlocked(expectedType) || get<PendingExpansionType>(expectedType)) - return block(expectedType, constraint); + std::optional<TypeId> expectedType = c.expectedType ? std::make_optional<TypeId>(follow(*c.expectedType)) : std::nullopt; + if (expectedType && (isBlocked(*expectedType) || get<PendingExpansionType>(*expectedType))) + return block(*expectedType, constraint); - LUAU_ASSERT(get<BlockedType>(c.resultType)); + const FreeType* freeType = get<FreeType>(follow(c.freeType)); - TypeId bindTo = maybeSingleton(expectedType) ? c.singletonType : c.multitonType; - asMutable(c.resultType)->ty.emplace<BoundType>(bindTo); - unblock(c.resultType, constraint->location); + // if this is no longer a free type, then we're done. + if (!freeType) + return true; + + // We will wait if there are any other references to the free type mentioned here. + // This is probably the only thing that makes this not insane to do. + if (auto refCount = unresolvedConstraints.find(c.freeType); refCount && *refCount > 1) + { + block(c.freeType, constraint); + return false; + } + + TypeId bindTo = c.primitiveType; + + if (freeType->upperBound != c.primitiveType && maybeSingleton(freeType->upperBound)) + bindTo = freeType->lowerBound; + else if (expectedType && maybeSingleton(*expectedType)) + bindTo = freeType->lowerBound; + + asMutable(c.freeType)->ty.emplace<BoundType>(bindTo); return true; } @@ -1163,7 +1220,7 @@ bool ConstraintSolver::tryDispatch(const HasPropConstraint& c, NotNull<const Con LUAU_ASSERT(get<BlockedType>(resultType)); - if (isBlocked(subjectType) || get<PendingExpansionType>(subjectType)) + if (isBlocked(subjectType) || get<PendingExpansionType>(subjectType) || get<TypeFamilyInstanceType>(subjectType)) return block(subjectType, constraint); auto [blocked, result] = lookupTableProp(subjectType, c.prop, c.suppressSimplification); @@ -1599,7 +1656,7 @@ bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const Iterabl auto unpack = [&](TypeId ty) { TypePackId variadic = arena->addTypePack(VariadicTypePack{ty}); - pushConstraint(constraint->scope, constraint->location, UnpackConstraint{c.variables, variadic}); + pushConstraint(constraint->scope, constraint->location, UnpackConstraint{c.variables, variadic, /* resultIsLValue */ true}); }; if (get<AnyType>(iteratorTy)) @@ -1639,6 +1696,23 @@ bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const Iterabl { TypePackId expectedVariablePack = arena->addTypePack({iteratorTable->indexer->indexType, iteratorTable->indexer->indexResultType}); unify(constraint->scope, constraint->location, c.variables, expectedVariablePack); + + auto [variableTys, variablesTail] = flatten(c.variables); + + // the local types for the indexer _should_ be all set after unification + for (TypeId ty : variableTys) + { + if (auto lt = getMutable<LocalType>(ty)) + { + LUAU_ASSERT(lt->blockCount > 0); + --lt->blockCount; + + LUAU_ASSERT(0 <= lt->blockCount); + + if (0 == lt->blockCount) + asMutable(ty)->ty.emplace<BoundType>(lt->domain); + } + } } else unpack(builtinTypes->errorType); @@ -1775,7 +1849,7 @@ bool ConstraintSolver::tryDispatchIterableFunction( modifiedNextRetHead.push_back(*it); TypePackId modifiedNextRetPack = arena->addTypePack(std::move(modifiedNextRetHead), it.tail()); - auto psc = pushConstraint(constraint->scope, constraint->location, UnpackConstraint{c.variables, modifiedNextRetPack}); + auto psc = pushConstraint(constraint->scope, constraint->location, UnpackConstraint{c.variables, modifiedNextRetPack, /* resultIsLValue */ true}); inheritBlocks(constraint, psc); return true; @@ -1876,7 +1950,7 @@ std::pair<std::vector<TypeId>, std::optional<TypeId>> ConstraintSolver::lookupTa { const TypeId upperBound = follow(ft->upperBound); - if (get<TableType>(upperBound)) + if (get<TableType>(upperBound) || get<PrimitiveType>(upperBound)) return lookupTableProp(upperBound, propName, suppressSimplification, seen); // TODO: The upper bound could be an intersection that contains suitable tables or classes. @@ -2008,46 +2082,63 @@ void ConstraintSolver::bindBlockedType(TypeId blockedTy, TypeId resultTy, TypeId asMutable(blockedTy)->ty.emplace<BoundType>(resultTy); } -void ConstraintSolver::block_(BlockedConstraintId target, NotNull<const Constraint> constraint) +bool ConstraintSolver::block_(BlockedConstraintId target, NotNull<const Constraint> constraint) { - blocked[target].push_back(constraint); + // If a set is not present for the target, construct a new DenseHashSet for it, + // else grab the address of the existing set. + NotNull<DenseHashSet<const Constraint*>> blockVec{&blocked.try_emplace(target, nullptr).first->second}; + + if (blockVec->find(constraint)) + return false; + + blockVec->insert(constraint); auto& count = blockedConstraints[constraint]; count += 1; + + return true; } void ConstraintSolver::block(NotNull<const Constraint> target, NotNull<const Constraint> constraint) { - if (logger) - logger->pushBlock(constraint, target); + const bool newBlock = block_(target.get(), constraint); + if (newBlock) + { + if (logger) + logger->pushBlock(constraint, target); - if (FFlag::DebugLuauLogSolver) - printf("block Constraint %s on\t%s\n", toString(*target, opts).c_str(), toString(*constraint, opts).c_str()); - - block_(target.get(), constraint); + if (FFlag::DebugLuauLogSolver) + printf("block Constraint %s on\t%s\n", toString(*target, opts).c_str(), toString(*constraint, opts).c_str()); + } } bool ConstraintSolver::block(TypeId target, NotNull<const Constraint> constraint) { - if (logger) - logger->pushBlock(constraint, target); + const bool newBlock = block_(follow(target), constraint); + if (newBlock) + { + if (logger) + logger->pushBlock(constraint, target); - if (FFlag::DebugLuauLogSolver) - printf("block TypeId %s on\t%s\n", toString(target, opts).c_str(), toString(*constraint, opts).c_str()); + if (FFlag::DebugLuauLogSolver) + printf("block TypeId %s on\t%s\n", toString(target, opts).c_str(), toString(*constraint, opts).c_str()); + } - block_(follow(target), constraint); return false; } bool ConstraintSolver::block(TypePackId target, NotNull<const Constraint> constraint) { - if (logger) - logger->pushBlock(constraint, target); + const bool newBlock = block_(target, constraint); + if (newBlock) + { + if (logger) + logger->pushBlock(constraint, target); - if (FFlag::DebugLuauLogSolver) - printf("block TypeId %s on\t%s\n", toString(target, opts).c_str(), toString(*constraint, opts).c_str()); + if (FFlag::DebugLuauLogSolver) + printf("block TypeId %s on\t%s\n", toString(target, opts).c_str(), toString(*constraint, opts).c_str()); + } - block_(target, constraint); return false; } @@ -2058,9 +2149,9 @@ void ConstraintSolver::inheritBlocks(NotNull<const Constraint> source, NotNull<c auto blockedIt = blocked.find(source.get()); if (blockedIt != blocked.end()) { - for (const auto& blockedConstraint : blockedIt->second) + for (const Constraint* blockedConstraint : blockedIt->second) { - block(addition, blockedConstraint); + block(addition, NotNull{blockedConstraint}); } } } @@ -2112,9 +2203,9 @@ void ConstraintSolver::unblock_(BlockedConstraintId progressed) return; // unblocked should contain a value always, because of the above check - for (NotNull<const Constraint> unblockedConstraint : it->second) + for (const Constraint* unblockedConstraint : it->second) { - auto& count = blockedConstraints[unblockedConstraint]; + auto& count = blockedConstraints[NotNull{unblockedConstraint}]; if (FFlag::DebugLuauLogSolver) printf("Unblocking count=%d\t%s\n", int(count), toString(*unblockedConstraint, opts).c_str()); diff --git a/Analysis/src/Subtyping.cpp b/Analysis/src/Subtyping.cpp index b8671d21..62b574ca 100644 --- a/Analysis/src/Subtyping.cpp +++ b/Analysis/src/Subtyping.cpp @@ -699,6 +699,9 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId results.push_back(SubtypingResult{ok}.withBothComponent(TypePath::PackField::Tail)); } } + else if (get<ErrorTypePack>(*subTail) || get<ErrorTypePack>(*superTail)) + // error type is fine on either side + results.push_back(SubtypingResult{true}.withBothComponent(TypePath::PackField::Tail)); else iceReporter->ice( format("Subtyping::isSubtype got unexpected type packs %s and %s", toString(*subTail).c_str(), toString(*superTail).c_str())); diff --git a/Analysis/src/ToString.cpp b/Analysis/src/ToString.cpp index b14df311..0f520116 100644 --- a/Analysis/src/ToString.cpp +++ b/Analysis/src/ToString.cpp @@ -1742,9 +1742,16 @@ std::string toString(const Constraint& constraint, ToStringOptions& opts) { return "call " + tos(c.fn) + "( " + tos(c.argsPack) + " )" + " with { result = " + tos(c.result) + " }"; } + else if constexpr (std::is_same_v<T, FunctionCheckConstraint>) + { + return "function_check " + tos(c.fn) + " " + tos(c.argsPack); + } else if constexpr (std::is_same_v<T, PrimitiveTypeConstraint>) { - return tos(c.resultType) + " ~ prim " + tos(c.expectedType) + ", " + tos(c.singletonType) + ", " + tos(c.multitonType); + if (c.expectedType) + return "prim " + tos(c.freeType) + "[expected: " + tos(*c.expectedType) + "] as " + tos(c.primitiveType); + else + return "prim " + tos(c.freeType) + " as " + tos(c.primitiveType); } else if constexpr (std::is_same_v<T, HasPropConstraint>) { diff --git a/Analysis/src/Type.cpp b/Analysis/src/Type.cpp index abe719e1..71a07d73 100644 --- a/Analysis/src/Type.cpp +++ b/Analysis/src/Type.cpp @@ -419,6 +419,10 @@ bool maybeSingleton(TypeId ty) for (TypeId option : utv) if (get<SingletonType>(follow(option))) return true; + if (const IntersectionType* itv = get<IntersectionType>(ty)) + for (TypeId part : itv) + if (maybeSingleton(part)) // will i regret this? + return true; return false; } diff --git a/Analysis/src/TypeChecker2.cpp b/Analysis/src/TypeChecker2.cpp index 349443ab..fdac6daf 100644 --- a/Analysis/src/TypeChecker2.cpp +++ b/Analysis/src/TypeChecker2.cpp @@ -261,6 +261,155 @@ struct TypeChecker2 { } + static bool allowsNoReturnValues(const TypePackId tp) + { + for (TypeId ty : tp) + { + if (!get<ErrorType>(follow(ty))) + return false; + } + + return true; + } + + static Location getEndLocation(const AstExprFunction* function) + { + Location loc = function->location; + if (loc.begin.line != loc.end.line) + { + Position begin = loc.end; + begin.column = std::max(0u, begin.column - 3); + loc = Location(begin, 3); + } + + return loc; + } + + bool isErrorCall(const AstExprCall* call) + { + const AstExprGlobal* global = call->func->as<AstExprGlobal>(); + if (!global) + return false; + + if (global->name == "error") + return true; + else if (global->name == "assert") + { + // assert() will error because it is missing the first argument + if (call->args.size == 0) + return true; + + if (AstExprConstantBool* expr = call->args.data[0]->as<AstExprConstantBool>()) + if (!expr->value) + return true; + } + + return false; + } + + bool hasBreak(AstStat* node) + { + if (AstStatBlock* stat = node->as<AstStatBlock>()) + { + for (size_t i = 0; i < stat->body.size; ++i) + { + if (hasBreak(stat->body.data[i])) + return true; + } + + return false; + } + + if (node->is<AstStatBreak>()) + return true; + + if (AstStatIf* stat = node->as<AstStatIf>()) + { + if (hasBreak(stat->thenbody)) + return true; + + if (stat->elsebody && hasBreak(stat->elsebody)) + return true; + + return false; + } + + return false; + } + + // returns the last statement before the block implicitly exits, or nullptr if the block does not implicitly exit + // i.e. returns nullptr if the block returns properly or never returns + const AstStat* getFallthrough(const AstStat* node) + { + if (const AstStatBlock* stat = node->as<AstStatBlock>()) + { + if (stat->body.size == 0) + return stat; + + for (size_t i = 0; i < stat->body.size - 1; ++i) + { + if (getFallthrough(stat->body.data[i]) == nullptr) + return nullptr; + } + + return getFallthrough(stat->body.data[stat->body.size - 1]); + } + + if (const AstStatIf* stat = node->as<AstStatIf>()) + { + if (const AstStat* thenf = getFallthrough(stat->thenbody)) + return thenf; + + if (stat->elsebody) + { + if (const AstStat* elsef = getFallthrough(stat->elsebody)) + return elsef; + + return nullptr; + } + else + return stat; + } + + if (node->is<AstStatReturn>()) + return nullptr; + + if (const AstStatExpr* stat = node->as<AstStatExpr>()) + { + if (AstExprCall* call = stat->expr->as<AstExprCall>(); call && isErrorCall(call)) + return nullptr; + + return stat; + } + + if (const AstStatWhile* stat = node->as<AstStatWhile>()) + { + if (AstExprConstantBool* expr = stat->condition->as<AstExprConstantBool>()) + { + if (expr->value && !hasBreak(stat->body)) + return nullptr; + } + + return node; + } + + if (const AstStatRepeat* stat = node->as<AstStatRepeat>()) + { + if (AstExprConstantBool* expr = stat->condition->as<AstExprConstantBool>()) + { + if (!expr->value && !hasBreak(stat->body)) + return nullptr; + } + + if (getFallthrough(stat->body) == nullptr) + return nullptr; + + return node; + } + + return node; + } + std::optional<StackPusher> pushStack(AstNode* node) { if (Scope** scope = module->astScopes.find(node)) @@ -1723,6 +1872,10 @@ struct TypeChecker2 ++argIt; } + + bool reachesImplicitReturn = getFallthrough(fn->body) != nullptr; + if (reachesImplicitReturn && !allowsNoReturnValues(follow(inferredFtv->retTypes))) + reportError(FunctionExitsWithoutReturning{inferredFtv->retTypes}, getEndLocation(fn)); } visit(fn->body); diff --git a/Analysis/src/TypeFamily.cpp b/Analysis/src/TypeFamily.cpp index ed81b663..4903865f 100644 --- a/Analysis/src/TypeFamily.cpp +++ b/Analysis/src/TypeFamily.cpp @@ -1052,6 +1052,73 @@ TypeFamilyReductionResult<TypeId> refineFamilyFn(const std::vector<TypeId>& type return {resultTy, false, {}, {}}; } +TypeFamilyReductionResult<TypeId> unionFamilyFn(const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx) +{ + if (typeParams.size() != 2 || !packParams.empty()) + { + ctx->ice->ice("union type family: encountered a type family instance without the required argument structure"); + LUAU_ASSERT(false); + } + + TypeId lhsTy = follow(typeParams.at(0)); + TypeId rhsTy = follow(typeParams.at(1)); + + // check to see if both operand types are resolved enough, and wait to reduce if not + if (isPending(lhsTy, ctx->solver)) + return {std::nullopt, false, {lhsTy}, {}}; + else if (get<NeverType>(lhsTy)) // if the lhs is never, we don't need this family anymore + return {rhsTy, false, {}, {}}; + else if (isPending(rhsTy, ctx->solver)) + return {std::nullopt, false, {rhsTy}, {}}; + else if (get<NeverType>(rhsTy)) // if the rhs is never, we don't need this family anymore + return {lhsTy, false, {}, {}}; + + + SimplifyResult result = simplifyUnion(ctx->builtins, ctx->arena, lhsTy, rhsTy); + if (!result.blockedTypes.empty()) + return {std::nullopt, false, {result.blockedTypes.begin(), result.blockedTypes.end()}, {}}; + + return {result.result, false, {}, {}}; +} + + +TypeFamilyReductionResult<TypeId> intersectFamilyFn(const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx) +{ + if (typeParams.size() != 2 || !packParams.empty()) + { + ctx->ice->ice("intersect type family: encountered a type family instance without the required argument structure"); + LUAU_ASSERT(false); + } + + TypeId lhsTy = follow(typeParams.at(0)); + TypeId rhsTy = follow(typeParams.at(1)); + + // check to see if both operand types are resolved enough, and wait to reduce if not + if (isPending(lhsTy, ctx->solver)) + return {std::nullopt, false, {lhsTy}, {}}; + else if (get<NeverType>(lhsTy)) // if the lhs is never, we don't need this family anymore + return {ctx->builtins->neverType, false, {}, {}}; + else if (isPending(rhsTy, ctx->solver)) + return {std::nullopt, false, {rhsTy}, {}}; + else if (get<NeverType>(rhsTy)) // if the rhs is never, we don't need this family anymore + return {ctx->builtins->neverType, false, {}, {}}; + + SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, lhsTy, rhsTy); + if (!result.blockedTypes.empty()) + return {std::nullopt, false, {result.blockedTypes.begin(), result.blockedTypes.end()}, {}}; + + // if the intersection simplifies to `never`, this gives us bad autocomplete. + // we'll just produce the intersection plainly instead, but this might be revisitable + // if we ever give `never` some kind of "explanation" trail. + if (get<NeverType>(result.result)) + { + TypeId intersection = ctx->arena->addType(IntersectionType{{lhsTy, rhsTy}}); + return {intersection, false, {}, {}}; + } + + return {result.result, false, {}, {}}; +} + // computes the keys of `ty` into `result` // `isRaw` parameter indicates whether or not we should follow __index metamethods // returns `false` if `result` should be ignored because the answer is "all strings" @@ -1262,6 +1329,8 @@ BuiltinTypeFamilies::BuiltinTypeFamilies() , leFamily{"le", leFamilyFn} , eqFamily{"eq", eqFamilyFn} , refineFamily{"refine", refineFamilyFn} + , unionFamily{"union", unionFamilyFn} + , intersectFamily{"intersect", intersectFamilyFn} , keyofFamily{"keyof", keyofFamilyFn} , rawkeyofFamily{"rawkeyof", rawkeyofFamilyFn} { diff --git a/Analysis/src/TypeInfer.cpp b/Analysis/src/TypeInfer.cpp index a1247915..653beb0e 100644 --- a/Analysis/src/TypeInfer.cpp +++ b/Analysis/src/TypeInfer.cpp @@ -40,6 +40,7 @@ LUAU_FASTFLAGVARIABLE(LuauLoopControlFlowAnalysis, false) LUAU_FASTFLAGVARIABLE(LuauAlwaysCommitInferencesOfFunctionCalls, false) LUAU_FASTFLAGVARIABLE(LuauRemoveBadRelationalOperatorWarning, false) LUAU_FASTFLAGVARIABLE(LuauForbidAliasNamedTypeof, false) +LUAU_FASTFLAGVARIABLE(LuauOkWithIteratingOverTableProperties, false) namespace Luau { @@ -1335,10 +1336,10 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatForIn& forin) for (size_t i = 2; i < varTypes.size(); ++i) unify(nilType, varTypes[i], scope, forin.location); } - else if (isNonstrictMode()) + else if (isNonstrictMode() || FFlag::LuauOkWithIteratingOverTableProperties) { for (TypeId var : varTypes) - unify(anyType, var, scope, forin.location); + unify(unknownType, var, scope, forin.location); } else { diff --git a/Analysis/src/Unifier2.cpp b/Analysis/src/Unifier2.cpp index 6b213aea..cba2f4bb 100644 --- a/Analysis/src/Unifier2.cpp +++ b/Analysis/src/Unifier2.cpp @@ -56,6 +56,12 @@ bool Unifier2::unify(TypeId subTy, TypeId superTy) if (subFree || superFree) return true; + if (auto subLocal = getMutable<LocalType>(subTy)) + { + subLocal->domain = mkUnion(subLocal->domain, superTy); + expandedFreeTypes[subTy].push_back(superTy); + } + auto subFn = get<FunctionType>(subTy); auto superFn = get<FunctionType>(superTy); if (subFn && superFn) diff --git a/Ast/include/Luau/Ast.h b/Ast/include/Luau/Ast.h index d4836bb5..993116d6 100644 --- a/Ast/include/Luau/Ast.h +++ b/Ast/include/Luau/Ast.h @@ -387,7 +387,7 @@ public: AstExprFunction(const Location& location, const AstArray<AstGenericType>& generics, const AstArray<AstGenericTypePack>& genericPacks, AstLocal* self, const AstArray<AstLocal*>& args, bool vararg, const Location& varargLocation, AstStatBlock* body, size_t functionDepth, const AstName& debugname, const std::optional<AstTypeList>& returnAnnotation = {}, AstTypePack* varargAnnotation = nullptr, - bool DEPRECATED_hasEnd = false, const std::optional<Location>& argLocation = std::nullopt); + const std::optional<Location>& argLocation = std::nullopt); void visit(AstVisitor* visitor) override; @@ -406,8 +406,6 @@ public: AstName debugname; - // TODO clip with FFlag::LuauClipExtraHasEndProps - bool DEPRECATED_hasEnd = false; std::optional<Location> argLocation; }; @@ -573,7 +571,7 @@ public: LUAU_RTTI(AstStatIf) AstStatIf(const Location& location, AstExpr* condition, AstStatBlock* thenbody, AstStat* elsebody, const std::optional<Location>& thenLocation, - const std::optional<Location>& elseLocation, bool DEPRECATED_hasEnd); + const std::optional<Location>& elseLocation); void visit(AstVisitor* visitor) override; @@ -585,9 +583,6 @@ public: // Active for 'elseif' as well std::optional<Location> elseLocation; - - // TODO clip with FFlag::LuauClipExtraHasEndProps - bool DEPRECATED_hasEnd = false; }; class AstStatWhile : public AstStat @@ -595,7 +590,7 @@ class AstStatWhile : public AstStat public: LUAU_RTTI(AstStatWhile) - AstStatWhile(const Location& location, AstExpr* condition, AstStatBlock* body, bool hasDo, const Location& doLocation, bool DEPRECATED_hasEnd); + AstStatWhile(const Location& location, AstExpr* condition, AstStatBlock* body, bool hasDo, const Location& doLocation); void visit(AstVisitor* visitor) override; @@ -604,9 +599,6 @@ public: bool hasDo = false; Location doLocation; - - // TODO clip with FFlag::LuauClipExtraHasEndProps - bool DEPRECATED_hasEnd = false; }; class AstStatRepeat : public AstStat @@ -690,7 +682,7 @@ public: LUAU_RTTI(AstStatFor) AstStatFor(const Location& location, AstLocal* var, AstExpr* from, AstExpr* to, AstExpr* step, AstStatBlock* body, bool hasDo, - const Location& doLocation, bool DEPRECATED_hasEnd); + const Location& doLocation); void visit(AstVisitor* visitor) override; @@ -702,9 +694,6 @@ public: bool hasDo = false; Location doLocation; - - // TODO clip with FFlag::LuauClipExtraHasEndProps - bool DEPRECATED_hasEnd = false; }; class AstStatForIn : public AstStat @@ -713,7 +702,7 @@ public: LUAU_RTTI(AstStatForIn) AstStatForIn(const Location& location, const AstArray<AstLocal*>& vars, const AstArray<AstExpr*>& values, AstStatBlock* body, bool hasIn, - const Location& inLocation, bool hasDo, const Location& doLocation, bool DEPRECATED_hasEnd); + const Location& inLocation, bool hasDo, const Location& doLocation); void visit(AstVisitor* visitor) override; @@ -726,9 +715,6 @@ public: bool hasDo = false; Location doLocation; - - // TODO clip with FFlag::LuauClipExtraHasEndProps - bool DEPRECATED_hasEnd = false; }; class AstStatAssign : public AstStat diff --git a/Ast/src/Ast.cpp b/Ast/src/Ast.cpp index 9a6ca4d7..0409a622 100644 --- a/Ast/src/Ast.cpp +++ b/Ast/src/Ast.cpp @@ -163,7 +163,7 @@ void AstExprIndexExpr::visit(AstVisitor* visitor) AstExprFunction::AstExprFunction(const Location& location, const AstArray<AstGenericType>& generics, const AstArray<AstGenericTypePack>& genericPacks, AstLocal* self, const AstArray<AstLocal*>& args, bool vararg, const Location& varargLocation, AstStatBlock* body, size_t functionDepth, - const AstName& debugname, const std::optional<AstTypeList>& returnAnnotation, AstTypePack* varargAnnotation, bool DEPRECATED_hasEnd, + const AstName& debugname, const std::optional<AstTypeList>& returnAnnotation, AstTypePack* varargAnnotation, const std::optional<Location>& argLocation) : AstExpr(ClassIndex(), location) , generics(generics) @@ -177,7 +177,6 @@ AstExprFunction::AstExprFunction(const Location& location, const AstArray<AstGen , body(body) , functionDepth(functionDepth) , debugname(debugname) - , DEPRECATED_hasEnd(DEPRECATED_hasEnd) , argLocation(argLocation) { } @@ -395,14 +394,13 @@ void AstStatBlock::visit(AstVisitor* visitor) } AstStatIf::AstStatIf(const Location& location, AstExpr* condition, AstStatBlock* thenbody, AstStat* elsebody, - const std::optional<Location>& thenLocation, const std::optional<Location>& elseLocation, bool DEPRECATED_hasEnd) + const std::optional<Location>& thenLocation, const std::optional<Location>& elseLocation) : AstStat(ClassIndex(), location) , condition(condition) , thenbody(thenbody) , elsebody(elsebody) , thenLocation(thenLocation) , elseLocation(elseLocation) - , DEPRECATED_hasEnd(DEPRECATED_hasEnd) { } @@ -418,13 +416,12 @@ void AstStatIf::visit(AstVisitor* visitor) } } -AstStatWhile::AstStatWhile(const Location& location, AstExpr* condition, AstStatBlock* body, bool hasDo, const Location& doLocation, bool DEPRECATED_hasEnd) +AstStatWhile::AstStatWhile(const Location& location, AstExpr* condition, AstStatBlock* body, bool hasDo, const Location& doLocation) : AstStat(ClassIndex(), location) , condition(condition) , body(body) , hasDo(hasDo) , doLocation(doLocation) - , DEPRECATED_hasEnd(DEPRECATED_hasEnd) { } @@ -526,7 +523,7 @@ void AstStatLocal::visit(AstVisitor* visitor) } AstStatFor::AstStatFor(const Location& location, AstLocal* var, AstExpr* from, AstExpr* to, AstExpr* step, AstStatBlock* body, bool hasDo, - const Location& doLocation, bool DEPRECATED_hasEnd) + const Location& doLocation) : AstStat(ClassIndex(), location) , var(var) , from(from) @@ -535,7 +532,6 @@ AstStatFor::AstStatFor(const Location& location, AstLocal* var, AstExpr* from, A , body(body) , hasDo(hasDo) , doLocation(doLocation) - , DEPRECATED_hasEnd(DEPRECATED_hasEnd) { } @@ -557,7 +553,7 @@ void AstStatFor::visit(AstVisitor* visitor) } AstStatForIn::AstStatForIn(const Location& location, const AstArray<AstLocal*>& vars, const AstArray<AstExpr*>& values, AstStatBlock* body, - bool hasIn, const Location& inLocation, bool hasDo, const Location& doLocation, bool DEPRECATED_hasEnd) + bool hasIn, const Location& inLocation, bool hasDo, const Location& doLocation) : AstStat(ClassIndex(), location) , vars(vars) , values(values) @@ -566,7 +562,6 @@ AstStatForIn::AstStatForIn(const Location& location, const AstArray<AstLocal*>& , inLocation(inLocation) , hasDo(hasDo) , doLocation(doLocation) - , DEPRECATED_hasEnd(DEPRECATED_hasEnd) { } diff --git a/Ast/src/Parser.cpp b/Ast/src/Parser.cpp index 6d15e709..c4d1c65d 100644 --- a/Ast/src/Parser.cpp +++ b/Ast/src/Parser.cpp @@ -16,7 +16,6 @@ LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100) // Warning: If you are introducing new syntax, ensure that it is behind a separate // flag so that we don't break production games by reverting syntax changes. // See docs/SyntaxChanges.md for an explanation. -LUAU_FASTFLAGVARIABLE(LuauClipExtraHasEndProps, false) LUAU_FASTFLAG(LuauCheckedFunctionSyntax) LUAU_FASTFLAGVARIABLE(LuauReadWritePropertySyntax, false) @@ -373,18 +372,15 @@ AstStat* Parser::parseIf() AstStat* elsebody = nullptr; Location end = start; std::optional<Location> elseLocation; - bool DEPRECATED_hasEnd = false; if (lexer.current().type == Lexeme::ReservedElseif) { - if (FFlag::LuauClipExtraHasEndProps) - thenbody->hasEnd = true; + thenbody->hasEnd = true; unsigned int oldRecursionCount = recursionCounter; incrementRecursionCounter("elseif"); elseLocation = lexer.current().location; elsebody = parseIf(); end = elsebody->location; - DEPRECATED_hasEnd = elsebody->as<AstStatIf>()->DEPRECATED_hasEnd; recursionCounter = oldRecursionCount; } else @@ -393,8 +389,7 @@ AstStat* Parser::parseIf() if (lexer.current().type == Lexeme::ReservedElse) { - if (FFlag::LuauClipExtraHasEndProps) - thenbody->hasEnd = true; + thenbody->hasEnd = true; elseLocation = lexer.current().location; matchThenElse = lexer.current(); nextLexeme(); @@ -406,21 +401,17 @@ AstStat* Parser::parseIf() end = lexer.current().location; bool hasEnd = expectMatchEndAndConsume(Lexeme::ReservedEnd, matchThenElse); - DEPRECATED_hasEnd = hasEnd; - if (FFlag::LuauClipExtraHasEndProps) + if (elsebody) { - if (elsebody) - { - if (AstStatBlock* elseBlock = elsebody->as<AstStatBlock>()) - elseBlock->hasEnd = hasEnd; - } - else - thenbody->hasEnd = hasEnd; + if (AstStatBlock* elseBlock = elsebody->as<AstStatBlock>()) + elseBlock->hasEnd = hasEnd; } + else + thenbody->hasEnd = hasEnd; } - return allocator.alloc<AstStatIf>(Location(start, end), cond, thenbody, elsebody, thenLocation, elseLocation, DEPRECATED_hasEnd); + return allocator.alloc<AstStatIf>(Location(start, end), cond, thenbody, elsebody, thenLocation, elseLocation); } // while exp do block end @@ -444,10 +435,9 @@ AstStat* Parser::parseWhile() Location end = lexer.current().location; bool hasEnd = expectMatchEndAndConsume(Lexeme::ReservedEnd, matchDo); - if (FFlag::LuauClipExtraHasEndProps) - body->hasEnd = hasEnd; + body->hasEnd = hasEnd; - return allocator.alloc<AstStatWhile>(Location(start, end), cond, body, hasDo, matchDo.location, hasEnd); + return allocator.alloc<AstStatWhile>(Location(start, end), cond, body, hasDo, matchDo.location); } // repeat block until exp @@ -467,8 +457,7 @@ AstStat* Parser::parseRepeat() functionStack.back().loopDepth--; bool hasUntil = expectMatchEndAndConsume(Lexeme::ReservedUntil, matchRepeat); - if (FFlag::LuauClipExtraHasEndProps) - body->hasEnd = hasUntil; + body->hasEnd = hasUntil; AstExpr* cond = parseExpr(); @@ -565,10 +554,9 @@ AstStat* Parser::parseFor() Location end = lexer.current().location; bool hasEnd = expectMatchEndAndConsume(Lexeme::ReservedEnd, matchDo); - if (FFlag::LuauClipExtraHasEndProps) - body->hasEnd = hasEnd; + body->hasEnd = hasEnd; - return allocator.alloc<AstStatFor>(Location(start, end), var, from, to, step, body, hasDo, matchDo.location, hasEnd); + return allocator.alloc<AstStatFor>(Location(start, end), var, from, to, step, body, hasDo, matchDo.location); } else { @@ -609,11 +597,10 @@ AstStat* Parser::parseFor() Location end = lexer.current().location; bool hasEnd = expectMatchEndAndConsume(Lexeme::ReservedEnd, matchDo); - if (FFlag::LuauClipExtraHasEndProps) - body->hasEnd = hasEnd; + body->hasEnd = hasEnd; return allocator.alloc<AstStatForIn>( - Location(start, end), copy(vars), copy(values), body, hasIn, inLocation, hasDo, matchDo.location, hasEnd); + Location(start, end), copy(vars), copy(values), body, hasIn, inLocation, hasDo, matchDo.location); } } @@ -1100,11 +1087,10 @@ std::pair<AstExprFunction*, AstLocal*> Parser::parseFunctionBody( Location end = lexer.current().location; bool hasEnd = expectMatchEndAndConsume(Lexeme::ReservedEnd, matchFunction); - if (FFlag::LuauClipExtraHasEndProps) - body->hasEnd = hasEnd; + body->hasEnd = hasEnd; return {allocator.alloc<AstExprFunction>(Location(start, end), generics, genericPacks, self, vars, vararg, varargLocation, body, - functionStack.size(), debugname, typelist, varargAnnotation, hasEnd, argLocation), + functionStack.size(), debugname, typelist, varargAnnotation, argLocation), funLocal}; } diff --git a/CodeGen/include/Luau/AssemblyBuilderA64.h b/CodeGen/include/Luau/AssemblyBuilderA64.h index 0ed35910..78251012 100644 --- a/CodeGen/include/Luau/AssemblyBuilderA64.h +++ b/CodeGen/include/Luau/AssemblyBuilderA64.h @@ -139,6 +139,10 @@ public: void fsqrt(RegisterA64 dst, RegisterA64 src); void fsub(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2); + void ins_4s(RegisterA64 dst, RegisterA64 src, uint8_t index); + void ins_4s(RegisterA64 dst, uint8_t dstIndex, RegisterA64 src, uint8_t srcIndex); + void dup_4s(RegisterA64 dst, RegisterA64 src, uint8_t index); + // Floating-point rounding and conversions void frinta(RegisterA64 dst, RegisterA64 src); void frintm(RegisterA64 dst, RegisterA64 src); @@ -207,6 +211,7 @@ private: void placeSR3(const char* name, RegisterA64 dst, RegisterA64 src1, RegisterA64 src2, uint8_t op, int shift = 0, int N = 0); void placeSR2(const char* name, RegisterA64 dst, RegisterA64 src, uint8_t op, uint8_t op2 = 0); void placeR3(const char* name, RegisterA64 dst, RegisterA64 src1, RegisterA64 src2, uint8_t op, uint8_t op2); + void placeR3(const char* name, RegisterA64 dst, RegisterA64 src1, RegisterA64 src2, uint8_t sizes, uint8_t op, uint8_t op2); void placeR1(const char* name, RegisterA64 dst, RegisterA64 src, uint32_t op); void placeI12(const char* name, RegisterA64 dst, RegisterA64 src1, int src2, uint8_t op); void placeI16(const char* name, RegisterA64 dst, int src, uint8_t op, int shift = 0); diff --git a/CodeGen/include/Luau/AssemblyBuilderX64.h b/CodeGen/include/Luau/AssemblyBuilderX64.h index e5b82f08..0be59fb1 100644 --- a/CodeGen/include/Luau/AssemblyBuilderX64.h +++ b/CodeGen/include/Luau/AssemblyBuilderX64.h @@ -119,13 +119,18 @@ public: void vaddss(OperandX64 dst, OperandX64 src1, OperandX64 src2); void vsubsd(OperandX64 dst, OperandX64 src1, OperandX64 src2); + void vsubps(OperandX64 dst, OperandX64 src1, OperandX64 src2); void vmulsd(OperandX64 dst, OperandX64 src1, OperandX64 src2); + void vmulps(OperandX64 dst, OperandX64 src1, OperandX64 src2); void vdivsd(OperandX64 dst, OperandX64 src1, OperandX64 src2); + void vdivps(OperandX64 dst, OperandX64 src1, OperandX64 src2); + void vandps(OperandX64 dst, OperandX64 src1, OperandX64 src2); void vandpd(OperandX64 dst, OperandX64 src1, OperandX64 src2); void vandnpd(OperandX64 dst, OperandX64 src1, OperandX64 src2); void vxorpd(OperandX64 dst, OperandX64 src1, OperandX64 src2); + void vorps(OperandX64 dst, OperandX64 src1, OperandX64 src2); void vorpd(OperandX64 dst, OperandX64 src1, OperandX64 src2); void vucomisd(OperandX64 src1, OperandX64 src2); @@ -159,6 +164,9 @@ public: void vblendvpd(RegisterX64 dst, RegisterX64 src1, OperandX64 mask, RegisterX64 src3); + void vpshufps(RegisterX64 dst, RegisterX64 src1, OperandX64 src2, uint8_t shuffle); + void vpinsrd(RegisterX64 dst, RegisterX64 src1, OperandX64 src2, uint8_t offset); + // Run final checks bool finalize(); @@ -176,9 +184,11 @@ public: } // Constant allocation (uses rip-relative addressing) + OperandX64 i32(int32_t value); OperandX64 i64(int64_t value); OperandX64 f32(float value); OperandX64 f64(double value); + OperandX64 u32x4(uint32_t x, uint32_t y, uint32_t z, uint32_t w); OperandX64 f32x4(float x, float y, float z, float w); OperandX64 f64x2(double x, double y); OperandX64 bytes(const void* ptr, size_t size, size_t align = 8); @@ -260,6 +270,7 @@ private: std::vector<Label> pendingLabels; std::vector<uint32_t> labelLocations; + DenseHashMap<uint32_t, int32_t> constCache32; DenseHashMap<uint64_t, int32_t> constCache64; bool finalized = false; diff --git a/CodeGen/include/Luau/CodeGen.h b/CodeGen/include/Luau/CodeGen.h index b42dc773..d84c7236 100644 --- a/CodeGen/include/Luau/CodeGen.h +++ b/CodeGen/include/Luau/CodeGen.h @@ -55,6 +55,34 @@ CodeGenCompilationResult compile(lua_State* L, int idx, unsigned int flags = 0, using AnnotatorFn = void (*)(void* context, std::string& result, int fid, int instpos); +// Output "#" before IR blocks and instructions +enum class IncludeIrPrefix +{ + No, + Yes +}; + +// Output user count and last use information of blocks and instructions +enum class IncludeUseInfo +{ + No, + Yes +}; + +// Output CFG informations like block predecessors, successors and etc +enum class IncludeCfgInfo +{ + No, + Yes +}; + +// Output VM register live in/out information for blocks +enum class IncludeRegFlowInfo +{ + No, + Yes +}; + struct AssemblyOptions { enum Target @@ -76,10 +104,10 @@ struct AssemblyOptions bool includeIr = false; bool includeOutlinedCode = false; - bool includeIrPrefix = true; // "#" before IR blocks and instructions - bool includeUseInfo = true; - bool includeCfgInfo = true; - bool includeRegFlowInfo = true; + IncludeIrPrefix includeIrPrefix = IncludeIrPrefix::Yes; + IncludeUseInfo includeUseInfo = IncludeUseInfo::Yes; + IncludeCfgInfo includeCfgInfo = IncludeCfgInfo::Yes; + IncludeRegFlowInfo includeRegFlowInfo = IncludeRegFlowInfo::Yes; // Optional annotator function can be provided to describe each instruction, it takes function id and sequential instruction id AnnotatorFn annotator = nullptr; diff --git a/CodeGen/include/Luau/IrData.h b/CodeGen/include/Luau/IrData.h index 2330e680..de79f6f2 100644 --- a/CodeGen/include/Luau/IrData.h +++ b/CodeGen/include/Luau/IrData.h @@ -52,6 +52,11 @@ enum class IrCmd : uint8_t // A: Rn LOAD_INT, + // Load a float field from vector as a double number + // A: Rn or Kn + // B: int (offset from the start of TValue) + LOAD_FLOAT, + // Load a TValue from memory // A: Rn or Kn or pointer (TValue) // B: int (optional 'A' pointer offset) @@ -173,6 +178,17 @@ enum class IrCmd : uint8_t // A: double ABS_NUM, + // Add/Sub/Mul/Div/Idiv two vectors + // A, B: TValue + ADD_VEC, + SUB_VEC, + MUL_VEC, + DIV_VEC, + + // Negate a vector + // A: TValue + UNM_VEC, + // Compute Luau 'not' operation on destructured TValue // A: tag // B: int (value) @@ -286,6 +302,10 @@ enum class IrCmd : uint8_t // A: double NUM_TO_UINT, + // Converts a double number to a vector with the value in X/Y/Z + // A: double + NUM_TO_VECTOR, + // Adjust stack top (L->top) to point at 'B' TValues *after* the specified register // This is used to return multiple values // A: Rn diff --git a/CodeGen/include/Luau/IrDump.h b/CodeGen/include/Luau/IrDump.h index bcd24767..ab7f154d 100644 --- a/CodeGen/include/Luau/IrDump.h +++ b/CodeGen/include/Luau/IrDump.h @@ -2,6 +2,7 @@ #pragma once #include "Luau/IrData.h" +#include "Luau/CodeGen.h" #include <string> #include <vector> @@ -31,11 +32,12 @@ void toString(IrToStringContext& ctx, IrOp op); void toString(std::string& result, IrConst constant); void toString(std::string& result, const BytecodeTypes& bcTypes); -void toStringDetailed(IrToStringContext& ctx, const IrBlock& block, uint32_t blockIdx, const IrInst& inst, uint32_t instIdx, bool includeUseInfo); void toStringDetailed( - IrToStringContext& ctx, const IrBlock& block, uint32_t blockIdx, bool includeUseInfo, bool includeCfgInfo, bool includeRegFlowInfo); + IrToStringContext& ctx, const IrBlock& block, uint32_t blockIdx, const IrInst& inst, uint32_t instIdx, IncludeUseInfo includeUseInfo); +void toStringDetailed(IrToStringContext& ctx, const IrBlock& block, uint32_t blockIdx, IncludeUseInfo includeUseInfo, IncludeCfgInfo includeCfgInfo, + IncludeRegFlowInfo includeRegFlowInfo); -std::string toString(const IrFunction& function, bool includeUseInfo); +std::string toString(const IrFunction& function, IncludeUseInfo includeUseInfo); std::string dump(const IrFunction& function); diff --git a/CodeGen/include/Luau/IrUtils.h b/CodeGen/include/Luau/IrUtils.h index 06c87448..47ef505b 100644 --- a/CodeGen/include/Luau/IrUtils.h +++ b/CodeGen/include/Luau/IrUtils.h @@ -145,6 +145,7 @@ inline bool hasResult(IrCmd cmd) case IrCmd::LOAD_POINTER: case IrCmd::LOAD_DOUBLE: case IrCmd::LOAD_INT: + case IrCmd::LOAD_FLOAT: case IrCmd::LOAD_TVALUE: case IrCmd::LOAD_ENV: case IrCmd::GET_ARR_ADDR: @@ -167,6 +168,11 @@ inline bool hasResult(IrCmd cmd) case IrCmd::ROUND_NUM: case IrCmd::SQRT_NUM: case IrCmd::ABS_NUM: + case IrCmd::ADD_VEC: + case IrCmd::SUB_VEC: + case IrCmd::MUL_VEC: + case IrCmd::DIV_VEC: + case IrCmd::UNM_VEC: case IrCmd::NOT_ANY: case IrCmd::CMP_ANY: case IrCmd::TABLE_LEN: @@ -180,6 +186,7 @@ inline bool hasResult(IrCmd cmd) case IrCmd::UINT_TO_NUM: case IrCmd::NUM_TO_INT: case IrCmd::NUM_TO_UINT: + case IrCmd::NUM_TO_VECTOR: case IrCmd::SUBSTITUTE: case IrCmd::INVOKE_FASTCALL: case IrCmd::BITAND_UINT: diff --git a/CodeGen/include/Luau/IrVisitUseDef.h b/CodeGen/include/Luau/IrVisitUseDef.h index a05229d6..6ae31440 100644 --- a/CodeGen/include/Luau/IrVisitUseDef.h +++ b/CodeGen/include/Luau/IrVisitUseDef.h @@ -19,6 +19,7 @@ static void visitVmRegDefsUses(T& visitor, IrFunction& function, const IrInst& i case IrCmd::LOAD_POINTER: case IrCmd::LOAD_DOUBLE: case IrCmd::LOAD_INT: + case IrCmd::LOAD_FLOAT: case IrCmd::LOAD_TVALUE: visitor.maybeUse(inst.a); // Argument can also be a VmConst break; diff --git a/CodeGen/src/AssemblyBuilderA64.cpp b/CodeGen/src/AssemblyBuilderA64.cpp index 3873a513..de0eb0cd 100644 --- a/CodeGen/src/AssemblyBuilderA64.cpp +++ b/CodeGen/src/AssemblyBuilderA64.cpp @@ -569,30 +569,66 @@ void AssemblyBuilderA64::fabs(RegisterA64 dst, RegisterA64 src) void AssemblyBuilderA64::fadd(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2) { - LUAU_ASSERT(dst.kind == KindA64::d && src1.kind == KindA64::d && src2.kind == KindA64::d); + if (dst.kind == KindA64::d) + { + LUAU_ASSERT(src1.kind == KindA64::d && src2.kind == KindA64::d); - placeR3("fadd", dst, src1, src2, 0b11110'01'1, 0b0010'10); + placeR3("fadd", dst, src1, src2, 0b11110'01'1, 0b0010'10); + } + else + { + LUAU_ASSERT(dst.kind == KindA64::s && src1.kind == KindA64::s && src2.kind == KindA64::s); + + placeR3("fadd", dst, src1, src2, 0b11110'00'1, 0b0010'10); + } } void AssemblyBuilderA64::fdiv(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2) { - LUAU_ASSERT(dst.kind == KindA64::d && src1.kind == KindA64::d && src2.kind == KindA64::d); + if (dst.kind == KindA64::d) + { + LUAU_ASSERT(src1.kind == KindA64::d && src2.kind == KindA64::d); - placeR3("fdiv", dst, src1, src2, 0b11110'01'1, 0b0001'10); + placeR3("fdiv", dst, src1, src2, 0b11110'01'1, 0b0001'10); + } + else + { + LUAU_ASSERT(dst.kind == KindA64::s && src1.kind == KindA64::s && src2.kind == KindA64::s); + + placeR3("fdiv", dst, src1, src2, 0b11110'00'1, 0b0001'10); + } } void AssemblyBuilderA64::fmul(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2) { - LUAU_ASSERT(dst.kind == KindA64::d && src1.kind == KindA64::d && src2.kind == KindA64::d); + if (dst.kind == KindA64::d) + { + LUAU_ASSERT(src1.kind == KindA64::d && src2.kind == KindA64::d); - placeR3("fmul", dst, src1, src2, 0b11110'01'1, 0b0000'10); + placeR3("fmul", dst, src1, src2, 0b11110'01'1, 0b0000'10); + } + else + { + LUAU_ASSERT(dst.kind == KindA64::s && src1.kind == KindA64::s && src2.kind == KindA64::s); + + placeR3("fmul", dst, src1, src2, 0b11110'00'1, 0b0000'10); + } } void AssemblyBuilderA64::fneg(RegisterA64 dst, RegisterA64 src) { - LUAU_ASSERT(dst.kind == KindA64::d && src.kind == KindA64::d); + if (dst.kind == KindA64::d) + { + LUAU_ASSERT(src.kind == KindA64::d); - placeR1("fneg", dst, src, 0b000'11110'01'1'0000'10'10000); + placeR1("fneg", dst, src, 0b000'11110'01'1'0000'10'10000); + } + else + { + LUAU_ASSERT(dst.kind == KindA64::s && src.kind == KindA64::s); + + placeR1("fneg", dst, src, 0b000'11110'00'1'0000'10'10000); + } } void AssemblyBuilderA64::fsqrt(RegisterA64 dst, RegisterA64 src) @@ -604,9 +640,77 @@ void AssemblyBuilderA64::fsqrt(RegisterA64 dst, RegisterA64 src) void AssemblyBuilderA64::fsub(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2) { - LUAU_ASSERT(dst.kind == KindA64::d && src1.kind == KindA64::d && src2.kind == KindA64::d); + if (dst.kind == KindA64::d) + { + LUAU_ASSERT(src1.kind == KindA64::d && src2.kind == KindA64::d); - placeR3("fsub", dst, src1, src2, 0b11110'01'1, 0b0011'10); + placeR3("fsub", dst, src1, src2, 0b11110'01'1, 0b0011'10); + } + else + { + LUAU_ASSERT(dst.kind == KindA64::s && src1.kind == KindA64::s && src2.kind == KindA64::s); + + placeR3("fsub", dst, src1, src2, 0b11110'00'1, 0b0011'10); + } +} + +void AssemblyBuilderA64::ins_4s(RegisterA64 dst, RegisterA64 src, uint8_t index) +{ + LUAU_ASSERT(dst.kind == KindA64::q && src.kind == KindA64::w); + LUAU_ASSERT(index < 4); + + if (logText) + logAppend(" %-12sv%d.s[%d],w%d\n", "ins", dst.index, index, src.index); + + uint32_t op = 0b0'1'0'01110000'00100'0'0011'1; + + place(dst.index | (src.index << 5) | (op << 10) | (index << 19)); + commit(); +} + +void AssemblyBuilderA64::ins_4s(RegisterA64 dst, uint8_t dstIndex, RegisterA64 src, uint8_t srcIndex) +{ + LUAU_ASSERT(dst.kind == KindA64::q && src.kind == KindA64::q); + LUAU_ASSERT(dstIndex < 4); + LUAU_ASSERT(srcIndex < 4); + + if (logText) + logAppend(" %-12sv%d.s[%d],v%d.s[%d]\n", "ins", dst.index, dstIndex, src.index, srcIndex); + + uint32_t op = 0b0'1'1'01110000'00100'0'0000'1; + + place(dst.index | (src.index << 5) | (op << 10) | (dstIndex << 19) | (srcIndex << 13)); + commit(); +} + +void AssemblyBuilderA64::dup_4s(RegisterA64 dst, RegisterA64 src, uint8_t index) +{ + if (dst.kind == KindA64::s) + { + LUAU_ASSERT(src.kind == KindA64::q); + LUAU_ASSERT(index < 4); + + if (logText) + logAppend(" %-12ss%d,v%d.s[%d]\n", "dup", dst.index, src.index, index); + + uint32_t op = 0b01'0'11110000'00100'0'0000'1; + + place(dst.index | (src.index << 5) | (op << 10) | (index << 19)); + } + else + { + LUAU_ASSERT(src.kind == KindA64::q); + LUAU_ASSERT(index < 4); + + if (logText) + logAppend(" %-12sv%d.4s,v%d.s[%d]\n", "dup", dst.index, src.index, index); + + uint32_t op = 0b010'01110000'00100'0'0000'1; + + place(dst.index | (src.index << 5) | (op << 10) | (index << 19)); + } + + commit(); } void AssemblyBuilderA64::frinta(RegisterA64 dst, RegisterA64 src) @@ -839,7 +943,7 @@ void AssemblyBuilderA64::placeR3(const char* name, RegisterA64 dst, RegisterA64 if (logText) log(name, dst, src1, src2); - LUAU_ASSERT(dst.kind == KindA64::w || dst.kind == KindA64::x || dst.kind == KindA64::d); + LUAU_ASSERT(dst.kind == KindA64::w || dst.kind == KindA64::x || dst.kind == KindA64::d || dst.kind == KindA64::s); LUAU_ASSERT(dst.kind == src1.kind && dst.kind == src2.kind); uint32_t sf = (dst.kind == KindA64::x) ? 0x80000000 : 0; @@ -848,6 +952,18 @@ void AssemblyBuilderA64::placeR3(const char* name, RegisterA64 dst, RegisterA64 commit(); } +void AssemblyBuilderA64::placeR3(const char* name, RegisterA64 dst, RegisterA64 src1, RegisterA64 src2, uint8_t sizes, uint8_t op, uint8_t op2) +{ + if (logText) + log(name, dst, src1, src2); + + LUAU_ASSERT(dst.kind == KindA64::w || dst.kind == KindA64::x || dst.kind == KindA64::d || dst.kind == KindA64::q); + LUAU_ASSERT(dst.kind == src1.kind && dst.kind == src2.kind); + + place(dst.index | (src1.index << 5) | (op2 << 10) | (src2.index << 16) | (op << 21) | (sizes << 29)); + commit(); +} + void AssemblyBuilderA64::placeR1(const char* name, RegisterA64 dst, RegisterA64 src, uint32_t op) { if (logText) diff --git a/CodeGen/src/AssemblyBuilderX64.cpp b/CodeGen/src/AssemblyBuilderX64.cpp index 482aca84..6e5b2d70 100644 --- a/CodeGen/src/AssemblyBuilderX64.cpp +++ b/CodeGen/src/AssemblyBuilderX64.cpp @@ -6,6 +6,8 @@ #include <stdarg.h> #include <stdio.h> +LUAU_FASTFLAGVARIABLE(LuauCache32BitAsmConsts, false) + namespace Luau { namespace CodeGen @@ -79,6 +81,7 @@ static ABIX64 getCurrentX64ABI() AssemblyBuilderX64::AssemblyBuilderX64(bool logText, ABIX64 abi) : logText(logText) , abi(abi) + , constCache32(~0u) , constCache64(~0ull) { data.resize(4096); @@ -738,16 +741,36 @@ void AssemblyBuilderX64::vsubsd(OperandX64 dst, OperandX64 src1, OperandX64 src2 placeAvx("vsubsd", dst, src1, src2, 0x5c, false, AVX_0F, AVX_F2); } +void AssemblyBuilderX64::vsubps(OperandX64 dst, OperandX64 src1, OperandX64 src2) +{ + placeAvx("vsubps", dst, src1, src2, 0x5c, false, AVX_0F, AVX_NP); +} + void AssemblyBuilderX64::vmulsd(OperandX64 dst, OperandX64 src1, OperandX64 src2) { placeAvx("vmulsd", dst, src1, src2, 0x59, false, AVX_0F, AVX_F2); } +void AssemblyBuilderX64::vmulps(OperandX64 dst, OperandX64 src1, OperandX64 src2) +{ + placeAvx("vmulps", dst, src1, src2, 0x59, false, AVX_0F, AVX_NP); +} + void AssemblyBuilderX64::vdivsd(OperandX64 dst, OperandX64 src1, OperandX64 src2) { placeAvx("vdivsd", dst, src1, src2, 0x5e, false, AVX_0F, AVX_F2); } +void AssemblyBuilderX64::vdivps(OperandX64 dst, OperandX64 src1, OperandX64 src2) +{ + placeAvx("vdivps", dst, src1, src2, 0x5e, false, AVX_0F, AVX_NP); +} + +void AssemblyBuilderX64::vandps(OperandX64 dst, OperandX64 src1, OperandX64 src2) +{ + placeAvx("vandps", dst, src1, src2, 0x54, false, AVX_0F, AVX_NP); +} + void AssemblyBuilderX64::vandpd(OperandX64 dst, OperandX64 src1, OperandX64 src2) { placeAvx("vandpd", dst, src1, src2, 0x54, false, AVX_0F, AVX_66); @@ -763,6 +786,11 @@ void AssemblyBuilderX64::vxorpd(OperandX64 dst, OperandX64 src1, OperandX64 src2 placeAvx("vxorpd", dst, src1, src2, 0x57, false, AVX_0F, AVX_66); } +void AssemblyBuilderX64::vorps(OperandX64 dst, OperandX64 src1, OperandX64 src2) +{ + placeAvx("vorps", dst, src1, src2, 0x56, false, AVX_0F, AVX_NP); +} + void AssemblyBuilderX64::vorpd(OperandX64 dst, OperandX64 src1, OperandX64 src2) { placeAvx("vorpd", dst, src1, src2, 0x56, false, AVX_0F, AVX_66); @@ -909,6 +937,16 @@ void AssemblyBuilderX64::vblendvpd(RegisterX64 dst, RegisterX64 src1, OperandX64 placeAvx("vblendvpd", dst, src1, mask, src3.index << 4, 0x4b, false, AVX_0F3A, AVX_66); } +void AssemblyBuilderX64::vpshufps(RegisterX64 dst, RegisterX64 src1, OperandX64 src2, uint8_t shuffle) +{ + placeAvx("vpshufps", dst, src1, src2, shuffle, 0xc6, false, AVX_0F, AVX_NP); +} + +void AssemblyBuilderX64::vpinsrd(RegisterX64 dst, RegisterX64 src1, OperandX64 src2, uint8_t offset) +{ + placeAvx("vpinsrd", dst, src1, src2, offset, 0x22, false, AVX_0F3A, AVX_66); +} + bool AssemblyBuilderX64::finalize() { code.resize(codePos - code.data()); @@ -961,6 +999,26 @@ void AssemblyBuilderX64::setLabel(Label& label) log(label); } +OperandX64 AssemblyBuilderX64::i32(int32_t value) +{ + uint32_t as32BitKey = value; + + if (as32BitKey != ~0u) + { + if (int32_t* prev = constCache32.find(as32BitKey)) + return OperandX64(SizeX64::dword, noreg, 1, rip, *prev); + } + + size_t pos = allocateData(4, 4); + writeu32(&data[pos], value); + int32_t offset = int32_t(pos - data.size()); + + if (as32BitKey != ~0u) + constCache32[as32BitKey] = offset; + + return OperandX64(SizeX64::dword, noreg, 1, rip, offset); +} + OperandX64 AssemblyBuilderX64::i64(int64_t value) { uint64_t as64BitKey = value; @@ -983,9 +1041,33 @@ OperandX64 AssemblyBuilderX64::i64(int64_t value) OperandX64 AssemblyBuilderX64::f32(float value) { - size_t pos = allocateData(4, 4); - writef32(&data[pos], value); - return OperandX64(SizeX64::dword, noreg, 1, rip, int32_t(pos - data.size())); + if (FFlag::LuauCache32BitAsmConsts) + { + uint32_t as32BitKey; + static_assert(sizeof(as32BitKey) == sizeof(value), "Expecting float to be 32-bit"); + memcpy(&as32BitKey, &value, sizeof(value)); + + if (as32BitKey != ~0u) + { + if (int32_t* prev = constCache32.find(as32BitKey)) + return OperandX64(SizeX64::dword, noreg, 1, rip, *prev); + } + + size_t pos = allocateData(4, 4); + writef32(&data[pos], value); + int32_t offset = int32_t(pos - data.size()); + + if (as32BitKey != ~0u) + constCache32[as32BitKey] = offset; + + return OperandX64(SizeX64::dword, noreg, 1, rip, offset); + } + else + { + size_t pos = allocateData(4, 4); + writef32(&data[pos], value); + return OperandX64(SizeX64::dword, noreg, 1, rip, int32_t(pos - data.size())); + } } OperandX64 AssemblyBuilderX64::f64(double value) @@ -1010,6 +1092,16 @@ OperandX64 AssemblyBuilderX64::f64(double value) return OperandX64(SizeX64::qword, noreg, 1, rip, offset); } +OperandX64 AssemblyBuilderX64::u32x4(uint32_t x, uint32_t y, uint32_t z, uint32_t w) +{ + size_t pos = allocateData(16, 16); + writeu32(&data[pos], x); + writeu32(&data[pos + 4], y); + writeu32(&data[pos + 8], z); + writeu32(&data[pos + 12], w); + return OperandX64(SizeX64::xmmword, noreg, 1, rip, int32_t(pos - data.size())); +} + OperandX64 AssemblyBuilderX64::f32x4(float x, float y, float z, float w) { size_t pos = allocateData(16, 16); @@ -1442,7 +1534,6 @@ void AssemblyBuilderX64::placeImm8(int32_t imm) { int8_t imm8 = int8_t(imm); - LUAU_ASSERT(imm8 == imm); place(imm8); } diff --git a/CodeGen/src/CodeGen.cpp b/CodeGen/src/CodeGen.cpp index 84d3f900..ab28daa2 100644 --- a/CodeGen/src/CodeGen.cpp +++ b/CodeGen/src/CodeGen.cpp @@ -21,6 +21,7 @@ #include "CodeGenX64.h" #include "lapi.h" +#include "lmem.h" #include <memory> #include <optional> @@ -56,6 +57,8 @@ LUAU_FASTINTVARIABLE(CodegenHeuristicsBlockLimit, 32'768) // 32 K // Current value is based on some member variables being limited to 16 bits LUAU_FASTINTVARIABLE(CodegenHeuristicsBlockInstructionLimit, 65'536) // 64 K +LUAU_FASTFLAGVARIABLE(DisableNativeCodegenIfBreakpointIsSet, false) + namespace Luau { namespace CodeGen @@ -164,6 +167,45 @@ static int onEnter(lua_State* L, Proto* proto) return GateFn(data->context.gateEntry)(L, proto, target, &data->context); } +void onDisable(lua_State* L, Proto* proto) +{ + // do nothing if proto already uses bytecode + if (proto->codeentry == proto->code) + return; + + // ensure that VM does not call native code for this proto + proto->codeentry = proto->code; + + // prevent native code from entering proto with breakpoints + proto->exectarget = 0; + + // walk all thread call stacks and clear the LUA_CALLINFO_NATIVE flag from any + // entries pointing to the current proto that has native code enabled. + luaM_visitgco(L, proto, [](void* context, lua_Page* page, GCObject* gco) { + Proto* proto = (Proto*)context; + + if (gco->gch.tt != LUA_TTHREAD) + return false; + + lua_State* th = gco2th(gco); + + for (CallInfo* ci = th->ci; ci > th->base_ci; ci--) + { + if (isLua(ci)) + { + Proto* p = clvalue(ci->func)->l.p; + + if (p == proto) + { + ci->flags &= ~LUA_CALLINFO_NATIVE; + } + } + } + + return false; + }); +} + #if defined(__aarch64__) unsigned int getCpuFeaturesA64() { @@ -257,6 +299,7 @@ void create(lua_State* L, AllocationCallback* allocationCallback, void* allocati ecb->close = onCloseState; ecb->destroy = onDestroyFunction; ecb->enter = onEnter; + ecb->disable = FFlag::DisableNativeCodegenIfBreakpointIsSet ? onDisable : nullptr; } void create(lua_State* L) diff --git a/CodeGen/src/CodeGenLower.h b/CodeGen/src/CodeGenLower.h index a62c1937..3165cc43 100644 --- a/CodeGen/src/CodeGenLower.h +++ b/CodeGen/src/CodeGenLower.h @@ -108,7 +108,7 @@ inline bool lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction& if (options.includeIr) { - if (options.includeIrPrefix) + if (options.includeIrPrefix == IncludeIrPrefix::Yes) build.logAppend("# "); toStringDetailed(ctx, block, blockIndex, options.includeUseInfo, options.includeCfgInfo, options.includeRegFlowInfo); @@ -174,7 +174,7 @@ inline bool lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction& if (options.includeIr) { - if (options.includeIrPrefix) + if (options.includeIrPrefix == IncludeIrPrefix::Yes) build.logAppend("# "); toStringDetailed(ctx, block, blockIndex, inst, index, options.includeUseInfo); @@ -201,7 +201,7 @@ inline bool lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction& lowering.finishBlock(block, nextBlock); - if (options.includeIr && options.includeIrPrefix) + if (options.includeIr && options.includeIrPrefix == IncludeIrPrefix::Yes) build.logAppend("#\n"); if (block.expectedNextBlock == ~0u) diff --git a/CodeGen/src/IrDump.cpp b/CodeGen/src/IrDump.cpp index 454eaf13..36c5c3d0 100644 --- a/CodeGen/src/IrDump.cpp +++ b/CodeGen/src/IrDump.cpp @@ -89,6 +89,8 @@ const char* getCmdName(IrCmd cmd) return "LOAD_DOUBLE"; case IrCmd::LOAD_INT: return "LOAD_INT"; + case IrCmd::LOAD_FLOAT: + return "LOAD_FLOAT"; case IrCmd::LOAD_TVALUE: return "LOAD_TVALUE"; case IrCmd::LOAD_ENV: @@ -149,6 +151,16 @@ const char* getCmdName(IrCmd cmd) return "SQRT_NUM"; case IrCmd::ABS_NUM: return "ABS_NUM"; + case IrCmd::ADD_VEC: + return "ADD_VEC"; + case IrCmd::SUB_VEC: + return "SUB_VEC"; + case IrCmd::MUL_VEC: + return "MUL_VEC"; + case IrCmd::DIV_VEC: + return "DIV_VEC"; + case IrCmd::UNM_VEC: + return "UNM_VEC"; case IrCmd::NOT_ANY: return "NOT_ANY"; case IrCmd::CMP_ANY: @@ -193,6 +205,8 @@ const char* getCmdName(IrCmd cmd) return "NUM_TO_INT"; case IrCmd::NUM_TO_UINT: return "NUM_TO_UINT"; + case IrCmd::NUM_TO_VECTOR: + return "NUM_TO_VECTOR"; case IrCmd::ADJUST_STACK_TO_REG: return "ADJUST_STACK_TO_REG"; case IrCmd::ADJUST_STACK_TO_TOP: @@ -581,13 +595,14 @@ static RegisterSet getJumpTargetExtraLiveIn(IrToStringContext& ctx, const IrBloc return extraRs; } -void toStringDetailed(IrToStringContext& ctx, const IrBlock& block, uint32_t blockIdx, const IrInst& inst, uint32_t instIdx, bool includeUseInfo) +void toStringDetailed( + IrToStringContext& ctx, const IrBlock& block, uint32_t blockIdx, const IrInst& inst, uint32_t instIdx, IncludeUseInfo includeUseInfo) { size_t start = ctx.result.size(); toString(ctx, inst, instIdx); - if (includeUseInfo) + if (includeUseInfo == IncludeUseInfo::Yes) { padToDetailColumn(ctx.result, start); @@ -624,11 +639,11 @@ void toStringDetailed(IrToStringContext& ctx, const IrBlock& block, uint32_t blo } } -void toStringDetailed( - IrToStringContext& ctx, const IrBlock& block, uint32_t blockIdx, bool includeUseInfo, bool includeCfgInfo, bool includeRegFlowInfo) +void toStringDetailed(IrToStringContext& ctx, const IrBlock& block, uint32_t blockIdx, IncludeUseInfo includeUseInfo, IncludeCfgInfo includeCfgInfo, + IncludeRegFlowInfo includeRegFlowInfo) { // Report captured registers for entry block - if (includeRegFlowInfo && block.useCount == 0 && block.kind != IrBlockKind::Dead && ctx.cfg.captured.regs.any()) + if (includeRegFlowInfo == IncludeRegFlowInfo::Yes && block.useCount == 0 && block.kind != IrBlockKind::Dead && ctx.cfg.captured.regs.any()) { append(ctx.result, "; captured regs: "); appendRegisterSet(ctx, ctx.cfg.captured, ", "); @@ -640,7 +655,7 @@ void toStringDetailed( toString(ctx, block, blockIdx); append(ctx.result, ":"); - if (includeUseInfo) + if (includeUseInfo == IncludeUseInfo::Yes) { padToDetailColumn(ctx.result, start); @@ -652,7 +667,7 @@ void toStringDetailed( } // Predecessor list - if (includeCfgInfo && blockIdx < ctx.cfg.predecessorsOffsets.size()) + if (includeCfgInfo == IncludeCfgInfo::Yes && blockIdx < ctx.cfg.predecessorsOffsets.size()) { BlockIteratorWrapper pred = predecessors(ctx.cfg, blockIdx); @@ -666,7 +681,7 @@ void toStringDetailed( } // Successor list - if (includeCfgInfo && blockIdx < ctx.cfg.successorsOffsets.size()) + if (includeCfgInfo == IncludeCfgInfo::Yes && blockIdx < ctx.cfg.successorsOffsets.size()) { BlockIteratorWrapper succ = successors(ctx.cfg, blockIdx); @@ -680,7 +695,7 @@ void toStringDetailed( } // Live-in VM regs - if (includeRegFlowInfo && blockIdx < ctx.cfg.in.size()) + if (includeRegFlowInfo == IncludeRegFlowInfo::Yes && blockIdx < ctx.cfg.in.size()) { const RegisterSet& in = ctx.cfg.in[blockIdx]; @@ -693,7 +708,7 @@ void toStringDetailed( } // Live-out VM regs - if (includeRegFlowInfo && blockIdx < ctx.cfg.out.size()) + if (includeRegFlowInfo == IncludeRegFlowInfo::Yes && blockIdx < ctx.cfg.out.size()) { const RegisterSet& out = ctx.cfg.out[blockIdx]; @@ -706,7 +721,7 @@ void toStringDetailed( } } -std::string toString(const IrFunction& function, bool includeUseInfo) +std::string toString(const IrFunction& function, IncludeUseInfo includeUseInfo) { std::string result; IrToStringContext ctx{result, function.blocks, function.constants, function.cfg}; @@ -718,7 +733,7 @@ std::string toString(const IrFunction& function, bool includeUseInfo) if (block.kind == IrBlockKind::Dead) continue; - toStringDetailed(ctx, block, uint32_t(i), includeUseInfo, /*includeCfgInfo*/ true, /*includeRegFlowInfo*/ true); + toStringDetailed(ctx, block, uint32_t(i), includeUseInfo, IncludeCfgInfo::Yes, IncludeRegFlowInfo::Yes); if (block.start == ~0u) { @@ -747,7 +762,7 @@ std::string toString(const IrFunction& function, bool includeUseInfo) std::string dump(const IrFunction& function) { - std::string result = toString(function, /* includeUseInfo */ true); + std::string result = toString(function, IncludeUseInfo::Yes); printf("%s\n", result.c_str()); diff --git a/CodeGen/src/IrLoweringA64.cpp b/CodeGen/src/IrLoweringA64.cpp index dc20d6e8..182dd6c0 100644 --- a/CodeGen/src/IrLoweringA64.cpp +++ b/CodeGen/src/IrLoweringA64.cpp @@ -290,6 +290,16 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) build.ldr(inst.regA64, addr); break; } + case IrCmd::LOAD_FLOAT: + { + inst.regA64 = regs.allocReg(KindA64::d, index); + RegisterA64 temp = castReg(KindA64::s, inst.regA64); // safe to alias a fresh register + AddressA64 addr = tempAddr(inst.a, intOp(inst.b)); + + build.ldr(temp, addr); + build.fcvt(inst.regA64, temp); + break; + } case IrCmd::LOAD_TVALUE: { inst.regA64 = regs.allocReg(KindA64::q, index); @@ -657,6 +667,84 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) build.fabs(inst.regA64, temp); break; } + case IrCmd::ADD_VEC: + { + inst.regA64 = regs.allocReuse(KindA64::q, index, {inst.a, inst.b}); + + 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); + } + break; + } + case IrCmd::SUB_VEC: + { + inst.regA64 = regs.allocReuse(KindA64::q, index, {inst.a, inst.b}); + + 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); + } + break; + } + case IrCmd::MUL_VEC: + { + inst.regA64 = regs.allocReuse(KindA64::q, index, {inst.a, inst.b}); + + 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); + } + break; + } + case IrCmd::DIV_VEC: + { + inst.regA64 = regs.allocReuse(KindA64::q, index, {inst.a, inst.b}); + + 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); + } + break; + } + case IrCmd::UNM_VEC: + { + inst.regA64 = regs.allocReuse(KindA64::q, index, {inst.a}); + + 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); + } + break; + } case IrCmd::NOT_ANY: { inst.regA64 = regs.allocReuse(KindA64::w, index, {inst.a, inst.b}); @@ -1010,6 +1098,21 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) build.fcvtzs(castReg(KindA64::x, inst.regA64), temp); break; } + case IrCmd::NUM_TO_VECTOR: + { + inst.regA64 = regs.allocReg(KindA64::q, index); + + 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); + + build.mov(tempw, LUA_TVECTOR); + build.ins_4s(inst.regA64, tempw, 3); + break; + } case IrCmd::ADJUST_STACK_TO_REG: { RegisterA64 temp = regs.allocTemp(KindA64::x); diff --git a/CodeGen/src/IrLoweringX64.cpp b/CodeGen/src/IrLoweringX64.cpp index 081c6320..103bcacf 100644 --- a/CodeGen/src/IrLoweringX64.cpp +++ b/CodeGen/src/IrLoweringX64.cpp @@ -108,6 +108,17 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) build.mov(inst.regX64, luauRegValueInt(vmRegOp(inst.a))); break; + case IrCmd::LOAD_FLOAT: + inst.regX64 = regs.allocReg(SizeX64::xmmword, index); + + if (inst.a.kind == IrOpKind::VmReg) + build.vcvtss2sd(inst.regX64, inst.regX64, dword[rBase + vmRegOp(inst.a) * sizeof(TValue) + offsetof(TValue, value) + intOp(inst.b)]); + else if (inst.a.kind == IrOpKind::VmConst) + build.vcvtss2sd( + inst.regX64, inst.regX64, dword[rConstants + vmConstOp(inst.a) * sizeof(TValue) + offsetof(TValue, value) + intOp(inst.b)]); + else + LUAU_ASSERT(!"Unsupported instruction form"); + break; case IrCmd::LOAD_TVALUE: { inst.regX64 = regs.allocReg(SizeX64::xmmword, index); @@ -586,6 +597,81 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) build.vandpd(inst.regX64, inst.regX64, build.i64(~(1LL << 63))); break; + case IrCmd::ADD_VEC: + { + inst.regX64 = regs.allocRegOrReuse(SizeX64::xmmword, index, {inst.a, inst.b}); + + ScopedRegX64 tmp1{regs, SizeX64::xmmword}; + ScopedRegX64 tmp2{regs, SizeX64::xmmword}; + + // Fourth component is the tag number which is interpreted as a denormal and has to be filtered out + build.vandps(tmp1.reg, regOp(inst.a), vectorAndMaskOp()); + build.vandps(tmp2.reg, regOp(inst.b), vectorAndMaskOp()); + build.vaddps(inst.regX64, tmp1.reg, tmp2.reg); + build.vorps(inst.regX64, inst.regX64, vectorOrMaskOp()); + break; + } + case IrCmd::SUB_VEC: + { + inst.regX64 = regs.allocRegOrReuse(SizeX64::xmmword, index, {inst.a, inst.b}); + + ScopedRegX64 tmp1{regs, SizeX64::xmmword}; + ScopedRegX64 tmp2{regs, SizeX64::xmmword}; + + // Fourth component is the tag number which is interpreted as a denormal and has to be filtered out + build.vandps(tmp1.reg, regOp(inst.a), vectorAndMaskOp()); + build.vandps(tmp2.reg, regOp(inst.b), vectorAndMaskOp()); + build.vsubps(inst.regX64, tmp1.reg, tmp2.reg); + build.vorps(inst.regX64, inst.regX64, vectorOrMaskOp()); + break; + } + case IrCmd::MUL_VEC: + { + inst.regX64 = regs.allocRegOrReuse(SizeX64::xmmword, index, {inst.a, inst.b}); + + ScopedRegX64 tmp1{regs, SizeX64::xmmword}; + ScopedRegX64 tmp2{regs, SizeX64::xmmword}; + + // Fourth component is the tag number which is interpreted as a denormal and has to be filtered out + build.vandps(tmp1.reg, regOp(inst.a), vectorAndMaskOp()); + build.vandps(tmp2.reg, regOp(inst.b), vectorAndMaskOp()); + build.vmulps(inst.regX64, tmp1.reg, tmp2.reg); + build.vorps(inst.regX64, inst.regX64, vectorOrMaskOp()); + break; + } + case IrCmd::DIV_VEC: + { + inst.regX64 = regs.allocRegOrReuse(SizeX64::xmmword, index, {inst.a, inst.b}); + + ScopedRegX64 tmp1{regs, SizeX64::xmmword}; + ScopedRegX64 tmp2{regs, SizeX64::xmmword}; + + // Fourth component is the tag number which is interpreted as a denormal and has to be filtered out + build.vandps(tmp1.reg, regOp(inst.a), vectorAndMaskOp()); + build.vandps(tmp2.reg, regOp(inst.b), vectorAndMaskOp()); + build.vdivps(inst.regX64, tmp1.reg, tmp2.reg); + build.vpinsrd(inst.regX64, inst.regX64, build.i32(LUA_TVECTOR), 3); + break; + } + case IrCmd::UNM_VEC: + { + inst.regX64 = regs.allocRegOrReuse(SizeX64::xmmword, index, {inst.a}); + + RegisterX64 src = regOp(inst.a); + + if (inst.regX64 == src) + { + build.vxorpd(inst.regX64, inst.regX64, build.f32x4(-0.0, -0.0, -0.0, -0.0)); + } + else + { + build.vmovsd(inst.regX64, src, src); + build.vxorpd(inst.regX64, inst.regX64, build.f32x4(-0.0, -0.0, -0.0, -0.0)); + } + + build.vpinsrd(inst.regX64, inst.regX64, build.i32(LUA_TVECTOR), 3); + break; + } case IrCmd::NOT_ANY: { // TODO: if we have a single user which is a STORE_INT, we are missing the opportunity to write directly to target @@ -878,6 +964,25 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) build.vcvttsd2si(qwordReg(inst.regX64), memRegDoubleOp(inst.a)); break; + case IrCmd::NUM_TO_VECTOR: + inst.regX64 = regs.allocReg(SizeX64::xmmword, index); + + if (inst.a.kind == IrOpKind::Constant) + { + float value = float(doubleOp(inst.a)); + uint32_t asU32; + static_assert(sizeof(asU32) == sizeof(value), "Expecting float to be 32-bit"); + memcpy(&asU32, &value, sizeof(value)); + + build.vmovaps(inst.regX64, build.u32x4(asU32, asU32, asU32, LUA_TVECTOR)); + } + else + { + build.vcvtsd2ss(inst.regX64, inst.regX64, memRegDoubleOp(inst.a)); + build.vpshufps(inst.regX64, inst.regX64, inst.regX64, 0b00'00'00'00); + build.vpinsrd(inst.regX64, inst.regX64, build.i32(LUA_TVECTOR), 3); + } + break; case IrCmd::ADJUST_STACK_TO_REG: { ScopedRegX64 tmp{regs, SizeX64::qword}; @@ -2146,6 +2251,22 @@ Label& IrLoweringX64::labelOp(IrOp op) const return blockOp(op).label; } +OperandX64 IrLoweringX64::vectorAndMaskOp() +{ + if (vectorAndMask.base == noreg) + vectorAndMask = build.u32x4(~0u, ~0u, ~0u, 0); + + return vectorAndMask; +} + +OperandX64 IrLoweringX64::vectorOrMaskOp() +{ + 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 5f12b303..f58a5d86 100644 --- a/CodeGen/src/IrLoweringX64.h +++ b/CodeGen/src/IrLoweringX64.h @@ -61,6 +61,9 @@ struct IrLoweringX64 IrBlock& blockOp(IrOp op) const; Label& labelOp(IrOp op) const; + OperandX64 vectorAndMaskOp(); + OperandX64 vectorOrMaskOp(); + struct InterruptHandler { Label self; @@ -87,6 +90,9 @@ struct IrLoweringX64 std::vector<InterruptHandler> interruptHandlers; std::vector<ExitHandler> exitHandlers; DenseHashMap<uint32_t, uint32_t> exitHandlerMap; + + OperandX64 vectorAndMask = noreg; + OperandX64 vectorOrMask = noreg; }; } // namespace X64 diff --git a/CodeGen/src/IrTranslation.cpp b/CodeGen/src/IrTranslation.cpp index 8897996e..242d0947 100644 --- a/CodeGen/src/IrTranslation.cpp +++ b/CodeGen/src/IrTranslation.cpp @@ -13,6 +13,7 @@ #include "ltm.h" LUAU_FASTFLAGVARIABLE(LuauCodegenLuData, false) +LUAU_FASTFLAGVARIABLE(LuauCodegenVector, false) namespace Luau { @@ -50,6 +51,21 @@ static IrOp getInitializedFallback(IrBuilder& build, IrOp& fallback) return fallback; } +static IrOp loadDoubleOrConstant(IrBuilder& build, IrOp arg) +{ + if (arg.kind == IrOpKind::VmConst) + { + LUAU_ASSERT(build.function.proto); + TValue protok = build.function.proto->k[vmConstOp(arg)]; + + LUAU_ASSERT(protok.tt == LUA_TNUMBER); + + return build.constDouble(protok.value.n); + } + + return build.inst(IrCmd::LOAD_DOUBLE, arg); +} + void translateInstLoadNil(IrBuilder& build, const Instruction* pc) { int ra = LUAU_INSN_A(*pc); @@ -335,9 +351,97 @@ void translateInstJumpxEqS(IrBuilder& build, const Instruction* pc, int pcpos) static void translateInstBinaryNumeric(IrBuilder& build, int ra, int rb, int rc, IrOp opb, IrOp opc, int pcpos, TMS tm) { - IrOp fallback; BytecodeTypes bcTypes = build.function.getBytecodeTypesAt(pcpos); + if (FFlag::LuauCodegenVector) + { + // Special fast-paths for vectors, matching the cases we have in VM + if (bcTypes.a == LBC_TYPE_VECTOR && bcTypes.b == LBC_TYPE_VECTOR && (tm == TM_ADD || tm == TM_SUB || tm == TM_MUL || tm == TM_DIV)) + { + build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(rb)), build.constTag(LUA_TVECTOR), build.vmExit(pcpos)); + build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(rc)), build.constTag(LUA_TVECTOR), build.vmExit(pcpos)); + + IrOp vb = build.inst(IrCmd::LOAD_TVALUE, opb); + IrOp vc = build.inst(IrCmd::LOAD_TVALUE, opc); + IrOp result; + + switch (tm) + { + case TM_ADD: + result = build.inst(IrCmd::ADD_VEC, vb, vc); + break; + case TM_SUB: + result = build.inst(IrCmd::SUB_VEC, vb, vc); + break; + case TM_MUL: + result = build.inst(IrCmd::MUL_VEC, vb, vc); + break; + case TM_DIV: + result = build.inst(IrCmd::DIV_VEC, vb, vc); + break; + default: + break; + } + + build.inst(IrCmd::STORE_TVALUE, build.vmReg(ra), result); + return; + } + else if (bcTypes.a == LBC_TYPE_NUMBER && bcTypes.b == LBC_TYPE_VECTOR && (tm == TM_MUL || tm == TM_DIV)) + { + if (rb != -1) + build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(rb)), build.constTag(LUA_TNUMBER), build.vmExit(pcpos)); + + build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(rc)), build.constTag(LUA_TVECTOR), build.vmExit(pcpos)); + + IrOp vb = build.inst(IrCmd::NUM_TO_VECTOR, loadDoubleOrConstant(build, opb)); + IrOp vc = build.inst(IrCmd::LOAD_TVALUE, opc); + IrOp result; + + switch (tm) + { + case TM_MUL: + result = build.inst(IrCmd::MUL_VEC, vb, vc); + break; + case TM_DIV: + result = build.inst(IrCmd::DIV_VEC, vb, vc); + break; + default: + break; + } + + build.inst(IrCmd::STORE_TVALUE, build.vmReg(ra), result); + return; + } + else if (bcTypes.a == LBC_TYPE_VECTOR && bcTypes.b == LBC_TYPE_NUMBER && (tm == TM_MUL || tm == TM_DIV)) + { + build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(rb)), build.constTag(LUA_TVECTOR), build.vmExit(pcpos)); + + if (rc != -1) + build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(rc)), build.constTag(LUA_TNUMBER), build.vmExit(pcpos)); + + IrOp vb = build.inst(IrCmd::LOAD_TVALUE, opb); + IrOp vc = build.inst(IrCmd::NUM_TO_VECTOR, loadDoubleOrConstant(build, opc)); + IrOp result; + + switch (tm) + { + case TM_MUL: + result = build.inst(IrCmd::MUL_VEC, vb, vc); + break; + case TM_DIV: + result = build.inst(IrCmd::DIV_VEC, vb, vc); + break; + default: + break; + } + + build.inst(IrCmd::STORE_TVALUE, build.vmReg(ra), result); + return; + } + } + + IrOp fallback; + // fast-path: number if (rb != -1) { @@ -356,18 +460,25 @@ static void translateInstBinaryNumeric(IrBuilder& build, int ra, int rb, int rc, IrOp vb, vc; IrOp result; - if (opb.kind == IrOpKind::VmConst) + if (FFlag::LuauCodegenVector) { - LUAU_ASSERT(build.function.proto); - TValue protok = build.function.proto->k[vmConstOp(opb)]; - - LUAU_ASSERT(protok.tt == LUA_TNUMBER); - - vb = build.constDouble(protok.value.n); + vb = loadDoubleOrConstant(build, opb); } else { - vb = build.inst(IrCmd::LOAD_DOUBLE, opb); + if (opb.kind == IrOpKind::VmConst) + { + LUAU_ASSERT(build.function.proto); + TValue protok = build.function.proto->k[vmConstOp(opb)]; + + LUAU_ASSERT(protok.tt == LUA_TNUMBER); + + vb = build.constDouble(protok.value.n); + } + else + { + vb = build.inst(IrCmd::LOAD_DOUBLE, opb); + } } if (opc.kind == IrOpKind::VmConst) @@ -474,12 +585,23 @@ void translateInstNot(IrBuilder& build, const Instruction* pc) void translateInstMinus(IrBuilder& build, const Instruction* pc, int pcpos) { - IrOp fallback; BytecodeTypes bcTypes = build.function.getBytecodeTypesAt(pcpos); int ra = LUAU_INSN_A(*pc); int rb = LUAU_INSN_B(*pc); + if (FFlag::LuauCodegenVector && bcTypes.a == LBC_TYPE_VECTOR) + { + build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(rb)), build.constTag(LUA_TVECTOR), build.vmExit(pcpos)); + + IrOp vb = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(rb)); + IrOp va = build.inst(IrCmd::UNM_VEC, vb); + build.inst(IrCmd::STORE_TVALUE, build.vmReg(ra), va); + return; + } + + IrOp fallback; + IrOp tb = build.inst(IrCmd::LOAD_TAG, build.vmReg(rb)); build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TNUMBER), bcTypes.a == LBC_TYPE_NUMBER ? build.vmExit(pcpos) : getInitializedFallback(build, fallback)); @@ -1087,10 +1209,45 @@ void translateInstGetTableKS(IrBuilder& build, const Instruction* pc, int pcpos) int rb = LUAU_INSN_B(*pc); uint32_t aux = pc[1]; - IrOp fallback = build.block(IrBlockKind::Fallback); BytecodeTypes bcTypes = build.function.getBytecodeTypesAt(pcpos); IrOp tb = build.inst(IrCmd::LOAD_TAG, build.vmReg(rb)); + + if (FFlag::LuauCodegenVector && bcTypes.a == LBC_TYPE_VECTOR) + { + build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TVECTOR), build.vmExit(pcpos)); + + TString* str = gco2ts(build.function.proto->k[aux].value.gc); + const char* field = getstr(str); + + if (*field == 'X' || *field == 'x') + { + IrOp value = build.inst(IrCmd::LOAD_FLOAT, build.vmReg(rb), build.constInt(0)); + build.inst(IrCmd::STORE_DOUBLE, build.vmReg(ra), value); + build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TNUMBER)); + } + else if (*field == 'Y' || *field == 'y') + { + IrOp value = build.inst(IrCmd::LOAD_FLOAT, build.vmReg(rb), build.constInt(4)); + build.inst(IrCmd::STORE_DOUBLE, build.vmReg(ra), value); + build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TNUMBER)); + } + else if (*field == 'Z' || *field == 'z') + { + IrOp value = build.inst(IrCmd::LOAD_FLOAT, build.vmReg(rb), build.constInt(8)); + build.inst(IrCmd::STORE_DOUBLE, build.vmReg(ra), value); + build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TNUMBER)); + } + else + { + build.inst(IrCmd::FALLBACK_GETTABLEKS, build.constUint(pcpos), build.vmReg(ra), build.vmReg(rb), build.vmConst(aux)); + } + + return; + } + + IrOp fallback = build.block(IrBlockKind::Fallback); + build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TTABLE), bcTypes.a == LBC_TYPE_TABLE ? build.vmExit(pcpos) : fallback); IrOp vb = build.inst(IrCmd::LOAD_POINTER, build.vmReg(rb)); diff --git a/CodeGen/src/IrUtils.cpp b/CodeGen/src/IrUtils.cpp index 46d638a9..c5abc37b 100644 --- a/CodeGen/src/IrUtils.cpp +++ b/CodeGen/src/IrUtils.cpp @@ -31,6 +31,8 @@ IrValueKind getCmdValueKind(IrCmd cmd) return IrValueKind::Double; case IrCmd::LOAD_INT: return IrValueKind::Int; + case IrCmd::LOAD_FLOAT: + return IrValueKind::Double; case IrCmd::LOAD_TVALUE: return IrValueKind::Tvalue; case IrCmd::LOAD_ENV: @@ -66,6 +68,12 @@ IrValueKind getCmdValueKind(IrCmd cmd) case IrCmd::SQRT_NUM: case IrCmd::ABS_NUM: return IrValueKind::Double; + case IrCmd::ADD_VEC: + case IrCmd::SUB_VEC: + case IrCmd::MUL_VEC: + case IrCmd::DIV_VEC: + case IrCmd::UNM_VEC: + return IrValueKind::Tvalue; case IrCmd::NOT_ANY: case IrCmd::CMP_ANY: return IrValueKind::Int; @@ -98,6 +106,8 @@ IrValueKind getCmdValueKind(IrCmd cmd) case IrCmd::NUM_TO_INT: case IrCmd::NUM_TO_UINT: return IrValueKind::Int; + case IrCmd::NUM_TO_VECTOR: + return IrValueKind::Tvalue; case IrCmd::ADJUST_STACK_TO_REG: case IrCmd::ADJUST_STACK_TO_TOP: return IrValueKind::None; diff --git a/CodeGen/src/IrValueLocationTracking.cpp b/CodeGen/src/IrValueLocationTracking.cpp index a0d92606..8a2117d4 100644 --- a/CodeGen/src/IrValueLocationTracking.cpp +++ b/CodeGen/src/IrValueLocationTracking.cpp @@ -94,6 +94,7 @@ void IrValueLocationTracking::beforeInstLowering(IrInst& inst) case IrCmd::LOAD_POINTER: case IrCmd::LOAD_DOUBLE: case IrCmd::LOAD_INT: + case IrCmd::LOAD_FLOAT: case IrCmd::LOAD_TVALUE: case IrCmd::CMP_ANY: case IrCmd::JUMP_IF_TRUTHY: @@ -130,6 +131,11 @@ void IrValueLocationTracking::beforeInstLowering(IrInst& inst) case IrCmd::MAX_NUM: case IrCmd::JUMP_EQ_TAG: case IrCmd::JUMP_CMP_NUM: + case IrCmd::FLOOR_NUM: + case IrCmd::CEIL_NUM: + case IrCmd::ROUND_NUM: + case IrCmd::SQRT_NUM: + case IrCmd::ABS_NUM: break; default: diff --git a/CodeGen/src/OptimizeConstProp.cpp b/CodeGen/src/OptimizeConstProp.cpp index 3c447ea7..539906c5 100644 --- a/CodeGen/src/OptimizeConstProp.cpp +++ b/CodeGen/src/OptimizeConstProp.cpp @@ -18,6 +18,7 @@ LUAU_FASTINTVARIABLE(LuauCodeGenMinLinearBlockPath, 3) LUAU_FASTINTVARIABLE(LuauCodeGenReuseSlotLimit, 64) LUAU_FASTFLAGVARIABLE(DebugLuauAbortingChecks, false) LUAU_FASTFLAGVARIABLE(LuauReuseBufferChecks, false) +LUAU_FASTFLAG(LuauCodegenVector) namespace Luau { @@ -582,6 +583,8 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& state.substituteOrRecordVmRegLoad(inst); break; } + case IrCmd::LOAD_FLOAT: + break; case IrCmd::LOAD_TVALUE: if (inst.a.kind == IrOpKind::VmReg) state.substituteOrRecordVmRegLoad(inst); @@ -706,6 +709,18 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& } uint8_t tag = state.tryGetTag(inst.b); + + // We know the tag of some instructions that result in TValue + if (FFlag::LuauCodegenVector && tag == 0xff) + { + if (IrInst* arg = function.asInstOp(inst.b)) + { + 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; + } + } + IrOp value = state.tryGetValue(inst.b); if (inst.a.kind == IrOpKind::VmReg) @@ -1244,7 +1259,6 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& case IrCmd::BITXOR_UINT: case IrCmd::BITOR_UINT: case IrCmd::BITNOT_UINT: - break; case IrCmd::BITLSHIFT_UINT: case IrCmd::BITRSHIFT_UINT: case IrCmd::BITARSHIFT_UINT: @@ -1257,6 +1271,12 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& case IrCmd::GET_TYPE: case IrCmd::GET_TYPEOF: case IrCmd::FINDUPVAL: + case IrCmd::ADD_VEC: + case IrCmd::SUB_VEC: + case IrCmd::MUL_VEC: + case IrCmd::DIV_VEC: + case IrCmd::UNM_VEC: + case IrCmd::NUM_TO_VECTOR: break; case IrCmd::DO_ARITH: diff --git a/CodeGen/src/OptimizeFinalX64.cpp b/CodeGen/src/OptimizeFinalX64.cpp index 3f0469fa..639b7293 100644 --- a/CodeGen/src/OptimizeFinalX64.cpp +++ b/CodeGen/src/OptimizeFinalX64.cpp @@ -5,6 +5,8 @@ #include <utility> +LUAU_FASTFLAGVARIABLE(LuauCodegenMathMemArgs, false) + namespace Luau { namespace CodeGen @@ -108,6 +110,21 @@ static void optimizeMemoryOperandsX64(IrFunction& function, IrBlock& block) } break; } + case IrCmd::FLOOR_NUM: + case IrCmd::CEIL_NUM: + case IrCmd::ROUND_NUM: + case IrCmd::SQRT_NUM: + case IrCmd::ABS_NUM: + { + if (FFlag::LuauCodegenMathMemArgs && inst.a.kind == IrOpKind::Inst) + { + IrInst& arg = function.instOp(inst.a); + + if (arg.useCount == 1 && arg.cmd == IrCmd::LOAD_DOUBLE && (arg.a.kind == IrOpKind::VmReg || arg.a.kind == IrOpKind::VmConst)) + replace(function, inst.a, arg.a); + } + break; + } default: break; } diff --git a/VM/src/ldebug.cpp b/VM/src/ldebug.cpp index 844fc6dd..78ccd422 100644 --- a/VM/src/ldebug.cpp +++ b/VM/src/ldebug.cpp @@ -333,8 +333,10 @@ void luaG_pusherror(lua_State* L, const char* error) void luaG_breakpoint(lua_State* L, Proto* p, int line, bool enable) { + void (*ondisable)(lua_State*, Proto*) = L->global->ecb.disable; + // since native code doesn't support breakpoints, we would need to update all call frames with LUAU_CALLINFO_NATIVE that refer to p - if (p->lineinfo && !p->execdata) + if (p->lineinfo && (ondisable || !p->execdata)) { for (int i = 0; i < p->sizecode; ++i) { @@ -360,6 +362,11 @@ void luaG_breakpoint(lua_State* L, Proto* p, int line, bool enable) p->code[i] |= op; LUAU_ASSERT(LUAU_INSN_OP(p->code[i]) == op); + // currently we don't restore native code when breakpoint is disabled. + // this will be addressed in the future. + if (enable && p->execdata && ondisable) + ondisable(L, p); + // note: this is important! // we only patch the *first* instruction in each proto that's attributed to a given line // this can be changed, but if requires making patching a bit more nuanced so that we don't patch AUX words diff --git a/VM/src/lstate.h b/VM/src/lstate.h index 2c6d35dc..a33c882f 100644 --- a/VM/src/lstate.h +++ b/VM/src/lstate.h @@ -154,6 +154,7 @@ struct lua_ExecutionCallbacks void (*close)(lua_State* L); // called when global VM state is closed void (*destroy)(lua_State* L, Proto* proto); // called when function is destroyed int (*enter)(lua_State* L, Proto* proto); // called when function is about to start/resume (when execdata is present), return 0 to exit VM + void (*disable)(lua_State* L, Proto* proto); // called when function has to be switched from native to bytecode in the debugger }; /* diff --git a/VM/src/lstrlib.cpp b/VM/src/lstrlib.cpp index 6fa0f53c..03d7cf39 100644 --- a/VM/src/lstrlib.cpp +++ b/VM/src/lstrlib.cpp @@ -8,6 +8,8 @@ #include <string.h> #include <stdio.h> +LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauInterruptablePatternMatch, false) + // macro to `unsign' a character #define uchar(c) ((unsigned char)(c)) @@ -429,6 +431,21 @@ static const char* match(MatchState* ms, const char* s, const char* p) { if (ms->matchdepth-- == 0) luaL_error(ms->L, "pattern too complex"); + + if (DFFlag::LuauInterruptablePatternMatch) + { + lua_State* L = ms->L; + void (*interrupt)(lua_State*, int) = L->global->cb.interrupt; + + if (LUAU_UNLIKELY(!!interrupt)) + { + // this interrupt is not yieldable + L->nCcalls++; + interrupt(L, -1); + L->nCcalls--; + } + } + init: // using goto's to optimize tail recursion if (p != ms->p_end) { // end of pattern? diff --git a/VM/src/lvmexecute.cpp b/VM/src/lvmexecute.cpp index b7c21a39..2ed4819e 100644 --- a/VM/src/lvmexecute.cpp +++ b/VM/src/lvmexecute.cpp @@ -3033,7 +3033,7 @@ int luau_precall(lua_State* L, StkId func, int nresults) ci->savedpc = p->code; #if VM_HAS_NATIVE - if (p->execdata) + if (p->exectarget != 0 && p->execdata) ci->flags = LUA_CALLINFO_NATIVE; #endif diff --git a/tests/AssemblyBuilderA64.test.cpp b/tests/AssemblyBuilderA64.test.cpp index b9df78a3..6657d889 100644 --- a/tests/AssemblyBuilderA64.test.cpp +++ b/tests/AssemblyBuilderA64.test.cpp @@ -367,11 +367,16 @@ TEST_CASE_FIXTURE(AssemblyBuilderA64Fixture, "FPMath") { SINGLE_COMPARE(fabs(d1, d2), 0x1E60C041); SINGLE_COMPARE(fadd(d1, d2, d3), 0x1E632841); + SINGLE_COMPARE(fadd(s29, s29, s28), 0x1E3C2BBD); SINGLE_COMPARE(fdiv(d1, d2, d3), 0x1E631841); + SINGLE_COMPARE(fdiv(s29, s29, s28), 0x1E3C1BBD); SINGLE_COMPARE(fmul(d1, d2, d3), 0x1E630841); + SINGLE_COMPARE(fmul(s29, s29, s28), 0x1E3C0BBD); SINGLE_COMPARE(fneg(d1, d2), 0x1E614041); + SINGLE_COMPARE(fneg(s30, s30), 0x1E2143DE); SINGLE_COMPARE(fsqrt(d1, d2), 0x1E61C041); SINGLE_COMPARE(fsub(d1, d2, d3), 0x1E633841); + SINGLE_COMPARE(fsub(s29, s29, s28), 0x1E3C3BBD); SINGLE_COMPARE(frinta(d1, d2), 0x1E664041); SINGLE_COMPARE(frintm(d1, d2), 0x1E654041); @@ -426,6 +431,14 @@ TEST_CASE_FIXTURE(AssemblyBuilderA64Fixture, "FPLoadStore") SINGLE_COMPARE(str(s0, mem(x1, 16)), 0xBD001020); } +TEST_CASE_FIXTURE(AssemblyBuilderA64Fixture, "FPInsertExtract") +{ + SINGLE_COMPARE(ins_4s(q29, w17, 3), 0x4E1C1E3D); + SINGLE_COMPARE(ins_4s(q31, 0, q29, 0), 0x6E0407BF); + SINGLE_COMPARE(dup_4s(s29, q31, 2), 0x5E1407FD); + SINGLE_COMPARE(dup_4s(q29, q30, 0), 0x4E0407DD); +} + TEST_CASE_FIXTURE(AssemblyBuilderA64Fixture, "FPCompare") { SINGLE_COMPARE(fcmp(d0, d1), 0x1E612000); @@ -535,6 +548,11 @@ TEST_CASE("LogTest") build.add(x1, x2, w3, 3); + build.ins_4s(q29, w17, 3); + build.ins_4s(q31, 1, q29, 2); + build.dup_4s(s29, q31, 2); + build.dup_4s(q29, q30, 0); + build.setLabel(l); build.ret(); @@ -572,6 +590,10 @@ TEST_CASE("LogTest") ldr x0,[x1,#1]! ldr x0,[x1]!,#1 add x1,x2,w3 UXTW #3 + ins v29.s[3],w17 + ins v31.s[1],v29.s[2] + dup s29,v31.s[2] + dup v29.4s,v30.s[0] .L1: ret )"; diff --git a/tests/AssemblyBuilderX64.test.cpp b/tests/AssemblyBuilderX64.test.cpp index c55de91a..758c522e 100644 --- a/tests/AssemblyBuilderX64.test.cpp +++ b/tests/AssemblyBuilderX64.test.cpp @@ -3,9 +3,12 @@ #include "Luau/StringUtils.h" #include "doctest.h" +#include "ScopedFlags.h" #include <string.h> +LUAU_FASTFLAG(LuauCache32BitAsmConsts) + using namespace Luau::CodeGen; using namespace Luau::CodeGen::X64; @@ -469,8 +472,13 @@ TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "AVXBinaryInstructionForms") SINGLE_COMPARE(vmulsd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0x2b, 0x59, 0xc6); SINGLE_COMPARE(vdivsd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0x2b, 0x5e, 0xc6); + SINGLE_COMPARE(vsubps(xmm8, xmm10, xmm14), 0xc4, 0x41, 0x28, 0x5c, 0xc6); + SINGLE_COMPARE(vmulps(xmm8, xmm10, xmm14), 0xc4, 0x41, 0x28, 0x59, 0xc6); + SINGLE_COMPARE(vdivps(xmm8, xmm10, xmm14), 0xc4, 0x41, 0x28, 0x5e, 0xc6); + SINGLE_COMPARE(vorpd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0x29, 0x56, 0xc6); SINGLE_COMPARE(vxorpd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0x29, 0x57, 0xc6); + SINGLE_COMPARE(vorps(xmm8, xmm10, xmm14), 0xc4, 0x41, 0x28, 0x56, 0xc6); SINGLE_COMPARE(vandpd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0x29, 0x54, 0xc6); SINGLE_COMPARE(vandnpd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0x29, 0x55, 0xc6); @@ -547,6 +555,9 @@ TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "AVXTernaryInstructionForms") vroundsd(xmm8, xmm13, xmmword[r13 + rdx], RoundingModeX64::RoundToPositiveInfinity), 0xc4, 0x43, 0x11, 0x0b, 0x44, 0x15, 0x00, 0x0a); SINGLE_COMPARE(vroundsd(xmm9, xmm14, xmmword[rcx + r10], RoundingModeX64::RoundToZero), 0xc4, 0x23, 0x09, 0x0b, 0x0c, 0x11, 0x0b); SINGLE_COMPARE(vblendvpd(xmm7, xmm12, xmmword[rcx + r10], xmm5), 0xc4, 0xa3, 0x19, 0x4b, 0x3c, 0x11, 0x50); + + SINGLE_COMPARE(vpshufps(xmm7, xmm12, xmmword[rcx + r10], 0b11010100), 0xc4, 0xa1, 0x18, 0xc6, 0x3c, 0x11, 0xd4); + SINGLE_COMPARE(vpinsrd(xmm7, xmm12, xmmword[rcx + r10], 2), 0xc4, 0xa3, 0x19, 0x22, 0x3c, 0x11, 0x02); } TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "MiscInstructions") @@ -720,7 +731,7 @@ TEST_CASE("ConstantStorage") AssemblyBuilderX64 build(/* logText= */ false); for (int i = 0; i <= 3000; i++) - build.vaddss(xmm0, xmm0, build.f32(1.0f)); + build.vaddss(xmm0, xmm0, build.i32(i)); build.finalize(); @@ -728,13 +739,31 @@ TEST_CASE("ConstantStorage") for (int i = 0; i <= 3000; i++) { - CHECK(build.data[i * 4 + 0] == 0x00); - CHECK(build.data[i * 4 + 1] == 0x00); - CHECK(build.data[i * 4 + 2] == 0x80); - CHECK(build.data[i * 4 + 3] == 0x3f); + CHECK(build.data[i * 4 + 0] == ((3000 - i) & 0xff)); + CHECK(build.data[i * 4 + 1] == ((3000 - i) >> 8)); + CHECK(build.data[i * 4 + 2] == 0x00); + CHECK(build.data[i * 4 + 3] == 0x00); } } +TEST_CASE("ConstantStorageDedup") +{ + ScopedFastFlag luauCache32BitAsmConsts{FFlag::LuauCache32BitAsmConsts, true}; + AssemblyBuilderX64 build(/* logText= */ false); + + for (int i = 0; i <= 3000; i++) + build.vaddss(xmm0, xmm0, build.f32(1.0f)); + + build.finalize(); + + CHECK(build.data.size() == 4); + + CHECK(build.data[0] == 0x00); + CHECK(build.data[1] == 0x00); + CHECK(build.data[2] == 0x80); + CHECK(build.data[3] == 0x3f); +} + TEST_CASE("ConstantCaching") { AssemblyBuilderX64 build(/* logText= */ false); diff --git a/tests/AstJsonEncoder.test.cpp b/tests/AstJsonEncoder.test.cpp index ff63dd29..5d6b53a3 100644 --- a/tests/AstJsonEncoder.test.cpp +++ b/tests/AstJsonEncoder.test.cpp @@ -11,15 +11,11 @@ using namespace Luau; -LUAU_FASTFLAG(LuauClipExtraHasEndProps); - struct JsonEncoderFixture { Allocator allocator; AstNameTable names{allocator}; - ScopedFastFlag sff{FFlag::LuauClipExtraHasEndProps, true}; - ParseResult parse(std::string_view src) { ParseOptions opts; @@ -95,8 +91,6 @@ TEST_CASE("basic_escaping") TEST_CASE("encode_AstStatBlock") { - ScopedFastFlag sff{FFlag::LuauClipExtraHasEndProps, true}; - AstLocal astlocal{AstName{"a_local"}, Location(), nullptr, 0, 0, nullptr}; AstLocal* astlocalarray[] = {&astlocal}; diff --git a/tests/Autocomplete.test.cpp b/tests/Autocomplete.test.cpp index 2cfd20d3..8bed89cd 100644 --- a/tests/Autocomplete.test.cpp +++ b/tests/Autocomplete.test.cpp @@ -107,6 +107,15 @@ struct ACFixtureImpl : BaseType globals, globals.globalScope, source, "@test", /* captureComments */ false, /* typeCheckForAutocomplete */ true); freeze(globals.globalTypes); + if (FFlag::DebugLuauDeferredConstraintResolution) + { + GlobalTypes& globals = this->frontend.globals; + unfreeze(globals.globalTypes); + LoadDefinitionFileResult result = this->frontend.loadDefinitionFile( + globals, globals.globalScope, source, "@test", /* captureComments */ false, /* typeCheckForAutocomplete */ true); + freeze(globals.globalTypes); + } + REQUIRE_MESSAGE(result.success, "loadDefinition: unable to load definition file"); return result; } diff --git a/tests/Conformance.test.cpp b/tests/Conformance.test.cpp index 803eac6c..2d6a5ac7 100644 --- a/tests/Conformance.test.cpp +++ b/tests/Conformance.test.cpp @@ -26,9 +26,10 @@ extern bool verbose; extern bool codegen; extern int optimizationLevel; -LUAU_FASTFLAG(LuauTaggedLuData); -LUAU_FASTFLAG(LuauSciNumberSkipTrailDot); -LUAU_FASTINT(CodegenHeuristicsInstructionLimit); +LUAU_FASTFLAG(LuauTaggedLuData) +LUAU_FASTFLAG(LuauSciNumberSkipTrailDot) +LUAU_DYNAMIC_FASTFLAG(LuauInterruptablePatternMatch) +LUAU_FASTINT(CodegenHeuristicsInstructionLimit) static lua_CompileOptions defaultOptions() { @@ -1607,6 +1608,47 @@ TEST_CASE("Interrupt") // abandon the thread lua_pop(L, 1); } + + lua_callbacks(L)->interrupt = [](lua_State* L, int gc) { + if (gc >= 0) + return; + + index++; + + if (index == 1'000) + { + index = 0; + luaL_error(L, "timeout"); + } + }; + + ScopedFastFlag luauInterruptablePatternMatch{DFFlag::LuauInterruptablePatternMatch, true}; + + for (int test = 1; test <= 5; ++test) + { + lua_State* T = lua_newthread(L); + + std::string name = "strhang" + std::to_string(test); + lua_getglobal(T, name.c_str()); + + index = 0; + int status = lua_resume(T, nullptr, 0); + CHECK(status == LUA_ERRRUN); + + lua_pop(L, 1); + } + + { + lua_State* T = lua_newthread(L); + + lua_getglobal(T, "strhangpcall"); + + index = 0; + int status = lua_resume(T, nullptr, 0); + CHECK(status == LUA_OK); + + lua_pop(L, 1); + } } TEST_CASE("UserdataApi") diff --git a/tests/IrBuilder.test.cpp b/tests/IrBuilder.test.cpp index c21b3471..beeabee0 100644 --- a/tests/IrBuilder.test.cpp +++ b/tests/IrBuilder.test.cpp @@ -137,7 +137,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "FinalX64OptCheckTag") optimizeMemoryOperandsX64(build.function); // Load from memory is 'inlined' into CHECK_TAG - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: CHECK_TAG R2, tnil, bb_fallback_1 CHECK_TAG K5, tnil, bb_fallback_1 @@ -163,7 +163,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "FinalX64OptBinaryArith") optimizeMemoryOperandsX64(build.function); // Load from memory is 'inlined' into second argument - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: %0 = LOAD_DOUBLE R1 %2 = ADD_NUM %0, R2 @@ -193,7 +193,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "FinalX64OptEqTag1") optimizeMemoryOperandsX64(build.function); // Load from memory is 'inlined' into first argument - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: %1 = LOAD_TAG R2 JUMP_EQ_TAG R1, %1, bb_1, bb_2 @@ -230,7 +230,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "FinalX64OptEqTag2") // Load from memory is 'inlined' into second argument is it can't be done for the first one // We also swap first and second argument to generate memory access on the LHS - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: %0 = LOAD_TAG R1 STORE_TAG R6, %0 @@ -267,7 +267,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "FinalX64OptEqTag3") optimizeMemoryOperandsX64(build.function); // Load from memory is 'inlined' into first argument - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: %0 = LOAD_POINTER R1 %1 = GET_ARR_ADDR %0, 0i @@ -304,7 +304,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "FinalX64OptJumpCmpNum") optimizeMemoryOperandsX64(build.function); // Load from memory is 'inlined' into first argument - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: %1 = LOAD_DOUBLE R2 JUMP_CMP_NUM R1, %1, bb_1, bb_2 @@ -359,7 +359,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "Numeric") updateUseCounts(build.function); constantFold(); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: STORE_INT R0, 30i STORE_INT R1, -2147483648i @@ -403,7 +403,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "NumericConversions") updateUseCounts(build.function); constantFold(); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: STORE_DOUBLE R0, 8 STORE_DOUBLE R1, 3740139520 @@ -431,7 +431,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "NumericConversionsBlocked") updateUseCounts(build.function); constantFold(); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: %1 = NUM_TO_INT 1e+20 STORE_INT R0, %1 @@ -489,7 +489,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "Bit32") updateUseCounts(build.function); constantFold(); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: %0 = LOAD_INT R0 STORE_INT R0, 14i @@ -547,7 +547,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "Bit32RangeReduction") updateUseCounts(build.function); constantFold(); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: STORE_INT R10, 62914560i STORE_INT R10, 61440i @@ -574,7 +574,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "ReplacementPreservesUses") updateUseCounts(build.function); constantFold(); - CHECK("\n" + toString(build.function, /* includeUseInfo */ true) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::Yes) == R"( bb_0: ; useCount: 0 %0 = LOAD_INT R0 ; useCount: 1, lastUse: %0 %1 = BITNOT_UINT %0 ; useCount: 1, lastUse: %0 @@ -601,7 +601,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "NumericNan") updateUseCounts(build.function); constantFold(); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: STORE_DOUBLE R0, 2 STORE_DOUBLE R0, nan @@ -633,7 +633,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "ControlFlowEq") updateUseCounts(build.function); constantFold(); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: JUMP bb_1 @@ -682,7 +682,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "NumToIndex") updateUseCounts(build.function); constantFold(); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: STORE_INT R0, 4i RETURN 0u @@ -717,7 +717,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "Guards") updateUseCounts(build.function); constantFold(); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: RETURN 0u @@ -838,7 +838,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "RememberTagsAndValues") updateUseCounts(build.function); constPropInBlockChains(build, true); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: STORE_TAG R0, tnumber STORE_INT R1, 10i @@ -882,7 +882,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "PropagateThroughTvalue") updateUseCounts(build.function); constPropInBlockChains(build, true); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: STORE_TAG R0, tnumber STORE_DOUBLE R0, 0.5 @@ -911,7 +911,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "SkipCheckTag") updateUseCounts(build.function); constPropInBlockChains(build, true); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: STORE_TAG R0, tnumber RETURN 0u @@ -938,7 +938,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "SkipOncePerBlockChecks") updateUseCounts(build.function); constPropInBlockChains(build, true); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: CHECK_SAFE_ENV CHECK_GC @@ -977,7 +977,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "RememberTableState") updateUseCounts(build.function); constPropInBlockChains(build, true); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: %0 = LOAD_POINTER R0 CHECK_NO_METATABLE %0, bb_fallback_1 @@ -1023,7 +1023,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "RememberNewTableState") updateUseCounts(build.function); constPropInBlockChains(build, true); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: %0 = NEW_TABLE 16u, 32u STORE_POINTER R0, %0 @@ -1055,7 +1055,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "SkipUselessBarriers") updateUseCounts(build.function); constPropInBlockChains(build, true); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: STORE_TAG R0, tnumber RETURN 0u @@ -1086,7 +1086,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "ConcatInvalidation") updateUseCounts(build.function); constPropInBlockChains(build, true); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: STORE_TAG R0, tnumber STORE_INT R1, 10i @@ -1135,7 +1135,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "BuiltinFastcallsMayInvalidateMemory") updateUseCounts(build.function); constPropInBlockChains(build, true); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: STORE_DOUBLE R0, 0.5 %1 = LOAD_POINTER R0 @@ -1168,7 +1168,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "RedundantStoreCheckConstantType") updateUseCounts(build.function); constPropInBlockChains(build, true); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: STORE_INT R0, 10i STORE_DOUBLE R0, 0.5 @@ -1198,7 +1198,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "TagCheckPropagation") updateUseCounts(build.function); constPropInBlockChains(build, true); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: %0 = LOAD_TAG R0 CHECK_TAG %0, tnumber, bb_fallback_1 @@ -1230,7 +1230,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "TagCheckPropagationConflicting") updateUseCounts(build.function); constPropInBlockChains(build, true); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: %0 = LOAD_TAG R0 CHECK_TAG %0, tnumber, bb_fallback_1 @@ -1266,7 +1266,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "TruthyTestRemoval") updateUseCounts(build.function); constPropInBlockChains(build, true); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: %0 = LOAD_TAG R1 CHECK_TAG %0, tnumber, bb_fallback_3 @@ -1305,7 +1305,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "FalsyTestRemoval") updateUseCounts(build.function); constPropInBlockChains(build, true); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: %0 = LOAD_TAG R1 CHECK_TAG %0, tnumber, bb_fallback_3 @@ -1340,7 +1340,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "TagEqRemoval") updateUseCounts(build.function); constPropInBlockChains(build, true); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: %0 = LOAD_TAG R1 CHECK_TAG %0, tboolean @@ -1372,7 +1372,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "IntEqRemoval") updateUseCounts(build.function); constPropInBlockChains(build, true); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: STORE_INT R1, 5i JUMP bb_1 @@ -1403,7 +1403,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "NumCmpRemoval") updateUseCounts(build.function); constPropInBlockChains(build, true); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: STORE_DOUBLE R1, 4 JUMP bb_2 @@ -1431,7 +1431,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "DataFlowsThroughDirectJumpToUniqueSuccessor updateUseCounts(build.function); constPropInBlockChains(build, true); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: STORE_TAG R0, tnumber JUMP bb_1 @@ -1464,7 +1464,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "DataDoesNotFlowThroughDirectJumpToNonUnique updateUseCounts(build.function); constPropInBlockChains(build, true); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: STORE_TAG R0, tnumber JUMP bb_1 @@ -1500,7 +1500,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "EntryBlockUseRemoval") updateUseCounts(build.function); constPropInBlockChains(build, true); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: STORE_TAG R0, tnumber JUMP bb_1 @@ -1535,7 +1535,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "RecursiveSccUseRemoval1") updateUseCounts(build.function); constPropInBlockChains(build, true); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: RETURN R0, 0i @@ -1577,7 +1577,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "RecursiveSccUseRemoval2") updateUseCounts(build.function); constPropInBlockChains(build, true); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: JUMP bb_1 @@ -1613,7 +1613,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "IntNumIntPeepholes") updateUseCounts(build.function); constPropInBlockChains(build, true); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: %0 = LOAD_INT R0 %1 = LOAD_INT R1 @@ -1649,7 +1649,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "InvalidateReglinkVersion") updateUseCounts(build.function); constPropInBlockChains(build, true); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: STORE_TAG R2, tstring %1 = LOAD_TVALUE R2 @@ -1710,7 +1710,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "SimplePathExtraction") constPropInBlockChains(build, true); createLinearBlocks(build, true); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: %0 = LOAD_TAG R2 CHECK_TAG %0, tnumber, bb_fallback_1 @@ -1786,7 +1786,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "NoPathExtractionForBlocksWithLiveOutValues" constPropInBlockChains(build, true); createLinearBlocks(build, true); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: %0 = LOAD_TAG R2 CHECK_TAG %0, tnumber, bb_fallback_1 @@ -1838,7 +1838,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "InfiniteLoopInPathAnalysis") constPropInBlockChains(build, true); createLinearBlocks(build, true); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: STORE_TAG R0, tnumber JUMP bb_1 @@ -1867,7 +1867,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "PartialStoreInvalidation") updateUseCounts(build.function); constPropInBlockChains(build, true); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: %0 = LOAD_TVALUE R0 STORE_TVALUE R1, %0 @@ -1895,7 +1895,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "VaridicRegisterRangeInvalidation") updateUseCounts(build.function); constPropInBlockChains(build, true); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: STORE_TAG R2, tnumber FALLBACK_GETVARARGS 0u, R1, -1i @@ -1921,7 +1921,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "LoadPropagatesOnlyRightType") updateUseCounts(build.function); constPropInBlockChains(build, true); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: STORE_INT R0, 2i %1 = LOAD_DOUBLE R0 @@ -1967,7 +1967,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateHashSlotChecks") // In the future, we might even see duplicate identical TValue loads go away // In the future, we might even see loads of different VM regs with the same value go away - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: %0 = LOAD_POINTER R1 %1 = GET_SLOT_NODE_ADDR %0, 3u, K1 @@ -2031,7 +2031,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateHashSlotChecksAvoidNil") updateUseCounts(build.function); constPropInBlockChains(build, true); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: %0 = LOAD_POINTER R1 %1 = GET_SLOT_NODE_ADDR %0, 3u, K1 @@ -2092,7 +2092,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateArrayElemChecksSameIndex") // In the future, we might even see duplicate identical TValue loads go away // In the future, we might even see loads of different VM regs with the same value go away - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: %0 = LOAD_POINTER R1 CHECK_ARRAY_SIZE %0, 0i, bb_fallback_1 @@ -2152,7 +2152,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateArrayElemChecksSameValue") // In the future, we might even see duplicate identical TValue loads go away // In the future, we might even see loads of different VM regs with the same value go away - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: %0 = LOAD_POINTER R1 %1 = LOAD_DOUBLE R2 @@ -2208,7 +2208,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateArrayElemChecksLowerIndex") updateUseCounts(build.function); constPropInBlockChains(build, true); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: %0 = LOAD_POINTER R1 CHECK_ARRAY_SIZE %0, 1i, bb_fallback_1 @@ -2264,7 +2264,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateArrayElemChecksInvalidations") updateUseCounts(build.function); constPropInBlockChains(build, true); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: %0 = LOAD_POINTER R1 CHECK_ARRAY_SIZE %0, 0i, bb_fallback_1 @@ -2320,7 +2320,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "ArrayElemChecksNegativeIndex") updateUseCounts(build.function); constPropInBlockChains(build, true); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: %0 = LOAD_POINTER R1 CHECK_ARRAY_SIZE %0, 0i, bb_fallback_1 @@ -2382,7 +2382,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateBufferLengthChecks") updateUseCounts(build.function); constPropInBlockChains(build, true); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: %0 = LOAD_TVALUE R0 STORE_TVALUE R2, %0 @@ -2428,7 +2428,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "BufferLenghtChecksNegativeIndex") updateUseCounts(build.function); constPropInBlockChains(build, true); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: %0 = LOAD_TVALUE R0 STORE_TVALUE R2, %0 @@ -2468,7 +2468,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "SimpleDiamond") updateUseCounts(build.function); computeCfgInfo(build.function); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: ; successors: bb_1, bb_2 ; in regs: R0, R1, R2, R3 @@ -2518,7 +2518,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "ImplicitFixedRegistersInVarargCall") updateUseCounts(build.function); computeCfgInfo(build.function); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: ; successors: bb_1 ; in regs: R0, R1, R2 @@ -2553,7 +2553,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "ExplicitUseOfRegisterInVarargSequence") updateUseCounts(build.function); computeCfgInfo(build.function); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: ; successors: bb_1 ; out regs: R0... @@ -2586,7 +2586,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "VariadicSequenceRestart") updateUseCounts(build.function); computeCfgInfo(build.function); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: ; successors: bb_1 ; in regs: R0, R1 @@ -2625,7 +2625,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "FallbackDoesNotFlowUp") updateUseCounts(build.function); computeCfgInfo(build.function); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: ; successors: bb_fallback_1, bb_2 ; in regs: R0 @@ -2677,7 +2677,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "VariadicSequencePeeling") updateUseCounts(build.function); computeCfgInfo(build.function); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: ; successors: bb_1, bb_2 ; in regs: R0, R1 @@ -2730,7 +2730,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "BuiltinVariadicStart") updateUseCounts(build.function); computeCfgInfo(build.function); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: ; successors: bb_1 ; in regs: R0 @@ -2760,7 +2760,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "SetTable") updateUseCounts(build.function); computeCfgInfo(build.function); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: ; in regs: R0, R1 SET_TABLE R0, R1, 1u @@ -2839,7 +2839,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "RemoveDuplicateCalculation") updateUseCounts(build.function); constPropInBlockChains(build, true); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: %0 = LOAD_DOUBLE R0 %1 = UNM_NUM %0 @@ -2875,7 +2875,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "LateTableStateLink") updateUseCounts(build.function); constPropInBlockChains(build, true); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: %0 = DUP_TABLE R0 STORE_POINTER R0, %0 @@ -2906,7 +2906,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "RegisterVersioning") updateUseCounts(build.function); constPropInBlockChains(build, true); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: %0 = LOAD_DOUBLE R0 %1 = UNM_NUM %0 @@ -2935,7 +2935,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "SetListIsABlocker") updateUseCounts(build.function); constPropInBlockChains(build, true); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: %0 = LOAD_DOUBLE R0 SETLIST @@ -2964,7 +2964,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "CallIsABlocker") updateUseCounts(build.function); constPropInBlockChains(build, true); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: %0 = LOAD_DOUBLE R0 CALL R1, 1i, R2, 1i @@ -2993,7 +2993,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "NoPropagationOfCapturedRegs") computeCfgInfo(build.function); constPropInBlockChains(build, true); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( ; captured regs: R0 bb_0: @@ -3025,7 +3025,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "NoDeadLoadReuse") updateUseCounts(build.function); constPropInBlockChains(build, true); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: %4 = LOAD_DOUBLE R0 %5 = ADD_NUM 0, %4 @@ -3052,7 +3052,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "NoDeadValueReuse") updateUseCounts(build.function); constPropInBlockChains(build, true); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: %0 = LOAD_DOUBLE R0 %3 = NUM_TO_INT %0 @@ -3094,7 +3094,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "TValueLoadToSplitStore") updateUseCounts(build.function); constPropInBlockChains(build, true); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: %0 = LOAD_DOUBLE R0 %1 = ADD_NUM %0, 4 @@ -3127,7 +3127,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "TagStoreUpdatesValueVersion") updateUseCounts(build.function); constPropInBlockChains(build, true); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: %0 = LOAD_POINTER R0 STORE_POINTER R1, %0 @@ -3155,7 +3155,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "TagStoreUpdatesSetUpval") updateUseCounts(build.function); constPropInBlockChains(build, true); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: STORE_TAG R0, tnumber STORE_DOUBLE R0, 0.5 @@ -3186,7 +3186,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "TagSelfEqualityCheckRemoval") updateUseCounts(build.function); constPropInBlockChains(build, true); - CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: JUMP bb_1 diff --git a/tests/IrLowering.test.cpp b/tests/IrLowering.test.cpp index c89e9a69..4a7f9e2d 100644 --- a/tests/IrLowering.test.cpp +++ b/tests/IrLowering.test.cpp @@ -14,6 +14,8 @@ LUAU_FASTFLAG(LuauFixDivrkInference) LUAU_FASTFLAG(LuauCompileRevK) +LUAU_FASTFLAG(LuauCodegenVector) +LUAU_FASTFLAG(LuauCodegenMathMemArgs) static std::string getCodegenAssembly(const char* source) { @@ -27,10 +29,10 @@ static std::string getCodegenAssembly(const char* source) options.includeIr = true; options.includeOutlinedCode = false; - options.includeIrPrefix = false; - options.includeUseInfo = false; - options.includeCfgInfo = false; - options.includeRegFlowInfo = false; + options.includeIrPrefix = Luau::CodeGen::IncludeIrPrefix::No; + options.includeUseInfo = Luau::CodeGen::IncludeUseInfo::No; + options.includeCfgInfo = Luau::CodeGen::IncludeCfgInfo::No; + options.includeRegFlowInfo = Luau::CodeGen::IncludeRegFlowInfo::No; Luau::Allocator allocator; Luau::AstNameTable names(allocator); @@ -66,6 +68,7 @@ TEST_CASE("VectorReciprocal") { ScopedFastFlag luauFixDivrkInference{FFlag::LuauFixDivrkInference, true}; ScopedFastFlag luauCompileRevK{FFlag::LuauCompileRevK, true}; + ScopedFastFlag luauCodegenVector{FFlag::LuauCodegenVector, true}; CHECK_EQ("\n" + getCodegenAssembly(R"( local function vecrcp(a: vector) @@ -80,11 +83,238 @@ bb_0: bb_2: JUMP bb_bytecode_1 bb_bytecode_1: - JUMP bb_fallback_3 -bb_4: + %6 = NUM_TO_VECTOR 1 + %7 = LOAD_TVALUE R0 + %8 = DIV_VEC %6, %7 + STORE_TVALUE R1, %8 INTERRUPT 1u RETURN R1, 1i )"); } +TEST_CASE("VectorComponentRead") +{ + ScopedFastFlag luauCodegenVector{FFlag::LuauCodegenVector, true}; + + CHECK_EQ("\n" + getCodegenAssembly(R"( +local function compsum(a: vector) + return a.X + a.Y + a.Z +end +)"), + R"( +; function compsum($arg0) line 2 +bb_0: + CHECK_TAG R0, tvector, exit(entry) + JUMP bb_2 +bb_2: + JUMP bb_bytecode_1 +bb_bytecode_1: + %6 = LOAD_FLOAT R0, 0i + STORE_DOUBLE R3, %6 + STORE_TAG R3, tnumber + %11 = LOAD_FLOAT R0, 4i + STORE_DOUBLE R4, %11 + STORE_TAG R4, tnumber + %20 = ADD_NUM %6, %11 + STORE_DOUBLE R2, %20 + STORE_TAG R2, tnumber + %25 = LOAD_FLOAT R0, 8i + STORE_DOUBLE R3, %25 + %34 = ADD_NUM %20, %25 + STORE_DOUBLE R1, %34 + STORE_TAG R1, tnumber + INTERRUPT 8u + RETURN R1, 1i +)"); +} + +TEST_CASE("VectorAdd") +{ + ScopedFastFlag luauCodegenVector{FFlag::LuauCodegenVector, true}; + + CHECK_EQ("\n" + getCodegenAssembly(R"( +local function vec3add(a: vector, b: vector) + return a + b +end +)"), + R"( +; function vec3add($arg0, $arg1) line 2 +bb_0: + CHECK_TAG R0, tvector, exit(entry) + CHECK_TAG R1, tvector, exit(entry) + JUMP bb_2 +bb_2: + JUMP bb_bytecode_1 +bb_bytecode_1: + %10 = LOAD_TVALUE R0 + %11 = LOAD_TVALUE R1 + %12 = ADD_VEC %10, %11 + STORE_TVALUE R2, %12 + INTERRUPT 1u + RETURN R2, 1i +)"); +} + +TEST_CASE("VectorMinus") +{ + ScopedFastFlag luauCodegenVector{FFlag::LuauCodegenVector, true}; + + CHECK_EQ("\n" + getCodegenAssembly(R"( +local function vec3minus(a: vector) + return -a +end +)"), + R"( +; function vec3minus($arg0) line 2 +bb_0: + CHECK_TAG R0, tvector, exit(entry) + JUMP bb_2 +bb_2: + JUMP bb_bytecode_1 +bb_bytecode_1: + %6 = LOAD_TVALUE R0 + %7 = UNM_VEC %6 + STORE_TVALUE R1, %7 + INTERRUPT 1u + RETURN R1, 1i +)"); +} + +TEST_CASE("VectorSubMulDiv") +{ + ScopedFastFlag luauCodegenVector{FFlag::LuauCodegenVector, true}; + + CHECK_EQ("\n" + getCodegenAssembly(R"( +local function vec3combo(a: vector, b: vector, c: vector, d: vector) + return a * b - c / d +end +)"), + R"( +; function vec3combo($arg0, $arg1, $arg2, $arg3) line 2 +bb_0: + CHECK_TAG R0, tvector, exit(entry) + CHECK_TAG R1, tvector, exit(entry) + CHECK_TAG R2, tvector, exit(entry) + CHECK_TAG R3, tvector, exit(entry) + JUMP bb_2 +bb_2: + JUMP bb_bytecode_1 +bb_bytecode_1: + %14 = LOAD_TVALUE R0 + %15 = LOAD_TVALUE R1 + %16 = MUL_VEC %14, %15 + STORE_TVALUE R5, %16 + %22 = LOAD_TVALUE R2 + %23 = LOAD_TVALUE R3 + %24 = DIV_VEC %22, %23 + STORE_TVALUE R6, %24 + %32 = SUB_VEC %16, %24 + STORE_TVALUE R4, %32 + INTERRUPT 3u + RETURN R4, 1i +)"); +} + +TEST_CASE("VectorMulDivMixed") +{ + ScopedFastFlag luauCodegenVector{FFlag::LuauCodegenVector, true}; + ScopedFastFlag luauFixDivrkInference{FFlag::LuauFixDivrkInference, true}; + ScopedFastFlag luauCompileRevK{FFlag::LuauCompileRevK, true}; + + CHECK_EQ("\n" + getCodegenAssembly(R"( +local function vec3combo(a: vector, b: vector, c: vector, d: vector) + return a * 2 + b / 4 + 0.5 * c + 40 / d +end +)"), + R"( +; function vec3combo($arg0, $arg1, $arg2, $arg3) line 2 +bb_0: + CHECK_TAG R0, tvector, exit(entry) + CHECK_TAG R1, tvector, exit(entry) + CHECK_TAG R2, tvector, exit(entry) + CHECK_TAG R3, tvector, exit(entry) + JUMP bb_2 +bb_2: + JUMP bb_bytecode_1 +bb_bytecode_1: + %12 = LOAD_TVALUE R0 + %13 = NUM_TO_VECTOR 2 + %14 = MUL_VEC %12, %13 + STORE_TVALUE R7, %14 + %18 = LOAD_TVALUE R1 + %19 = NUM_TO_VECTOR 4 + %20 = DIV_VEC %18, %19 + STORE_TVALUE R8, %20 + %28 = ADD_VEC %14, %20 + STORE_TVALUE R6, %28 + STORE_DOUBLE R8, 0.5 + STORE_TAG R8, tnumber + %37 = NUM_TO_VECTOR 0.5 + %38 = LOAD_TVALUE R2 + %39 = MUL_VEC %37, %38 + STORE_TVALUE R7, %39 + %47 = ADD_VEC %28, %39 + STORE_TVALUE R5, %47 + %51 = NUM_TO_VECTOR 40 + %52 = LOAD_TVALUE R3 + %53 = DIV_VEC %51, %52 + STORE_TVALUE R6, %53 + %61 = ADD_VEC %47, %53 + STORE_TVALUE R4, %61 + INTERRUPT 8u + RETURN R4, 1i +)"); +} + +TEST_CASE("ExtraMathMemoryOperands") +{ + ScopedFastFlag luauCodegenMathMemArgs{FFlag::LuauCodegenMathMemArgs, true}; + + CHECK_EQ("\n" + getCodegenAssembly(R"( +local function foo(a: number, b: number, c: number, d: number, e: number) + return math.floor(a) + math.ceil(b) + math.round(c) + math.sqrt(d) + math.abs(e) +end +)"), + R"( +; function foo($arg0, $arg1, $arg2, $arg3, $arg4) line 2 +bb_0: + CHECK_TAG R0, tnumber, exit(entry) + CHECK_TAG R1, tnumber, exit(entry) + CHECK_TAG R2, tnumber, exit(entry) + CHECK_TAG R3, tnumber, exit(entry) + CHECK_TAG R4, tnumber, exit(entry) + JUMP bb_2 +bb_2: + JUMP bb_bytecode_1 +bb_bytecode_1: + CHECK_SAFE_ENV exit(1) + %16 = FLOOR_NUM R0 + STORE_DOUBLE R9, %16 + STORE_TAG R9, tnumber + %23 = CEIL_NUM R1 + STORE_DOUBLE R10, %23 + STORE_TAG R10, tnumber + %32 = ADD_NUM %16, %23 + STORE_DOUBLE R8, %32 + STORE_TAG R8, tnumber + %39 = ROUND_NUM R2 + STORE_DOUBLE R9, %39 + %48 = ADD_NUM %32, %39 + STORE_DOUBLE R7, %48 + STORE_TAG R7, tnumber + %55 = SQRT_NUM R3 + STORE_DOUBLE R8, %55 + %64 = ADD_NUM %48, %55 + STORE_DOUBLE R6, %64 + STORE_TAG R6, tnumber + %71 = ABS_NUM R4 + STORE_DOUBLE R7, %71 + %80 = ADD_NUM %64, %71 + STORE_DOUBLE R5, %80 + STORE_TAG R5, tnumber + INTERRUPT 29u + RETURN R5, 1i +)"); +} + TEST_SUITE_END(); diff --git a/tests/TypeFamily.test.cpp b/tests/TypeFamily.test.cpp index 17e037d9..6e8e3bd1 100644 --- a/tests/TypeFamily.test.cpp +++ b/tests/TypeFamily.test.cpp @@ -579,10 +579,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_rfc_example") LUAU_REQUIRE_ERROR_COUNT(1, result); - TypePackMismatch* tpm = get<TypePackMismatch>(result.errors[0]); - REQUIRE(tpm); - CHECK_EQ("\"cat\" | \"dog\" | \"monkey\" | \"fox\"", toString(tpm->wantedTp)); - CHECK_EQ("\"cactus\"", toString(tpm->givenTp)); + TypeMismatch* tm = get<TypeMismatch>(result.errors[0]); + REQUIRE(tm); + CHECK_EQ("\"cat\" | \"dog\" | \"fox\" | \"monkey\"", toString(tm->wantedType)); + CHECK_EQ("\"cactus\"", toString(tm->givenType)); } TEST_SUITE_END(); diff --git a/tests/TypeInfer.loops.test.cpp b/tests/TypeInfer.loops.test.cpp index f0932050..eb0a7898 100644 --- a/tests/TypeInfer.loops.test.cpp +++ b/tests/TypeInfer.loops.test.cpp @@ -15,6 +15,7 @@ using namespace Luau; LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution) +LUAU_FASTFLAG(LuauOkWithIteratingOverTableProperties) TEST_SUITE_BEGIN("TypeInferLoops"); @@ -575,6 +576,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "unreachable_code_after_infinite_loop") TEST_CASE_FIXTURE(BuiltinsFixture, "loop_typecheck_crash_on_empty_optional") { + ScopedFastFlag sff{FFlag::LuauOkWithIteratingOverTableProperties, true}; + CheckResult result = check(R"( local t = {} for _ in t do @@ -583,7 +586,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "loop_typecheck_crash_on_empty_optional") end )"); - LUAU_REQUIRE_ERROR_COUNT(2, result); + LUAU_REQUIRE_ERROR_COUNT(1, result); } TEST_CASE_FIXTURE(Fixture, "fuzz_fail_missing_instantitation_follow") @@ -624,7 +627,22 @@ TEST_CASE_FIXTURE(Fixture, "loop_iter_basic") )"); LUAU_REQUIRE_NO_ERRORS(result); - CHECK_EQ(*builtinTypes->numberType, *requireType("key")); + + // The old solver just infers the wrong type here. + // The right type for `key` is `number?` + if (FFlag::DebugLuauDeferredConstraintResolution) + { + TypeId keyTy = requireType("key"); + + const UnionType* ut = get<UnionType>(keyTy); + REQUIRE(ut); + + REQUIRE(ut->options.size() == 2); + CHECK_EQ(builtinTypes->nilType, ut->options[0]); + CHECK_EQ(*builtinTypes->numberType, *ut->options[1]); + } + else + CHECK_EQ(*builtinTypes->numberType, *requireType("key")); } TEST_CASE_FIXTURE(Fixture, "loop_iter_trailing_nil") @@ -643,17 +661,15 @@ TEST_CASE_FIXTURE(Fixture, "loop_iter_trailing_nil") TEST_CASE_FIXTURE(Fixture, "loop_iter_no_indexer_strict") { + ScopedFastFlag sff{FFlag::LuauOkWithIteratingOverTableProperties, true}; + CheckResult result = check(R"( local t = {} for k, v in t do end )"); - LUAU_REQUIRE_ERROR_COUNT(1, result); - - GenericError* ge = get<GenericError>(result.errors[0]); - REQUIRE(ge); - CHECK_EQ("Cannot iterate over a table without indexer", ge->message); + LUAU_REQUIRE_NO_ERRORS(result); } TEST_CASE_FIXTURE(Fixture, "loop_iter_no_indexer_nonstrict") @@ -786,6 +802,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "cli_68448_iterators_need_not_accept_nil") TEST_CASE_FIXTURE(Fixture, "iterate_over_free_table") { + ScopedFastFlag sff{FFlag::LuauOkWithIteratingOverTableProperties, true}; + CheckResult result = check(R"( function print(x) end @@ -798,12 +816,7 @@ TEST_CASE_FIXTURE(Fixture, "iterate_over_free_table") end )"); - LUAU_REQUIRE_ERROR_COUNT(1, result); - - GenericError* ge = get<GenericError>(result.errors[0]); - REQUIRE(ge); - - CHECK("Cannot iterate over a table without indexer" == ge->message); + LUAU_REQUIRE_NO_ERRORS(result); } TEST_CASE_FIXTURE(BuiltinsFixture, "dcr_iteration_explore_raycast_minimization") @@ -934,4 +947,59 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "dcr_iteration_on_never_gives_never") CHECK(toString(requireType("ans")) == "never"); } +TEST_CASE_FIXTURE(BuiltinsFixture, "iterate_over_properties") +{ + ScopedFastFlag sff{FFlag::LuauOkWithIteratingOverTableProperties, true}; + + CheckResult result = check(R"( + local function f() + local t = { p = 5, q = "hello" } + for k, v in t do + return k, v + end + + error("") + end + + local k, v = f() + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + + CHECK_EQ("unknown", toString(requireType("k"))); + CHECK_EQ("unknown", toString(requireType("v"))); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "iterate_over_properties_nonstrict") +{ + ScopedFastFlag sff{FFlag::LuauOkWithIteratingOverTableProperties, true}; + + CheckResult result = check(R"( + --!nonstrict + local function f() + local t = { p = 5, q = "hello" } + for k, v in t do + return k, v + end + + error("") + end + + local k, v = f() + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "lti_fuzzer_uninitialized_loop_crash") +{ + CheckResult result = check(R"( + for l0=_,_ do + return _() + end + )"); + + LUAU_REQUIRE_ERROR_COUNT(3, result); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.oop.test.cpp b/tests/TypeInfer.oop.test.cpp index 0ef302f6..6d6658da 100644 --- a/tests/TypeInfer.oop.test.cpp +++ b/tests/TypeInfer.oop.test.cpp @@ -219,7 +219,7 @@ TEST_CASE_FIXTURE(Fixture, "inferred_methods_of_free_tables_have_the_same_level_ check(R"( function Base64FileReader(data) local reader = {} - local index: number + local index: number = 0 function reader:PeekByte() return data:byte(index) diff --git a/tests/TypeInfer.refinements.test.cpp b/tests/TypeInfer.refinements.test.cpp index ed9c1fdd..1a9c6dcc 100644 --- a/tests/TypeInfer.refinements.test.cpp +++ b/tests/TypeInfer.refinements.test.cpp @@ -1933,6 +1933,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "refine_unknown_to_table") return i, v end end + + error("") end )"); diff --git a/tests/TypeInfer.typestates.test.cpp b/tests/TypeInfer.typestates.test.cpp index 07ee670c..fb9213d5 100644 --- a/tests/TypeInfer.typestates.test.cpp +++ b/tests/TypeInfer.typestates.test.cpp @@ -369,8 +369,12 @@ TEST_CASE_FIXTURE(TypeStateFixture, "prototyped_recursive_functions") CHECK("(() -> ())?" == toString(requireType("f"))); } -TEST_CASE_FIXTURE(TypeStateFixture, "prototyped_recursive_functions_but_has_future_assignments") +TEST_CASE_FIXTURE(BuiltinsFixture, "prototyped_recursive_functions_but_has_future_assignments") { + // early return if the flag isn't set since this is blocking gated commits + if (!FFlag::DebugLuauDeferredConstraintResolution) + return; + CheckResult result = check(R"( local f function f() diff --git a/tests/conformance/interrupt.lua b/tests/conformance/interrupt.lua index 4a662bd6..86712be5 100644 --- a/tests/conformance/interrupt.lua +++ b/tests/conformance/interrupt.lua @@ -75,4 +75,37 @@ function infloop10() end end +local haystack = string.rep("x", 100) +local pattern = string.rep("x?", 100) .. string.rep("x", 100) + +function strhang1() + string.find(haystack, pattern) +end + +function strhang2() + string.match(haystack, pattern) +end + +function strhang3() + string.gsub(haystack, pattern, "%0") +end + +function strhang4() + for k, v in string.gmatch(haystack, pattern) do + end +end + +function strhang5() + local x = string.rep('x', 1000) + string.match(x, string.rep('x.*', 100) .. 'y') +end + +function strhangpcall() + for i = 1,100 do + local status, msg = pcall(string.find, haystack, pattern) + assert(status == false) + assert(msg == "timeout") + end +end + return "OK" diff --git a/tests/conformance/native.lua b/tests/conformance/native.lua index 63c6ff09..22171077 100644 --- a/tests/conformance/native.lua +++ b/tests/conformance/native.lua @@ -293,4 +293,28 @@ end assert(loopIteratorProtocol(0, table.create(100, 5)) == 5058) +local function vec3compsum(a: vector) + return a.X + a.Y + a.Z +end + +assert(vec3compsum(vector(1, 2, 4)) == 7.0) + +local function vec3add(a: vector, b: vector) return a + b end +local function vec3sub(a: vector, b: vector) return a - b end +local function vec3mul(a: vector, b: vector) return a * b end +local function vec3div(a: vector, b: vector) return a / b end +local function vec3neg(a: vector) return -a end + +assert(vec3add(vector(10, 20, 40), vector(1, 0, 2)) == vector(11, 20, 42)) +assert(vec3sub(vector(10, 20, 40), vector(1, 0, 2)) == vector(9, 20, 38)) +assert(vec3mul(vector(10, 20, 40), vector(1, 0, 2)) == vector(10, 0, 80)) +assert(vec3div(vector(10, 20, 40), vector(1, 0, 2)) == vector(10, math.huge, 20)) +assert(vec3neg(vector(10, 20, 40)) == vector(-10, -20, -40)) + +local function vec3mulnum(a: vector, b: number) return a * b end +local function vec3mulconst(a: vector) return a * 4 end + +assert(vec3mulnum(vector(10, 20, 40), 4) == vector(40, 80, 160)) +assert(vec3mulconst(vector(10, 20, 40), 4) == vector(40, 80, 160)) + return('OK') diff --git a/tools/faillist.txt b/tools/faillist.txt index d23d8362..5a98890e 100644 --- a/tools/faillist.txt +++ b/tools/faillist.txt @@ -48,7 +48,6 @@ BuiltinTests.setmetatable_should_not_mutate_persisted_types BuiltinTests.sort BuiltinTests.sort_with_bad_predicate BuiltinTests.sort_with_predicate -BuiltinTests.string_format_arg_count_mismatch BuiltinTests.string_format_as_method BuiltinTests.string_format_correctly_ordered_types BuiltinTests.string_format_report_all_type_errors_at_correct_positions @@ -59,10 +58,6 @@ BuiltinTests.table_freeze_is_generic BuiltinTests.table_insert_correctly_infers_type_of_array_2_args_overload BuiltinTests.table_insert_correctly_infers_type_of_array_3_args_overload BuiltinTests.tonumber_returns_optional_number_type -BuiltinTests.xpcall -ControlFlowAnalysis.for_record_do_if_not_x_break -ControlFlowAnalysis.for_record_do_if_not_x_continue -ControlFlowAnalysis.if_not_x_break ControlFlowAnalysis.if_not_x_break_elif_not_y_break ControlFlowAnalysis.if_not_x_break_elif_not_y_continue ControlFlowAnalysis.if_not_x_break_elif_not_y_fallthrough_elif_not_z_break @@ -70,7 +65,6 @@ ControlFlowAnalysis.if_not_x_break_elif_rand_break_elif_not_y_break ControlFlowAnalysis.if_not_x_break_elif_rand_break_elif_not_y_fallthrough ControlFlowAnalysis.if_not_x_break_if_not_y_break ControlFlowAnalysis.if_not_x_break_if_not_y_continue -ControlFlowAnalysis.if_not_x_continue ControlFlowAnalysis.if_not_x_continue_elif_not_y_continue ControlFlowAnalysis.if_not_x_continue_elif_not_y_fallthrough_elif_not_z_continue ControlFlowAnalysis.if_not_x_continue_elif_not_y_throw_elif_not_z_fallthrough @@ -80,13 +74,7 @@ ControlFlowAnalysis.if_not_x_continue_if_not_y_continue ControlFlowAnalysis.if_not_x_continue_if_not_y_throw ControlFlowAnalysis.if_not_x_return_elif_not_y_break ControlFlowAnalysis.if_not_x_return_elif_not_y_fallthrough_elif_not_z_break -ControlFlowAnalysis.prototyping_and_visiting_alias_has_the_same_scope_breaking -ControlFlowAnalysis.prototyping_and_visiting_alias_has_the_same_scope_continuing ControlFlowAnalysis.tagged_unions -ControlFlowAnalysis.tagged_unions_breaking -ControlFlowAnalysis.tagged_unions_continuing -ControlFlowAnalysis.type_alias_does_not_leak_out_breaking -ControlFlowAnalysis.type_alias_does_not_leak_out_continuing DefinitionTests.class_definition_indexer DefinitionTests.class_definition_overload_metamethods DefinitionTests.class_definition_string_props @@ -118,7 +106,6 @@ FrontendTest.it_should_be_safe_to_stringify_errors_when_full_type_graph_is_disca FrontendTest.nocheck_cycle_used_by_checked FrontendTest.trace_requires_in_nonstrict_mode GenericsTests.apply_type_function_nested_generics1 -GenericsTests.apply_type_function_nested_generics2 GenericsTests.better_mismatch_error_messages GenericsTests.bound_tables_do_not_clone_original_fields GenericsTests.check_generic_function @@ -129,7 +116,6 @@ GenericsTests.check_nested_generic_function GenericsTests.check_recursive_generic_function GenericsTests.correctly_instantiate_polymorphic_member_functions GenericsTests.do_not_always_instantiate_generic_intersection_types -GenericsTests.do_not_infer_generic_functions GenericsTests.dont_substitute_bound_types GenericsTests.error_detailed_function_mismatch_generic_pack GenericsTests.error_detailed_function_mismatch_generic_types @@ -147,7 +133,6 @@ GenericsTests.generic_type_pack_unification3 GenericsTests.higher_rank_polymorphism_should_not_accept_instantiated_arguments GenericsTests.hof_subtype_instantiation_regression GenericsTests.infer_generic_function -GenericsTests.infer_generic_function_function_argument GenericsTests.infer_generic_function_function_argument_2 GenericsTests.infer_generic_function_function_argument_3 GenericsTests.infer_generic_function_function_argument_overloaded @@ -156,6 +141,7 @@ GenericsTests.infer_generic_local_function GenericsTests.infer_generic_property GenericsTests.infer_nested_generic_function GenericsTests.inferred_local_vars_can_be_polytypes +GenericsTests.instantiate_cyclic_generic_function GenericsTests.instantiated_function_argument_names GenericsTests.local_vars_can_be_polytypes GenericsTests.no_stack_overflow_from_quantifying @@ -166,6 +152,7 @@ GenericsTests.rank_N_types_via_typeof GenericsTests.self_recursive_instantiated_param GenericsTests.type_parameters_can_be_polytypes GenericsTests.typefuns_sharing_types +IntersectionTypes.CLI-44817 IntersectionTypes.error_detailed_intersection_all IntersectionTypes.error_detailed_intersection_part IntersectionTypes.intersect_bool_and_false @@ -231,7 +218,6 @@ ProvisionalTests.table_insert_with_a_singleton_argument ProvisionalTests.table_unification_infinite_recursion ProvisionalTests.typeguard_inference_incomplete ProvisionalTests.while_body_are_also_refined -ProvisionalTests.xpcall_returns_what_f_returns RefinementTest.assert_a_to_be_truthy_then_assert_a_to_be_number RefinementTest.correctly_lookup_property_whose_base_was_previously_refined RefinementTest.dataflow_analysis_can_tell_refinements_when_its_appropriate_to_refine_into_nil_or_never @@ -243,6 +229,7 @@ RefinementTest.else_with_no_explicit_expression_should_also_refine_the_tagged_un RefinementTest.fail_to_refine_a_property_of_subscript_expression RefinementTest.falsiness_of_TruthyPredicate_narrows_into_nil RefinementTest.function_call_with_colon_after_refining_not_to_be_nil +RefinementTest.globals_can_be_narrowed_too RefinementTest.impossible_type_narrow_is_not_an_error RefinementTest.index_on_a_refined_property RefinementTest.isa_type_refinement_must_be_known_ahead_of_time @@ -259,6 +246,7 @@ RefinementTest.refinements_should_preserve_error_suppression RefinementTest.string_not_equal_to_string_or_nil RefinementTest.truthy_constraint_on_properties RefinementTest.type_annotations_arent_relevant_when_doing_dataflow_analysis +RefinementTest.type_guard_narrowed_into_nothingness RefinementTest.type_narrow_to_vector RefinementTest.typeguard_cast_free_table_to_vector RefinementTest.typeguard_in_assert_position @@ -303,6 +291,7 @@ TableTests.generic_table_instantiation_potential_regression TableTests.indexer_mismatch TableTests.indexers_get_quantified_too TableTests.inequality_operators_imply_exactly_matching_types +TableTests.infer_indexer_from_array_like_table TableTests.infer_indexer_from_its_variable_type_and_unifiable TableTests.inferred_return_type_of_free_table TableTests.instantiate_table_cloning_3 @@ -377,7 +366,6 @@ ToString.toStringNamedFunction_generic_pack ToString.toStringNamedFunction_map TryUnifyTests.members_of_failed_typepack_unification_are_unified_with_errorType TryUnifyTests.result_of_failed_typepack_unification_is_constrained -TryUnifyTests.typepack_unification_should_trim_free_tails TryUnifyTests.uninhabited_table_sub_anything TryUnifyTests.uninhabited_table_sub_never TryUnifyTests.variadics_should_use_reversed_properly @@ -402,12 +390,10 @@ TypeFamilyTests.family_as_fn_arg TypeFamilyTests.family_as_fn_ret TypeFamilyTests.function_internal_families TypeFamilyTests.internal_families_raise_errors -TypeFamilyTests.keyof_rfc_example TypeFamilyTests.table_internal_families TypeFamilyTests.type_families_inhabited_with_normalization TypeFamilyTests.unsolvable_family TypeInfer.bidirectional_checking_of_callback_property -TypeInfer.check_expr_recursion_limit TypeInfer.check_type_infer_recursion_count TypeInfer.checking_should_not_ice TypeInfer.cli_39932_use_unifier_in_ensure_methods @@ -422,7 +408,6 @@ TypeInfer.infer_assignment_value_types TypeInfer.infer_locals_via_assignment_from_its_call_site TypeInfer.infer_through_group_expr TypeInfer.infer_type_assertion_value_type -TypeInfer.no_infinite_loop_when_trying_to_unify_uh_this TypeInfer.no_stack_overflow_from_isoptional TypeInfer.promote_tail_type_packs TypeInfer.recursive_function_that_invokes_itself_with_a_refinement_of_its_parameter @@ -447,7 +432,6 @@ TypeInferAnyError.intersection_of_any_can_have_props 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 -TypeInferAnyError.type_error_addition TypeInferClasses.callable_classes TypeInferClasses.cannot_unify_class_instance_with_primitive TypeInferClasses.class_type_mismatch_with_name_conflict @@ -512,7 +496,6 @@ TypeInferFunctions.param_1_and_2_both_takes_the_same_generic_but_their_arguments TypeInferFunctions.record_matching_overload TypeInferFunctions.regex_benchmark_string_format_minimization TypeInferFunctions.report_exiting_without_return_nonstrict -TypeInferFunctions.report_exiting_without_return_strict TypeInferFunctions.return_type_by_overload TypeInferFunctions.too_few_arguments_variadic TypeInferFunctions.too_few_arguments_variadic_generic @@ -537,21 +520,21 @@ TypeInferLoops.for_in_loop_with_incompatible_args_to_iterator TypeInferLoops.for_in_loop_with_next TypeInferLoops.for_in_with_an_iterator_of_type_any TypeInferLoops.for_in_with_generic_next -TypeInferLoops.for_in_with_just_one_iterator_is_ok TypeInferLoops.for_loop TypeInferLoops.ipairs_produces_integral_indices +TypeInferLoops.iterate_over_free_table +TypeInferLoops.iterate_over_properties TypeInferLoops.iteration_no_table_passed TypeInferLoops.iteration_regression_issue_69967 TypeInferLoops.iteration_regression_issue_69967_alt -TypeInferLoops.loop_iter_basic TypeInferLoops.loop_iter_metamethod_nil TypeInferLoops.loop_iter_metamethod_ok TypeInferLoops.loop_iter_metamethod_ok_with_inference +TypeInferLoops.loop_iter_no_indexer_strict TypeInferLoops.loop_iter_trailing_nil TypeInferLoops.loop_typecheck_crash_on_empty_optional TypeInferLoops.properly_infer_iteratee_is_a_free_table TypeInferLoops.repeat_loop -TypeInferLoops.unreachable_code_after_infinite_loop TypeInferLoops.varlist_declared_by_for_in_loop_should_be_free TypeInferLoops.while_loop TypeInferModules.bound_free_table_export_is_ok @@ -559,12 +542,12 @@ TypeInferModules.custom_require_global TypeInferModules.do_not_modify_imported_types TypeInferModules.do_not_modify_imported_types_5 TypeInferModules.require -TypeInferModules.require_failed_module 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 +TypeInferOOP.inferred_methods_of_free_tables_have_the_same_level_as_the_enclosing_table TypeInferOOP.inferring_hundreds_of_self_calls_should_not_suffocate_memory TypeInferOOP.method_depends_on_table TypeInferOOP.methods_are_topologically_sorted @@ -582,7 +565,6 @@ TypeInferOperators.equality_operations_succeed_if_any_union_branch_succeeds TypeInferOperators.error_on_invalid_operand_types_to_relational_operators2 TypeInferOperators.luau_polyfill_is_array TypeInferOperators.mm_comparisons_must_return_a_boolean -TypeInferOperators.normalize_strings_comparison TypeInferOperators.operator_eq_verifies_types_do_intersect TypeInferOperators.reducing_and TypeInferOperators.refine_and_or @@ -615,12 +597,10 @@ TypePackTests.unify_variadic_tails_in_arguments TypeSingletons.enums_using_singletons_mismatch TypeSingletons.error_detailed_tagged_union_mismatch_bool TypeSingletons.error_detailed_tagged_union_mismatch_string -TypeSingletons.function_args_infer_singletons -TypeSingletons.function_call_with_singletons -TypeSingletons.function_call_with_singletons_mismatch TypeSingletons.return_type_of_f_is_not_widened TypeSingletons.table_properties_type_error_escapes TypeSingletons.widen_the_supertype_if_it_is_free_and_subtype_has_singleton +TypeStatesTest.prototyped_recursive_functions_but_has_future_assignments UnionTypes.error_detailed_optional UnionTypes.error_detailed_union_all UnionTypes.generic_function_with_optional_arg