mirror of
https://github.com/luau-lang/luau.git
synced 2025-01-31 23:03:10 +00:00
Merge branch 'upstream' of https://github.com/luau-lang/luau into upstream
This commit is contained in:
commit
d4883bfb32
153 changed files with 1649 additions and 857 deletions
|
@ -197,24 +197,6 @@ struct UnpackConstraint
|
||||||
bool resultIsLValue = false;
|
bool resultIsLValue = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// resultType ~ refine type mode discriminant
|
|
||||||
//
|
|
||||||
// Compute type & discriminant (or type | discriminant) as soon as possible (but
|
|
||||||
// no sooner), simplify, and bind resultType to that type.
|
|
||||||
struct RefineConstraint
|
|
||||||
{
|
|
||||||
enum
|
|
||||||
{
|
|
||||||
Intersection,
|
|
||||||
Union
|
|
||||||
} mode;
|
|
||||||
|
|
||||||
TypeId resultType;
|
|
||||||
|
|
||||||
TypeId type;
|
|
||||||
TypeId discriminant;
|
|
||||||
};
|
|
||||||
|
|
||||||
// resultType ~ T0 op T1 op ... op TN
|
// resultType ~ T0 op T1 op ... op TN
|
||||||
//
|
//
|
||||||
// op is either union or intersection. If any of the input types are blocked,
|
// op is either union or intersection. If any of the input types are blocked,
|
||||||
|
@ -249,7 +231,7 @@ struct ReducePackConstraint
|
||||||
|
|
||||||
using ConstraintV = Variant<SubtypeConstraint, PackSubtypeConstraint, GeneralizationConstraint, InstantiationConstraint, IterableConstraint,
|
using ConstraintV = Variant<SubtypeConstraint, PackSubtypeConstraint, GeneralizationConstraint, InstantiationConstraint, IterableConstraint,
|
||||||
NameConstraint, TypeAliasExpansionConstraint, FunctionCallConstraint, PrimitiveTypeConstraint, HasPropConstraint, SetPropConstraint,
|
NameConstraint, TypeAliasExpansionConstraint, FunctionCallConstraint, PrimitiveTypeConstraint, HasPropConstraint, SetPropConstraint,
|
||||||
SetIndexerConstraint, SingletonOrTopTypeConstraint, UnpackConstraint, RefineConstraint, SetOpConstraint, ReduceConstraint, ReducePackConstraint>;
|
SetIndexerConstraint, SingletonOrTopTypeConstraint, UnpackConstraint, SetOpConstraint, ReduceConstraint, ReducePackConstraint>;
|
||||||
|
|
||||||
struct Constraint
|
struct Constraint
|
||||||
{
|
{
|
||||||
|
|
|
@ -150,7 +150,7 @@ private:
|
||||||
*/
|
*/
|
||||||
ScopePtr childScope(AstNode* node, const ScopePtr& parent);
|
ScopePtr childScope(AstNode* node, const ScopePtr& parent);
|
||||||
|
|
||||||
std::optional<TypeId> lookup(Scope* scope, DefId def);
|
std::optional<TypeId> lookup(Scope* scope, DefId def, bool prototype = true);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a new constraint with no dependencies to a given scope.
|
* Adds a new constraint with no dependencies to a given scope.
|
||||||
|
|
|
@ -132,7 +132,6 @@ struct ConstraintSolver
|
||||||
bool tryDispatch(const SetIndexerConstraint& c, NotNull<const Constraint> constraint, bool force);
|
bool tryDispatch(const SetIndexerConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||||
bool tryDispatch(const SingletonOrTopTypeConstraint& c, NotNull<const Constraint> constraint);
|
bool tryDispatch(const SingletonOrTopTypeConstraint& c, NotNull<const Constraint> constraint);
|
||||||
bool tryDispatch(const UnpackConstraint& c, NotNull<const Constraint> constraint);
|
bool tryDispatch(const UnpackConstraint& c, NotNull<const Constraint> constraint);
|
||||||
bool tryDispatch(const RefineConstraint& c, NotNull<const Constraint> constraint, bool force);
|
|
||||||
bool tryDispatch(const SetOpConstraint& c, NotNull<const Constraint> constraint, bool force);
|
bool tryDispatch(const SetOpConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||||
bool tryDispatch(const ReduceConstraint& c, NotNull<const Constraint> constraint, bool force);
|
bool tryDispatch(const ReduceConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||||
bool tryDispatch(const ReducePackConstraint& c, NotNull<const Constraint> constraint, bool force);
|
bool tryDispatch(const ReducePackConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||||
|
|
|
@ -74,8 +74,15 @@ private:
|
||||||
|
|
||||||
struct DfgScope
|
struct DfgScope
|
||||||
{
|
{
|
||||||
|
enum ScopeType
|
||||||
|
{
|
||||||
|
Linear,
|
||||||
|
Loop,
|
||||||
|
Function,
|
||||||
|
};
|
||||||
|
|
||||||
DfgScope* parent;
|
DfgScope* parent;
|
||||||
bool isLoopScope;
|
ScopeType scopeType;
|
||||||
|
|
||||||
using Bindings = DenseHashMap<Symbol, const Def*>;
|
using Bindings = DenseHashMap<Symbol, const Def*>;
|
||||||
using Props = DenseHashMap<const Def*, std::unordered_map<std::string, const Def*>>;
|
using Props = DenseHashMap<const Def*, std::unordered_map<std::string, const Def*>>;
|
||||||
|
@ -117,11 +124,21 @@ private:
|
||||||
|
|
||||||
std::vector<std::unique_ptr<DfgScope>> scopes;
|
std::vector<std::unique_ptr<DfgScope>> scopes;
|
||||||
|
|
||||||
DfgScope* childScope(DfgScope* scope, bool isLoopScope = false);
|
struct FunctionCapture
|
||||||
|
{
|
||||||
|
std::vector<DefId> captureDefs;
|
||||||
|
std::vector<DefId> allVersions;
|
||||||
|
size_t versionOffset = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
DenseHashMap<Symbol, FunctionCapture> captures{Symbol{}};
|
||||||
|
void resolveCaptures();
|
||||||
|
|
||||||
|
DfgScope* childScope(DfgScope* scope, DfgScope::ScopeType scopeType = DfgScope::Linear);
|
||||||
|
|
||||||
void join(DfgScope* p, DfgScope* a, DfgScope* b);
|
void join(DfgScope* p, DfgScope* a, DfgScope* b);
|
||||||
void joinBindings(DfgScope::Bindings& p, const DfgScope::Bindings& a, const DfgScope::Bindings& b);
|
void joinBindings(DfgScope* p, const DfgScope& a, const DfgScope& b);
|
||||||
void joinProps(DfgScope::Props& p, const DfgScope::Props& a, const DfgScope::Props& b);
|
void joinProps(DfgScope* p, const DfgScope& a, const DfgScope& b);
|
||||||
|
|
||||||
DefId lookup(DfgScope* scope, Symbol symbol);
|
DefId lookup(DfgScope* scope, Symbol symbol);
|
||||||
DefId lookup(DfgScope* scope, DefId def, const std::string& key);
|
DefId lookup(DfgScope* scope, DefId def, const std::string& key);
|
||||||
|
@ -167,11 +184,11 @@ private:
|
||||||
DataFlowResult visitExpr(DfgScope* scope, AstExprError* error);
|
DataFlowResult visitExpr(DfgScope* scope, AstExprError* error);
|
||||||
|
|
||||||
void visitLValue(DfgScope* scope, AstExpr* e, DefId incomingDef, bool isCompoundAssignment = false);
|
void visitLValue(DfgScope* scope, AstExpr* e, DefId incomingDef, bool isCompoundAssignment = false);
|
||||||
void visitLValue(DfgScope* scope, AstExprLocal* l, DefId incomingDef, bool isCompoundAssignment);
|
DefId visitLValue(DfgScope* scope, AstExprLocal* l, DefId incomingDef, bool isCompoundAssignment);
|
||||||
void visitLValue(DfgScope* scope, AstExprGlobal* g, DefId incomingDef, bool isCompoundAssignment);
|
DefId visitLValue(DfgScope* scope, AstExprGlobal* g, DefId incomingDef, bool isCompoundAssignment);
|
||||||
void visitLValue(DfgScope* scope, AstExprIndexName* i, DefId incomingDef);
|
DefId visitLValue(DfgScope* scope, AstExprIndexName* i, DefId incomingDef);
|
||||||
void visitLValue(DfgScope* scope, AstExprIndexExpr* i, DefId incomingDef);
|
DefId visitLValue(DfgScope* scope, AstExprIndexExpr* i, DefId incomingDef);
|
||||||
void visitLValue(DfgScope* scope, AstExprError* e, DefId incomingDef);
|
DefId visitLValue(DfgScope* scope, AstExprError* e, DefId incomingDef);
|
||||||
|
|
||||||
void visitType(DfgScope* scope, AstType* t);
|
void visitType(DfgScope* scope, AstType* t);
|
||||||
void visitType(DfgScope* scope, AstTypeReference* r);
|
void visitType(DfgScope* scope, AstTypeReference* r);
|
||||||
|
|
|
@ -73,6 +73,7 @@ const T* get(DefId def)
|
||||||
}
|
}
|
||||||
|
|
||||||
bool containsSubscriptedDefinition(DefId def);
|
bool containsSubscriptedDefinition(DefId def);
|
||||||
|
void collectOperands(DefId def, std::vector<DefId>* operands);
|
||||||
|
|
||||||
struct DefArena
|
struct DefArena
|
||||||
{
|
{
|
||||||
|
|
|
@ -118,6 +118,12 @@ public:
|
||||||
class const_iterator
|
class const_iterator
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
using value_type = T;
|
||||||
|
using reference = T&;
|
||||||
|
using pointer = T*;
|
||||||
|
using difference_type = ptrdiff_t;
|
||||||
|
using iterator_category = std::forward_iterator_tag;
|
||||||
|
|
||||||
const_iterator(typename Impl::const_iterator impl, typename Impl::const_iterator end)
|
const_iterator(typename Impl::const_iterator impl, typename Impl::const_iterator end)
|
||||||
: impl(impl)
|
: impl(impl)
|
||||||
, end(end)
|
, end(end)
|
||||||
|
|
|
@ -176,7 +176,7 @@ struct PrimitiveType
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Singleton types https://github.com/Roblox/luau/blob/master/rfcs/syntax-singleton-types.md
|
// Singleton types https://github.com/luau-lang/rfcs/blob/master/docs/syntax-singleton-types.md
|
||||||
// Types for true and false
|
// Types for true and false
|
||||||
struct BooleanSingleton
|
struct BooleanSingleton
|
||||||
{
|
{
|
||||||
|
|
|
@ -162,6 +162,8 @@ struct BuiltinTypeFamilies
|
||||||
TypeFamily leFamily;
|
TypeFamily leFamily;
|
||||||
TypeFamily eqFamily;
|
TypeFamily eqFamily;
|
||||||
|
|
||||||
|
TypeFamily refineFamily;
|
||||||
|
|
||||||
void addToScope(NotNull<TypeArena> arena, NotNull<Scope> scope) const;
|
void addToScope(NotNull<TypeArena> arena, NotNull<Scope> scope) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -200,6 +200,23 @@ struct AstJsonEncoder : public AstVisitor
|
||||||
{
|
{
|
||||||
writeString(name.value ? name.value : "");
|
writeString(name.value ? name.value : "");
|
||||||
}
|
}
|
||||||
|
void write(std::optional<AstArgumentName> name)
|
||||||
|
{
|
||||||
|
if (name)
|
||||||
|
write(*name);
|
||||||
|
else
|
||||||
|
writeRaw("null");
|
||||||
|
}
|
||||||
|
void write(AstArgumentName name)
|
||||||
|
{
|
||||||
|
writeRaw("{");
|
||||||
|
bool c = pushComma();
|
||||||
|
writeType("AstArgumentName");
|
||||||
|
write("name", name.first);
|
||||||
|
write("location", name.second);
|
||||||
|
popComma(c);
|
||||||
|
writeRaw("}");
|
||||||
|
}
|
||||||
|
|
||||||
void write(const Position& position)
|
void write(const Position& position)
|
||||||
{
|
{
|
||||||
|
@ -848,6 +865,7 @@ struct AstJsonEncoder : public AstVisitor
|
||||||
PROP(generics);
|
PROP(generics);
|
||||||
PROP(genericPacks);
|
PROP(genericPacks);
|
||||||
PROP(argTypes);
|
PROP(argTypes);
|
||||||
|
PROP(argNames);
|
||||||
PROP(returnTypes);
|
PROP(returnTypes);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -902,6 +920,22 @@ struct AstJsonEncoder : public AstVisitor
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool visit(class AstTypeSingletonBool* node) override
|
||||||
|
{
|
||||||
|
writeNode(node, "AstTypeSingletonBool", [&]() {
|
||||||
|
write("value", node->value);
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool visit(class AstTypeSingletonString* node) override
|
||||||
|
{
|
||||||
|
writeNode(node, "AstTypeSingletonString", [&]() {
|
||||||
|
write("value", node->value);
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool visit(class AstExprGroup* node) override
|
bool visit(class AstExprGroup* node) override
|
||||||
{
|
{
|
||||||
write(node);
|
write(node);
|
||||||
|
|
|
@ -205,7 +205,7 @@ ScopePtr ConstraintGenerator::childScope(AstNode* node, const ScopePtr& parent)
|
||||||
return scope;
|
return scope;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<TypeId> ConstraintGenerator::lookup(Scope* scope, DefId def)
|
std::optional<TypeId> ConstraintGenerator::lookup(Scope* scope, DefId def, bool prototype)
|
||||||
{
|
{
|
||||||
if (get<Cell>(def))
|
if (get<Cell>(def))
|
||||||
return scope->lookup(def);
|
return scope->lookup(def);
|
||||||
|
@ -213,22 +213,24 @@ std::optional<TypeId> ConstraintGenerator::lookup(Scope* scope, DefId def)
|
||||||
{
|
{
|
||||||
if (auto found = scope->lookup(def))
|
if (auto found = scope->lookup(def))
|
||||||
return *found;
|
return *found;
|
||||||
|
else if (!prototype)
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
TypeId res = builtinTypes->neverType;
|
TypeId res = builtinTypes->neverType;
|
||||||
|
|
||||||
for (DefId operand : phi->operands)
|
for (DefId operand : phi->operands)
|
||||||
{
|
{
|
||||||
// `scope->lookup(operand)` may return nothing because it could be a phi node of globals, but one of
|
// `scope->lookup(operand)` may return nothing because we only bind a type to that operand
|
||||||
// the operand of that global has never been assigned a type, and so it should be an error.
|
// once we've seen that particular `DefId`. In this case, we need to prototype those types
|
||||||
// e.g.
|
// and use those at a later time.
|
||||||
// ```
|
std::optional<TypeId> ty = lookup(scope, operand, /*prototype*/false);
|
||||||
// if foo() then
|
if (!ty)
|
||||||
// g = 5
|
{
|
||||||
// end
|
ty = arena->addType(BlockedType{});
|
||||||
// -- `g` here is a phi node of the assignment to `g`, or the original revision of `g` before the branch.
|
rootScope->lvalueTypes[operand] = *ty;
|
||||||
// ```
|
}
|
||||||
TypeId ty = scope->lookup(operand).value_or(builtinTypes->errorRecoveryType());
|
|
||||||
res = simplifyUnion(builtinTypes, arena, res, ty).result;
|
res = simplifyUnion(builtinTypes, arena, res, *ty).result;
|
||||||
}
|
}
|
||||||
|
|
||||||
scope->lvalueTypes[def] = res;
|
scope->lvalueTypes[def] = res;
|
||||||
|
@ -255,7 +257,7 @@ void ConstraintGenerator::unionRefinements(const RefinementContext& lhs, const R
|
||||||
return types[0];
|
return types[0];
|
||||||
else if (2 == types.size())
|
else if (2 == types.size())
|
||||||
{
|
{
|
||||||
// TODO: It may be advantageous to create a RefineConstraint here when there are blockedTypes.
|
// 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]);
|
SimplifyResult sr = simplifyIntersection(builtinTypes, arena, types[0], types[1]);
|
||||||
if (sr.blockedTypes.empty())
|
if (sr.blockedTypes.empty())
|
||||||
return sr.result;
|
return sr.result;
|
||||||
|
@ -439,10 +441,14 @@ void ConstraintGenerator::applyRefinements(const ScopePtr& scope, Location locat
|
||||||
{
|
{
|
||||||
if (mustDeferIntersection(ty) || mustDeferIntersection(dt))
|
if (mustDeferIntersection(ty) || mustDeferIntersection(dt))
|
||||||
{
|
{
|
||||||
TypeId r = arena->addType(BlockedType{});
|
TypeId resultType = arena->addType(TypeFamilyInstanceType{
|
||||||
addConstraint(scope, location, RefineConstraint{RefineConstraint::Intersection, r, ty, dt});
|
NotNull{&kBuiltinTypeFamilies.refineFamily},
|
||||||
|
{ty, dt},
|
||||||
|
{},
|
||||||
|
});
|
||||||
|
addConstraint(scope, location, ReduceConstraint{resultType});
|
||||||
|
|
||||||
ty = r;
|
ty = resultType;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -861,7 +867,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f
|
||||||
DenseHashSet<Constraint*> excludeList{nullptr};
|
DenseHashSet<Constraint*> excludeList{nullptr};
|
||||||
|
|
||||||
DefId def = dfg->getDef(function->name);
|
DefId def = dfg->getDef(function->name);
|
||||||
std::optional<TypeId> existingFunctionTy = scope->lookup(def);
|
std::optional<TypeId> existingFunctionTy = lookup(scope.get(), def);
|
||||||
|
|
||||||
if (AstExprLocal* localName = function->name->as<AstExprLocal>())
|
if (AstExprLocal* localName = function->name->as<AstExprLocal>())
|
||||||
{
|
{
|
||||||
|
@ -1003,9 +1009,6 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatAssign* ass
|
||||||
|
|
||||||
checkLValue(scope, lvalue, assignee);
|
checkLValue(scope, lvalue, assignee);
|
||||||
assignees.push_back(assignee);
|
assignees.push_back(assignee);
|
||||||
|
|
||||||
DefId def = dfg->getDef(lvalue);
|
|
||||||
scope->lvalueTypes[def] = assignee;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TypePackId resultPack = checkPack(scope, assign->values).tp;
|
TypePackId resultPack = checkPack(scope, assign->values).tp;
|
||||||
|
@ -1724,16 +1727,14 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprGlobal* globa
|
||||||
/* prepopulateGlobalScope() has already added all global functions to the environment by this point, so any
|
/* 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.
|
* global that is not already in-scope is definitely an unknown symbol.
|
||||||
*/
|
*/
|
||||||
if (auto ty = lookup(scope.get(), def))
|
if (auto ty = lookup(scope.get(), def, /*prototype=*/false))
|
||||||
return Inference{*ty, refinementArena.proposition(key, builtinTypes->truthyType)};
|
|
||||||
else if (auto ty = scope->lookup(global->name))
|
|
||||||
{
|
{
|
||||||
rootScope->lvalueTypes[def] = *ty;
|
rootScope->lvalueTypes[def] = *ty;
|
||||||
return Inference{*ty, refinementArena.proposition(key, builtinTypes->truthyType)};
|
return Inference{*ty, refinementArena.proposition(key, builtinTypes->truthyType)};
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
reportError(global->location, UnknownSymbol{global->name.value});
|
reportError(global->location, UnknownSymbol{global->name.value, UnknownSymbol::Binding});
|
||||||
return Inference{builtinTypes->errorRecoveryType()};
|
return Inference{builtinTypes->errorRecoveryType()};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3110,6 +3111,16 @@ struct GlobalPrepopulator : AstVisitor
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool visit(AstType*) override
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool visit(class AstTypePack* node) override
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
void ConstraintGenerator::prepopulateGlobalScope(const ScopePtr& globalScope, AstStatBlock* program)
|
void ConstraintGenerator::prepopulateGlobalScope(const ScopePtr& globalScope, AstStatBlock* program)
|
||||||
|
|
|
@ -545,8 +545,6 @@ bool ConstraintSolver::tryDispatch(NotNull<const Constraint> constraint, bool fo
|
||||||
success = tryDispatch(*sottc, constraint);
|
success = tryDispatch(*sottc, constraint);
|
||||||
else if (auto uc = get<UnpackConstraint>(*constraint))
|
else if (auto uc = get<UnpackConstraint>(*constraint))
|
||||||
success = tryDispatch(*uc, constraint);
|
success = tryDispatch(*uc, constraint);
|
||||||
else if (auto rc = get<RefineConstraint>(*constraint))
|
|
||||||
success = tryDispatch(*rc, constraint, force);
|
|
||||||
else if (auto soc = get<SetOpConstraint>(*constraint))
|
else if (auto soc = get<SetOpConstraint>(*constraint))
|
||||||
success = tryDispatch(*soc, constraint, force);
|
success = tryDispatch(*soc, constraint, force);
|
||||||
else if (auto rc = get<ReduceConstraint>(*constraint))
|
else if (auto rc = get<ReduceConstraint>(*constraint))
|
||||||
|
@ -887,9 +885,9 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
|
||||||
// In order to prevent infinite types from being expanded and causing us to
|
// In order to prevent infinite types from being expanded and causing us to
|
||||||
// cycle infinitely, we need to scan the type function for cases where we
|
// cycle infinitely, we need to scan the type function for cases where we
|
||||||
// expand the same alias with different type saturatedTypeArguments. See
|
// expand the same alias with different type saturatedTypeArguments. See
|
||||||
// https://github.com/Roblox/luau/pull/68 for the RFC responsible for this.
|
// https://github.com/luau-lang/luau/pull/68 for the RFC responsible for
|
||||||
// This is a little nicer than using a recursion limit because we can catch
|
// this. This is a little nicer than using a recursion limit because we can
|
||||||
// the infinite expansion before actually trying to expand it.
|
// catch the infinite expansion before actually trying to expand it.
|
||||||
InfiniteTypeFinder itf{this, signature, constraint->scope};
|
InfiniteTypeFinder itf{this, signature, constraint->scope};
|
||||||
itf.traverse(tf->type);
|
itf.traverse(tf->type);
|
||||||
|
|
||||||
|
@ -1505,151 +1503,6 @@ bool ConstraintSolver::tryDispatch(const UnpackConstraint& c, NotNull<const Cons
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Search for types that prevent us from being ready to dispatch a particular
|
|
||||||
* RefineConstraint.
|
|
||||||
*/
|
|
||||||
struct FindRefineConstraintBlockers : TypeOnceVisitor
|
|
||||||
{
|
|
||||||
DenseHashSet<TypeId> found{nullptr};
|
|
||||||
bool visit(TypeId ty, const BlockedType&) override
|
|
||||||
{
|
|
||||||
found.insert(ty);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool visit(TypeId ty, const PendingExpansionType&) override
|
|
||||||
{
|
|
||||||
found.insert(ty);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool visit(TypeId ty, const ClassType&) override
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
static bool isNegatedAny(TypeId ty)
|
|
||||||
{
|
|
||||||
ty = follow(ty);
|
|
||||||
const NegationType* nt = get<NegationType>(ty);
|
|
||||||
if (!nt)
|
|
||||||
return false;
|
|
||||||
TypeId negatedTy = follow(nt->ty);
|
|
||||||
return bool(get<AnyType>(negatedTy));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ConstraintSolver::tryDispatch(const RefineConstraint& c, NotNull<const Constraint> constraint, bool force)
|
|
||||||
{
|
|
||||||
if (isBlocked(c.discriminant))
|
|
||||||
return block(c.discriminant, constraint);
|
|
||||||
|
|
||||||
FindRefineConstraintBlockers fbt;
|
|
||||||
fbt.traverse(c.discriminant);
|
|
||||||
|
|
||||||
if (!fbt.found.empty())
|
|
||||||
{
|
|
||||||
bool foundOne = false;
|
|
||||||
|
|
||||||
for (TypeId blocked : fbt.found)
|
|
||||||
{
|
|
||||||
if (blocked == c.type)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
block(blocked, constraint);
|
|
||||||
foundOne = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (foundOne)
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* HACK: Refinements sometimes produce a type T & ~any under the assumption
|
|
||||||
* that ~any is the same as any. This is so so weird, but refinements needs
|
|
||||||
* some way to say "I may refine this, but I'm not sure."
|
|
||||||
*
|
|
||||||
* It does this by refining on a blocked type and deferring the decision
|
|
||||||
* until it is unblocked.
|
|
||||||
*
|
|
||||||
* Refinements also get negated, so we wind up with types like T & ~*blocked*
|
|
||||||
*
|
|
||||||
* We need to treat T & ~any as T in this case.
|
|
||||||
*/
|
|
||||||
|
|
||||||
if (c.mode == RefineConstraint::Intersection && isNegatedAny(c.discriminant))
|
|
||||||
{
|
|
||||||
asMutable(c.resultType)->ty.emplace<BoundType>(c.type);
|
|
||||||
unblock(c.resultType, constraint->location);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const TypeId type = follow(c.type);
|
|
||||||
|
|
||||||
if (hasUnresolvedConstraints(type))
|
|
||||||
return block(type, constraint);
|
|
||||||
|
|
||||||
LUAU_ASSERT(get<BlockedType>(c.resultType));
|
|
||||||
|
|
||||||
if (type == c.resultType)
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* Sometimes, we get a constraint of the form
|
|
||||||
*
|
|
||||||
* *blocked-N* ~ refine *blocked-N* & U
|
|
||||||
*
|
|
||||||
* The constraint essentially states that a particular type is a
|
|
||||||
* refinement of itself. This is weird and I think vacuous.
|
|
||||||
*
|
|
||||||
* I *believe* it is safe to replace the result with a fresh type that
|
|
||||||
* is constrained by U. We effect this by minting a fresh type for the
|
|
||||||
* result when U = any, else we bind the result to whatever discriminant
|
|
||||||
* was offered.
|
|
||||||
*/
|
|
||||||
if (get<AnyType>(follow(c.discriminant)))
|
|
||||||
{
|
|
||||||
TypeId f = freshType(arena, builtinTypes, constraint->scope);
|
|
||||||
asMutable(c.resultType)->ty.emplace<BoundType>(f);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
asMutable(c.resultType)->ty.emplace<BoundType>(c.discriminant);
|
|
||||||
|
|
||||||
unblock(c.resultType, constraint->location);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto [result, blockedTypes] = c.mode == RefineConstraint::Intersection ? simplifyIntersection(builtinTypes, NotNull{arena}, type, c.discriminant)
|
|
||||||
: simplifyUnion(builtinTypes, NotNull{arena}, type, c.discriminant);
|
|
||||||
|
|
||||||
if (!force && !blockedTypes.empty())
|
|
||||||
return block(blockedTypes, constraint);
|
|
||||||
|
|
||||||
switch (shouldSuppressErrors(normalizer, c.type))
|
|
||||||
{
|
|
||||||
case ErrorSuppression::Suppress:
|
|
||||||
{
|
|
||||||
auto resultOrError = simplifyUnion(builtinTypes, arena, result, builtinTypes->errorType).result;
|
|
||||||
asMutable(c.resultType)->ty.emplace<BoundType>(resultOrError);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ErrorSuppression::DoNotSuppress:
|
|
||||||
asMutable(c.resultType)->ty.emplace<BoundType>(result);
|
|
||||||
break;
|
|
||||||
case ErrorSuppression::NormalizationFailed:
|
|
||||||
reportError(NormalizationTooComplex{}, constraint->location);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
unblock(c.resultType, constraint->location);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ConstraintSolver::tryDispatch(const SetOpConstraint& c, NotNull<const Constraint> constraint, bool force)
|
bool ConstraintSolver::tryDispatch(const SetOpConstraint& c, NotNull<const Constraint> constraint, bool force)
|
||||||
{
|
{
|
||||||
bool blocked = false;
|
bool blocked = false;
|
||||||
|
|
|
@ -116,7 +116,7 @@ bool DfgScope::canUpdateDefinition(Symbol symbol) const
|
||||||
{
|
{
|
||||||
if (current->bindings.find(symbol))
|
if (current->bindings.find(symbol))
|
||||||
return true;
|
return true;
|
||||||
else if (current->isLoopScope)
|
else if (current->scopeType == DfgScope::Loop)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,7 +129,7 @@ bool DfgScope::canUpdateDefinition(DefId def, const std::string& key) const
|
||||||
{
|
{
|
||||||
if (auto props = current->props.find(def))
|
if (auto props = current->props.find(def))
|
||||||
return true;
|
return true;
|
||||||
else if (current->isLoopScope)
|
else if (current->scopeType == DfgScope::Loop)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,6 +144,7 @@ DataFlowGraph DataFlowGraphBuilder::build(AstStatBlock* block, NotNull<InternalE
|
||||||
builder.handle = handle;
|
builder.handle = handle;
|
||||||
builder.moduleScope = builder.childScope(nullptr); // nullptr is the root DFG scope.
|
builder.moduleScope = builder.childScope(nullptr); // nullptr is the root DFG scope.
|
||||||
builder.visitBlockWithoutChildScope(builder.moduleScope, block);
|
builder.visitBlockWithoutChildScope(builder.moduleScope, block);
|
||||||
|
builder.resolveCaptures();
|
||||||
|
|
||||||
if (FFlag::DebugLuauFreezeArena)
|
if (FFlag::DebugLuauFreezeArena)
|
||||||
{
|
{
|
||||||
|
@ -154,43 +155,64 @@ DataFlowGraph DataFlowGraphBuilder::build(AstStatBlock* block, NotNull<InternalE
|
||||||
return std::move(builder.graph);
|
return std::move(builder.graph);
|
||||||
}
|
}
|
||||||
|
|
||||||
DfgScope* DataFlowGraphBuilder::childScope(DfgScope* scope, bool isLoopScope)
|
void DataFlowGraphBuilder::resolveCaptures()
|
||||||
{
|
{
|
||||||
return scopes.emplace_back(new DfgScope{scope, isLoopScope}).get();
|
for (const auto& [_, capture] : captures)
|
||||||
|
{
|
||||||
|
std::vector<DefId> operands;
|
||||||
|
for (size_t i = capture.versionOffset; i < capture.allVersions.size(); ++i)
|
||||||
|
collectOperands(capture.allVersions[i], &operands);
|
||||||
|
|
||||||
|
for (DefId captureDef : capture.captureDefs)
|
||||||
|
{
|
||||||
|
Phi* phi = const_cast<Phi*>(get<Phi>(captureDef));
|
||||||
|
LUAU_ASSERT(phi);
|
||||||
|
LUAU_ASSERT(phi->operands.empty());
|
||||||
|
phi->operands = operands;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DfgScope* DataFlowGraphBuilder::childScope(DfgScope* scope, DfgScope::ScopeType scopeType)
|
||||||
|
{
|
||||||
|
return scopes.emplace_back(new DfgScope{scope, scopeType}).get();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DataFlowGraphBuilder::join(DfgScope* p, DfgScope* a, DfgScope* b)
|
void DataFlowGraphBuilder::join(DfgScope* p, DfgScope* a, DfgScope* b)
|
||||||
{
|
{
|
||||||
joinBindings(p->bindings, a->bindings, b->bindings);
|
joinBindings(p, *a, *b);
|
||||||
joinProps(p->props, a->props, b->props);
|
joinProps(p, *a, *b);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DataFlowGraphBuilder::joinBindings(DfgScope::Bindings& p, const DfgScope::Bindings& a, const DfgScope::Bindings& b)
|
void DataFlowGraphBuilder::joinBindings(DfgScope* p, const DfgScope& a, const DfgScope& b)
|
||||||
{
|
{
|
||||||
for (const auto& [sym, def1] : a)
|
for (const auto& [sym, def1] : a.bindings)
|
||||||
{
|
{
|
||||||
if (auto def2 = b.find(sym))
|
if (auto def2 = b.bindings.find(sym))
|
||||||
p[sym] = defArena->phi(NotNull{def1}, NotNull{*def2});
|
p->bindings[sym] = defArena->phi(NotNull{def1}, NotNull{*def2});
|
||||||
else if (auto def2 = p.find(sym))
|
else if (auto def2 = p->lookup(sym))
|
||||||
p[sym] = defArena->phi(NotNull{def1}, NotNull{*def2});
|
p->bindings[sym] = defArena->phi(NotNull{def1}, NotNull{*def2});
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& [sym, def1] : b)
|
for (const auto& [sym, def1] : b.bindings)
|
||||||
{
|
{
|
||||||
if (auto def2 = p.find(sym))
|
if (auto def2 = p->lookup(sym))
|
||||||
p[sym] = defArena->phi(NotNull{def1}, NotNull{*def2});
|
p->bindings[sym] = defArena->phi(NotNull{def1}, NotNull{*def2});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DataFlowGraphBuilder::joinProps(DfgScope::Props& p, const DfgScope::Props& a, const DfgScope::Props& b)
|
void DataFlowGraphBuilder::joinProps(DfgScope* result, const DfgScope& a, const DfgScope& b)
|
||||||
{
|
{
|
||||||
auto phinodify = [this](auto& p, const auto& a, const auto& b) mutable {
|
auto phinodify = [this](DfgScope* scope, const auto& a, const auto& b, DefId parent) mutable {
|
||||||
|
auto& p = scope->props[parent];
|
||||||
for (const auto& [k, defA] : a)
|
for (const auto& [k, defA] : a)
|
||||||
{
|
{
|
||||||
if (auto it = b.find(k); it != b.end())
|
if (auto it = b.find(k); it != b.end())
|
||||||
p[k] = defArena->phi(NotNull{it->second}, NotNull{defA});
|
p[k] = defArena->phi(NotNull{it->second}, NotNull{defA});
|
||||||
else if (auto it = p.find(k); it != p.end())
|
else if (auto it = p.find(k); it != p.end())
|
||||||
p[k] = defArena->phi(NotNull{it->second}, NotNull{defA});
|
p[k] = defArena->phi(NotNull{it->second}, NotNull{defA});
|
||||||
|
else if (auto def2 = scope->lookup(parent, k))
|
||||||
|
p[k] = defArena->phi(*def2, NotNull{defA});
|
||||||
else
|
else
|
||||||
p[k] = defA;
|
p[k] = defA;
|
||||||
}
|
}
|
||||||
|
@ -201,50 +223,72 @@ void DataFlowGraphBuilder::joinProps(DfgScope::Props& p, const DfgScope::Props&
|
||||||
continue;
|
continue;
|
||||||
else if (auto it = p.find(k); it != p.end())
|
else if (auto it = p.find(k); it != p.end())
|
||||||
p[k] = defArena->phi(NotNull{it->second}, NotNull{defB});
|
p[k] = defArena->phi(NotNull{it->second}, NotNull{defB});
|
||||||
|
else if (auto def2 = scope->lookup(parent, k))
|
||||||
|
p[k] = defArena->phi(*def2, NotNull{defB});
|
||||||
else
|
else
|
||||||
p[k] = defB;
|
p[k] = defB;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const auto& [def, a1] : a)
|
for (const auto& [def, a1] : a.props)
|
||||||
{
|
{
|
||||||
p.try_insert(def, {});
|
result->props.try_insert(def, {});
|
||||||
if (auto a2 = b.find(def))
|
if (auto a2 = b.props.find(def))
|
||||||
phinodify(p[def], a1, *a2);
|
phinodify(result, a1, *a2, NotNull{def});
|
||||||
else if (auto a2 = p.find(def))
|
else if (auto a2 = result->props.find(def))
|
||||||
phinodify(p[def], a1, *a2);
|
phinodify(result, a1, *a2, NotNull{def});
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& [def, a1] : b)
|
for (const auto& [def, a1] : b.props)
|
||||||
{
|
{
|
||||||
p.try_insert(def, {});
|
result->props.try_insert(def, {});
|
||||||
if (a.find(def))
|
if (a.props.find(def))
|
||||||
continue;
|
continue;
|
||||||
else if (auto a2 = p.find(def))
|
else if (auto a2 = result->props.find(def))
|
||||||
phinodify(p[def], a1, *a2);
|
phinodify(result, a1, *a2, NotNull{def});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DefId DataFlowGraphBuilder::lookup(DfgScope* scope, Symbol symbol)
|
DefId DataFlowGraphBuilder::lookup(DfgScope* scope, Symbol symbol)
|
||||||
{
|
{
|
||||||
if (auto found = scope->lookup(symbol))
|
for (DfgScope* current = scope; current; current = current->parent)
|
||||||
return *found;
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
DefId result = defArena->freshCell();
|
if (auto found = current->bindings.find(symbol))
|
||||||
if (symbol.local)
|
return NotNull{*found};
|
||||||
scope->bindings[symbol] = result;
|
else if (current->scopeType == DfgScope::Function)
|
||||||
else
|
{
|
||||||
moduleScope->bindings[symbol] = result;
|
FunctionCapture& capture = captures[symbol];
|
||||||
return result;
|
DefId captureDef = defArena->phi({});
|
||||||
|
capture.captureDefs.push_back(captureDef);
|
||||||
|
scope->bindings[symbol] = captureDef;
|
||||||
|
return NotNull{captureDef};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DefId result = defArena->freshCell();
|
||||||
|
scope->bindings[symbol] = result;
|
||||||
|
captures[symbol].allVersions.push_back(result);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
DefId DataFlowGraphBuilder::lookup(DfgScope* scope, DefId def, const std::string& key)
|
DefId DataFlowGraphBuilder::lookup(DfgScope* scope, DefId def, const std::string& key)
|
||||||
{
|
{
|
||||||
if (auto found = scope->lookup(def, key))
|
for (DfgScope* current = scope; current; current = current->parent)
|
||||||
return *found;
|
{
|
||||||
else if (auto phi = get<Phi>(def))
|
if (auto props = current->props.find(def))
|
||||||
|
{
|
||||||
|
if (auto it = props->find(key); it != props->end())
|
||||||
|
return NotNull{it->second};
|
||||||
|
}
|
||||||
|
else if (auto phi = get<Phi>(def); phi && phi->operands.empty()) // Unresolved phi nodes
|
||||||
|
{
|
||||||
|
DefId result = defArena->freshCell();
|
||||||
|
scope->props[def][key] = result;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto phi = get<Phi>(def))
|
||||||
{
|
{
|
||||||
std::vector<DefId> defs;
|
std::vector<DefId> defs;
|
||||||
for (DefId operand : phi->operands)
|
for (DefId operand : phi->operands)
|
||||||
|
@ -361,7 +405,7 @@ ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatIf* i)
|
||||||
ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatWhile* w)
|
ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatWhile* w)
|
||||||
{
|
{
|
||||||
// TODO(controlflow): entry point has a back edge from exit point
|
// TODO(controlflow): entry point has a back edge from exit point
|
||||||
DfgScope* whileScope = childScope(scope, /*isLoopScope=*/true);
|
DfgScope* whileScope = childScope(scope, DfgScope::Loop);
|
||||||
visitExpr(whileScope, w->condition);
|
visitExpr(whileScope, w->condition);
|
||||||
visit(whileScope, w->body);
|
visit(whileScope, w->body);
|
||||||
|
|
||||||
|
@ -373,7 +417,7 @@ ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatWhile* w)
|
||||||
ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatRepeat* r)
|
ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatRepeat* r)
|
||||||
{
|
{
|
||||||
// TODO(controlflow): entry point has a back edge from exit point
|
// TODO(controlflow): entry point has a back edge from exit point
|
||||||
DfgScope* repeatScope = childScope(scope, /*isLoopScope=*/true);
|
DfgScope* repeatScope = childScope(scope, DfgScope::Loop);
|
||||||
visitBlockWithoutChildScope(repeatScope, r->body);
|
visitBlockWithoutChildScope(repeatScope, r->body);
|
||||||
visitExpr(repeatScope, r->condition);
|
visitExpr(repeatScope, r->condition);
|
||||||
|
|
||||||
|
@ -427,8 +471,17 @@ ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatLocal* l)
|
||||||
// make sure that the non-aliased defs are also marked as a subscript for refinements.
|
// make sure that the non-aliased defs are also marked as a subscript for refinements.
|
||||||
bool subscripted = i < defs.size() && containsSubscriptedDefinition(defs[i]);
|
bool subscripted = i < defs.size() && containsSubscriptedDefinition(defs[i]);
|
||||||
DefId def = defArena->freshCell(subscripted);
|
DefId def = defArena->freshCell(subscripted);
|
||||||
|
if (i < l->values.size)
|
||||||
|
{
|
||||||
|
AstExpr* e = l->values.data[i];
|
||||||
|
if (const AstExprTable* tbl = e->as<AstExprTable>())
|
||||||
|
{
|
||||||
|
def = defs[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
graph.localDefs[local] = def;
|
graph.localDefs[local] = def;
|
||||||
scope->bindings[local] = def;
|
scope->bindings[local] = def;
|
||||||
|
captures[local].allVersions.push_back(def);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ControlFlow::None;
|
return ControlFlow::None;
|
||||||
|
@ -436,7 +489,7 @@ ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatLocal* l)
|
||||||
|
|
||||||
ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatFor* f)
|
ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatFor* f)
|
||||||
{
|
{
|
||||||
DfgScope* forScope = childScope(scope, /*isLoopScope=*/true);
|
DfgScope* forScope = childScope(scope, DfgScope::Loop);
|
||||||
|
|
||||||
visitExpr(scope, f->from);
|
visitExpr(scope, f->from);
|
||||||
visitExpr(scope, f->to);
|
visitExpr(scope, f->to);
|
||||||
|
@ -449,6 +502,7 @@ ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatFor* f)
|
||||||
DefId def = defArena->freshCell();
|
DefId def = defArena->freshCell();
|
||||||
graph.localDefs[f->var] = def;
|
graph.localDefs[f->var] = def;
|
||||||
scope->bindings[f->var] = def;
|
scope->bindings[f->var] = def;
|
||||||
|
captures[f->var].allVersions.push_back(def);
|
||||||
|
|
||||||
// TODO(controlflow): entry point has a back edge from exit point
|
// TODO(controlflow): entry point has a back edge from exit point
|
||||||
visit(forScope, f->body);
|
visit(forScope, f->body);
|
||||||
|
@ -460,7 +514,7 @@ ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatFor* f)
|
||||||
|
|
||||||
ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatForIn* f)
|
ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatForIn* f)
|
||||||
{
|
{
|
||||||
DfgScope* forScope = childScope(scope, /*isLoopScope=*/true);
|
DfgScope* forScope = childScope(scope, DfgScope::Loop);
|
||||||
|
|
||||||
for (AstLocal* local : f->vars)
|
for (AstLocal* local : f->vars)
|
||||||
{
|
{
|
||||||
|
@ -470,6 +524,7 @@ ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatForIn* f)
|
||||||
DefId def = defArena->freshCell();
|
DefId def = defArena->freshCell();
|
||||||
graph.localDefs[local] = def;
|
graph.localDefs[local] = def;
|
||||||
forScope->bindings[local] = def;
|
forScope->bindings[local] = def;
|
||||||
|
captures[local].allVersions.push_back(def);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(controlflow): entry point has a back edge from exit point
|
// TODO(controlflow): entry point has a back edge from exit point
|
||||||
|
@ -527,10 +582,21 @@ ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatFunction* f)
|
||||||
//
|
//
|
||||||
// which is evidence that references to variables must be a phi node of all possible definitions,
|
// which is evidence that references to variables must be a phi node of all possible definitions,
|
||||||
// but for bug compatibility, we'll assume the same thing here.
|
// but for bug compatibility, we'll assume the same thing here.
|
||||||
DefId prototype = defArena->freshCell();
|
visitLValue(scope, f->name, defArena->freshCell());
|
||||||
visitLValue(scope, f->name, prototype);
|
|
||||||
visitExpr(scope, f->func);
|
visitExpr(scope, f->func);
|
||||||
|
|
||||||
|
if (auto local = f->name->as<AstExprLocal>())
|
||||||
|
{
|
||||||
|
// local f
|
||||||
|
// function f()
|
||||||
|
// if cond() then
|
||||||
|
// f() -- should reference only the function version and other future version, and nothing prior
|
||||||
|
// end
|
||||||
|
// end
|
||||||
|
FunctionCapture& capture = captures[local->local];
|
||||||
|
capture.versionOffset = capture.allVersions.size() - 1;
|
||||||
|
}
|
||||||
|
|
||||||
return ControlFlow::None;
|
return ControlFlow::None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -539,6 +605,7 @@ ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatLocalFunction* l
|
||||||
DefId def = defArena->freshCell();
|
DefId def = defArena->freshCell();
|
||||||
graph.localDefs[l->name] = def;
|
graph.localDefs[l->name] = def;
|
||||||
scope->bindings[l->name] = def;
|
scope->bindings[l->name] = def;
|
||||||
|
captures[l->name].allVersions.push_back(def);
|
||||||
visitExpr(scope, l->func);
|
visitExpr(scope, l->func);
|
||||||
|
|
||||||
return ControlFlow::None;
|
return ControlFlow::None;
|
||||||
|
@ -559,6 +626,7 @@ ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatDeclareGlobal* d
|
||||||
DefId def = defArena->freshCell();
|
DefId def = defArena->freshCell();
|
||||||
graph.declaredDefs[d] = def;
|
graph.declaredDefs[d] = def;
|
||||||
scope->bindings[d->name] = def;
|
scope->bindings[d->name] = def;
|
||||||
|
captures[d->name].allVersions.push_back(def);
|
||||||
|
|
||||||
visitType(scope, d->type);
|
visitType(scope, d->type);
|
||||||
|
|
||||||
|
@ -570,6 +638,7 @@ ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatDeclareFunction*
|
||||||
DefId def = defArena->freshCell();
|
DefId def = defArena->freshCell();
|
||||||
graph.declaredDefs[d] = def;
|
graph.declaredDefs[d] = def;
|
||||||
scope->bindings[d->name] = def;
|
scope->bindings[d->name] = def;
|
||||||
|
captures[d->name].allVersions.push_back(def);
|
||||||
|
|
||||||
DfgScope* unreachable = childScope(scope);
|
DfgScope* unreachable = childScope(scope);
|
||||||
visitGenerics(unreachable, d->generics);
|
visitGenerics(unreachable, d->generics);
|
||||||
|
@ -669,14 +738,9 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprGroup* gr
|
||||||
|
|
||||||
DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprLocal* l)
|
DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprLocal* l)
|
||||||
{
|
{
|
||||||
// DfgScope::lookup is intentional here: we want to be able to ice.
|
DefId def = lookup(scope, l->local);
|
||||||
if (auto def = scope->lookup(l->local))
|
const RefinementKey* key = keyArena->leaf(def);
|
||||||
{
|
return {def, key};
|
||||||
const RefinementKey* key = keyArena->leaf(*def);
|
|
||||||
return {*def, key};
|
|
||||||
}
|
|
||||||
|
|
||||||
handle->ice("DFG: AstExprLocal came before its declaration?");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprGlobal* g)
|
DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprGlobal* g)
|
||||||
|
@ -718,12 +782,12 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprIndexExpr
|
||||||
return {def, keyArena->node(parentKey, def, index)};
|
return {def, keyArena->node(parentKey, def, index)};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {defArena->freshCell(/* subscripted= */true), nullptr};
|
return {defArena->freshCell(/* subscripted= */ true), nullptr};
|
||||||
}
|
}
|
||||||
|
|
||||||
DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprFunction* f)
|
DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprFunction* f)
|
||||||
{
|
{
|
||||||
DfgScope* signatureScope = childScope(scope);
|
DfgScope* signatureScope = childScope(scope, DfgScope::Function);
|
||||||
|
|
||||||
if (AstLocal* self = f->self)
|
if (AstLocal* self = f->self)
|
||||||
{
|
{
|
||||||
|
@ -733,6 +797,7 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprFunction*
|
||||||
DefId def = defArena->freshCell();
|
DefId def = defArena->freshCell();
|
||||||
graph.localDefs[self] = def;
|
graph.localDefs[self] = def;
|
||||||
signatureScope->bindings[self] = def;
|
signatureScope->bindings[self] = def;
|
||||||
|
captures[self].allVersions.push_back(def);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (AstLocal* param : f->args)
|
for (AstLocal* param : f->args)
|
||||||
|
@ -743,6 +808,7 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprFunction*
|
||||||
DefId def = defArena->freshCell();
|
DefId def = defArena->freshCell();
|
||||||
graph.localDefs[param] = def;
|
graph.localDefs[param] = def;
|
||||||
signatureScope->bindings[param] = def;
|
signatureScope->bindings[param] = def;
|
||||||
|
captures[param].allVersions.push_back(def);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (f->varargAnnotation)
|
if (f->varargAnnotation)
|
||||||
|
@ -766,14 +832,20 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprFunction*
|
||||||
|
|
||||||
DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprTable* t)
|
DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprTable* t)
|
||||||
{
|
{
|
||||||
|
DefId tableCell = defArena->freshCell();
|
||||||
|
scope->props[tableCell] = {};
|
||||||
for (AstExprTable::Item item : t->items)
|
for (AstExprTable::Item item : t->items)
|
||||||
{
|
{
|
||||||
|
DataFlowResult result = visitExpr(scope, item.value);
|
||||||
if (item.key)
|
if (item.key)
|
||||||
|
{
|
||||||
visitExpr(scope, item.key);
|
visitExpr(scope, item.key);
|
||||||
visitExpr(scope, item.value);
|
if (auto string = item.key->as<AstExprConstantString>())
|
||||||
|
scope->props[tableCell][string->value.data] = result.def;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {defArena->freshCell(), nullptr};
|
return {tableCell, nullptr};
|
||||||
}
|
}
|
||||||
|
|
||||||
DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprUnary* u)
|
DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprUnary* u)
|
||||||
|
@ -827,41 +899,46 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprError* er
|
||||||
|
|
||||||
void DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExpr* e, DefId incomingDef, bool isCompoundAssignment)
|
void DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExpr* e, DefId incomingDef, bool isCompoundAssignment)
|
||||||
{
|
{
|
||||||
if (auto l = e->as<AstExprLocal>())
|
auto go = [&]() {
|
||||||
return visitLValue(scope, l, incomingDef, isCompoundAssignment);
|
if (auto l = e->as<AstExprLocal>())
|
||||||
else if (auto g = e->as<AstExprGlobal>())
|
return visitLValue(scope, l, incomingDef, isCompoundAssignment);
|
||||||
return visitLValue(scope, g, incomingDef, isCompoundAssignment);
|
else if (auto g = e->as<AstExprGlobal>())
|
||||||
else if (auto i = e->as<AstExprIndexName>())
|
return visitLValue(scope, g, incomingDef, isCompoundAssignment);
|
||||||
return visitLValue(scope, i, incomingDef);
|
else if (auto i = e->as<AstExprIndexName>())
|
||||||
else if (auto i = e->as<AstExprIndexExpr>())
|
return visitLValue(scope, i, incomingDef);
|
||||||
return visitLValue(scope, i, incomingDef);
|
else if (auto i = e->as<AstExprIndexExpr>())
|
||||||
else if (auto error = e->as<AstExprError>())
|
return visitLValue(scope, i, incomingDef);
|
||||||
return visitLValue(scope, error, incomingDef);
|
else if (auto error = e->as<AstExprError>())
|
||||||
else
|
return visitLValue(scope, error, incomingDef);
|
||||||
handle->ice("Unknown AstExpr in DataFlowGraphBuilder::visitLValue");
|
else
|
||||||
|
handle->ice("Unknown AstExpr in DataFlowGraphBuilder::visitLValue");
|
||||||
|
};
|
||||||
|
|
||||||
|
graph.astDefs[e] = go();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExprLocal* l, DefId incomingDef, bool isCompoundAssignment)
|
DefId DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExprLocal* l, DefId incomingDef, bool isCompoundAssignment)
|
||||||
{
|
{
|
||||||
// We need to keep the previous def around for a compound assignment.
|
// We need to keep the previous def around for a compound assignment.
|
||||||
if (isCompoundAssignment)
|
if (isCompoundAssignment)
|
||||||
{
|
{
|
||||||
if (auto def = scope->lookup(l->local))
|
DefId def = lookup(scope, l->local);
|
||||||
graph.compoundAssignDefs[l] = *def;
|
graph.compoundAssignDefs[l] = def;
|
||||||
}
|
}
|
||||||
|
|
||||||
// In order to avoid alias tracking, we need to clip the reference to the parent def.
|
// In order to avoid alias tracking, we need to clip the reference to the parent def.
|
||||||
if (scope->canUpdateDefinition(l->local))
|
if (scope->canUpdateDefinition(l->local))
|
||||||
{
|
{
|
||||||
DefId updated = defArena->freshCell(containsSubscriptedDefinition(incomingDef));
|
DefId updated = defArena->freshCell(containsSubscriptedDefinition(incomingDef));
|
||||||
graph.astDefs[l] = updated;
|
|
||||||
scope->bindings[l->local] = updated;
|
scope->bindings[l->local] = updated;
|
||||||
|
captures[l->local].allVersions.push_back(updated);
|
||||||
|
return updated;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
visitExpr(scope, static_cast<AstExpr*>(l));
|
return visitExpr(scope, static_cast<AstExpr*>(l)).def;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExprGlobal* g, DefId incomingDef, bool isCompoundAssignment)
|
DefId DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExprGlobal* g, DefId incomingDef, bool isCompoundAssignment)
|
||||||
{
|
{
|
||||||
// We need to keep the previous def around for a compound assignment.
|
// We need to keep the previous def around for a compound assignment.
|
||||||
if (isCompoundAssignment)
|
if (isCompoundAssignment)
|
||||||
|
@ -874,28 +951,29 @@ void DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExprGlobal* g, DefId
|
||||||
if (scope->canUpdateDefinition(g->name))
|
if (scope->canUpdateDefinition(g->name))
|
||||||
{
|
{
|
||||||
DefId updated = defArena->freshCell(containsSubscriptedDefinition(incomingDef));
|
DefId updated = defArena->freshCell(containsSubscriptedDefinition(incomingDef));
|
||||||
graph.astDefs[g] = updated;
|
|
||||||
scope->bindings[g->name] = updated;
|
scope->bindings[g->name] = updated;
|
||||||
|
captures[g->name].allVersions.push_back(updated);
|
||||||
|
return updated;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
visitExpr(scope, static_cast<AstExpr*>(g));
|
return visitExpr(scope, static_cast<AstExpr*>(g)).def;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExprIndexName* i, DefId incomingDef)
|
DefId DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExprIndexName* i, DefId incomingDef)
|
||||||
{
|
{
|
||||||
DefId parentDef = visitExpr(scope, i->expr).def;
|
DefId parentDef = visitExpr(scope, i->expr).def;
|
||||||
|
|
||||||
if (scope->canUpdateDefinition(parentDef, i->index.value))
|
if (scope->canUpdateDefinition(parentDef, i->index.value))
|
||||||
{
|
{
|
||||||
DefId updated = defArena->freshCell(containsSubscriptedDefinition(incomingDef));
|
DefId updated = defArena->freshCell(containsSubscriptedDefinition(incomingDef));
|
||||||
graph.astDefs[i] = updated;
|
|
||||||
scope->props[parentDef][i->index.value] = updated;
|
scope->props[parentDef][i->index.value] = updated;
|
||||||
|
return updated;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
visitExpr(scope, static_cast<AstExpr*>(i));
|
return visitExpr(scope, static_cast<AstExpr*>(i)).def;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExprIndexExpr* i, DefId incomingDef)
|
DefId DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExprIndexExpr* i, DefId incomingDef)
|
||||||
{
|
{
|
||||||
DefId parentDef = visitExpr(scope, i->expr).def;
|
DefId parentDef = visitExpr(scope, i->expr).def;
|
||||||
visitExpr(scope, i->index);
|
visitExpr(scope, i->index);
|
||||||
|
@ -905,20 +983,19 @@ void DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExprIndexExpr* i, Def
|
||||||
if (scope->canUpdateDefinition(parentDef, string->value.data))
|
if (scope->canUpdateDefinition(parentDef, string->value.data))
|
||||||
{
|
{
|
||||||
DefId updated = defArena->freshCell(containsSubscriptedDefinition(incomingDef));
|
DefId updated = defArena->freshCell(containsSubscriptedDefinition(incomingDef));
|
||||||
graph.astDefs[i] = updated;
|
|
||||||
scope->props[parentDef][string->value.data] = updated;
|
scope->props[parentDef][string->value.data] = updated;
|
||||||
|
return updated;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
visitExpr(scope, static_cast<AstExpr*>(i));
|
return visitExpr(scope, static_cast<AstExpr*>(i)).def;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
graph.astDefs[i] = defArena->freshCell();
|
return defArena->freshCell(/*subscripted=*/true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExprError* error, DefId incomingDef)
|
DefId DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExprError* error, DefId incomingDef)
|
||||||
{
|
{
|
||||||
DefId def = visitExpr(scope, error).def;
|
return visitExpr(scope, error).def;
|
||||||
graph.astDefs[error] = def;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DataFlowGraphBuilder::visitType(DfgScope* scope, AstType* t)
|
void DataFlowGraphBuilder::visitType(DfgScope* scope, AstType* t)
|
||||||
|
|
|
@ -19,17 +19,13 @@ bool containsSubscriptedDefinition(DefId def)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
DefId DefArena::freshCell(bool subscripted)
|
void collectOperands(DefId def, std::vector<DefId>* operands)
|
||||||
{
|
{
|
||||||
return NotNull{allocator.allocate(Def{Cell{subscripted}})};
|
LUAU_ASSERT(operands);
|
||||||
}
|
if (std::find(operands->begin(), operands->end(), def) != operands->end())
|
||||||
|
|
||||||
static void collectOperands(DefId def, std::vector<DefId>& operands)
|
|
||||||
{
|
|
||||||
if (std::find(operands.begin(), operands.end(), def) != operands.end())
|
|
||||||
return;
|
return;
|
||||||
else if (get<Cell>(def))
|
else if (get<Cell>(def))
|
||||||
operands.push_back(def);
|
operands->push_back(def);
|
||||||
else if (auto phi = get<Phi>(def))
|
else if (auto phi = get<Phi>(def))
|
||||||
{
|
{
|
||||||
for (const Def* operand : phi->operands)
|
for (const Def* operand : phi->operands)
|
||||||
|
@ -37,6 +33,11 @@ static void collectOperands(DefId def, std::vector<DefId>& operands)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DefId DefArena::freshCell(bool subscripted)
|
||||||
|
{
|
||||||
|
return NotNull{allocator.allocate(Def{Cell{subscripted}})};
|
||||||
|
}
|
||||||
|
|
||||||
DefId DefArena::phi(DefId a, DefId b)
|
DefId DefArena::phi(DefId a, DefId b)
|
||||||
{
|
{
|
||||||
return phi({a, b});
|
return phi({a, b});
|
||||||
|
@ -46,7 +47,7 @@ DefId DefArena::phi(const std::vector<DefId>& defs)
|
||||||
{
|
{
|
||||||
std::vector<DefId> operands;
|
std::vector<DefId> operands;
|
||||||
for (DefId operand : defs)
|
for (DefId operand : defs)
|
||||||
collectOperands(operand, operands);
|
collectOperands(operand, &operands);
|
||||||
|
|
||||||
// There's no need to allocate a Phi node for a singleton set.
|
// There's no need to allocate a Phi node for a singleton set.
|
||||||
if (operands.size() == 1)
|
if (operands.size() == 1)
|
||||||
|
|
|
@ -32,13 +32,12 @@ LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||||
LUAU_FASTINT(LuauTarjanChildLimit)
|
LUAU_FASTINT(LuauTarjanChildLimit)
|
||||||
LUAU_FASTFLAG(LuauInferInNoCheckMode)
|
LUAU_FASTFLAG(LuauInferInNoCheckMode)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3, false)
|
LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3, false)
|
||||||
LUAU_FASTINTVARIABLE(LuauAutocompleteCheckTimeoutMs, 100) // TODO: Remove with FFlagLuauTypecheckLimitControls
|
|
||||||
LUAU_FASTFLAGVARIABLE(DebugLuauDeferredConstraintResolution, false)
|
LUAU_FASTFLAGVARIABLE(DebugLuauDeferredConstraintResolution, false)
|
||||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson, false)
|
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson, false)
|
||||||
LUAU_FASTFLAGVARIABLE(DebugLuauReadWriteProperties, false)
|
LUAU_FASTFLAGVARIABLE(DebugLuauReadWriteProperties, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauTypecheckLimitControls, false)
|
|
||||||
LUAU_FASTFLAGVARIABLE(CorrectEarlyReturnInMarkDirty, false)
|
LUAU_FASTFLAGVARIABLE(CorrectEarlyReturnInMarkDirty, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauDefinitionFileSetModuleName, false)
|
LUAU_FASTFLAGVARIABLE(LuauDefinitionFileSetModuleName, false)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauRethrowSingleModuleIce, false)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -681,8 +680,7 @@ std::vector<ModuleName> Frontend::checkQueuedModules(std::optional<FrontendOptio
|
||||||
sendItemTask(i);
|
sendItemTask(i);
|
||||||
nextItems.clear();
|
nextItems.clear();
|
||||||
|
|
||||||
// If we aren't done, but don't have anything processing, we hit a cycle
|
if (FFlag::LuauRethrowSingleModuleIce && processing == 0)
|
||||||
if (remaining != 0 && processing == 0)
|
|
||||||
{
|
{
|
||||||
// Typechecking might have been cancelled by user, don't return partial results
|
// Typechecking might have been cancelled by user, don't return partial results
|
||||||
if (cancelled)
|
if (cancelled)
|
||||||
|
@ -690,9 +688,24 @@ std::vector<ModuleName> Frontend::checkQueuedModules(std::optional<FrontendOptio
|
||||||
|
|
||||||
// We might have stopped because of a pending exception
|
// We might have stopped because of a pending exception
|
||||||
if (itemWithException)
|
if (itemWithException)
|
||||||
{
|
|
||||||
recordItemResult(buildQueueItems[*itemWithException]);
|
recordItemResult(buildQueueItems[*itemWithException]);
|
||||||
break;
|
}
|
||||||
|
|
||||||
|
// If we aren't done, but don't have anything processing, we hit a cycle
|
||||||
|
if (remaining != 0 && processing == 0)
|
||||||
|
{
|
||||||
|
if (!FFlag::LuauRethrowSingleModuleIce)
|
||||||
|
{
|
||||||
|
// Typechecking might have been cancelled by user, don't return partial results
|
||||||
|
if (cancelled)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
// We might have stopped because of a pending exception
|
||||||
|
if (itemWithException)
|
||||||
|
{
|
||||||
|
recordItemResult(buildQueueItems[*itemWithException]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sendCycleItemTask();
|
sendCycleItemTask();
|
||||||
|
@ -902,82 +915,41 @@ void Frontend::checkBuildQueueItem(BuildQueueItem& item)
|
||||||
|
|
||||||
TypeCheckLimits typeCheckLimits;
|
TypeCheckLimits typeCheckLimits;
|
||||||
|
|
||||||
if (FFlag::LuauTypecheckLimitControls)
|
if (item.options.moduleTimeLimitSec)
|
||||||
|
typeCheckLimits.finishTime = TimeTrace::getClock() + *item.options.moduleTimeLimitSec;
|
||||||
|
else
|
||||||
|
typeCheckLimits.finishTime = std::nullopt;
|
||||||
|
|
||||||
|
// TODO: This is a dirty ad hoc solution for autocomplete timeouts
|
||||||
|
// We are trying to dynamically adjust our existing limits to lower total typechecking time under the limit
|
||||||
|
// so that we'll have type information for the whole file at lower quality instead of a full abort in the middle
|
||||||
|
if (item.options.applyInternalLimitScaling)
|
||||||
{
|
{
|
||||||
if (item.options.moduleTimeLimitSec)
|
if (FInt::LuauTarjanChildLimit > 0)
|
||||||
typeCheckLimits.finishTime = TimeTrace::getClock() + *item.options.moduleTimeLimitSec;
|
typeCheckLimits.instantiationChildLimit = std::max(1, int(FInt::LuauTarjanChildLimit * sourceNode.autocompleteLimitsMult));
|
||||||
else
|
else
|
||||||
typeCheckLimits.finishTime = std::nullopt;
|
typeCheckLimits.instantiationChildLimit = std::nullopt;
|
||||||
|
|
||||||
// TODO: This is a dirty ad hoc solution for autocomplete timeouts
|
if (FInt::LuauTypeInferIterationLimit > 0)
|
||||||
// We are trying to dynamically adjust our existing limits to lower total typechecking time under the limit
|
typeCheckLimits.unifierIterationLimit = std::max(1, int(FInt::LuauTypeInferIterationLimit * sourceNode.autocompleteLimitsMult));
|
||||||
// so that we'll have type information for the whole file at lower quality instead of a full abort in the middle
|
else
|
||||||
if (item.options.applyInternalLimitScaling)
|
typeCheckLimits.unifierIterationLimit = std::nullopt;
|
||||||
{
|
|
||||||
if (FInt::LuauTarjanChildLimit > 0)
|
|
||||||
typeCheckLimits.instantiationChildLimit = std::max(1, int(FInt::LuauTarjanChildLimit * sourceNode.autocompleteLimitsMult));
|
|
||||||
else
|
|
||||||
typeCheckLimits.instantiationChildLimit = std::nullopt;
|
|
||||||
|
|
||||||
if (FInt::LuauTypeInferIterationLimit > 0)
|
|
||||||
typeCheckLimits.unifierIterationLimit = std::max(1, int(FInt::LuauTypeInferIterationLimit * sourceNode.autocompleteLimitsMult));
|
|
||||||
else
|
|
||||||
typeCheckLimits.unifierIterationLimit = std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
typeCheckLimits.cancellationToken = item.options.cancellationToken;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
typeCheckLimits.cancellationToken = item.options.cancellationToken;
|
||||||
|
|
||||||
if (item.options.forAutocomplete)
|
if (item.options.forAutocomplete)
|
||||||
{
|
{
|
||||||
double autocompleteTimeLimit = FInt::LuauAutocompleteCheckTimeoutMs / 1000.0;
|
|
||||||
|
|
||||||
if (!FFlag::LuauTypecheckLimitControls)
|
|
||||||
{
|
|
||||||
// The autocomplete typecheck is always in strict mode with DM awareness
|
|
||||||
// to provide better type information for IDE features
|
|
||||||
|
|
||||||
if (autocompleteTimeLimit != 0.0)
|
|
||||||
typeCheckLimits.finishTime = TimeTrace::getClock() + autocompleteTimeLimit;
|
|
||||||
else
|
|
||||||
typeCheckLimits.finishTime = std::nullopt;
|
|
||||||
|
|
||||||
// TODO: This is a dirty ad hoc solution for autocomplete timeouts
|
|
||||||
// We are trying to dynamically adjust our existing limits to lower total typechecking time under the limit
|
|
||||||
// so that we'll have type information for the whole file at lower quality instead of a full abort in the middle
|
|
||||||
if (FInt::LuauTarjanChildLimit > 0)
|
|
||||||
typeCheckLimits.instantiationChildLimit = std::max(1, int(FInt::LuauTarjanChildLimit * sourceNode.autocompleteLimitsMult));
|
|
||||||
else
|
|
||||||
typeCheckLimits.instantiationChildLimit = std::nullopt;
|
|
||||||
|
|
||||||
if (FInt::LuauTypeInferIterationLimit > 0)
|
|
||||||
typeCheckLimits.unifierIterationLimit = std::max(1, int(FInt::LuauTypeInferIterationLimit * sourceNode.autocompleteLimitsMult));
|
|
||||||
else
|
|
||||||
typeCheckLimits.unifierIterationLimit = std::nullopt;
|
|
||||||
|
|
||||||
typeCheckLimits.cancellationToken = item.options.cancellationToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The autocomplete typecheck is always in strict mode with DM awareness to provide better type information for IDE features
|
// The autocomplete typecheck is always in strict mode with DM awareness to provide better type information for IDE features
|
||||||
ModulePtr moduleForAutocomplete = check(sourceModule, Mode::Strict, requireCycles, environmentScope, /*forAutocomplete*/ true,
|
ModulePtr moduleForAutocomplete = check(sourceModule, Mode::Strict, requireCycles, environmentScope, /*forAutocomplete*/ true,
|
||||||
/*recordJsonLog*/ false, typeCheckLimits);
|
/*recordJsonLog*/ false, typeCheckLimits);
|
||||||
|
|
||||||
double duration = getTimestamp() - timestamp;
|
double duration = getTimestamp() - timestamp;
|
||||||
|
|
||||||
if (FFlag::LuauTypecheckLimitControls)
|
moduleForAutocomplete->checkDurationSec = duration;
|
||||||
{
|
|
||||||
moduleForAutocomplete->checkDurationSec = duration;
|
|
||||||
|
|
||||||
if (item.options.moduleTimeLimitSec && item.options.applyInternalLimitScaling)
|
if (item.options.moduleTimeLimitSec && item.options.applyInternalLimitScaling)
|
||||||
applyInternalLimitScaling(sourceNode, moduleForAutocomplete, *item.options.moduleTimeLimitSec);
|
applyInternalLimitScaling(sourceNode, moduleForAutocomplete, *item.options.moduleTimeLimitSec);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (moduleForAutocomplete->timeout)
|
|
||||||
sourceNode.autocompleteLimitsMult = sourceNode.autocompleteLimitsMult / 2.0;
|
|
||||||
else if (duration < autocompleteTimeLimit / 2.0)
|
|
||||||
sourceNode.autocompleteLimitsMult = std::min(sourceNode.autocompleteLimitsMult * 2.0, 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
item.stats.timeCheck += duration;
|
item.stats.timeCheck += duration;
|
||||||
item.stats.filesStrict += 1;
|
item.stats.filesStrict += 1;
|
||||||
|
@ -986,29 +958,16 @@ void Frontend::checkBuildQueueItem(BuildQueueItem& item)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!FFlag::LuauTypecheckLimitControls)
|
|
||||||
{
|
|
||||||
typeCheckLimits.cancellationToken = item.options.cancellationToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
ModulePtr module = check(sourceModule, mode, requireCycles, environmentScope, /*forAutocomplete*/ false, item.recordJsonLog, typeCheckLimits);
|
ModulePtr module = check(sourceModule, mode, requireCycles, environmentScope, /*forAutocomplete*/ false, item.recordJsonLog, typeCheckLimits);
|
||||||
|
|
||||||
if (FFlag::LuauTypecheckLimitControls)
|
double duration = getTimestamp() - timestamp;
|
||||||
{
|
|
||||||
double duration = getTimestamp() - timestamp;
|
|
||||||
|
|
||||||
module->checkDurationSec = duration;
|
module->checkDurationSec = duration;
|
||||||
|
|
||||||
if (item.options.moduleTimeLimitSec && item.options.applyInternalLimitScaling)
|
if (item.options.moduleTimeLimitSec && item.options.applyInternalLimitScaling)
|
||||||
applyInternalLimitScaling(sourceNode, module, *item.options.moduleTimeLimitSec);
|
applyInternalLimitScaling(sourceNode, module, *item.options.moduleTimeLimitSec);
|
||||||
|
|
||||||
item.stats.timeCheck += duration;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
item.stats.timeCheck += getTimestamp() - timestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
item.stats.timeCheck += duration;
|
||||||
item.stats.filesStrict += mode == Mode::Strict;
|
item.stats.filesStrict += mode == Mode::Strict;
|
||||||
item.stats.filesNonstrict += mode == Mode::Nonstrict;
|
item.stats.filesNonstrict += mode == Mode::Nonstrict;
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
#include "Luau/TypeArena.h"
|
#include "Luau/TypeArena.h"
|
||||||
#include "Luau/TypeFamily.h"
|
#include "Luau/TypeFamily.h"
|
||||||
#include "Luau/Def.h"
|
#include "Luau/Def.h"
|
||||||
|
#include "Luau/TypeFwd.h"
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
|
@ -57,8 +58,6 @@ struct StackPusher
|
||||||
|
|
||||||
struct NonStrictContext
|
struct NonStrictContext
|
||||||
{
|
{
|
||||||
std::unordered_map<const Def*, TypeId> context;
|
|
||||||
|
|
||||||
NonStrictContext() = default;
|
NonStrictContext() = default;
|
||||||
|
|
||||||
NonStrictContext(const NonStrictContext&) = delete;
|
NonStrictContext(const NonStrictContext&) = delete;
|
||||||
|
@ -109,7 +108,12 @@ struct NonStrictContext
|
||||||
// Returns true if the removal was successful
|
// Returns true if the removal was successful
|
||||||
bool remove(const DefId& def)
|
bool remove(const DefId& def)
|
||||||
{
|
{
|
||||||
return context.erase(def.get()) == 1;
|
std::vector<DefId> defs;
|
||||||
|
collectOperands(def, &defs);
|
||||||
|
bool result = true;
|
||||||
|
for (DefId def : defs)
|
||||||
|
result = result && context.erase(def.get()) == 1;
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<TypeId> find(const DefId& def) const
|
std::optional<TypeId> find(const DefId& def) const
|
||||||
|
@ -118,6 +122,14 @@ struct NonStrictContext
|
||||||
return find(d);
|
return find(d);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void addContext(const DefId& def, TypeId ty)
|
||||||
|
{
|
||||||
|
std::vector<DefId> defs;
|
||||||
|
collectOperands(def, &defs);
|
||||||
|
for (DefId def : defs)
|
||||||
|
context[def.get()] = ty;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::optional<TypeId> find(const Def* d) const
|
std::optional<TypeId> find(const Def* d) const
|
||||||
{
|
{
|
||||||
|
@ -126,6 +138,9 @@ private:
|
||||||
return {it->second};
|
return {it->second};
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::unordered_map<const Def*, TypeId> context;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct NonStrictTypeChecker
|
struct NonStrictTypeChecker
|
||||||
|
@ -508,8 +523,25 @@ struct NonStrictTypeChecker
|
||||||
// ...
|
// ...
|
||||||
// (unknown^N-1, ~S_N) -> error
|
// (unknown^N-1, ~S_N) -> error
|
||||||
std::vector<TypeId> argTypes;
|
std::vector<TypeId> argTypes;
|
||||||
for (TypeId ty : fn->argTypes)
|
argTypes.reserve(call->args.size);
|
||||||
argTypes.push_back(ty);
|
// Pad out the arg types array with the types you would expect to see
|
||||||
|
TypePackIterator curr = begin(fn->argTypes);
|
||||||
|
TypePackIterator fin = end(fn->argTypes);
|
||||||
|
while (curr != fin)
|
||||||
|
{
|
||||||
|
argTypes.push_back(*curr);
|
||||||
|
++curr;
|
||||||
|
}
|
||||||
|
if (auto argTail = curr.tail())
|
||||||
|
{
|
||||||
|
if (const VariadicTypePack* vtp = get<VariadicTypePack>(follow(*argTail)))
|
||||||
|
{
|
||||||
|
while (argTypes.size() < call->args.size)
|
||||||
|
{
|
||||||
|
argTypes.push_back(vtp->ty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// For a checked function, these gotta be the same size
|
// For a checked function, these gotta be the same size
|
||||||
LUAU_ASSERT(call->args.size == argTypes.size());
|
LUAU_ASSERT(call->args.size == argTypes.size());
|
||||||
for (size_t i = 0; i < call->args.size; i++)
|
for (size_t i = 0; i < call->args.size; i++)
|
||||||
|
@ -523,7 +555,7 @@ struct NonStrictTypeChecker
|
||||||
TypeId expectedArgType = argTypes[i];
|
TypeId expectedArgType = argTypes[i];
|
||||||
DefId def = dfg->getDef(arg);
|
DefId def = dfg->getDef(arg);
|
||||||
TypeId runTimeErrorTy = getOrCreateNegation(expectedArgType);
|
TypeId runTimeErrorTy = getOrCreateNegation(expectedArgType);
|
||||||
fresh.context[def.get()] = runTimeErrorTy;
|
fresh.addContext(def, runTimeErrorTy);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Populate the context and now iterate through each of the arguments to the call to find out if we satisfy the types
|
// Populate the context and now iterate through each of the arguments to the call to find out if we satisfy the types
|
||||||
|
@ -613,15 +645,20 @@ struct NonStrictTypeChecker
|
||||||
std::optional<TypeId> willRunTimeError(AstExpr* fragment, const NonStrictContext& context)
|
std::optional<TypeId> willRunTimeError(AstExpr* fragment, const NonStrictContext& context)
|
||||||
{
|
{
|
||||||
DefId def = dfg->getDef(fragment);
|
DefId def = dfg->getDef(fragment);
|
||||||
if (std::optional<TypeId> contextTy = context.find(def))
|
std::vector<DefId> defs;
|
||||||
|
collectOperands(def, &defs);
|
||||||
|
for (DefId def : defs)
|
||||||
{
|
{
|
||||||
|
if (std::optional<TypeId> contextTy = context.find(def))
|
||||||
|
{
|
||||||
|
|
||||||
TypeId actualType = lookupType(fragment);
|
TypeId actualType = lookupType(fragment);
|
||||||
SubtypingResult r = subtyping.isSubtype(actualType, *contextTy);
|
SubtypingResult r = subtyping.isSubtype(actualType, *contextTy);
|
||||||
if (r.normalizationTooComplex)
|
if (r.normalizationTooComplex)
|
||||||
reportError(NormalizationTooComplex{}, fragment->location);
|
reportError(NormalizationTooComplex{}, fragment->location);
|
||||||
if (r.isSubtype)
|
if (r.isSubtype)
|
||||||
return {actualType};
|
return {actualType};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
|
@ -630,15 +667,20 @@ struct NonStrictTypeChecker
|
||||||
std::optional<TypeId> willRunTimeErrorFunctionDefinition(AstLocal* fragment, const NonStrictContext& context)
|
std::optional<TypeId> willRunTimeErrorFunctionDefinition(AstLocal* fragment, const NonStrictContext& context)
|
||||||
{
|
{
|
||||||
DefId def = dfg->getDef(fragment);
|
DefId def = dfg->getDef(fragment);
|
||||||
if (std::optional<TypeId> contextTy = context.find(def))
|
std::vector<DefId> defs;
|
||||||
|
collectOperands(def, &defs);
|
||||||
|
for (DefId def : defs)
|
||||||
{
|
{
|
||||||
SubtypingResult r1 = subtyping.isSubtype(builtinTypes->unknownType, *contextTy);
|
if (std::optional<TypeId> contextTy = context.find(def))
|
||||||
SubtypingResult r2 = subtyping.isSubtype(*contextTy, builtinTypes->unknownType);
|
{
|
||||||
if (r1.normalizationTooComplex || r2.normalizationTooComplex)
|
SubtypingResult r1 = subtyping.isSubtype(builtinTypes->unknownType, *contextTy);
|
||||||
reportError(NormalizationTooComplex{}, fragment->location);
|
SubtypingResult r2 = subtyping.isSubtype(*contextTy, builtinTypes->unknownType);
|
||||||
bool isUnknown = r1.isSubtype && r2.isSubtype;
|
if (r1.normalizationTooComplex || r2.normalizationTooComplex)
|
||||||
if (isUnknown)
|
reportError(NormalizationTooComplex{}, fragment->location);
|
||||||
return {builtinTypes->unknownType};
|
bool isUnknown = r1.isSubtype && r2.isSubtype;
|
||||||
|
if (isUnknown)
|
||||||
|
return {builtinTypes->unknownType};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,10 @@
|
||||||
|
|
||||||
#include "Luau/Common.h"
|
#include "Luau/Common.h"
|
||||||
#include "Luau/Constraint.h"
|
#include "Luau/Constraint.h"
|
||||||
|
#include "Luau/DenseHash.h"
|
||||||
#include "Luau/Location.h"
|
#include "Luau/Location.h"
|
||||||
#include "Luau/Scope.h"
|
#include "Luau/Scope.h"
|
||||||
|
#include "Luau/Set.h"
|
||||||
#include "Luau/TxnLog.h"
|
#include "Luau/TxnLog.h"
|
||||||
#include "Luau/TypeInfer.h"
|
#include "Luau/TypeInfer.h"
|
||||||
#include "Luau/TypePack.h"
|
#include "Luau/TypePack.h"
|
||||||
|
@ -53,8 +55,8 @@ struct FindCyclicTypes final : TypeVisitor
|
||||||
FindCyclicTypes& operator=(const FindCyclicTypes&) = delete;
|
FindCyclicTypes& operator=(const FindCyclicTypes&) = delete;
|
||||||
|
|
||||||
bool exhaustive = false;
|
bool exhaustive = false;
|
||||||
std::unordered_set<TypeId> visited;
|
Luau::Set<TypeId> visited{{}};
|
||||||
std::unordered_set<TypePackId> visitedPacks;
|
Luau::Set<TypePackId> visitedPacks{{}};
|
||||||
std::set<TypeId> cycles;
|
std::set<TypeId> cycles;
|
||||||
std::set<TypePackId> cycleTPs;
|
std::set<TypePackId> cycleTPs;
|
||||||
|
|
||||||
|
@ -70,17 +72,17 @@ struct FindCyclicTypes final : TypeVisitor
|
||||||
|
|
||||||
bool visit(TypeId ty) override
|
bool visit(TypeId ty) override
|
||||||
{
|
{
|
||||||
return visited.insert(ty).second;
|
return visited.insert(ty);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool visit(TypePackId tp) override
|
bool visit(TypePackId tp) override
|
||||||
{
|
{
|
||||||
return visitedPacks.insert(tp).second;
|
return visitedPacks.insert(tp);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool visit(TypeId ty, const FreeType& ft) override
|
bool visit(TypeId ty, const FreeType& ft) override
|
||||||
{
|
{
|
||||||
if (!visited.insert(ty).second)
|
if (!visited.insert(ty))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
@ -102,7 +104,7 @@ struct FindCyclicTypes final : TypeVisitor
|
||||||
|
|
||||||
bool visit(TypeId ty, const LocalType& lt) override
|
bool visit(TypeId ty, const LocalType& lt) override
|
||||||
{
|
{
|
||||||
if (!visited.insert(ty).second)
|
if (!visited.insert(ty))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
traverse(lt.domain);
|
traverse(lt.domain);
|
||||||
|
@ -112,7 +114,7 @@ struct FindCyclicTypes final : TypeVisitor
|
||||||
|
|
||||||
bool visit(TypeId ty, const TableType& ttv) override
|
bool visit(TypeId ty, const TableType& ttv) override
|
||||||
{
|
{
|
||||||
if (!visited.insert(ty).second)
|
if (!visited.insert(ty))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (ttv.name || ttv.syntheticName)
|
if (ttv.name || ttv.syntheticName)
|
||||||
|
@ -175,10 +177,11 @@ struct StringifierState
|
||||||
ToStringOptions& opts;
|
ToStringOptions& opts;
|
||||||
ToStringResult& result;
|
ToStringResult& result;
|
||||||
|
|
||||||
std::unordered_map<TypeId, std::string> cycleNames;
|
DenseHashMap<TypeId, std::string> cycleNames{{}};
|
||||||
std::unordered_map<TypePackId, std::string> cycleTpNames;
|
DenseHashMap<TypePackId, std::string> cycleTpNames{{}};
|
||||||
std::unordered_set<void*> seen;
|
Set<void*> seen{{}};
|
||||||
std::unordered_set<std::string> usedNames;
|
// `$$$` was chosen as the tombstone for `usedNames` since it is not a valid name syntactically and is relatively short for string comparison reasons.
|
||||||
|
DenseHashSet<std::string> usedNames{"$$$"};
|
||||||
size_t indentation = 0;
|
size_t indentation = 0;
|
||||||
|
|
||||||
bool exhaustive;
|
bool exhaustive;
|
||||||
|
@ -197,7 +200,7 @@ struct StringifierState
|
||||||
bool hasSeen(const void* tv)
|
bool hasSeen(const void* tv)
|
||||||
{
|
{
|
||||||
void* ttv = const_cast<void*>(tv);
|
void* ttv = const_cast<void*>(tv);
|
||||||
if (seen.find(ttv) != seen.end())
|
if (seen.contains(ttv))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
seen.insert(ttv);
|
seen.insert(ttv);
|
||||||
|
@ -207,9 +210,9 @@ struct StringifierState
|
||||||
void unsee(const void* tv)
|
void unsee(const void* tv)
|
||||||
{
|
{
|
||||||
void* ttv = const_cast<void*>(tv);
|
void* ttv = const_cast<void*>(tv);
|
||||||
auto iter = seen.find(ttv);
|
|
||||||
if (iter != seen.end())
|
if (seen.contains(ttv))
|
||||||
seen.erase(iter);
|
seen.erase(ttv);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string getName(TypeId ty)
|
std::string getName(TypeId ty)
|
||||||
|
@ -222,7 +225,7 @@ struct StringifierState
|
||||||
for (int count = 0; count < 256; ++count)
|
for (int count = 0; count < 256; ++count)
|
||||||
{
|
{
|
||||||
std::string candidate = generateName(usedNames.size() + count);
|
std::string candidate = generateName(usedNames.size() + count);
|
||||||
if (!usedNames.count(candidate))
|
if (!usedNames.contains(candidate))
|
||||||
{
|
{
|
||||||
usedNames.insert(candidate);
|
usedNames.insert(candidate);
|
||||||
n = candidate;
|
n = candidate;
|
||||||
|
@ -245,7 +248,7 @@ struct StringifierState
|
||||||
for (int count = 0; count < 256; ++count)
|
for (int count = 0; count < 256; ++count)
|
||||||
{
|
{
|
||||||
std::string candidate = generateName(previousNameIndex + count);
|
std::string candidate = generateName(previousNameIndex + count);
|
||||||
if (!usedNames.count(candidate))
|
if (!usedNames.contains(candidate))
|
||||||
{
|
{
|
||||||
previousNameIndex += count;
|
previousNameIndex += count;
|
||||||
usedNames.insert(candidate);
|
usedNames.insert(candidate);
|
||||||
|
@ -358,10 +361,9 @@ struct TypeStringifier
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto it = state.cycleNames.find(tv);
|
if (auto p = state.cycleNames.find(tv))
|
||||||
if (it != state.cycleNames.end())
|
|
||||||
{
|
{
|
||||||
state.emit(it->second);
|
state.emit(*p);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -886,7 +888,7 @@ struct TypeStringifier
|
||||||
|
|
||||||
std::string saved = std::move(state.result.name);
|
std::string saved = std::move(state.result.name);
|
||||||
|
|
||||||
bool needParens = !state.cycleNames.count(el) && (get<IntersectionType>(el) || get<FunctionType>(el));
|
bool needParens = !state.cycleNames.contains(el) && (get<IntersectionType>(el) || get<FunctionType>(el));
|
||||||
|
|
||||||
if (needParens)
|
if (needParens)
|
||||||
state.emit("(");
|
state.emit("(");
|
||||||
|
@ -953,7 +955,7 @@ struct TypeStringifier
|
||||||
|
|
||||||
std::string saved = std::move(state.result.name);
|
std::string saved = std::move(state.result.name);
|
||||||
|
|
||||||
bool needParens = !state.cycleNames.count(el) && (get<UnionType>(el) || get<FunctionType>(el));
|
bool needParens = !state.cycleNames.contains(el) && (get<UnionType>(el) || get<FunctionType>(el));
|
||||||
|
|
||||||
if (needParens)
|
if (needParens)
|
||||||
state.emit("(");
|
state.emit("(");
|
||||||
|
@ -1101,10 +1103,9 @@ struct TypePackStringifier
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto it = state.cycleTpNames.find(tp);
|
if (auto p = state.cycleTpNames.find(tp))
|
||||||
if (it != state.cycleTpNames.end())
|
|
||||||
{
|
{
|
||||||
state.emit(it->second);
|
state.emit(*p);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1278,7 +1279,7 @@ void TypeStringifier::stringify(TypePackId tpid, const std::vector<std::optional
|
||||||
}
|
}
|
||||||
|
|
||||||
static void assignCycleNames(const std::set<TypeId>& cycles, const std::set<TypePackId>& cycleTPs,
|
static void assignCycleNames(const std::set<TypeId>& cycles, const std::set<TypePackId>& cycleTPs,
|
||||||
std::unordered_map<TypeId, std::string>& cycleNames, std::unordered_map<TypePackId, std::string>& cycleTpNames, bool exhaustive)
|
DenseHashMap<TypeId, std::string>& cycleNames, DenseHashMap<TypePackId, std::string>& cycleTpNames, bool exhaustive)
|
||||||
{
|
{
|
||||||
int nextIndex = 1;
|
int nextIndex = 1;
|
||||||
|
|
||||||
|
@ -1372,9 +1373,8 @@ ToStringResult toStringDetailed(TypeId ty, ToStringOptions& opts)
|
||||||
*
|
*
|
||||||
* t1 where t1 = the_whole_root_type
|
* t1 where t1 = the_whole_root_type
|
||||||
*/
|
*/
|
||||||
auto it = state.cycleNames.find(ty);
|
if (auto p = state.cycleNames.find(ty))
|
||||||
if (it != state.cycleNames.end())
|
state.emit(*p);
|
||||||
state.emit(it->second);
|
|
||||||
else
|
else
|
||||||
tvs.stringify(ty);
|
tvs.stringify(ty);
|
||||||
|
|
||||||
|
@ -1466,9 +1466,8 @@ ToStringResult toStringDetailed(TypePackId tp, ToStringOptions& opts)
|
||||||
*
|
*
|
||||||
* t1 where t1 = the_whole_root_type
|
* t1 where t1 = the_whole_root_type
|
||||||
*/
|
*/
|
||||||
auto it = state.cycleTpNames.find(tp);
|
if (auto p = state.cycleTpNames.find(tp))
|
||||||
if (it != state.cycleTpNames.end())
|
state.emit(*p);
|
||||||
state.emit(it->second);
|
|
||||||
else
|
else
|
||||||
tvs.stringify(tp);
|
tvs.stringify(tp);
|
||||||
|
|
||||||
|
@ -1766,11 +1765,6 @@ std::string toString(const Constraint& constraint, ToStringOptions& opts)
|
||||||
}
|
}
|
||||||
else if constexpr (std::is_same_v<T, UnpackConstraint>)
|
else if constexpr (std::is_same_v<T, UnpackConstraint>)
|
||||||
return tos(c.resultPack) + " ~ unpack " + tos(c.sourcePack);
|
return tos(c.resultPack) + " ~ unpack " + tos(c.sourcePack);
|
||||||
else if constexpr (std::is_same_v<T, RefineConstraint>)
|
|
||||||
{
|
|
||||||
const char* op = c.mode == RefineConstraint::Union ? "union" : "intersect";
|
|
||||||
return tos(c.resultType) + " ~ refine " + tos(c.type) + " " + op + " " + tos(c.discriminant);
|
|
||||||
}
|
|
||||||
else if constexpr (std::is_same_v<T, SetOpConstraint>)
|
else if constexpr (std::is_same_v<T, SetOpConstraint>)
|
||||||
{
|
{
|
||||||
const char* op = c.mode == SetOpConstraint::Union ? " | " : " & ";
|
const char* op = c.mode == SetOpConstraint::Union ? " | " : " & ";
|
||||||
|
|
|
@ -106,7 +106,12 @@ public:
|
||||||
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName("thread"), std::nullopt, Location());
|
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName("thread"), std::nullopt, Location());
|
||||||
case PrimitiveType::Buffer:
|
case PrimitiveType::Buffer:
|
||||||
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName("buffer"), std::nullopt, Location());
|
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName("buffer"), std::nullopt, Location());
|
||||||
|
case PrimitiveType::Function:
|
||||||
|
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName("function"), std::nullopt, Location());
|
||||||
|
case PrimitiveType::Table:
|
||||||
|
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName("table"), std::nullopt, Location());
|
||||||
default:
|
default:
|
||||||
|
LUAU_ASSERT(false); // this should be unreachable.
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1603,8 +1603,8 @@ struct TypeChecker2
|
||||||
visit(indexExpr->expr, ValueContext::RValue);
|
visit(indexExpr->expr, ValueContext::RValue);
|
||||||
visit(indexExpr->index, ValueContext::RValue);
|
visit(indexExpr->index, ValueContext::RValue);
|
||||||
|
|
||||||
TypeId exprType = lookupType(indexExpr->expr);
|
TypeId exprType = follow(lookupType(indexExpr->expr));
|
||||||
TypeId indexType = lookupType(indexExpr->index);
|
TypeId indexType = follow(lookupType(indexExpr->index));
|
||||||
|
|
||||||
if (auto tt = get<TableType>(exprType))
|
if (auto tt = get<TableType>(exprType))
|
||||||
{
|
{
|
||||||
|
|
|
@ -349,7 +349,8 @@ TypeFamilyReductionResult<TypeId> lenFamilyFn(const std::vector<TypeId>& typePar
|
||||||
TypeId operandTy = follow(typeParams.at(0));
|
TypeId operandTy = follow(typeParams.at(0));
|
||||||
|
|
||||||
// check to see if the operand type is resolved enough, and wait to reduce if not
|
// check to see if the operand type is resolved enough, and wait to reduce if not
|
||||||
if (isPending(operandTy, ctx->solver))
|
// the use of `typeFromNormal` later necessitates blocking on local types.
|
||||||
|
if (isPending(operandTy, ctx->solver) || get<LocalType>(operandTy))
|
||||||
return {std::nullopt, false, {operandTy}, {}};
|
return {std::nullopt, false, {operandTy}, {}};
|
||||||
|
|
||||||
const NormalizedType* normTy = ctx->normalizer->normalize(operandTy);
|
const NormalizedType* normTy = ctx->normalizer->normalize(operandTy);
|
||||||
|
@ -964,6 +965,92 @@ TypeFamilyReductionResult<TypeId> eqFamilyFn(const std::vector<TypeId>& typePara
|
||||||
return {ctx->builtins->booleanType, false, {}, {}};
|
return {ctx->builtins->booleanType, false, {}, {}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Collect types that prevent us from reducing a particular refinement.
|
||||||
|
struct FindRefinementBlockers : TypeOnceVisitor
|
||||||
|
{
|
||||||
|
DenseHashSet<TypeId> found{nullptr};
|
||||||
|
bool visit(TypeId ty, const BlockedType&) override
|
||||||
|
{
|
||||||
|
found.insert(ty);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool visit(TypeId ty, const PendingExpansionType&) override
|
||||||
|
{
|
||||||
|
found.insert(ty);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool visit(TypeId ty, const LocalType&) override
|
||||||
|
{
|
||||||
|
found.insert(ty);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool visit(TypeId ty, const ClassType&) override
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
TypeFamilyReductionResult<TypeId> refineFamilyFn(const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||||
|
{
|
||||||
|
if (typeParams.size() != 2 || !packParams.empty())
|
||||||
|
{
|
||||||
|
ctx->ice->ice("refine type family: encountered a type family instance without the required argument structure");
|
||||||
|
LUAU_ASSERT(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeId targetTy = follow(typeParams.at(0));
|
||||||
|
TypeId discriminantTy = follow(typeParams.at(1));
|
||||||
|
|
||||||
|
// check to see if both operand types are resolved enough, and wait to reduce if not
|
||||||
|
if (isPending(targetTy, ctx->solver))
|
||||||
|
return {std::nullopt, false, {targetTy}, {}};
|
||||||
|
else if (isPending(discriminantTy, ctx->solver))
|
||||||
|
return {std::nullopt, false, {discriminantTy}, {}};
|
||||||
|
|
||||||
|
// we need a more complex check for blocking on the discriminant in particular
|
||||||
|
FindRefinementBlockers frb;
|
||||||
|
frb.traverse(discriminantTy);
|
||||||
|
|
||||||
|
if (!frb.found.empty())
|
||||||
|
return {std::nullopt, false, {frb.found.begin(), frb.found.end()}, {}};
|
||||||
|
|
||||||
|
/* HACK: Refinements sometimes produce a type T & ~any under the assumption
|
||||||
|
* that ~any is the same as any. This is so so weird, but refinements needs
|
||||||
|
* some way to say "I may refine this, but I'm not sure."
|
||||||
|
*
|
||||||
|
* It does this by refining on a blocked type and deferring the decision
|
||||||
|
* until it is unblocked.
|
||||||
|
*
|
||||||
|
* Refinements also get negated, so we wind up with types like T & ~*blocked*
|
||||||
|
*
|
||||||
|
* We need to treat T & ~any as T in this case.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (auto nt = get<NegationType>(discriminantTy))
|
||||||
|
if (get<AnyType>(follow(nt->ty)))
|
||||||
|
return {targetTy, false, {}, {}};
|
||||||
|
|
||||||
|
TypeId intersection = ctx->arena->addType(IntersectionType{{targetTy, discriminantTy}});
|
||||||
|
const NormalizedType* normIntersection = ctx->normalizer->normalize(intersection);
|
||||||
|
const NormalizedType* normType = ctx->normalizer->normalize(targetTy);
|
||||||
|
|
||||||
|
// if the intersection failed to normalize, we can't reduce, but know nothing about inhabitance.
|
||||||
|
if (!normIntersection || !normType)
|
||||||
|
return {std::nullopt, false, {}, {}};
|
||||||
|
|
||||||
|
TypeId resultTy = ctx->normalizer->typeFromNormal(*normIntersection);
|
||||||
|
|
||||||
|
// include the error type if the target type is error-suppressing and the intersection we computed is not
|
||||||
|
if (normType->shouldSuppressErrors() && !normIntersection->shouldSuppressErrors())
|
||||||
|
resultTy = ctx->arena->addType(UnionType{{resultTy, ctx->builtins->errorType}});
|
||||||
|
|
||||||
|
return {resultTy, false, {}, {}};
|
||||||
|
}
|
||||||
|
|
||||||
BuiltinTypeFamilies::BuiltinTypeFamilies()
|
BuiltinTypeFamilies::BuiltinTypeFamilies()
|
||||||
: notFamily{"not", notFamilyFn}
|
: notFamily{"not", notFamilyFn}
|
||||||
, lenFamily{"len", lenFamilyFn}
|
, lenFamily{"len", lenFamilyFn}
|
||||||
|
@ -981,6 +1068,7 @@ BuiltinTypeFamilies::BuiltinTypeFamilies()
|
||||||
, ltFamily{"lt", ltFamilyFn}
|
, ltFamily{"lt", ltFamilyFn}
|
||||||
, leFamily{"le", leFamilyFn}
|
, leFamily{"le", leFamilyFn}
|
||||||
, eqFamily{"eq", eqFamilyFn}
|
, eqFamily{"eq", eqFamilyFn}
|
||||||
|
, refineFamily{"refine", refineFamilyFn}
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -325,7 +325,7 @@ int main(int argc, char** argv)
|
||||||
else if (strncmp(argv[i], "--fflags=", 9) == 0)
|
else if (strncmp(argv[i], "--fflags=", 9) == 0)
|
||||||
setLuauFlags(argv[i] + 9);
|
setLuauFlags(argv[i] + 9);
|
||||||
else if (strncmp(argv[i], "-j", 2) == 0)
|
else if (strncmp(argv[i], "-j", 2) == 0)
|
||||||
threadCount = strtol(argv[i] + 2, nullptr, 10);
|
threadCount = int(strtol(argv[i] + 2, nullptr, 10));
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !defined(LUAU_ENABLE_TIME_TRACE)
|
#if !defined(LUAU_ENABLE_TIME_TRACE)
|
||||||
|
@ -363,6 +363,7 @@ int main(int argc, char** argv)
|
||||||
if (threadCount <= 0)
|
if (threadCount <= 0)
|
||||||
threadCount = std::min(TaskScheduler::getThreadCount(), 8u);
|
threadCount = std::min(TaskScheduler::getThreadCount(), 8u);
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
TaskScheduler scheduler(threadCount);
|
TaskScheduler scheduler(threadCount);
|
||||||
|
|
||||||
|
@ -370,6 +371,19 @@ int main(int argc, char** argv)
|
||||||
scheduler.push(std::move(f));
|
scheduler.push(std::move(f));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
catch (const Luau::InternalCompilerError& ice)
|
||||||
|
{
|
||||||
|
Luau::Location location = ice.location ? *ice.location : Luau::Location();
|
||||||
|
|
||||||
|
std::string moduleName = ice.moduleName ? *ice.moduleName : "<unknown module>";
|
||||||
|
std::string humanReadableName = frontend.fileResolver->getHumanReadableModuleName(moduleName);
|
||||||
|
|
||||||
|
Luau::TypeError error(location, moduleName, Luau::InternalError{ice.message});
|
||||||
|
|
||||||
|
report(format, humanReadableName.c_str(), location, "InternalCompilerError",
|
||||||
|
Luau::toString(error, Luau::TypeErrorToStringOptions{frontend.fileResolver}).c_str());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
int failed = 0;
|
int failed = 0;
|
||||||
|
|
||||||
|
|
|
@ -124,7 +124,7 @@ static bool analyzeFile(const char* name, const unsigned nestingLimit, std::vect
|
||||||
{
|
{
|
||||||
Luau::BytecodeBuilder bcb;
|
Luau::BytecodeBuilder bcb;
|
||||||
|
|
||||||
compileOrThrow(bcb, source.value(), copts());
|
compileOrThrow(bcb, *source, copts());
|
||||||
|
|
||||||
const std::string& bytecode = bcb.getBytecode();
|
const std::string& bytecode = bcb.getBytecode();
|
||||||
|
|
||||||
|
|
|
@ -22,16 +22,9 @@ RequireResolver::RequireResolver(lua_State* L, std::string path)
|
||||||
if (isAbsolutePath(pathToResolve))
|
if (isAbsolutePath(pathToResolve))
|
||||||
luaL_argerrorL(L, 1, "cannot require an absolute path");
|
luaL_argerrorL(L, 1, "cannot require an absolute path");
|
||||||
|
|
||||||
bool isAlias = !pathToResolve.empty() && pathToResolve[0] == '@';
|
|
||||||
if (!isAlias && !isExplicitlyRelative(pathToResolve))
|
|
||||||
luaL_argerrorL(L, 1, "must require an alias prepended with '@' or an explicitly relative path");
|
|
||||||
|
|
||||||
std::replace(pathToResolve.begin(), pathToResolve.end(), '\\', '/');
|
std::replace(pathToResolve.begin(), pathToResolve.end(), '\\', '/');
|
||||||
if (isAlias)
|
|
||||||
{
|
substituteAliasIfPresent(pathToResolve);
|
||||||
pathToResolve = pathToResolve.substr(1);
|
|
||||||
substituteAliasIfPresent(pathToResolve);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] RequireResolver::ResolvedRequire RequireResolver::resolveRequire(lua_State* L, std::string path)
|
[[nodiscard]] RequireResolver::ResolvedRequire RequireResolver::resolveRequire(lua_State* L, std::string path)
|
||||||
|
@ -209,16 +202,22 @@ std::string RequireResolver::getRequiringContextRelative()
|
||||||
|
|
||||||
void RequireResolver::substituteAliasIfPresent(std::string& path)
|
void RequireResolver::substituteAliasIfPresent(std::string& path)
|
||||||
{
|
{
|
||||||
std::string potentialAlias = path.substr(0, path.find_first_of("\\/"));
|
if (path.size() < 1 || path[0] != '@')
|
||||||
|
return;
|
||||||
|
std::string potentialAlias = path.substr(1, path.find_first_of("\\/"));
|
||||||
|
|
||||||
// Not worth searching when potentialAlias cannot be an alias
|
// Not worth searching when potentialAlias cannot be an alias
|
||||||
if (!Luau::isValidAlias(potentialAlias))
|
if (!Luau::isValidAlias(potentialAlias))
|
||||||
return;
|
luaL_errorL(L, "@%s is not a valid alias", potentialAlias.c_str());
|
||||||
|
|
||||||
std::optional<std::string> alias = getAlias(potentialAlias);
|
std::optional<std::string> alias = getAlias(potentialAlias);
|
||||||
if (alias)
|
if (alias)
|
||||||
{
|
{
|
||||||
path = *alias + path.substr(potentialAlias.size());
|
path = *alias + path.substr(potentialAlias.size() + 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
luaL_errorL(L, "@%s is not a valid alias", potentialAlias.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#include "Luau/UnwindBuilder.h"
|
#include "Luau/UnwindBuilder.h"
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
#if defined(_WIN32) && defined(_M_X64)
|
#if defined(_WIN32) && defined(_M_X64)
|
||||||
|
|
||||||
|
|
|
@ -42,8 +42,18 @@
|
||||||
LUAU_FASTFLAGVARIABLE(DebugCodegenNoOpt, false)
|
LUAU_FASTFLAGVARIABLE(DebugCodegenNoOpt, false)
|
||||||
LUAU_FASTFLAGVARIABLE(DebugCodegenOptSize, false)
|
LUAU_FASTFLAGVARIABLE(DebugCodegenOptSize, false)
|
||||||
LUAU_FASTFLAGVARIABLE(DebugCodegenSkipNumbering, false)
|
LUAU_FASTFLAGVARIABLE(DebugCodegenSkipNumbering, false)
|
||||||
|
|
||||||
|
// Per-module IR instruction count limit
|
||||||
LUAU_FASTINTVARIABLE(CodegenHeuristicsInstructionLimit, 1'048'576) // 1 M
|
LUAU_FASTINTVARIABLE(CodegenHeuristicsInstructionLimit, 1'048'576) // 1 M
|
||||||
LUAU_FASTINTVARIABLE(CodegenHeuristicsBlockLimit, 65'536) // 64 K
|
|
||||||
|
// Per-function IR block limit
|
||||||
|
// Current value is based on some member variables being limited to 16 bits
|
||||||
|
// Because block check is made before optimization passes and optimization can generate new blocks, limit is lowered 2x
|
||||||
|
// The limit will probably be adjusted in the future to avoid performance issues with analysis that's more complex than O(n)
|
||||||
|
LUAU_FASTINTVARIABLE(CodegenHeuristicsBlockLimit, 32'768) // 32 K
|
||||||
|
|
||||||
|
// Per-function IR instruction limit
|
||||||
|
// Current value is based on some member variables being limited to 16 bits
|
||||||
LUAU_FASTINTVARIABLE(CodegenHeuristicsBlockInstructionLimit, 65'536) // 64 K
|
LUAU_FASTINTVARIABLE(CodegenHeuristicsBlockInstructionLimit, 65'536) // 64 K
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
|
@ -104,11 +114,18 @@ static void logPerfFunction(Proto* p, uintptr_t addr, unsigned size)
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename AssemblyBuilder>
|
template<typename AssemblyBuilder>
|
||||||
static std::optional<NativeProto> createNativeFunction(AssemblyBuilder& build, ModuleHelpers& helpers, Proto* proto)
|
static std::optional<NativeProto> createNativeFunction(AssemblyBuilder& build, ModuleHelpers& helpers, Proto* proto, uint32_t& totalIrInstCount)
|
||||||
{
|
{
|
||||||
IrBuilder ir;
|
IrBuilder ir;
|
||||||
ir.buildFunctionIr(proto);
|
ir.buildFunctionIr(proto);
|
||||||
|
|
||||||
|
unsigned instCount = unsigned(ir.function.instructions.size());
|
||||||
|
|
||||||
|
if (totalIrInstCount + instCount >= unsigned(FInt::CodegenHeuristicsInstructionLimit.value))
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
totalIrInstCount += instCount;
|
||||||
|
|
||||||
if (!lowerFunction(ir, build, helpers, proto, {}, /* stats */ nullptr))
|
if (!lowerFunction(ir, build, helpers, proto, {}, /* stats */ nullptr))
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
|
|
||||||
|
@ -291,9 +308,13 @@ CodeGenCompilationResult compile(lua_State* L, int idx, unsigned int flags, Comp
|
||||||
std::vector<NativeProto> results;
|
std::vector<NativeProto> results;
|
||||||
results.reserve(protos.size());
|
results.reserve(protos.size());
|
||||||
|
|
||||||
|
uint32_t totalIrInstCount = 0;
|
||||||
|
|
||||||
for (Proto* p : protos)
|
for (Proto* p : protos)
|
||||||
if (std::optional<NativeProto> np = createNativeFunction(build, helpers, p))
|
{
|
||||||
|
if (std::optional<NativeProto> np = createNativeFunction(build, helpers, p, totalIrInstCount))
|
||||||
results.push_back(*np);
|
results.push_back(*np);
|
||||||
|
}
|
||||||
|
|
||||||
// Very large modules might result in overflowing a jump offset; in this case we currently abandon the entire module
|
// Very large modules might result in overflowing a jump offset; in this case we currently abandon the entire module
|
||||||
if (!build.finalize())
|
if (!build.finalize())
|
||||||
|
|
|
@ -253,11 +253,6 @@ inline bool lowerIr(A64::AssemblyBuilderA64& build, IrBuilder& ir, const std::ve
|
||||||
template<typename AssemblyBuilder>
|
template<typename AssemblyBuilder>
|
||||||
inline bool lowerFunction(IrBuilder& ir, AssemblyBuilder& build, ModuleHelpers& helpers, Proto* proto, AssemblyOptions options, LoweringStats* stats)
|
inline bool lowerFunction(IrBuilder& ir, AssemblyBuilder& build, ModuleHelpers& helpers, Proto* proto, AssemblyOptions options, LoweringStats* stats)
|
||||||
{
|
{
|
||||||
helpers.bytecodeInstructionCount += unsigned(ir.function.instructions.size());
|
|
||||||
|
|
||||||
if (helpers.bytecodeInstructionCount >= unsigned(FInt::CodegenHeuristicsInstructionLimit.value))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
killUnusedBlocks(ir.function);
|
killUnusedBlocks(ir.function);
|
||||||
|
|
||||||
unsigned preOptBlockCount = 0;
|
unsigned preOptBlockCount = 0;
|
||||||
|
@ -268,9 +263,7 @@ inline bool lowerFunction(IrBuilder& ir, AssemblyBuilder& build, ModuleHelpers&
|
||||||
preOptBlockCount += (block.kind != IrBlockKind::Dead);
|
preOptBlockCount += (block.kind != IrBlockKind::Dead);
|
||||||
unsigned blockInstructions = block.finish - block.start;
|
unsigned blockInstructions = block.finish - block.start;
|
||||||
maxBlockInstructions = std::max(maxBlockInstructions, blockInstructions);
|
maxBlockInstructions = std::max(maxBlockInstructions, blockInstructions);
|
||||||
};
|
}
|
||||||
|
|
||||||
helpers.preOptBlockCount += preOptBlockCount;
|
|
||||||
|
|
||||||
// we update stats before checking the heuristic so that even if we bail out
|
// we update stats before checking the heuristic so that even if we bail out
|
||||||
// our stats include information about the limit that was exceeded.
|
// our stats include information about the limit that was exceeded.
|
||||||
|
@ -280,9 +273,7 @@ inline bool lowerFunction(IrBuilder& ir, AssemblyBuilder& build, ModuleHelpers&
|
||||||
stats->maxBlockInstructions = maxBlockInstructions;
|
stats->maxBlockInstructions = maxBlockInstructions;
|
||||||
}
|
}
|
||||||
|
|
||||||
// we use helpers.blocksPreOpt instead of stats.blocksPreOpt since
|
if (preOptBlockCount >= unsigned(FInt::CodegenHeuristicsBlockLimit.value))
|
||||||
// stats can be null across some code paths.
|
|
||||||
if (helpers.preOptBlockCount >= unsigned(FInt::CodegenHeuristicsBlockLimit.value))
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (maxBlockInstructions >= unsigned(FInt::CodegenHeuristicsBlockInstructionLimit.value))
|
if (maxBlockInstructions >= unsigned(FInt::CodegenHeuristicsBlockInstructionLimit.value))
|
||||||
|
|
|
@ -31,9 +31,6 @@ struct ModuleHelpers
|
||||||
|
|
||||||
// A64
|
// A64
|
||||||
Label continueCall; // x0: closure
|
Label continueCall; // x0: closure
|
||||||
|
|
||||||
unsigned bytecodeInstructionCount = 0;
|
|
||||||
unsigned preOptBlockCount = 0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace CodeGen
|
} // namespace CodeGen
|
||||||
|
|
|
@ -285,9 +285,8 @@ public:
|
||||||
using value_type = Item;
|
using value_type = Item;
|
||||||
using reference = Item&;
|
using reference = Item&;
|
||||||
using pointer = Item*;
|
using pointer = Item*;
|
||||||
using iterator = pointer;
|
using difference_type = ptrdiff_t;
|
||||||
using difference_type = size_t;
|
using iterator_category = std::forward_iterator_tag;
|
||||||
using iterator_category = std::input_iterator_tag;
|
|
||||||
|
|
||||||
const_iterator()
|
const_iterator()
|
||||||
: set(0)
|
: set(0)
|
||||||
|
@ -348,6 +347,12 @@ public:
|
||||||
class iterator
|
class iterator
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
using value_type = MutableItem;
|
||||||
|
using reference = MutableItem&;
|
||||||
|
using pointer = MutableItem*;
|
||||||
|
using difference_type = ptrdiff_t;
|
||||||
|
using iterator_category = std::forward_iterator_tag;
|
||||||
|
|
||||||
iterator()
|
iterator()
|
||||||
: set(0)
|
: set(0)
|
||||||
, index(0)
|
, index(0)
|
||||||
|
|
|
@ -14,6 +14,7 @@ inline bool isFlagExperimental(const char* flag)
|
||||||
"LuauInstantiateInSubtyping", // requires some fixes to lua-apps code
|
"LuauInstantiateInSubtyping", // requires some fixes to lua-apps code
|
||||||
"LuauTinyControlFlowAnalysis", // waiting for updates to packages depended by internal builtin plugins
|
"LuauTinyControlFlowAnalysis", // waiting for updates to packages depended by internal builtin plugins
|
||||||
"LuauFixIndexerSubtypingOrdering", // requires some small fixes to lua-apps code since this fixes a false negative
|
"LuauFixIndexerSubtypingOrdering", // requires some small fixes to lua-apps code since this fixes a false negative
|
||||||
|
"LuauUpdatedRequireByStringSemantics", // requires some small fixes to fully implement some proposed changes
|
||||||
// makes sure we always have at least one entry
|
// makes sure we always have at least one entry
|
||||||
nullptr,
|
nullptr,
|
||||||
};
|
};
|
||||||
|
|
|
@ -66,7 +66,8 @@ end
|
||||||
-- and 'false' otherwise.
|
-- and 'false' otherwise.
|
||||||
--
|
--
|
||||||
-- Example usage:
|
-- Example usage:
|
||||||
-- local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
-- local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
-- local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
-- function testFunc()
|
-- function testFunc()
|
||||||
-- ...
|
-- ...
|
||||||
-- end
|
-- end
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
--!nonstrict
|
--!nonstrict
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
local stretchTreeDepth = 18 -- about 16Mb
|
local stretchTreeDepth = 18 -- about 16Mb
|
||||||
local longLivedTreeDepth = 16 -- about 4Mb
|
local longLivedTreeDepth = 16 -- about 4Mb
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
local count = 1
|
local count = 1
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
local count = 1
|
local count = 1
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
local count = 1
|
local count = 1
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
local t = {}
|
local t = {}
|
||||||
|
|
|
@ -21,7 +21,8 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
SOFTWARE.
|
SOFTWARE.
|
||||||
]]
|
]]
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,8 @@
|
||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
]]
|
]]
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
bench.runCode(function()
|
bench.runCode(function()
|
||||||
for j=1,1e6 do
|
for j=1,1e6 do
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
local t = table.create(250001, 0)
|
local t = table.create(250001, 0)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
local t = table.create(5000001, 0)
|
local t = table.create(5000001, 0)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
local t = table.create(250001, 0)
|
local t = table.create(250001, 0)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
local arr_months = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}
|
local arr_months = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
bench.runCode(function()
|
bench.runCode(function()
|
||||||
for j=1,1e6 do
|
for j=1,1e6 do
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
bench.runCode(function()
|
bench.runCode(function()
|
||||||
local src = string.rep("abcdefghijklmnopqrstuvwxyz", 100)
|
local src = string.rep("abcdefghijklmnopqrstuvwxyz", 100)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end
|
||||||
|
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
|
||||||
|
|
||||||
bench.runCode(function()
|
bench.runCode(function()
|
||||||
for outer=1,28,3 do
|
for outer=1,28,3 do
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue