Skip to content

Commit

Permalink
Sync to upstream/release/582 (#960)
Browse files Browse the repository at this point in the history
* Optimized operations like instantiation and module export for very
large types

In our new typechecker:
* Typechecking of function calls was rewritten to handle more cases
correctly
* Fixed a crash that can happen after self-referential type is exported
from a module
* Fixed a false positive error in string comparison
* Added handling of `for...in` variable type annotations and fixed
issues with the iterator call inside
* Self-referential 'hasProp' and 'setProp' constraints are now handled
correctly
 
In our native code generation (jit):
* Added '--target' argument to luau-compile to test multiple
architectures different from host architecture
* GC barrier tag check is skipped if type is already known to be
GC-collectable
* Added GET_TYPE/GET_TYPEOF instructions for type/typeof fast-calls
* Improved code size of interrupt handlers on X64
  • Loading branch information
vegorov-rbx authored Jun 24, 2023
1 parent d458d24 commit 76bea81
Show file tree
Hide file tree
Showing 63 changed files with 1,422 additions and 963 deletions.
2 changes: 1 addition & 1 deletion Analysis/include/Luau/Constraint.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ struct IterableConstraint
TypePackId variables;

const AstNode* nextAstFragment;
DenseHashMap<const AstNode*, TypeId>* astOverloadResolvedTypes;
DenseHashMap<const AstNode*, TypeId>* astForInNextTypes;
};

// name(namedType) = name
Expand Down
11 changes: 11 additions & 0 deletions Analysis/include/Luau/ConstraintSolver.h
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,17 @@ struct ConstraintSolver
template <typename TID>
bool tryUnify(NotNull<const Constraint> constraint, TID subTy, TID superTy);

/**
* Bind a BlockedType to another type while taking care not to bind it to
* itself in the case that resultTy == blockedTy. This can happen if we
* have a tautological constraint. When it does, we must instead bind
* blockedTy to a fresh type belonging to an appropriate scope.
*
* To determine which scope is appropriate, we also accept rootTy, which is
* to be the type that contains blockedTy.
*/
void bindBlockedType(TypeId blockedTy, TypeId resultTy, TypeId rootTy, Location location);

/**
* Marks a constraint as being blocked on a type or type pack. The constraint
* solver will not attempt to dispatch blocked constraints until their
Expand Down
23 changes: 20 additions & 3 deletions Analysis/include/Luau/Module.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,29 @@ struct Module
DenseHashMap<const AstExpr*, TypePackId> astTypePacks{nullptr};
DenseHashMap<const AstExpr*, TypeId> astExpectedTypes{nullptr};

// For AST nodes that are function calls, this map provides the
// unspecialized type of the function that was called. If a function call
// resolves to a __call metamethod application, this map will point at that
// metamethod.
//
// This is useful for type checking and Signature Help.
DenseHashMap<const AstNode*, TypeId> astOriginalCallTypes{nullptr};

// The specialization of a function that was selected. If the function is
// generic, those generic type parameters will be replaced with the actual
// types that were passed. If the function is an overload, this map will
// point at the specific overloads that were selected.
DenseHashMap<const AstNode*, TypeId> astOverloadResolvedTypes{nullptr};

// Only used with for...in loops. The computed type of the next() function
// is kept here for type checking.
DenseHashMap<const AstNode*, TypeId> astForInNextTypes{nullptr};

DenseHashMap<const AstType*, TypeId> astResolvedTypes{nullptr};
DenseHashMap<const AstTypePack*, TypePackId> astResolvedTypePacks{nullptr};

// Map AST nodes to the scope they create. Cannot be NotNull<Scope> because we need a sentinel value for the map.
// Map AST nodes to the scope they create. Cannot be NotNull<Scope> because
// we need a sentinel value for the map.
DenseHashMap<const AstNode*, Scope*> astScopes{nullptr};

std::unordered_map<Name, TypeId> declaredGlobals;
Expand All @@ -103,8 +119,9 @@ struct Module
bool hasModuleScope() const;
ScopePtr getModuleScope() const;

// Once a module has been typechecked, we clone its public interface into a separate arena.
// This helps us to force Type ownership into a DAG rather than a DCG.
// Once a module has been typechecked, we clone its public interface into a
// separate arena. This helps us to force Type ownership into a DAG rather
// than a DCG.
void clonePublicInterface(NotNull<BuiltinTypes> builtinTypes, InternalErrorReporter& ice);
};

Expand Down
2 changes: 0 additions & 2 deletions Analysis/include/Luau/Normalize.h
Original file line number Diff line number Diff line change
Expand Up @@ -207,8 +207,6 @@ struct NormalizedFunctionType
struct NormalizedType;
using NormalizedTyvars = std::unordered_map<TypeId, std::unique_ptr<NormalizedType>>;

bool isInhabited_DEPRECATED(const NormalizedType& norm);

// A normalized type is either any, unknown, or one of the form P | T | F | G where
// * P is a union of primitive types (including singletons, classes and the error type)
// * T is a union of table types
Expand Down
73 changes: 40 additions & 33 deletions Analysis/include/Luau/Substitution.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,24 +69,32 @@ struct TarjanWorklistVertex
int lastEdge;
};

struct TarjanNode
{
TypeId ty;
TypePackId tp;

bool onStack;
bool dirty;

// Tarjan calculates the lowlink for each vertex,
// which is the lowest ancestor index reachable from the vertex.
int lowlink;
};

// Tarjan's algorithm for finding the SCCs in a cyclic structure.
// https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
struct Tarjan
{
// Vertices (types and type packs) are indexed, using pre-order traversal.
DenseHashMap<TypeId, int> typeToIndex{nullptr};
DenseHashMap<TypePackId, int> packToIndex{nullptr};
std::vector<TypeId> indexToType;
std::vector<TypePackId> indexToPack;

std::vector<TarjanNode> nodes;

// Tarjan keeps a stack of vertices where we're still in the process
// of finding their SCC.
std::vector<int> stack;
std::vector<bool> onStack;

// Tarjan calculates the lowlink for each vertex,
// which is the lowest ancestor index reachable from the vertex.
std::vector<int> lowlink;

int childCount = 0;
int childLimit = 0;
Expand All @@ -98,6 +106,7 @@ struct Tarjan
std::vector<TypeId> edgesTy;
std::vector<TypePackId> edgesTp;
std::vector<TarjanWorklistVertex> worklist;

// This is hot code, so we optimize recursion to a stack.
TarjanResult loop();

Expand All @@ -124,10 +133,22 @@ struct Tarjan
TarjanResult visitRoot(TypeId ty);
TarjanResult visitRoot(TypePackId ty);

// Each subclass gets called back once for each edge,
// and once for each SCC.
virtual void visitEdge(int index, int parentIndex) {}
virtual void visitSCC(int index) {}
void clearTarjan();

// Get/set the dirty bit for an index (grows the vector if needed)
bool getDirty(int index);
void setDirty(int index, bool d);

// Find all the dirty vertices reachable from `t`.
TarjanResult findDirty(TypeId t);
TarjanResult findDirty(TypePackId t);

// We find dirty vertices using Tarjan
void visitEdge(int index, int parentIndex);
void visitSCC(int index);

TarjanResult loop_DEPRECATED();
void visitSCC_DEPRECATED(int index);

// Each subclass can decide to ignore some nodes.
virtual bool ignoreChildren(TypeId ty)
Expand All @@ -150,39 +171,25 @@ struct Tarjan
{
return ignoreChildren(ty);
}
};

// We use Tarjan to calculate dirty bits. We set `dirty[i]` true
// if the vertex with index `i` can reach a dirty vertex.
struct FindDirty : Tarjan
{
std::vector<bool> dirty;

void clearTarjan();

// Get/set the dirty bit for an index (grows the vector if needed)
bool getDirty(int index);
void setDirty(int index, bool d);

// Find all the dirty vertices reachable from `t`.
TarjanResult findDirty(TypeId t);
TarjanResult findDirty(TypePackId t);

// We find dirty vertices using Tarjan
void visitEdge(int index, int parentIndex) override;
void visitSCC(int index) override;

// Subclasses should say which vertices are dirty,
// and what to do with dirty vertices.
virtual bool isDirty(TypeId ty) = 0;
virtual bool isDirty(TypePackId tp) = 0;
virtual void foundDirty(TypeId ty) = 0;
virtual void foundDirty(TypePackId tp) = 0;

// TODO: remove with FFlagLuauTarjanSingleArr
std::vector<TypeId> indexToType;
std::vector<TypePackId> indexToPack;
std::vector<bool> onStack;
std::vector<int> lowlink;
std::vector<bool> dirty;
};

// And finally substitution, which finds all the reachable dirty vertices
// and replaces them with clean ones.
struct Substitution : FindDirty
struct Substitution : Tarjan
{
protected:
Substitution(const TxnLog* log_, TypeArena* arena)
Expand Down
2 changes: 0 additions & 2 deletions Analysis/include/Luau/TypeInfer.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@
#include <unordered_map>
#include <unordered_set>

LUAU_FASTFLAG(LuauClassTypeVarsInSubstitution)

namespace Luau
{

Expand Down
9 changes: 9 additions & 0 deletions Analysis/include/Luau/TypeUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,13 @@ const T* get(std::optional<Ty> ty)
return nullptr;
}

template<typename Ty>
std::optional<Ty> follow(std::optional<Ty> ty)
{
if (ty)
return follow(*ty);
else
return std::nullopt;
}

} // namespace Luau
4 changes: 1 addition & 3 deletions Analysis/src/Anyification.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
#include "Luau/Normalize.h"
#include "Luau/TxnLog.h"

LUAU_FASTFLAG(LuauClassTypeVarsInSubstitution)

namespace Luau
{

Expand Down Expand Up @@ -78,7 +76,7 @@ TypePackId Anyification::clean(TypePackId tp)

bool Anyification::ignoreChildren(TypeId ty)
{
if (FFlag::LuauClassTypeVarsInSubstitution && get<ClassType>(ty))
if (get<ClassType>(ty))
return true;

return ty->persistent;
Expand Down
4 changes: 1 addition & 3 deletions Analysis/src/ApplyTypeFunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

#include "Luau/ApplyTypeFunction.h"

LUAU_FASTFLAG(LuauClassTypeVarsInSubstitution)

namespace Luau
{

Expand Down Expand Up @@ -33,7 +31,7 @@ bool ApplyTypeFunction::ignoreChildren(TypeId ty)
{
if (get<GenericType>(ty))
return true;
else if (FFlag::LuauClassTypeVarsInSubstitution && get<ClassType>(ty))
else if (get<ClassType>(ty))
return true;
else
return false;
Expand Down
9 changes: 7 additions & 2 deletions Analysis/src/ConstraintGraphBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -751,7 +751,12 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatForIn* f
variableTypes.reserve(forIn->vars.size);
for (AstLocal* var : forIn->vars)
{
TypeId ty = freshType(loopScope);
TypeId ty = nullptr;
if (var->annotation)
ty = resolveType(loopScope, var->annotation, /*inTypeArguments*/ false);
else
ty = freshType(loopScope);

loopScope->bindings[var] = Binding{ty, var->location};
variableTypes.push_back(ty);

Expand All @@ -763,7 +768,7 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatForIn* f
TypePackId variablePack = arena->addTypePack(std::move(variableTypes), arena->addTypePack(FreeTypePack{loopScope.get()}));

addConstraint(
loopScope, getLocation(forIn->values), IterableConstraint{iterator, variablePack, forIn->values.data[0], &module->astOverloadResolvedTypes});
loopScope, getLocation(forIn->values), IterableConstraint{iterator, variablePack, forIn->values.data[0], &module->astForInNextTypes});
visit(loopScope, forIn->body);

return ControlFlow::None;
Expand Down
35 changes: 31 additions & 4 deletions Analysis/src/ConstraintSolver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1453,7 +1453,7 @@ bool ConstraintSolver::tryDispatch(const HasPropConstraint& c, NotNull<const Con
return false;
}

asMutable(c.resultType)->ty.emplace<BoundType>(result.value_or(builtinTypes->anyType));
bindBlockedType(c.resultType, result.value_or(builtinTypes->anyType), c.subjectType, constraint->location);
unblock(c.resultType, constraint->location);
return true;
}
Expand Down Expand Up @@ -1559,8 +1559,8 @@ bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull<const Con
existingPropType = result;
}

auto bind = [](TypeId a, TypeId b) {
asMutable(a)->ty.emplace<BoundType>(b);
auto bind = [&](TypeId a, TypeId b) {
bindBlockedType(a, b, c.subjectType, constraint->location);
};

if (existingPropType)
Expand Down Expand Up @@ -2143,7 +2143,9 @@ bool ConstraintSolver::tryDispatchIterableFunction(

// if there are no errors from unifying the two, we can pass forward the expected type as our selected resolution.
if (errors.empty())
(*c.astOverloadResolvedTypes)[c.nextAstFragment] = expectedNextTy;
{
(*c.astForInNextTypes)[c.nextAstFragment] = expectedNextTy;
}

auto it = begin(nextRetPack);
std::vector<TypeId> modifiedNextRetHead;
Expand Down Expand Up @@ -2380,6 +2382,31 @@ bool ConstraintSolver::tryUnify(NotNull<const Constraint> constraint, TID subTy,
return true;
}

void ConstraintSolver::bindBlockedType(TypeId blockedTy, TypeId resultTy, TypeId rootTy, Location location)
{
resultTy = follow(resultTy);

LUAU_ASSERT(get<BlockedType>(blockedTy));

if (blockedTy == resultTy)
{
rootTy = follow(rootTy);
Scope* freeScope = nullptr;
if (auto ft = get<FreeType>(rootTy))
freeScope = ft->scope;
else if (auto tt = get<TableType>(rootTy); tt && tt->state == TableState::Free)
freeScope = tt->scope;
else
iceReporter.ice("bindBlockedType couldn't find an appropriate scope for a fresh type!", location);

LUAU_ASSERT(freeScope);

asMutable(blockedTy)->ty.emplace<BoundType>(arena->freshType(freeScope));
}
else
asMutable(blockedTy)->ty.emplace<BoundType>(resultTy);
}

void ConstraintSolver::block_(BlockedConstraintId target, NotNull<const Constraint> constraint)
{
blocked[target].push_back(constraint);
Expand Down
3 changes: 1 addition & 2 deletions Analysis/src/Error.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
#include <stdexcept>
#include <type_traits>

LUAU_FASTFLAGVARIABLE(LuauTypeMismatchInvarianceInError, false)

static std::string wrongNumberOfArgsString(
size_t expectedCount, std::optional<size_t> maximumCount, size_t actualCount, const char* argPrefix = nullptr, bool isVariadic = false)
Expand Down Expand Up @@ -106,7 +105,7 @@ struct ErrorConverter
{
result += "; " + tm.reason;
}
else if (FFlag::LuauTypeMismatchInvarianceInError && tm.context == TypeMismatch::InvariantContext)
else if (tm.context == TypeMismatch::InvariantContext)
{
result += " in an invariant context";
}
Expand Down
1 change: 1 addition & 0 deletions Analysis/src/Frontend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -950,6 +950,7 @@ void Frontend::checkBuildQueueItem(BuildQueueItem& item)
module->astExpectedTypes.clear();
module->astOriginalCallTypes.clear();
module->astOverloadResolvedTypes.clear();
module->astForInNextTypes.clear();
module->astResolvedTypes.clear();
module->astResolvedTypePacks.clear();
module->astScopes.clear();
Expand Down
6 changes: 2 additions & 4 deletions Analysis/src/Instantiation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
#include "Luau/TxnLog.h"
#include "Luau/TypeArena.h"

LUAU_FASTFLAG(LuauClassTypeVarsInSubstitution)

namespace Luau
{

Expand Down Expand Up @@ -33,7 +31,7 @@ bool Instantiation::ignoreChildren(TypeId ty)
{
if (log->getMutable<FunctionType>(ty))
return true;
else if (FFlag::LuauClassTypeVarsInSubstitution && get<ClassType>(ty))
else if (get<ClassType>(ty))
return true;
else
return false;
Expand Down Expand Up @@ -84,7 +82,7 @@ bool ReplaceGenerics::ignoreChildren(TypeId ty)
// whenever we quantify, so the vectors overlap if and only if they are equal.
return (!generics.empty() || !genericPacks.empty()) && (ftv->generics == generics) && (ftv->genericPacks == genericPacks);
}
else if (FFlag::LuauClassTypeVarsInSubstitution && get<ClassType>(ty))
else if (get<ClassType>(ty))
return true;
else
{
Expand Down
Loading

0 comments on commit 76bea81

Please sign in to comment.