Merge branch 'upstream' of https://github.com/luau-lang/luau into upstream

This commit is contained in:
Vighnesh 2024-01-11 14:55:06 -08:00
commit d4883bfb32
153 changed files with 1649 additions and 857 deletions

View file

@ -197,24 +197,6 @@ struct UnpackConstraint
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
//
// 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,
NameConstraint, TypeAliasExpansionConstraint, FunctionCallConstraint, PrimitiveTypeConstraint, HasPropConstraint, SetPropConstraint,
SetIndexerConstraint, SingletonOrTopTypeConstraint, UnpackConstraint, RefineConstraint, SetOpConstraint, ReduceConstraint, ReducePackConstraint>;
SetIndexerConstraint, SingletonOrTopTypeConstraint, UnpackConstraint, SetOpConstraint, ReduceConstraint, ReducePackConstraint>;
struct Constraint
{

View file

@ -150,7 +150,7 @@ private:
*/
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.

View file

@ -132,7 +132,6 @@ struct ConstraintSolver
bool tryDispatch(const SetIndexerConstraint& c, NotNull<const Constraint> constraint, bool force);
bool tryDispatch(const SingletonOrTopTypeConstraint& 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 ReduceConstraint& c, NotNull<const Constraint> constraint, bool force);
bool tryDispatch(const ReducePackConstraint& c, NotNull<const Constraint> constraint, bool force);

View file

@ -74,8 +74,15 @@ private:
struct DfgScope
{
enum ScopeType
{
Linear,
Loop,
Function,
};
DfgScope* parent;
bool isLoopScope;
ScopeType scopeType;
using Bindings = DenseHashMap<Symbol, 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;
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 joinBindings(DfgScope::Bindings& p, const DfgScope::Bindings& a, const DfgScope::Bindings& b);
void joinProps(DfgScope::Props& p, const DfgScope::Props& a, const DfgScope::Props& b);
void joinBindings(DfgScope* p, const DfgScope& a, const DfgScope& b);
void joinProps(DfgScope* p, const DfgScope& a, const DfgScope& b);
DefId lookup(DfgScope* scope, Symbol symbol);
DefId lookup(DfgScope* scope, DefId def, const std::string& key);
@ -167,11 +184,11 @@ private:
DataFlowResult visitExpr(DfgScope* scope, AstExprError* error);
void visitLValue(DfgScope* scope, AstExpr* e, DefId incomingDef, bool isCompoundAssignment = false);
void visitLValue(DfgScope* scope, AstExprLocal* l, DefId incomingDef, bool isCompoundAssignment);
void visitLValue(DfgScope* scope, AstExprGlobal* g, DefId incomingDef, bool isCompoundAssignment);
void visitLValue(DfgScope* scope, AstExprIndexName* i, DefId incomingDef);
void visitLValue(DfgScope* scope, AstExprIndexExpr* i, DefId incomingDef);
void visitLValue(DfgScope* scope, AstExprError* e, DefId incomingDef);
DefId visitLValue(DfgScope* scope, AstExprLocal* l, DefId incomingDef, bool isCompoundAssignment);
DefId visitLValue(DfgScope* scope, AstExprGlobal* g, DefId incomingDef, bool isCompoundAssignment);
DefId visitLValue(DfgScope* scope, AstExprIndexName* i, DefId incomingDef);
DefId visitLValue(DfgScope* scope, AstExprIndexExpr* i, DefId incomingDef);
DefId visitLValue(DfgScope* scope, AstExprError* e, DefId incomingDef);
void visitType(DfgScope* scope, AstType* t);
void visitType(DfgScope* scope, AstTypeReference* r);

View file

@ -73,6 +73,7 @@ const T* get(DefId def)
}
bool containsSubscriptedDefinition(DefId def);
void collectOperands(DefId def, std::vector<DefId>* operands);
struct DefArena
{

View file

@ -118,6 +118,12 @@ public:
class const_iterator
{
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)
: impl(impl)
, end(end)

View file

@ -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
struct BooleanSingleton
{

View file

@ -162,6 +162,8 @@ struct BuiltinTypeFamilies
TypeFamily leFamily;
TypeFamily eqFamily;
TypeFamily refineFamily;
void addToScope(NotNull<TypeArena> arena, NotNull<Scope> scope) const;
};

View file

@ -200,6 +200,23 @@ struct AstJsonEncoder : public AstVisitor
{
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)
{
@ -848,6 +865,7 @@ struct AstJsonEncoder : public AstVisitor
PROP(generics);
PROP(genericPacks);
PROP(argTypes);
PROP(argNames);
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
{
write(node);

View file

@ -205,7 +205,7 @@ ScopePtr ConstraintGenerator::childScope(AstNode* node, const ScopePtr& parent)
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))
return scope->lookup(def);
@ -213,22 +213,24 @@ std::optional<TypeId> ConstraintGenerator::lookup(Scope* scope, DefId def)
{
if (auto found = scope->lookup(def))
return *found;
else if (!prototype)
return std::nullopt;
TypeId res = builtinTypes->neverType;
for (DefId operand : phi->operands)
{
// `scope->lookup(operand)` may return nothing because it could be a phi node of globals, but one of
// the operand of that global has never been assigned a type, and so it should be an error.
// e.g.
// ```
// if foo() then
// g = 5
// end
// -- `g` here is a phi node of the assignment to `g`, or the original revision of `g` before the branch.
// ```
TypeId ty = scope->lookup(operand).value_or(builtinTypes->errorRecoveryType());
res = simplifyUnion(builtinTypes, arena, res, ty).result;
// `scope->lookup(operand)` may return nothing because we only bind a type to that operand
// once we've seen that particular `DefId`. In this case, we need to prototype those types
// and use those at a later time.
std::optional<TypeId> ty = lookup(scope, operand, /*prototype*/false);
if (!ty)
{
ty = arena->addType(BlockedType{});
rootScope->lvalueTypes[operand] = *ty;
}
res = simplifyUnion(builtinTypes, arena, res, *ty).result;
}
scope->lvalueTypes[def] = res;
@ -255,7 +257,7 @@ void ConstraintGenerator::unionRefinements(const RefinementContext& lhs, const R
return types[0];
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]);
if (sr.blockedTypes.empty())
return sr.result;
@ -439,10 +441,14 @@ void ConstraintGenerator::applyRefinements(const ScopePtr& scope, Location locat
{
if (mustDeferIntersection(ty) || mustDeferIntersection(dt))
{
TypeId r = arena->addType(BlockedType{});
addConstraint(scope, location, RefineConstraint{RefineConstraint::Intersection, r, ty, dt});
TypeId resultType = arena->addType(TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.refineFamily},
{ty, dt},
{},
});
addConstraint(scope, location, ReduceConstraint{resultType});
ty = r;
ty = resultType;
}
else
{
@ -861,7 +867,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f
DenseHashSet<Constraint*> excludeList{nullptr};
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>())
{
@ -1003,9 +1009,6 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatAssign* ass
checkLValue(scope, lvalue, assignee);
assignees.push_back(assignee);
DefId def = dfg->getDef(lvalue);
scope->lvalueTypes[def] = assignee;
}
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
* global that is not already in-scope is definitely an unknown symbol.
*/
if (auto ty = lookup(scope.get(), def))
return Inference{*ty, refinementArena.proposition(key, builtinTypes->truthyType)};
else if (auto ty = scope->lookup(global->name))
if (auto ty = lookup(scope.get(), def, /*prototype=*/false))
{
rootScope->lvalueTypes[def] = *ty;
return Inference{*ty, refinementArena.proposition(key, builtinTypes->truthyType)};
}
else
{
reportError(global->location, UnknownSymbol{global->name.value});
reportError(global->location, UnknownSymbol{global->name.value, UnknownSymbol::Binding});
return Inference{builtinTypes->errorRecoveryType()};
}
}
@ -3110,6 +3111,16 @@ struct GlobalPrepopulator : AstVisitor
return true;
}
bool visit(AstType*) override
{
return true;
}
bool visit(class AstTypePack* node) override
{
return true;
}
};
void ConstraintGenerator::prepopulateGlobalScope(const ScopePtr& globalScope, AstStatBlock* program)

View file

@ -545,8 +545,6 @@ bool ConstraintSolver::tryDispatch(NotNull<const Constraint> constraint, bool fo
success = tryDispatch(*sottc, constraint);
else if (auto uc = get<UnpackConstraint>(*constraint))
success = tryDispatch(*uc, constraint);
else if (auto rc = get<RefineConstraint>(*constraint))
success = tryDispatch(*rc, constraint, force);
else if (auto soc = get<SetOpConstraint>(*constraint))
success = tryDispatch(*soc, constraint, force);
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
// cycle infinitely, we need to scan the type function for cases where we
// expand the same alias with different type saturatedTypeArguments. See
// https://github.com/Roblox/luau/pull/68 for the RFC responsible for this.
// This is a little nicer than using a recursion limit because we can catch
// the infinite expansion before actually trying to expand it.
// https://github.com/luau-lang/luau/pull/68 for the RFC responsible for
// this. This is a little nicer than using a recursion limit because we can
// catch the infinite expansion before actually trying to expand it.
InfiniteTypeFinder itf{this, signature, constraint->scope};
itf.traverse(tf->type);
@ -1505,151 +1503,6 @@ bool ConstraintSolver::tryDispatch(const UnpackConstraint& c, NotNull<const Cons
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 blocked = false;

View file

@ -116,7 +116,7 @@ bool DfgScope::canUpdateDefinition(Symbol symbol) const
{
if (current->bindings.find(symbol))
return true;
else if (current->isLoopScope)
else if (current->scopeType == DfgScope::Loop)
return false;
}
@ -129,7 +129,7 @@ bool DfgScope::canUpdateDefinition(DefId def, const std::string& key) const
{
if (auto props = current->props.find(def))
return true;
else if (current->isLoopScope)
else if (current->scopeType == DfgScope::Loop)
return false;
}
@ -144,6 +144,7 @@ DataFlowGraph DataFlowGraphBuilder::build(AstStatBlock* block, NotNull<InternalE
builder.handle = handle;
builder.moduleScope = builder.childScope(nullptr); // nullptr is the root DFG scope.
builder.visitBlockWithoutChildScope(builder.moduleScope, block);
builder.resolveCaptures();
if (FFlag::DebugLuauFreezeArena)
{
@ -154,43 +155,64 @@ DataFlowGraph DataFlowGraphBuilder::build(AstStatBlock* block, NotNull<InternalE
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)
{
joinBindings(p->bindings, a->bindings, b->bindings);
joinProps(p->props, a->props, b->props);
joinBindings(p, *a, *b);
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))
p[sym] = defArena->phi(NotNull{def1}, NotNull{*def2});
else if (auto def2 = p.find(sym))
p[sym] = defArena->phi(NotNull{def1}, NotNull{*def2});
if (auto def2 = b.bindings.find(sym))
p->bindings[sym] = defArena->phi(NotNull{def1}, NotNull{*def2});
else if (auto def2 = p->lookup(sym))
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))
p[sym] = defArena->phi(NotNull{def1}, NotNull{*def2});
if (auto def2 = p->lookup(sym))
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)
{
if (auto it = b.find(k); it != b.end())
p[k] = defArena->phi(NotNull{it->second}, NotNull{defA});
else if (auto it = p.find(k); it != p.end())
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
p[k] = defA;
}
@ -201,50 +223,72 @@ void DataFlowGraphBuilder::joinProps(DfgScope::Props& p, const DfgScope::Props&
continue;
else if (auto it = p.find(k); it != p.end())
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
p[k] = defB;
}
};
for (const auto& [def, a1] : a)
for (const auto& [def, a1] : a.props)
{
p.try_insert(def, {});
if (auto a2 = b.find(def))
phinodify(p[def], a1, *a2);
else if (auto a2 = p.find(def))
phinodify(p[def], a1, *a2);
result->props.try_insert(def, {});
if (auto a2 = b.props.find(def))
phinodify(result, a1, *a2, NotNull{def});
else if (auto a2 = result->props.find(def))
phinodify(result, a1, *a2, NotNull{def});
}
for (const auto& [def, a1] : b)
for (const auto& [def, a1] : b.props)
{
p.try_insert(def, {});
if (a.find(def))
result->props.try_insert(def, {});
if (a.props.find(def))
continue;
else if (auto a2 = p.find(def))
phinodify(p[def], a1, *a2);
else if (auto a2 = result->props.find(def))
phinodify(result, a1, *a2, NotNull{def});
}
}
DefId DataFlowGraphBuilder::lookup(DfgScope* scope, Symbol symbol)
{
if (auto found = scope->lookup(symbol))
return *found;
else
for (DfgScope* current = scope; current; current = current->parent)
{
DefId result = defArena->freshCell();
if (symbol.local)
scope->bindings[symbol] = result;
else
moduleScope->bindings[symbol] = result;
return result;
if (auto found = current->bindings.find(symbol))
return NotNull{*found};
else if (current->scopeType == DfgScope::Function)
{
FunctionCapture& capture = captures[symbol];
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)
{
if (auto found = scope->lookup(def, key))
return *found;
else if (auto phi = get<Phi>(def))
for (DfgScope* current = scope; current; current = current->parent)
{
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;
for (DefId operand : phi->operands)
@ -361,7 +405,7 @@ ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatIf* i)
ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatWhile* w)
{
// 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);
visit(whileScope, w->body);
@ -373,7 +417,7 @@ ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatWhile* w)
ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatRepeat* r)
{
// 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);
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.
bool subscripted = i < defs.size() && containsSubscriptedDefinition(defs[i]);
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;
scope->bindings[local] = def;
captures[local].allVersions.push_back(def);
}
return ControlFlow::None;
@ -436,7 +489,7 @@ ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatLocal* l)
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->to);
@ -449,6 +502,7 @@ ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatFor* f)
DefId def = defArena->freshCell();
graph.localDefs[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
visit(forScope, f->body);
@ -460,7 +514,7 @@ ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatFor* 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)
{
@ -470,6 +524,7 @@ ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatForIn* f)
DefId def = defArena->freshCell();
graph.localDefs[local] = def;
forScope->bindings[local] = def;
captures[local].allVersions.push_back(def);
}
// 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,
// but for bug compatibility, we'll assume the same thing here.
DefId prototype = defArena->freshCell();
visitLValue(scope, f->name, prototype);
visitLValue(scope, f->name, defArena->freshCell());
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;
}
@ -539,6 +605,7 @@ ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatLocalFunction* l
DefId def = defArena->freshCell();
graph.localDefs[l->name] = def;
scope->bindings[l->name] = def;
captures[l->name].allVersions.push_back(def);
visitExpr(scope, l->func);
return ControlFlow::None;
@ -559,6 +626,7 @@ ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatDeclareGlobal* d
DefId def = defArena->freshCell();
graph.declaredDefs[d] = def;
scope->bindings[d->name] = def;
captures[d->name].allVersions.push_back(def);
visitType(scope, d->type);
@ -570,6 +638,7 @@ ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatDeclareFunction*
DefId def = defArena->freshCell();
graph.declaredDefs[d] = def;
scope->bindings[d->name] = def;
captures[d->name].allVersions.push_back(def);
DfgScope* unreachable = childScope(scope);
visitGenerics(unreachable, d->generics);
@ -669,14 +738,9 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprGroup* gr
DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprLocal* l)
{
// DfgScope::lookup is intentional here: we want to be able to ice.
if (auto def = scope->lookup(l->local))
{
const RefinementKey* key = keyArena->leaf(*def);
return {*def, key};
}
handle->ice("DFG: AstExprLocal came before its declaration?");
DefId def = lookup(scope, l->local);
const RefinementKey* key = keyArena->leaf(def);
return {def, key};
}
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 {defArena->freshCell(/* subscripted= */true), nullptr};
return {defArena->freshCell(/* subscripted= */ true), nullptr};
}
DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprFunction* f)
{
DfgScope* signatureScope = childScope(scope);
DfgScope* signatureScope = childScope(scope, DfgScope::Function);
if (AstLocal* self = f->self)
{
@ -733,6 +797,7 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprFunction*
DefId def = defArena->freshCell();
graph.localDefs[self] = def;
signatureScope->bindings[self] = def;
captures[self].allVersions.push_back(def);
}
for (AstLocal* param : f->args)
@ -743,6 +808,7 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprFunction*
DefId def = defArena->freshCell();
graph.localDefs[param] = def;
signatureScope->bindings[param] = def;
captures[param].allVersions.push_back(def);
}
if (f->varargAnnotation)
@ -766,14 +832,20 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprFunction*
DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprTable* t)
{
DefId tableCell = defArena->freshCell();
scope->props[tableCell] = {};
for (AstExprTable::Item item : t->items)
{
DataFlowResult result = visitExpr(scope, item.value);
if (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)
@ -827,41 +899,46 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprError* er
void DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExpr* e, DefId incomingDef, bool isCompoundAssignment)
{
if (auto l = e->as<AstExprLocal>())
return visitLValue(scope, l, incomingDef, isCompoundAssignment);
else if (auto g = e->as<AstExprGlobal>())
return visitLValue(scope, g, incomingDef, isCompoundAssignment);
else if (auto i = e->as<AstExprIndexName>())
return visitLValue(scope, i, incomingDef);
else if (auto i = e->as<AstExprIndexExpr>())
return visitLValue(scope, i, incomingDef);
else if (auto error = e->as<AstExprError>())
return visitLValue(scope, error, incomingDef);
else
handle->ice("Unknown AstExpr in DataFlowGraphBuilder::visitLValue");
auto go = [&]() {
if (auto l = e->as<AstExprLocal>())
return visitLValue(scope, l, incomingDef, isCompoundAssignment);
else if (auto g = e->as<AstExprGlobal>())
return visitLValue(scope, g, incomingDef, isCompoundAssignment);
else if (auto i = e->as<AstExprIndexName>())
return visitLValue(scope, i, incomingDef);
else if (auto i = e->as<AstExprIndexExpr>())
return visitLValue(scope, i, incomingDef);
else if (auto error = e->as<AstExprError>())
return visitLValue(scope, error, incomingDef);
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.
if (isCompoundAssignment)
{
if (auto def = scope->lookup(l->local))
graph.compoundAssignDefs[l] = *def;
DefId def = lookup(scope, l->local);
graph.compoundAssignDefs[l] = def;
}
// In order to avoid alias tracking, we need to clip the reference to the parent def.
if (scope->canUpdateDefinition(l->local))
{
DefId updated = defArena->freshCell(containsSubscriptedDefinition(incomingDef));
graph.astDefs[l] = updated;
scope->bindings[l->local] = updated;
captures[l->local].allVersions.push_back(updated);
return updated;
}
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.
if (isCompoundAssignment)
@ -874,28 +951,29 @@ void DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExprGlobal* g, DefId
if (scope->canUpdateDefinition(g->name))
{
DefId updated = defArena->freshCell(containsSubscriptedDefinition(incomingDef));
graph.astDefs[g] = updated;
scope->bindings[g->name] = updated;
captures[g->name].allVersions.push_back(updated);
return updated;
}
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;
if (scope->canUpdateDefinition(parentDef, i->index.value))
{
DefId updated = defArena->freshCell(containsSubscriptedDefinition(incomingDef));
graph.astDefs[i] = updated;
scope->props[parentDef][i->index.value] = updated;
return updated;
}
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;
visitExpr(scope, i->index);
@ -905,20 +983,19 @@ void DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExprIndexExpr* i, Def
if (scope->canUpdateDefinition(parentDef, string->value.data))
{
DefId updated = defArena->freshCell(containsSubscriptedDefinition(incomingDef));
graph.astDefs[i] = updated;
scope->props[parentDef][string->value.data] = updated;
return updated;
}
else
visitExpr(scope, static_cast<AstExpr*>(i));
return visitExpr(scope, static_cast<AstExpr*>(i)).def;
}
graph.astDefs[i] = defArena->freshCell();
else
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;
graph.astDefs[error] = def;
return visitExpr(scope, error).def;
}
void DataFlowGraphBuilder::visitType(DfgScope* scope, AstType* t)

View file

@ -19,17 +19,13 @@ bool containsSubscriptedDefinition(DefId def)
return false;
}
DefId DefArena::freshCell(bool subscripted)
void collectOperands(DefId def, std::vector<DefId>* operands)
{
return NotNull{allocator.allocate(Def{Cell{subscripted}})};
}
static void collectOperands(DefId def, std::vector<DefId>& operands)
{
if (std::find(operands.begin(), operands.end(), def) != operands.end())
LUAU_ASSERT(operands);
if (std::find(operands->begin(), operands->end(), def) != operands->end())
return;
else if (get<Cell>(def))
operands.push_back(def);
operands->push_back(def);
else if (auto phi = get<Phi>(def))
{
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)
{
return phi({a, b});
@ -46,7 +47,7 @@ DefId DefArena::phi(const std::vector<DefId>& defs)
{
std::vector<DefId> operands;
for (DefId operand : defs)
collectOperands(operand, operands);
collectOperands(operand, &operands);
// There's no need to allocate a Phi node for a singleton set.
if (operands.size() == 1)

View file

@ -32,13 +32,12 @@ LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTINT(LuauTarjanChildLimit)
LUAU_FASTFLAG(LuauInferInNoCheckMode)
LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3, false)
LUAU_FASTINTVARIABLE(LuauAutocompleteCheckTimeoutMs, 100) // TODO: Remove with FFlagLuauTypecheckLimitControls
LUAU_FASTFLAGVARIABLE(DebugLuauDeferredConstraintResolution, false)
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson, false)
LUAU_FASTFLAGVARIABLE(DebugLuauReadWriteProperties, false)
LUAU_FASTFLAGVARIABLE(LuauTypecheckLimitControls, false)
LUAU_FASTFLAGVARIABLE(CorrectEarlyReturnInMarkDirty, false)
LUAU_FASTFLAGVARIABLE(LuauDefinitionFileSetModuleName, false)
LUAU_FASTFLAGVARIABLE(LuauRethrowSingleModuleIce, false)
namespace Luau
{
@ -681,8 +680,7 @@ std::vector<ModuleName> Frontend::checkQueuedModules(std::optional<FrontendOptio
sendItemTask(i);
nextItems.clear();
// If we aren't done, but don't have anything processing, we hit a cycle
if (remaining != 0 && processing == 0)
if (FFlag::LuauRethrowSingleModuleIce && processing == 0)
{
// Typechecking might have been cancelled by user, don't return partial results
if (cancelled)
@ -690,9 +688,24 @@ std::vector<ModuleName> Frontend::checkQueuedModules(std::optional<FrontendOptio
// We might have stopped because of a pending exception
if (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();
@ -902,82 +915,41 @@ void Frontend::checkBuildQueueItem(BuildQueueItem& item)
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)
typeCheckLimits.finishTime = TimeTrace::getClock() + *item.options.moduleTimeLimitSec;
if (FInt::LuauTarjanChildLimit > 0)
typeCheckLimits.instantiationChildLimit = std::max(1, int(FInt::LuauTarjanChildLimit * sourceNode.autocompleteLimitsMult));
else
typeCheckLimits.finishTime = std::nullopt;
typeCheckLimits.instantiationChildLimit = 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 (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;
if (FInt::LuauTypeInferIterationLimit > 0)
typeCheckLimits.unifierIterationLimit = std::max(1, int(FInt::LuauTypeInferIterationLimit * sourceNode.autocompleteLimitsMult));
else
typeCheckLimits.unifierIterationLimit = std::nullopt;
}
typeCheckLimits.cancellationToken = item.options.cancellationToken;
if (item.options.forAutocomplete)
{
double autocompleteTimeLimit = FInt::LuauAutocompleteCheckTimeoutMs / 1000.0;
if (!FFlag::LuauTypecheckLimitControls)
{
// The autocomplete typecheck is always in strict mode with DM awareness
// to provide better type information for IDE features
if (autocompleteTimeLimit != 0.0)
typeCheckLimits.finishTime = TimeTrace::getClock() + autocompleteTimeLimit;
else
typeCheckLimits.finishTime = std::nullopt;
// TODO: This is a dirty ad hoc solution for autocomplete timeouts
// We are trying to dynamically adjust our existing limits to lower total typechecking time under the limit
// so that we'll have type information for the whole file at lower quality instead of a full abort in the middle
if (FInt::LuauTarjanChildLimit > 0)
typeCheckLimits.instantiationChildLimit = std::max(1, int(FInt::LuauTarjanChildLimit * sourceNode.autocompleteLimitsMult));
else
typeCheckLimits.instantiationChildLimit = std::nullopt;
if (FInt::LuauTypeInferIterationLimit > 0)
typeCheckLimits.unifierIterationLimit = std::max(1, int(FInt::LuauTypeInferIterationLimit * sourceNode.autocompleteLimitsMult));
else
typeCheckLimits.unifierIterationLimit = std::nullopt;
typeCheckLimits.cancellationToken = item.options.cancellationToken;
}
// The autocomplete typecheck is always in strict mode with DM awareness to provide better type information for IDE features
ModulePtr moduleForAutocomplete = check(sourceModule, Mode::Strict, requireCycles, environmentScope, /*forAutocomplete*/ true,
/*recordJsonLog*/ false, typeCheckLimits);
double duration = getTimestamp() - timestamp;
if (FFlag::LuauTypecheckLimitControls)
{
moduleForAutocomplete->checkDurationSec = duration;
moduleForAutocomplete->checkDurationSec = duration;
if (item.options.moduleTimeLimitSec && item.options.applyInternalLimitScaling)
applyInternalLimitScaling(sourceNode, moduleForAutocomplete, *item.options.moduleTimeLimitSec);
}
else
{
if (moduleForAutocomplete->timeout)
sourceNode.autocompleteLimitsMult = sourceNode.autocompleteLimitsMult / 2.0;
else if (duration < autocompleteTimeLimit / 2.0)
sourceNode.autocompleteLimitsMult = std::min(sourceNode.autocompleteLimitsMult * 2.0, 1.0);
}
if (item.options.moduleTimeLimitSec && item.options.applyInternalLimitScaling)
applyInternalLimitScaling(sourceNode, moduleForAutocomplete, *item.options.moduleTimeLimitSec);
item.stats.timeCheck += duration;
item.stats.filesStrict += 1;
@ -986,29 +958,16 @@ void Frontend::checkBuildQueueItem(BuildQueueItem& item)
return;
}
if (!FFlag::LuauTypecheckLimitControls)
{
typeCheckLimits.cancellationToken = item.options.cancellationToken;
}
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)
applyInternalLimitScaling(sourceNode, module, *item.options.moduleTimeLimitSec);
item.stats.timeCheck += duration;
}
else
{
item.stats.timeCheck += getTimestamp() - timestamp;
}
if (item.options.moduleTimeLimitSec && item.options.applyInternalLimitScaling)
applyInternalLimitScaling(sourceNode, module, *item.options.moduleTimeLimitSec);
item.stats.timeCheck += duration;
item.stats.filesStrict += mode == Mode::Strict;
item.stats.filesNonstrict += mode == Mode::Nonstrict;

View file

@ -12,6 +12,7 @@
#include "Luau/TypeArena.h"
#include "Luau/TypeFamily.h"
#include "Luau/Def.h"
#include "Luau/TypeFwd.h"
#include <iostream>
#include <iterator>
@ -57,8 +58,6 @@ struct StackPusher
struct NonStrictContext
{
std::unordered_map<const Def*, TypeId> context;
NonStrictContext() = default;
NonStrictContext(const NonStrictContext&) = delete;
@ -109,7 +108,12 @@ struct NonStrictContext
// Returns true if the removal was successful
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
@ -118,6 +122,14 @@ struct NonStrictContext
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:
std::optional<TypeId> find(const Def* d) const
{
@ -126,6 +138,9 @@ private:
return {it->second};
return {};
}
std::unordered_map<const Def*, TypeId> context;
};
struct NonStrictTypeChecker
@ -508,8 +523,25 @@ struct NonStrictTypeChecker
// ...
// (unknown^N-1, ~S_N) -> error
std::vector<TypeId> argTypes;
for (TypeId ty : fn->argTypes)
argTypes.push_back(ty);
argTypes.reserve(call->args.size);
// 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
LUAU_ASSERT(call->args.size == argTypes.size());
for (size_t i = 0; i < call->args.size; i++)
@ -523,7 +555,7 @@ struct NonStrictTypeChecker
TypeId expectedArgType = argTypes[i];
DefId def = dfg->getDef(arg);
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
@ -613,15 +645,20 @@ struct NonStrictTypeChecker
std::optional<TypeId> willRunTimeError(AstExpr* fragment, const NonStrictContext& context)
{
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);
SubtypingResult r = subtyping.isSubtype(actualType, *contextTy);
if (r.normalizationTooComplex)
reportError(NormalizationTooComplex{}, fragment->location);
if (r.isSubtype)
return {actualType};
TypeId actualType = lookupType(fragment);
SubtypingResult r = subtyping.isSubtype(actualType, *contextTy);
if (r.normalizationTooComplex)
reportError(NormalizationTooComplex{}, fragment->location);
if (r.isSubtype)
return {actualType};
}
}
return {};
@ -630,15 +667,20 @@ struct NonStrictTypeChecker
std::optional<TypeId> willRunTimeErrorFunctionDefinition(AstLocal* fragment, const NonStrictContext& context)
{
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);
SubtypingResult r2 = subtyping.isSubtype(*contextTy, builtinTypes->unknownType);
if (r1.normalizationTooComplex || r2.normalizationTooComplex)
reportError(NormalizationTooComplex{}, fragment->location);
bool isUnknown = r1.isSubtype && r2.isSubtype;
if (isUnknown)
return {builtinTypes->unknownType};
if (std::optional<TypeId> contextTy = context.find(def))
{
SubtypingResult r1 = subtyping.isSubtype(builtinTypes->unknownType, *contextTy);
SubtypingResult r2 = subtyping.isSubtype(*contextTy, builtinTypes->unknownType);
if (r1.normalizationTooComplex || r2.normalizationTooComplex)
reportError(NormalizationTooComplex{}, fragment->location);
bool isUnknown = r1.isSubtype && r2.isSubtype;
if (isUnknown)
return {builtinTypes->unknownType};
}
}
return {};
}

View file

@ -3,8 +3,10 @@
#include "Luau/Common.h"
#include "Luau/Constraint.h"
#include "Luau/DenseHash.h"
#include "Luau/Location.h"
#include "Luau/Scope.h"
#include "Luau/Set.h"
#include "Luau/TxnLog.h"
#include "Luau/TypeInfer.h"
#include "Luau/TypePack.h"
@ -53,8 +55,8 @@ struct FindCyclicTypes final : TypeVisitor
FindCyclicTypes& operator=(const FindCyclicTypes&) = delete;
bool exhaustive = false;
std::unordered_set<TypeId> visited;
std::unordered_set<TypePackId> visitedPacks;
Luau::Set<TypeId> visited{{}};
Luau::Set<TypePackId> visitedPacks{{}};
std::set<TypeId> cycles;
std::set<TypePackId> cycleTPs;
@ -70,17 +72,17 @@ struct FindCyclicTypes final : TypeVisitor
bool visit(TypeId ty) override
{
return visited.insert(ty).second;
return visited.insert(ty);
}
bool visit(TypePackId tp) override
{
return visitedPacks.insert(tp).second;
return visitedPacks.insert(tp);
}
bool visit(TypeId ty, const FreeType& ft) override
{
if (!visited.insert(ty).second)
if (!visited.insert(ty))
return false;
if (FFlag::DebugLuauDeferredConstraintResolution)
@ -102,7 +104,7 @@ struct FindCyclicTypes final : TypeVisitor
bool visit(TypeId ty, const LocalType& lt) override
{
if (!visited.insert(ty).second)
if (!visited.insert(ty))
return false;
traverse(lt.domain);
@ -112,7 +114,7 @@ struct FindCyclicTypes final : TypeVisitor
bool visit(TypeId ty, const TableType& ttv) override
{
if (!visited.insert(ty).second)
if (!visited.insert(ty))
return false;
if (ttv.name || ttv.syntheticName)
@ -175,10 +177,11 @@ struct StringifierState
ToStringOptions& opts;
ToStringResult& result;
std::unordered_map<TypeId, std::string> cycleNames;
std::unordered_map<TypePackId, std::string> cycleTpNames;
std::unordered_set<void*> seen;
std::unordered_set<std::string> usedNames;
DenseHashMap<TypeId, std::string> cycleNames{{}};
DenseHashMap<TypePackId, std::string> cycleTpNames{{}};
Set<void*> seen{{}};
// `$$$` 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;
bool exhaustive;
@ -197,7 +200,7 @@ struct StringifierState
bool hasSeen(const void* tv)
{
void* ttv = const_cast<void*>(tv);
if (seen.find(ttv) != seen.end())
if (seen.contains(ttv))
return true;
seen.insert(ttv);
@ -207,9 +210,9 @@ struct StringifierState
void unsee(const void* tv)
{
void* ttv = const_cast<void*>(tv);
auto iter = seen.find(ttv);
if (iter != seen.end())
seen.erase(iter);
if (seen.contains(ttv))
seen.erase(ttv);
}
std::string getName(TypeId ty)
@ -222,7 +225,7 @@ struct StringifierState
for (int count = 0; count < 256; ++count)
{
std::string candidate = generateName(usedNames.size() + count);
if (!usedNames.count(candidate))
if (!usedNames.contains(candidate))
{
usedNames.insert(candidate);
n = candidate;
@ -245,7 +248,7 @@ struct StringifierState
for (int count = 0; count < 256; ++count)
{
std::string candidate = generateName(previousNameIndex + count);
if (!usedNames.count(candidate))
if (!usedNames.contains(candidate))
{
previousNameIndex += count;
usedNames.insert(candidate);
@ -358,10 +361,9 @@ struct TypeStringifier
return;
}
auto it = state.cycleNames.find(tv);
if (it != state.cycleNames.end())
if (auto p = state.cycleNames.find(tv))
{
state.emit(it->second);
state.emit(*p);
return;
}
@ -886,7 +888,7 @@ struct TypeStringifier
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)
state.emit("(");
@ -953,7 +955,7 @@ struct TypeStringifier
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)
state.emit("(");
@ -1101,10 +1103,9 @@ struct TypePackStringifier
return;
}
auto it = state.cycleTpNames.find(tp);
if (it != state.cycleTpNames.end())
if (auto p = state.cycleTpNames.find(tp))
{
state.emit(it->second);
state.emit(*p);
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,
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;
@ -1372,9 +1373,8 @@ ToStringResult toStringDetailed(TypeId ty, ToStringOptions& opts)
*
* t1 where t1 = the_whole_root_type
*/
auto it = state.cycleNames.find(ty);
if (it != state.cycleNames.end())
state.emit(it->second);
if (auto p = state.cycleNames.find(ty))
state.emit(*p);
else
tvs.stringify(ty);
@ -1466,9 +1466,8 @@ ToStringResult toStringDetailed(TypePackId tp, ToStringOptions& opts)
*
* t1 where t1 = the_whole_root_type
*/
auto it = state.cycleTpNames.find(tp);
if (it != state.cycleTpNames.end())
state.emit(it->second);
if (auto p = state.cycleTpNames.find(tp))
state.emit(*p);
else
tvs.stringify(tp);
@ -1766,11 +1765,6 @@ std::string toString(const Constraint& constraint, ToStringOptions& opts)
}
else if constexpr (std::is_same_v<T, UnpackConstraint>)
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>)
{
const char* op = c.mode == SetOpConstraint::Union ? " | " : " & ";

View file

@ -106,7 +106,12 @@ public:
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName("thread"), std::nullopt, Location());
case PrimitiveType::Buffer:
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:
LUAU_ASSERT(false); // this should be unreachable.
return nullptr;
}
}

View file

@ -1603,8 +1603,8 @@ struct TypeChecker2
visit(indexExpr->expr, ValueContext::RValue);
visit(indexExpr->index, ValueContext::RValue);
TypeId exprType = lookupType(indexExpr->expr);
TypeId indexType = lookupType(indexExpr->index);
TypeId exprType = follow(lookupType(indexExpr->expr));
TypeId indexType = follow(lookupType(indexExpr->index));
if (auto tt = get<TableType>(exprType))
{

View file

@ -349,7 +349,8 @@ TypeFamilyReductionResult<TypeId> lenFamilyFn(const std::vector<TypeId>& typePar
TypeId operandTy = follow(typeParams.at(0));
// 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}, {}};
const NormalizedType* normTy = ctx->normalizer->normalize(operandTy);
@ -964,6 +965,92 @@ TypeFamilyReductionResult<TypeId> eqFamilyFn(const std::vector<TypeId>& typePara
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()
: notFamily{"not", notFamilyFn}
, lenFamily{"len", lenFamilyFn}
@ -981,6 +1068,7 @@ BuiltinTypeFamilies::BuiltinTypeFamilies()
, ltFamily{"lt", ltFamilyFn}
, leFamily{"le", leFamilyFn}
, eqFamily{"eq", eqFamilyFn}
, refineFamily{"refine", refineFamilyFn}
{
}

View file

@ -325,7 +325,7 @@ int main(int argc, char** argv)
else if (strncmp(argv[i], "--fflags=", 9) == 0)
setLuauFlags(argv[i] + 9);
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)
@ -363,6 +363,7 @@ int main(int argc, char** argv)
if (threadCount <= 0)
threadCount = std::min(TaskScheduler::getThreadCount(), 8u);
try
{
TaskScheduler scheduler(threadCount);
@ -370,6 +371,19 @@ int main(int argc, char** argv)
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;

View file

@ -124,7 +124,7 @@ static bool analyzeFile(const char* name, const unsigned nestingLimit, std::vect
{
Luau::BytecodeBuilder bcb;
compileOrThrow(bcb, source.value(), copts());
compileOrThrow(bcb, *source, copts());
const std::string& bytecode = bcb.getBytecode();

View file

@ -22,16 +22,9 @@ RequireResolver::RequireResolver(lua_State* L, std::string path)
if (isAbsolutePath(pathToResolve))
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(), '\\', '/');
if (isAlias)
{
pathToResolve = pathToResolve.substr(1);
substituteAliasIfPresent(pathToResolve);
}
substituteAliasIfPresent(pathToResolve);
}
[[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)
{
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
if (!Luau::isValidAlias(potentialAlias))
return;
luaL_errorL(L, "@%s is not a valid alias", potentialAlias.c_str());
std::optional<std::string> alias = getAlias(potentialAlias);
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());
}
}

View file

@ -5,6 +5,7 @@
#include "Luau/UnwindBuilder.h"
#include <string.h>
#include <stdlib.h>
#if defined(_WIN32) && defined(_M_X64)

View file

@ -42,8 +42,18 @@
LUAU_FASTFLAGVARIABLE(DebugCodegenNoOpt, false)
LUAU_FASTFLAGVARIABLE(DebugCodegenOptSize, false)
LUAU_FASTFLAGVARIABLE(DebugCodegenSkipNumbering, false)
// Per-module IR instruction count limit
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
namespace Luau
@ -104,11 +114,18 @@ static void logPerfFunction(Proto* p, uintptr_t addr, unsigned size)
}
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;
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))
return std::nullopt;
@ -291,9 +308,13 @@ CodeGenCompilationResult compile(lua_State* L, int idx, unsigned int flags, Comp
std::vector<NativeProto> results;
results.reserve(protos.size());
uint32_t totalIrInstCount = 0;
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);
}
// Very large modules might result in overflowing a jump offset; in this case we currently abandon the entire module
if (!build.finalize())

View file

@ -253,11 +253,6 @@ inline bool lowerIr(A64::AssemblyBuilderA64& build, IrBuilder& ir, const std::ve
template<typename AssemblyBuilder>
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);
unsigned preOptBlockCount = 0;
@ -268,9 +263,7 @@ inline bool lowerFunction(IrBuilder& ir, AssemblyBuilder& build, ModuleHelpers&
preOptBlockCount += (block.kind != IrBlockKind::Dead);
unsigned blockInstructions = block.finish - block.start;
maxBlockInstructions = std::max(maxBlockInstructions, blockInstructions);
};
helpers.preOptBlockCount += preOptBlockCount;
}
// we update stats before checking the heuristic so that even if we bail out
// 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;
}
// we use helpers.blocksPreOpt instead of stats.blocksPreOpt since
// stats can be null across some code paths.
if (helpers.preOptBlockCount >= unsigned(FInt::CodegenHeuristicsBlockLimit.value))
if (preOptBlockCount >= unsigned(FInt::CodegenHeuristicsBlockLimit.value))
return false;
if (maxBlockInstructions >= unsigned(FInt::CodegenHeuristicsBlockInstructionLimit.value))

View file

@ -31,9 +31,6 @@ struct ModuleHelpers
// A64
Label continueCall; // x0: closure
unsigned bytecodeInstructionCount = 0;
unsigned preOptBlockCount = 0;
};
} // namespace CodeGen

View file

@ -285,9 +285,8 @@ public:
using value_type = Item;
using reference = Item&;
using pointer = Item*;
using iterator = pointer;
using difference_type = size_t;
using iterator_category = std::input_iterator_tag;
using difference_type = ptrdiff_t;
using iterator_category = std::forward_iterator_tag;
const_iterator()
: set(0)
@ -348,6 +347,12 @@ public:
class iterator
{
public:
using value_type = MutableItem;
using reference = MutableItem&;
using pointer = MutableItem*;
using difference_type = ptrdiff_t;
using iterator_category = std::forward_iterator_tag;
iterator()
: set(0)
, index(0)

View file

@ -14,6 +14,7 @@ inline bool isFlagExperimental(const char* flag)
"LuauInstantiateInSubtyping", // requires some fixes to lua-apps code
"LuauTinyControlFlowAnalysis", // waiting for updates to packages depended by internal builtin plugins
"LuauFixIndexerSubtypingOrdering", // requires some small fixes to lua-apps code since this fixes a false negative
"LuauUpdatedRequireByStringSemantics", // requires some small fixes to fully implement some proposed changes
// makes sure we always have at least one entry
nullptr,
};

View file

@ -66,7 +66,8 @@ end
-- and 'false' otherwise.
--
-- 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()
-- ...
-- end

View file

@ -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()

View file

@ -1,5 +1,6 @@
--!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 longLivedTreeDepth = 16 -- about 4Mb

View file

@ -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()
local count = 1

View file

@ -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()
local count = 1

View file

@ -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()
local count = 1

View file

@ -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()
local t = {}

View file

@ -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
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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -22,7 +22,8 @@
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* 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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()
for j=1,1e6 do

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()
local t = table.create(250001, 0)

View file

@ -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()
local t = table.create(5000001, 0)

View file

@ -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()
local t = table.create(250001, 0)

View file

@ -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()

View file

@ -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"}

View file

@ -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()
for j=1,1e6 do

View file

@ -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()

View file

@ -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()

View file

@ -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()
local src = string.rep("abcdefghijklmnopqrstuvwxyz", 100)

View file

@ -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()
for outer=1,28,3 do

Some files were not shown because too many files have changed in this diff Show more