- Add SUBRK and DIVRK bytecode instructions
    - Enables future performance optimizations

Miscellaneous
- Small performance improvements to new non-strict mode
- Introduce more scripts for fuzzing
- Improcements to dataflow analysis
This commit is contained in:
Vighnesh 2023-12-01 18:04:44 -08:00
parent 674c6c40c0
commit 557e77a676
140 changed files with 4321 additions and 1052 deletions

View File

@ -49,8 +49,8 @@ struct InstantiationConstraint
TypeId superType;
};
// iteratee is iterable
// iterators is the iteration types.
// variables ~ iterate iterator
// Unpack the iterator, figure out what types it iterates over, and bind those types to variables.
struct IterableConstraint
{
TypePackId iterator;

View File

@ -225,6 +225,7 @@ private:
Inference check(const ScopePtr& scope, AstExprConstantBool* bool_, std::optional<TypeId> expectedType, bool forceSingleton);
Inference check(const ScopePtr& scope, AstExprLocal* local);
Inference check(const ScopePtr& scope, AstExprGlobal* global);
Inference checkIndexName(const ScopePtr& scope, const RefinementKey* key, AstExpr* indexee, std::string index);
Inference check(const ScopePtr& scope, AstExprIndexName* indexName);
Inference check(const ScopePtr& scope, AstExprIndexExpr* indexExpr);
Inference check(const ScopePtr& scope, AstExprFunction* func, std::optional<TypeId> expectedType, bool generalize);

View File

@ -372,13 +372,21 @@ struct CheckedFunctionCallError
bool operator==(const CheckedFunctionCallError& rhs) const;
};
struct NonStrictFunctionDefinitionError
{
std::string functionName;
std::string argument;
TypeId argumentType;
bool operator==(const NonStrictFunctionDefinitionError& rhs) const;
};
using TypeErrorData = Variant<TypeMismatch, UnknownSymbol, UnknownProperty, NotATable, CannotExtendTable, OnlyTablesCanHaveMethods,
DuplicateTypeDefinition, CountMismatch, FunctionDoesNotTakeSelf, FunctionRequiresSelf, OccursCheckFailed, UnknownRequire,
IncorrectGenericParameterCount, SyntaxError, CodeTooComplex, UnificationTooComplex, UnknownPropButFoundLikeProp, GenericError, InternalError,
CannotCallNonFunction, ExtraInformation, DeprecatedApiUsed, ModuleHasCyclicDependency, IllegalRequire, FunctionExitsWithoutReturning,
DuplicateGenericParameter, CannotInferBinaryOperation, MissingProperties, SwappedGenericTypeParameter, OptionalValueAccess, MissingUnionProperty,
TypesAreUnrelated, NormalizationTooComplex, TypePackMismatch, DynamicPropertyLookupOnClassesUnsafe, UninhabitedTypeFamily,
UninhabitedTypePackFamily, WhereClauseNeeded, PackWhereClauseNeeded, CheckedFunctionCallError>;
UninhabitedTypePackFamily, WhereClauseNeeded, PackWhereClauseNeeded, CheckedFunctionCallError, NonStrictFunctionDefinitionError>;
struct TypeErrorSummary
{

View File

@ -32,12 +32,17 @@ enum class SubtypingVariance
// Used for an empty key. Should never appear in actual code.
Invalid,
Covariant,
// This is used to identify cases where we have a covariant + a
// contravariant reason and we need to merge them.
Contravariant,
Invariant,
};
struct SubtypingReasoning
{
// The path, relative to the _root subtype_, where subtyping failed.
Path subPath;
// The path, relative to the _root supertype_, where subtyping failed.
Path superPath;
SubtypingVariance variance = SubtypingVariance::Covariant;
@ -49,6 +54,9 @@ struct SubtypingReasoningHash
size_t operator()(const SubtypingReasoning& r) const;
};
using SubtypingReasonings = DenseHashSet<SubtypingReasoning, SubtypingReasoningHash>;
static const SubtypingReasoning kEmptyReasoning = SubtypingReasoning{TypePath::kEmpty, TypePath::kEmpty, SubtypingVariance::Invalid};
struct SubtypingResult
{
bool isSubtype = false;
@ -58,8 +66,7 @@ struct SubtypingResult
/// The reason for isSubtype to be false. May not be present even if
/// isSubtype is false, depending on the input types.
DenseHashSet<SubtypingReasoning, SubtypingReasoningHash> reasoning{
SubtypingReasoning{TypePath::kEmpty, TypePath::kEmpty, SubtypingVariance::Invalid}};
SubtypingReasonings reasoning{kEmptyReasoning};
SubtypingResult& andAlso(const SubtypingResult& other);
SubtypingResult& orElse(const SubtypingResult& other);
@ -69,7 +76,6 @@ struct SubtypingResult
SubtypingResult& withBothPath(TypePath::Path path);
SubtypingResult& withSubPath(TypePath::Path path);
SubtypingResult& withSuperPath(TypePath::Path path);
SubtypingResult& withVariance(SubtypingVariance variance);
// Only negates the `isSubtype`.
static SubtypingResult negate(const SubtypingResult& result);

View File

@ -61,7 +61,7 @@ struct Unifier2
bool unify(TypeId subTy, const UnionType* superUnion);
bool unify(const IntersectionType* subIntersection, TypeId superTy);
bool unify(TypeId subTy, const IntersectionType* superIntersection);
bool unify(const TableType* subTable, const TableType* superTable);
bool unify(TableType* subTable, const TableType* superTable);
bool unify(const MetatableType* subMetatable, const MetatableType* superMetatable);
// TODO think about this one carefully. We don't do unions or intersections of type packs

View File

@ -750,17 +750,18 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatForIn* forI
for (AstLocal* var : forIn->vars)
{
TypeId ty = nullptr;
if (var->annotation)
ty = resolveType(loopScope, var->annotation, /*inTypeArguments*/ false);
else
ty = freshType(loopScope);
loopScope->bindings[var] = Binding{ty, var->location};
TypeId assignee = arena->addType(BlockedType{});
variableTypes.push_back(assignee);
if (var->annotation)
{
TypeId annotationTy = resolveType(loopScope, var->annotation, /*inTypeArguments*/ false);
loopScope->bindings[var] = Binding{annotationTy, var->location};
addConstraint(scope, var->location, SubtypeConstraint{assignee, annotationTy});
}
else
loopScope->bindings[var] = Binding{assignee, var->location};
DefId def = dfg->getDef(var);
loopScope->lvalueTypes[def] = assignee;
}
@ -1439,9 +1440,6 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall*
module->astOriginalCallTypes[call->func] = fnType;
module->astOriginalCallTypes[call] = fnType;
TypeId instantiatedFnType = arena->addType(BlockedType{});
addConstraint(scope, call->location, InstantiationConstraint{instantiatedFnType, fnType});
Checkpoint argBeginCheckpoint = checkpoint(this);
std::vector<TypeId> args;
@ -1740,12 +1738,11 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprGlobal* globa
}
}
Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprIndexName* indexName)
Inference ConstraintGenerator::checkIndexName(const ScopePtr& scope, const RefinementKey* key, AstExpr* indexee, std::string index)
{
TypeId obj = check(scope, indexName->expr).ty;
TypeId obj = check(scope, indexee).ty;
TypeId result = arena->addType(BlockedType{});
const RefinementKey* key = dfg->getRefinementKey(indexName);
if (key)
{
if (auto ty = lookup(scope.get(), key->def))
@ -1754,7 +1751,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprIndexName* in
scope->rvalueRefinements[key->def] = result;
}
addConstraint(scope, indexName->expr->location, HasPropConstraint{result, obj, indexName->index.value});
addConstraint(scope, indexee->location, HasPropConstraint{result, obj, std::move(index)});
if (key)
return Inference{result, refinementArena.proposition(key, builtinTypes->truthyType)};
@ -1762,10 +1759,23 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprIndexName* in
return Inference{result};
}
Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprIndexName* indexName)
{
const RefinementKey* key = dfg->getRefinementKey(indexName);
return checkIndexName(scope, key, indexName->expr, indexName->index.value);
}
Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprIndexExpr* indexExpr)
{
if (auto constantString = indexExpr->index->as<AstExprConstantString>())
{
const RefinementKey* key = dfg->getRefinementKey(indexExpr);
return checkIndexName(scope, key, indexExpr->expr, constantString->value.data);
}
TypeId obj = check(scope, indexExpr->expr).ty;
TypeId indexType = check(scope, indexExpr->index).ty;
TypeId result = freshType(scope);
const RefinementKey* key = dfg->getRefinementKey(indexExpr);
@ -3079,15 +3089,23 @@ struct GlobalPrepopulator : AstVisitor
{
}
bool visit(AstExprGlobal* global) override
{
if (auto ty = globalScope->lookup(global->name))
{
DefId def = dfg->getDef(global);
globalScope->lvalueTypes[def] = *ty;
}
return true;
}
bool visit(AstStatFunction* function) override
{
if (AstExprGlobal* g = function->name->as<AstExprGlobal>())
{
TypeId bt = arena->addType(BlockedType{});
globalScope->bindings[g->name] = Binding{bt};
DefId def = dfg->getDef(function->name);
globalScope->lvalueTypes[def] = bt;
}
return true;

View File

@ -17,6 +17,7 @@
#include "Luau/TypeUtils.h"
#include "Luau/Unifier2.h"
#include "Luau/VisitType.h"
#include <algorithm>
#include <utility>
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolver, false);
@ -1262,9 +1263,6 @@ bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull<const Con
if (isBlocked(subjectType))
return block(subjectType, constraint);
if (!force && get<FreeType>(subjectType))
return block(subjectType, constraint);
std::optional<TypeId> existingPropType = subjectType;
for (const std::string& segment : c.path)
{
@ -1300,25 +1298,13 @@ bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull<const Con
if (get<FreeType>(subjectType))
{
TypeId ty = freshType(arena, builtinTypes, constraint->scope);
// Mint a chain of free tables per c.path
for (auto it = rbegin(c.path); it != rend(c.path); ++it)
{
TableType t{TableState::Free, TypeLevel{}, constraint->scope};
t.props[*it] = {ty};
ty = arena->addType(std::move(t));
}
LUAU_ASSERT(ty);
bind(subjectType, ty);
if (follow(c.resultType) != follow(ty))
bind(c.resultType, ty);
unblock(subjectType, constraint->location);
unblock(c.resultType, constraint->location);
return true;
/*
* This should never occur because lookupTableProp() will add bounds to
* any free types it encounters. There will always be an
* existingPropType if the subject is free.
*/
LUAU_ASSERT(false);
return false;
}
else if (auto ttv = getMutable<TableType>(subjectType))
{
@ -1327,7 +1313,7 @@ bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull<const Con
LUAU_ASSERT(!subjectType->persistent);
ttv->props[c.path[0]] = Property{c.propType};
bind(c.resultType, c.subjectType);
bind(c.resultType, subjectType);
unblock(c.resultType, constraint->location);
return true;
}
@ -1336,26 +1322,12 @@ bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull<const Con
LUAU_ASSERT(!subjectType->persistent);
updateTheTableType(builtinTypes, NotNull{arena}, subjectType, c.path, c.propType);
bind(c.resultType, c.subjectType);
unblock(subjectType, constraint->location);
unblock(c.resultType, constraint->location);
return true;
}
else
{
bind(c.resultType, subjectType);
unblock(c.resultType, constraint->location);
return true;
}
}
else
{
// Other kinds of types don't change shape when properties are assigned
// to them. (if they allow properties at all!)
bind(c.resultType, subjectType);
unblock(c.resultType, constraint->location);
return true;
}
bind(c.resultType, subjectType);
unblock(c.resultType, constraint->location);
return true;
}
bool ConstraintSolver::tryDispatch(const SetIndexerConstraint& c, NotNull<const Constraint> constraint, bool force)
@ -1907,6 +1879,7 @@ bool ConstraintSolver::tryDispatchIterableFunction(
TypeId retIndex;
if (isNil(firstIndexTy) || isOptional(firstIndexTy))
{
// FIXME freshType is suspect here
firstIndex = arena->addType(UnionType{{freshType(arena, builtinTypes, constraint->scope), builtinTypes->nilType}});
retIndex = firstIndex;
}
@ -1948,7 +1921,7 @@ bool ConstraintSolver::tryDispatchIterableFunction(
modifiedNextRetHead.push_back(*it);
TypePackId modifiedNextRetPack = arena->addTypePack(std::move(modifiedNextRetHead), it.tail());
auto psc = pushConstraint(constraint->scope, constraint->location, PackSubtypeConstraint{c.variables, modifiedNextRetPack});
auto psc = pushConstraint(constraint->scope, constraint->location, UnpackConstraint{c.variables, modifiedNextRetPack});
inheritBlocks(constraint, psc);
return true;

View File

@ -533,6 +533,12 @@ struct ErrorConverter
return "Function '" + e.checkedFunctionName + "' expects '" + toString(e.expected) + "' at argument #" + std::to_string(e.argumentIndex) +
", but got '" + Luau::toString(e.passed) + "'";
}
std::string operator()(const NonStrictFunctionDefinitionError& e) const
{
return "Argument " + e.argument + " with type '" + toString(e.argumentType) + "' in function '" + e.functionName +
"' is used in a way that will run time error";
}
};
struct InvalidNameChecker
@ -861,6 +867,11 @@ bool CheckedFunctionCallError::operator==(const CheckedFunctionCallError& rhs) c
argumentIndex == rhs.argumentIndex;
}
bool NonStrictFunctionDefinitionError::operator==(const NonStrictFunctionDefinitionError& rhs) const
{
return functionName == rhs.functionName && argument == rhs.argument && argumentType == rhs.argumentType;
}
std::string toString(const TypeError& error)
{
return toString(error, TypeErrorToStringOptions{});
@ -1032,6 +1043,10 @@ void copyError(T& e, TypeArena& destArena, CloneState& cloneState)
e.expected = clone(e.expected);
e.passed = clone(e.passed);
}
else if constexpr (std::is_same_v<T, NonStrictFunctionDefinitionError>)
{
e.argumentType = clone(e.argumentType);
}
else
static_assert(always_false_v<T>, "Non-exhaustive type switch");
}

View File

@ -38,6 +38,7 @@ LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson, false)
LUAU_FASTFLAGVARIABLE(DebugLuauReadWriteProperties, false)
LUAU_FASTFLAGVARIABLE(LuauTypecheckLimitControls, false)
LUAU_FASTFLAGVARIABLE(CorrectEarlyReturnInMarkDirty, false)
LUAU_FASTFLAGVARIABLE(LuauDefinitionFileSetModuleName, false)
namespace Luau
{
@ -165,6 +166,11 @@ LoadDefinitionFileResult Frontend::loadDefinitionFile(GlobalTypes& globals, Scop
LUAU_TIMETRACE_SCOPE("loadDefinitionFile", "Frontend");
Luau::SourceModule sourceModule;
if (FFlag::LuauDefinitionFileSetModuleName)
{
sourceModule.name = packageName;
sourceModule.humanReadableName = packageName;
}
Luau::ParseResult parseResult = parseSourceForModule(source, sourceModule, captureComments);
if (parseResult.errors.size() > 0)
return LoadDefinitionFileResult{false, parseResult, sourceModule, nullptr};

View File

@ -7,6 +7,8 @@
#include "Luau/TypeArena.h"
#include "Luau/TypeCheckLimits.h"
#include <algorithm>
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
namespace Luau

View File

@ -204,6 +204,9 @@ static void errorToString(std::ostream& stream, const T& err)
else if constexpr (std::is_same_v<T, CheckedFunctionCallError>)
stream << "CheckedFunctionCallError { expected = '" << toString(err.expected) << "', passed = '" << toString(err.passed)
<< "', checkedFunctionName = " << err.checkedFunctionName << ", argumentIndex = " << std::to_string(err.argumentIndex) << " }";
else if constexpr (std::is_same_v<T, NonStrictFunctionDefinitionError>)
stream << "NonStrictFunctionDefinitionError { functionName = '" + err.functionName + "', argument = '" + err.argument +
"', argumentType = '" + toString(err.argumentType) + "' }";
else
static_assert(always_false_v<T>, "Non-exhaustive type switch");
}

View File

@ -14,6 +14,7 @@
#include "Luau/Def.h"
#include <iostream>
#include <iterator>
namespace Luau
{
@ -105,9 +106,10 @@ struct NonStrictContext
return conj;
}
void removeFromContext(const std::vector<DefId>& defs)
// Returns true if the removal was successful
bool remove(const DefId& def)
{
// TODO: unimplemented
return context.erase(def.get()) == 1;
}
std::optional<TypeId> find(const DefId& def) const
@ -138,6 +140,7 @@ struct NonStrictTypeChecker
NotNull<const DataFlowGraph> dfg;
DenseHashSet<TypeId> noTypeFamilyErrors{nullptr};
std::vector<NotNull<Scope>> stack;
DenseHashMap<TypeId, TypeId> cachedNegations{nullptr};
const NotNull<TypeCheckLimits> limits;
@ -271,8 +274,22 @@ struct NonStrictTypeChecker
{
auto StackPusher = pushStack(block);
NonStrictContext ctx;
for (AstStat* statement : block->body)
ctx = NonStrictContext::disjunction(builtinTypes, NotNull{&arena}, ctx, visit(statement));
for (auto it = block->body.rbegin(); it != block->body.rend(); it++)
{
AstStat* stat = *it;
if (AstStatLocal* local = stat->as<AstStatLocal>())
{
// Iterating in reverse order
// local x ; B generates the context of B without x
visit(local);
for (auto local : local->vars)
ctx.remove(dfg->getDef(local));
}
else
ctx = NonStrictContext::disjunction(builtinTypes, NotNull{&arena}, visit(stat), ctx);
}
return ctx;
}
@ -505,9 +522,7 @@ struct NonStrictTypeChecker
AstExpr* arg = call->args.data[i];
TypeId expectedArgType = argTypes[i];
DefId def = dfg->getDef(arg);
// TODO: Cache negations created here!!!
// See Jira Ticket: https://roblox.atlassian.net/browse/CLI-87539
TypeId runTimeErrorTy = arena.addType(NegationType{expectedArgType});
TypeId runTimeErrorTy = getOrCreateNegation(expectedArgType);
fresh.context[def.get()] = runTimeErrorTy;
}
@ -537,8 +552,16 @@ struct NonStrictTypeChecker
NonStrictContext visit(AstExprFunction* exprFn)
{
// TODO: should a function being used as an expression generate a context without the arguments?
auto pusher = pushStack(exprFn);
return visit(exprFn->body);
NonStrictContext remainder = visit(exprFn->body);
for (AstLocal* local : exprFn->args)
{
if (std::optional<TypeId> ty = willRunTimeErrorFunctionDefinition(local, remainder))
reportError(NonStrictFunctionDefinitionError{exprFn->debugname.value, local->name.value, *ty}, local->location);
remainder.remove(dfg->getDef(local));
}
return remainder;
}
NonStrictContext visit(AstExprTable* table)
@ -603,6 +626,31 @@ struct NonStrictTypeChecker
return {};
}
std::optional<TypeId> willRunTimeErrorFunctionDefinition(AstLocal* fragment, const NonStrictContext& context)
{
DefId def = dfg->getDef(fragment);
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 {};
}
private:
TypeId getOrCreateNegation(TypeId baseType)
{
TypeId& cachedResult = cachedNegations[baseType];
if (!cachedResult)
cachedResult = arena.addType(NegationType{baseType});
return cachedResult;
};
};
void checkNonStrict(NotNull<BuiltinTypes> builtinTypes, NotNull<InternalErrorReporter> ice, NotNull<UnifierSharedState> unifierState,

View File

@ -2772,7 +2772,7 @@ bool Normalizer::intersectNormalWithTy(NormalizedType& here, TypeId there, Set<T
return true;
}
else if (get<GenericType>(there) || get<FreeType>(there) || get<BlockedType>(there) || get<PendingExpansionType>(there) ||
get<TypeFamilyInstanceType>(there))
get<TypeFamilyInstanceType>(there) || get<LocalType>(there))
{
NormalizedType thereNorm{builtinTypes};
NormalizedType topNorm{builtinTypes};
@ -2780,6 +2780,10 @@ bool Normalizer::intersectNormalWithTy(NormalizedType& here, TypeId there, Set<T
thereNorm.tyvars.insert_or_assign(there, std::make_unique<NormalizedType>(std::move(topNorm)));
return intersectNormals(here, thereNorm);
}
else if (auto lt = get<LocalType>(there))
{
return intersectNormalWithTy(here, lt->domain, seenSetTypes);
}
NormalizedTyvars tyvars = std::move(here.tyvars);

View File

@ -16,6 +16,8 @@
#include <algorithm>
LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity, false);
namespace Luau
{
@ -55,16 +57,69 @@ size_t SubtypingReasoningHash::operator()(const SubtypingReasoning& r) const
return TypePath::PathHash()(r.subPath) ^ (TypePath::PathHash()(r.superPath) << 1) ^ (static_cast<size_t>(r.variance) << 1);
}
template<typename TID>
static void assertReasoningValid(TID subTy, TID superTy, const SubtypingResult& result, NotNull<BuiltinTypes> builtinTypes)
{
if (!FFlag::DebugLuauSubtypingCheckPathValidity)
return;
for (const SubtypingReasoning& reasoning : result.reasoning)
{
LUAU_ASSERT(traverse(subTy, reasoning.subPath, builtinTypes));
LUAU_ASSERT(traverse(superTy, reasoning.superPath, builtinTypes));
}
}
template<>
void assertReasoningValid<TableIndexer>(TableIndexer subIdx, TableIndexer superIdx, const SubtypingResult& result, NotNull<BuiltinTypes> builtinTypes)
{
// Empty method to satisfy the compiler.
}
static SubtypingReasonings mergeReasonings(const SubtypingReasonings& a, const SubtypingReasonings& b)
{
SubtypingReasonings result{kEmptyReasoning};
for (const SubtypingReasoning& r : a)
{
if (r.variance == SubtypingVariance::Invariant)
result.insert(r);
else if (r.variance == SubtypingVariance::Covariant || r.variance == SubtypingVariance::Contravariant)
{
SubtypingReasoning inverseReasoning = SubtypingReasoning{
r.subPath, r.superPath, r.variance == SubtypingVariance::Covariant ? SubtypingVariance::Contravariant : SubtypingVariance::Covariant};
if (b.contains(inverseReasoning))
result.insert(SubtypingReasoning{r.subPath, r.superPath, SubtypingVariance::Invariant});
else
result.insert(r);
}
}
for (const SubtypingReasoning& r : b)
{
if (r.variance == SubtypingVariance::Invariant)
result.insert(r);
else if (r.variance == SubtypingVariance::Covariant || r.variance == SubtypingVariance::Contravariant)
{
SubtypingReasoning inverseReasoning = SubtypingReasoning{
r.subPath, r.superPath, r.variance == SubtypingVariance::Covariant ? SubtypingVariance::Contravariant : SubtypingVariance::Covariant};
if (a.contains(inverseReasoning))
result.insert(SubtypingReasoning{r.subPath, r.superPath, SubtypingVariance::Invariant});
else
result.insert(r);
}
}
return result;
}
SubtypingResult& SubtypingResult::andAlso(const SubtypingResult& other)
{
// If the other result is not a subtype, we want to join all of its
// reasonings to this one. If this result already has reasonings of its own,
// those need to be attributed here.
if (!other.isSubtype)
{
for (const SubtypingReasoning& r : other.reasoning)
reasoning.insert(r);
}
reasoning = mergeReasonings(reasoning, other.reasoning);
isSubtype &= other.isSubtype;
// `|=` is intentional here, we want to preserve error related flags.
@ -86,10 +141,7 @@ SubtypingResult& SubtypingResult::orElse(const SubtypingResult& other)
if (other.isSubtype)
reasoning.clear();
else
{
for (const SubtypingReasoning& r : other.reasoning)
reasoning.insert(r);
}
reasoning = mergeReasonings(reasoning, other.reasoning);
}
isSubtype |= other.isSubtype;
@ -162,19 +214,6 @@ SubtypingResult& SubtypingResult::withSuperPath(TypePath::Path path)
return *this;
}
SubtypingResult& SubtypingResult::withVariance(SubtypingVariance variance)
{
if (reasoning.empty())
reasoning.insert(SubtypingReasoning{TypePath::kEmpty, TypePath::kEmpty, variance});
else
{
for (auto& r : reasoning)
r.variance = variance;
}
return *this;
}
SubtypingResult SubtypingResult::negate(const SubtypingResult& result)
{
return SubtypingResult{
@ -245,7 +284,10 @@ SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy)
result.isSubtype = false;
}
result.andAlso(isCovariantWith(env, lowerBound, upperBound));
SubtypingResult boundsResult = isCovariantWith(env, lowerBound, upperBound);
boundsResult.reasoning.clear();
result.andAlso(boundsResult);
}
/* TODO: We presently don't store subtype test results in the persistent
@ -370,7 +412,10 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
{
SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy));
if (semantic.isSubtype)
{
semantic.reasoning.clear();
result = semantic;
}
}
}
else if (auto superIntersection = get<IntersectionType>(superTy))
@ -382,7 +427,12 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
{
SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy));
if (semantic.isSubtype)
{
// Clear the semantic reasoning, as any reasonings within
// potentially contain invalid paths.
semantic.reasoning.clear();
result = semantic;
}
}
}
else if (get<AnyType>(superTy))
@ -411,9 +461,31 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
else if (auto p = get2<NegationType, NegationType>(subTy, superTy))
result = isCovariantWith(env, p.first->ty, p.second->ty).withBothComponent(TypePath::TypeField::Negated);
else if (auto subNegation = get<NegationType>(subTy))
{
result = isCovariantWith(env, subNegation, superTy);
if (!result.isSubtype && !result.isErrorSuppressing && !result.normalizationTooComplex)
{
SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy));
if (semantic.isSubtype)
{
semantic.reasoning.clear();
result = semantic;
}
}
}
else if (auto superNegation = get<NegationType>(superTy))
{
result = isCovariantWith(env, subTy, superNegation);
if (!result.isSubtype && !result.isErrorSuppressing && !result.normalizationTooComplex)
{
SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy));
if (semantic.isSubtype)
{
semantic.reasoning.clear();
result = semantic;
}
}
}
else if (auto subGeneric = get<GenericType>(subTy); subGeneric && variance == Variance::Covariant)
{
bool ok = bindGeneric(env, subTy, superTy);
@ -449,6 +521,8 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
else if (auto p = get2<SingletonType, TableType>(subTy, superTy))
result = isCovariantWith(env, p);
assertReasoningValid(subTy, superTy, result, builtinTypes);
return cache(env, result, subTy, superTy);
}
@ -536,7 +610,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
for (size_t i = headSize; i < subHead.size(); ++i)
results.push_back(isCovariantWith(env, subHead[i], vt->ty)
.withSubComponent(TypePath::Index{i})
.withSuperComponent(TypePath::TypeField::Variadic));
.withSuperPath(TypePath::PathBuilder().tail().variadic().build()));
}
else if (auto gt = get<GenericTypePack>(*superTail))
{
@ -664,19 +738,38 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
iceReporter->ice("Subtyping test encountered the unexpected type pack: " + toString(*superTail));
}
return SubtypingResult::all(results);
SubtypingResult result = SubtypingResult::all(results);
assertReasoningValid(subTp, superTp, result, builtinTypes);
return result;
}
template<typename SubTy, typename SuperTy>
SubtypingResult Subtyping::isContravariantWith(SubtypingEnvironment& env, SubTy&& subTy, SuperTy&& superTy)
{
SubtypingResult result = isCovariantWith(env, superTy, subTy);
// If we don't swap the paths here, we will end up producing an invalid path
// whenever we involve contravariance. We'll end up appending path
// components that should belong to the supertype to the subtype, and vice
// versa.
for (auto& reasoning : result.reasoning)
std::swap(reasoning.subPath, reasoning.superPath);
if (result.reasoning.empty())
result.reasoning.insert(SubtypingReasoning{TypePath::kEmpty, TypePath::kEmpty, SubtypingVariance::Contravariant});
else
{
// If we don't swap the paths here, we will end up producing an invalid path
// whenever we involve contravariance. We'll end up appending path
// components that should belong to the supertype to the subtype, and vice
// versa.
for (auto& reasoning : result.reasoning)
{
std::swap(reasoning.subPath, reasoning.superPath);
// Also swap covariant/contravariant, since those are also the other way
// around.
if (reasoning.variance == SubtypingVariance::Covariant)
reasoning.variance = SubtypingVariance::Contravariant;
else if (reasoning.variance == SubtypingVariance::Contravariant)
reasoning.variance = SubtypingVariance::Covariant;
}
}
assertReasoningValid(subTy, superTy, result, builtinTypes);
return result;
}
@ -684,7 +777,17 @@ SubtypingResult Subtyping::isContravariantWith(SubtypingEnvironment& env, SubTy&
template<typename SubTy, typename SuperTy>
SubtypingResult Subtyping::isInvariantWith(SubtypingEnvironment& env, SubTy&& subTy, SuperTy&& superTy)
{
return isCovariantWith(env, subTy, superTy).andAlso(isContravariantWith(env, subTy, superTy)).withVariance(SubtypingVariance::Invariant);
SubtypingResult result = isCovariantWith(env, subTy, superTy).andAlso(isContravariantWith(env, subTy, superTy));
if (result.reasoning.empty())
result.reasoning.insert(SubtypingReasoning{TypePath::kEmpty, TypePath::kEmpty, SubtypingVariance::Invariant});
else
{
for (auto& reasoning : result.reasoning)
reasoning.variance = SubtypingVariance::Invariant;
}
assertReasoningValid(subTy, superTy, result, builtinTypes);
return result;
}
template<typename SubTy, typename SuperTy>
@ -696,13 +799,13 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TryP
template<typename SubTy, typename SuperTy>
SubtypingResult Subtyping::isContravariantWith(SubtypingEnvironment& env, const TryPair<const SubTy*, const SuperTy*>& pair)
{
return isCovariantWith(env, pair.second, pair.first);
return isContravariantWith(env, pair.first, pair.second);
}
template<typename SubTy, typename SuperTy>
SubtypingResult Subtyping::isInvariantWith(SubtypingEnvironment& env, const TryPair<const SubTy*, const SuperTy*>& pair)
{
return isCovariantWith(env, pair).andAlso(isContravariantWith(pair)).withVariance(SubtypingVariance::Invariant);
return isInvariantWith(env, pair.first, pair.second);
}
/*
@ -788,17 +891,17 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Nega
if (is<NeverType>(negatedTy))
{
// ¬never ~ unknown
result = isCovariantWith(env, builtinTypes->unknownType, superTy);
result = isCovariantWith(env, builtinTypes->unknownType, superTy).withSubComponent(TypePath::TypeField::Negated);
}
else if (is<UnknownType>(negatedTy))
{
// ¬unknown ~ never
result = isCovariantWith(env, builtinTypes->neverType, superTy);
result = isCovariantWith(env, builtinTypes->neverType, superTy).withSubComponent(TypePath::TypeField::Negated);
}
else if (is<AnyType>(negatedTy))
{
// ¬any ~ any
result = isCovariantWith(env, negatedTy, superTy);
result = isCovariantWith(env, negatedTy, superTy).withSubComponent(TypePath::TypeField::Negated);
}
else if (auto u = get<UnionType>(negatedTy))
{
@ -808,8 +911,13 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Nega
for (TypeId ty : u)
{
NegationType negatedTmp{ty};
subtypings.push_back(isCovariantWith(env, &negatedTmp, superTy));
if (auto negatedPart = get<NegationType>(follow(ty)))
subtypings.push_back(isCovariantWith(env, negatedPart->ty, superTy).withSubComponent(TypePath::TypeField::Negated));
else
{
NegationType negatedTmp{ty};
subtypings.push_back(isCovariantWith(env, &negatedTmp, superTy));
}
}
result = SubtypingResult::all(subtypings);
@ -823,7 +931,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Nega
for (TypeId ty : i)
{
if (auto negatedPart = get<NegationType>(follow(ty)))
subtypings.push_back(isCovariantWith(env, negatedPart->ty, superTy));
subtypings.push_back(isCovariantWith(env, negatedPart->ty, superTy).withSubComponent(TypePath::TypeField::Negated));
else
{
NegationType negatedTmp{ty};
@ -841,10 +949,10 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Nega
// subtype of other stuff.
else
{
result = {false};
result = SubtypingResult{false}.withSubComponent(TypePath::TypeField::Negated);
}
return result.withSubComponent(TypePath::TypeField::Negated);
return result;
}
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TypeId subTy, const NegationType* superNegation)
@ -885,7 +993,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Type
}
}
result = SubtypingResult::all(subtypings);
return SubtypingResult::all(subtypings);
}
else if (auto i = get<IntersectionType>(negatedTy))
{
@ -904,7 +1012,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Type
}
}
result = SubtypingResult::any(subtypings);
return SubtypingResult::any(subtypings);
}
else if (auto p = get2<PrimitiveType, PrimitiveType>(subTy, negatedTy))
{
@ -986,8 +1094,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Tabl
{
std::vector<SubtypingResult> results;
if (auto it = subTable->props.find(name); it != subTable->props.end())
results.push_back(isInvariantWith(env, it->second.type(), prop.type())
.withBothComponent(TypePath::Property(name)));
results.push_back(isInvariantWith(env, it->second.type(), prop.type()).withBothComponent(TypePath::Property(name)));
if (subTable->indexer)
{
@ -1122,7 +1229,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Tabl
{
return isInvariantWith(env, subIndexer.indexType, superIndexer.indexType)
.withBothComponent(TypePath::TypeField::IndexLookup)
.andAlso(isInvariantWith(env, superIndexer.indexResultType, subIndexer.indexResultType).withBothComponent(TypePath::TypeField::IndexResult));
.andAlso(isInvariantWith(env, subIndexer.indexResultType, superIndexer.indexResultType).withBothComponent(TypePath::TypeField::IndexResult));
}
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const NormalizedType* subNorm, const NormalizedType* superNorm)
@ -1249,12 +1356,11 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Type
{
std::vector<SubtypingResult> results;
size_t i = 0;
for (TypeId subTy : subTypes)
{
results.emplace_back();
for (TypeId superTy : superTypes)
results.back().orElse(isCovariantWith(env, subTy, superTy).withBothComponent(TypePath::Index{i++}));
results.back().orElse(isCovariantWith(env, subTy, superTy));
}
return SubtypingResult::all(results);

View File

@ -1721,7 +1721,7 @@ std::string toString(const Constraint& constraint, ToStringOptions& opts)
std::string iteratorStr = tos(c.iterator);
std::string variableStr = tos(c.variables);
return variableStr + " ~ Iterate<" + iteratorStr + ">";
return variableStr + " ~ iterate " + iteratorStr;
}
else if constexpr (std::is_same_v<T, NameConstraint>)
{

View File

@ -2467,6 +2467,8 @@ struct TypeChecker2
std::string relation = "a subtype of";
if (reasoning.variance == SubtypingVariance::Invariant)
relation = "exactly";
else if (reasoning.variance == SubtypingVariance::Contravariant)
relation = "a supertype of";
std::string reason;
if (reasoning.subPath == reasoning.superPath)

View File

@ -12,7 +12,6 @@
#include <optional>
#include <sstream>
#include <type_traits>
#include <unordered_set>
LUAU_FASTFLAG(DebugLuauReadWriteProperties);
@ -252,8 +251,6 @@ struct TraversalState
TypeOrPack current;
NotNull<BuiltinTypes> builtinTypes;
DenseHashSet<const void*> seen{nullptr};
int steps = 0;
void updateCurrent(TypeId ty)
@ -268,18 +265,6 @@ struct TraversalState
current = follow(tp);
}
bool haveCycle()
{
const void* currentPtr = ptr(current);
if (seen.contains(currentPtr))
return true;
else
seen.insert(currentPtr);
return false;
}
bool tooLong()
{
return ++steps > DFInt::LuauTypePathMaximumTraverseSteps;
@ -287,7 +272,7 @@ struct TraversalState
bool checkInvariants()
{
return haveCycle() || tooLong();
return tooLong();
}
bool traverse(const TypePath::Property& property)
@ -313,18 +298,36 @@ struct TraversalState
{
prop = lookupClassProp(c, property.name);
}
else if (auto m = getMetatable(*currentType, builtinTypes))
// For a metatable type, the table takes priority; check that before
// falling through to the metatable entry below.
else if (auto m = get<MetatableType>(*currentType))
{
// Weird: rather than use findMetatableEntry, which requires a lot
// of stuff that we don't have and don't want to pull in, we use the
// path traversal logic to grab __index and then re-enter the lookup
// logic there.
updateCurrent(*m);
TypeOrPack pinned = current;
updateCurrent(m->table);
if (!traverse(TypePath::Property{"__index"}))
return false;
if (traverse(property))
return true;
return traverse(property);
// Restore the old current type if we didn't traverse the metatable
// successfully; we'll use the next branch to address this.
current = pinned;
}
if (!prop)
{
if (auto m = getMetatable(*currentType, builtinTypes))
{
// Weird: rather than use findMetatableEntry, which requires a lot
// of stuff that we don't have and don't want to pull in, we use the
// path traversal logic to grab __index and then re-enter the lookup
// logic there.
updateCurrent(*m);
if (!traverse(TypePath::Property{"__index"}))
return false;
return traverse(property);
}
}
if (prop)

View File

@ -113,7 +113,7 @@ bool Unifier2::unify(TypeId subTy, TypeId superTy)
return argResult && retResult;
}
auto subTable = get<TableType>(subTy);
auto subTable = getMutable<TableType>(subTy);
auto superTable = get<TableType>(superTy);
if (subTable && superTable)
{
@ -210,7 +210,7 @@ bool Unifier2::unify(TypeId subTy, const IntersectionType* superIntersection)
return result;
}
bool Unifier2::unify(const TableType* subTable, const TableType* superTable)
bool Unifier2::unify(TableType* subTable, const TableType* superTable)
{
bool result = true;
@ -256,6 +256,21 @@ bool Unifier2::unify(const TableType* subTable, const TableType* superTable)
result &= unify(subTable->indexer->indexResultType, superTable->indexer->indexResultType);
}
if (!subTable->indexer && subTable->state == TableState::Unsealed && superTable->indexer)
{
/*
* Unsealed tables are always created from literal table expressions. We
* can't be completely certain whether such a table has an indexer just
* by the content of the expression itself, so we need to be a bit more
* flexible here.
*
* If we are trying to reconcile an unsealed table with a table that has
* an indexer, we therefore conclude that the unsealed table has the
* same indexer.
*/
subTable->indexer = *superTable->indexer;
}
return result;
}

View File

@ -3,6 +3,7 @@
#include "Luau/Location.h"
#include <iterator>
#include <optional>
#include <functional>
#include <string>
@ -91,10 +92,21 @@ struct AstArray
{
return data;
}
const T* end() const
{
return data + size;
}
std::reverse_iterator<const T*> rbegin() const
{
return std::make_reverse_iterator(end());
}
std::reverse_iterator<const T*> rend() const
{
return std::make_reverse_iterator(begin());
}
};
struct AstTypeList

View File

@ -19,8 +19,6 @@ LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100)
LUAU_FASTFLAGVARIABLE(LuauClipExtraHasEndProps, false)
LUAU_FASTFLAG(LuauCheckedFunctionSyntax)
LUAU_FASTFLAGVARIABLE(LuauParseImpreciseNumber, false)
namespace Luau
{
@ -2168,11 +2166,8 @@ static ConstantNumberParseResult parseInteger(double& result, const char* data,
return base == 2 ? ConstantNumberParseResult::BinOverflow : ConstantNumberParseResult::HexOverflow;
}
if (FFlag::LuauParseImpreciseNumber)
{
if (value >= (1ull << 53) && static_cast<unsigned long long>(result) != value)
return ConstantNumberParseResult::Imprecise;
}
if (value >= (1ull << 53) && static_cast<unsigned long long>(result) != value)
return ConstantNumberParseResult::Imprecise;
return ConstantNumberParseResult::Ok;
}
@ -2190,32 +2185,24 @@ static ConstantNumberParseResult parseDouble(double& result, const char* data)
char* end = nullptr;
double value = strtod(data, &end);
if (FFlag::LuauParseImpreciseNumber)
// trailing non-numeric characters
if (*end != 0)
return ConstantNumberParseResult::Malformed;
result = value;
// for linting, we detect integer constants that are parsed imprecisely
// since the check is expensive we only perform it when the number is larger than the precise integer range
if (value >= double(1ull << 53) && strspn(data, "0123456789") == strlen(data))
{
// trailing non-numeric characters
if (*end != 0)
return ConstantNumberParseResult::Malformed;
char repr[512];
snprintf(repr, sizeof(repr), "%.0f", value);
result = value;
// for linting, we detect integer constants that are parsed imprecisely
// since the check is expensive we only perform it when the number is larger than the precise integer range
if (value >= double(1ull << 53) && strspn(data, "0123456789") == strlen(data))
{
char repr[512];
snprintf(repr, sizeof(repr), "%.0f", value);
if (strcmp(repr, data) != 0)
return ConstantNumberParseResult::Imprecise;
}
return ConstantNumberParseResult::Ok;
}
else
{
result = value;
return *end == 0 ? ConstantNumberParseResult::Ok : ConstantNumberParseResult::Malformed;
if (strcmp(repr, data) != 0)
return ConstantNumberParseResult::Imprecise;
}
return ConstantNumberParseResult::Ok;
}
// simpleexp -> NUMBER | STRING | NIL | true | false | ... | constructor | FUNCTION body | primaryexp

View File

@ -37,13 +37,19 @@ enum class RecordStats
{
None,
Total,
Split
File,
Function
};
struct GlobalOptions
{
int optimizationLevel = 1;
int debugLevel = 1;
std::string vectorLib;
std::string vectorCtor;
std::string vectorType;
} globalOptions;
static Luau::CompileOptions copts()
@ -52,6 +58,11 @@ static Luau::CompileOptions copts()
result.optimizationLevel = globalOptions.optimizationLevel;
result.debugLevel = globalOptions.debugLevel;
// globalOptions outlive the CompileOptions, so it's safe to use string data pointers here
result.vectorLib = globalOptions.vectorLib.c_str();
result.vectorCtor = globalOptions.vectorCtor.c_str();
result.vectorType = globalOptions.vectorType.c_str();
return result;
}
@ -159,11 +170,26 @@ struct CompileStats
\"blockLinearizationStats\": {\
\"constPropInstructionCount\": %u, \
\"timeSeconds\": %f\
}}",
}",
lines, bytecode, bytecodeInstructionCount, codegen, readTime, miscTime, parseTime, compileTime, codegenTime, lowerStats.totalFunctions,
lowerStats.skippedFunctions, lowerStats.spillsToSlot, lowerStats.spillsToRestore, lowerStats.maxSpillSlotsUsed, lowerStats.blocksPreOpt,
lowerStats.blocksPostOpt, lowerStats.maxBlockInstructions, lowerStats.regAllocErrors, lowerStats.loweringErrors,
lowerStats.blockLinearizationStats.constPropInstructionCount, lowerStats.blockLinearizationStats.timeSeconds);
if (lowerStats.collectFunctionStats)
{
fprintf(fp, ", \"functions\": [");
auto functionCount = lowerStats.functions.size();
for (size_t i = 0; i < functionCount; ++i)
{
const Luau::CodeGen::FunctionStats& fstat = lowerStats.functions[i];
fprintf(fp, "{\"name\": \"%s\", \"line\": %d, \"bcodeCount\": %u, \"irCount\": %u, \"asmCount\": %u}", fstat.name.c_str(), fstat.line,
fstat.bcodeCount, fstat.irCount, fstat.asmCount);
if (i < functionCount - 1)
fprintf(fp, ", ");
}
fprintf(fp, "]");
}
fprintf(fp, "}");
}
CompileStats& operator+=(const CompileStats& that)
@ -321,7 +347,11 @@ static void displayHelp(const char* argv0)
printf(" -g<n>: compile with debug level n (default 1, n should be between 0 and 2).\n");
printf(" --target=<target>: compile code for specific architecture (a64, x64, a64_nf, x64_ms).\n");
printf(" --timetrace: record compiler time tracing information into trace.json\n");
printf(" --record-stats=<style>: records compilation stats in stats.json (total, split).\n");
printf(" --stats-file=<filename>: file in which compilation stats will be recored (default 'stats.json').\n");
printf(" --record-stats=<granularity>: granularity of compilation stats recorded in stats.json (total, file, function).\n");
printf(" --vector-lib=<name>: name of the library providing vector type operations.\n");
printf(" --vector-ctor=<name>: name of the function constructing a vector value.\n");
printf(" --vector-type=<name>: name of the vector type.\n");
}
static int assertionHandler(const char* expr, const char* file, int line, const char* function)
@ -420,11 +450,13 @@ int main(int argc, char** argv)
if (strcmp(value, "total") == 0)
recordStats = RecordStats::Total;
else if (strcmp(value, "split") == 0)
recordStats = RecordStats::Split;
else if (strcmp(value, "file") == 0)
recordStats = RecordStats::File;
else if (strcmp(value, "function") == 0)
recordStats = RecordStats::Function;
else
{
fprintf(stderr, "Error: unknown 'style' for '--record-stats'\n");
fprintf(stderr, "Error: unknown 'granularity' for '--record-stats'\n");
return 1;
}
}
@ -442,6 +474,18 @@ int main(int argc, char** argv)
{
setLuauFlags(argv[i] + 9);
}
else if (strncmp(argv[i], "--vector-lib=", 13) == 0)
{
globalOptions.vectorLib = argv[i] + 13;
}
else if (strncmp(argv[i], "--vector-ctor=", 14) == 0)
{
globalOptions.vectorCtor = argv[i] + 14;
}
else if (strncmp(argv[i], "--vector-type=", 14) == 0)
{
globalOptions.vectorType = argv[i] + 14;
}
else if (argv[i][0] == '-' && argv[i][1] == '-' && getCompileFormat(argv[i] + 2))
{
compileFormat = *getCompileFormat(argv[i] + 2);
@ -473,7 +517,7 @@ int main(int argc, char** argv)
CompileStats stats = {};
std::vector<CompileStats> fileStats;
if (recordStats == RecordStats::Split)
if (recordStats == RecordStats::File || recordStats == RecordStats::Function)
fileStats.reserve(fileCount);
int failed = 0;
@ -481,9 +525,10 @@ int main(int argc, char** argv)
for (const std::string& path : files)
{
CompileStats fileStat = {};
fileStat.lowerStats.collectFunctionStats = (recordStats == RecordStats::Function);
failed += !compileFile(path.c_str(), compileFormat, assemblyTarget, fileStat);
stats += fileStat;
if (recordStats == RecordStats::Split)
if (recordStats == RecordStats::File || recordStats == RecordStats::Function)
fileStats.push_back(fileStat);
}
@ -506,7 +551,6 @@ int main(int argc, char** argv)
if (recordStats != RecordStats::None)
{
FILE* fp = fopen(statsFile.c_str(), "w");
if (!fp)
@ -519,7 +563,7 @@ int main(int argc, char** argv)
{
stats.serializeToJson(fp);
}
else if (recordStats == RecordStats::Split)
else if (recordStats == RecordStats::File || recordStats == RecordStats::Function)
{
fprintf(fp, "{\n");
for (size_t i = 0; i < fileCount; ++i)

View File

@ -10,6 +10,7 @@
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <direct.h>
#include <windows.h>
#else
#include <dirent.h>
@ -44,6 +45,148 @@ static std::string toUtf8(const std::wstring& path)
}
#endif
bool isAbsolutePath(std::string_view path)
{
#ifdef _WIN32
// Must either begin with "X:/", "X:\", "/", or "\", where X is a drive letter
return (path.size() >= 3 && isalpha(path[0]) && path[1] == ':' && (path[2] == '/' || path[2] == '\\')) ||
(path.size() >= 1 && (path[0] == '/' || path[0] == '\\'));
#else
// Must begin with '/'
return path.size() >= 1 && path[0] == '/';
#endif
}
bool isExplicitlyRelative(std::string_view path)
{
return (path == ".") || (path == "..") || (path.size() >= 2 && path[0] == '.' && path[1] == '/') ||
(path.size() >= 3 && path[0] == '.' && path[1] == '.' && path[2] == '/');
}
std::optional<std::string> getCurrentWorkingDirectory()
{
// 2^17 - derived from the Windows path length limit
constexpr size_t maxPathLength = 131072;
constexpr size_t initialPathLength = 260;
std::string directory(initialPathLength, '\0');
char* cstr = nullptr;
while (!cstr && directory.size() <= maxPathLength)
{
#ifdef _WIN32
cstr = _getcwd(directory.data(), static_cast<int>(directory.size()));
#else
cstr = getcwd(directory.data(), directory.size());
#endif
if (cstr)
{
directory.resize(strlen(cstr));
return directory;
}
else if (errno != ERANGE || directory.size() * 2 > maxPathLength)
{
return std::nullopt;
}
else
{
directory.resize(directory.size() * 2);
}
}
return std::nullopt;
}
// Returns the normal/canonical form of a path (e.g. "../subfolder/../module.luau" -> "../module.luau")
std::string normalizePath(std::string_view path)
{
return resolvePath(path, "");
}
// Takes a path that is relative to the file at baseFilePath and returns the path explicitly rebased onto baseFilePath.
// For absolute paths, baseFilePath will be ignored, and this function will resolve the path to a canonical path:
// (e.g. "/Users/.././Users/johndoe" -> "/Users/johndoe").
std::string resolvePath(std::string_view path, std::string_view baseFilePath)
{
std::vector<std::string_view> pathComponents;
std::vector<std::string_view> baseFilePathComponents;
// Dependent on whether the final resolved path is absolute or relative
// - if relative (when path and baseFilePath are both relative), resolvedPathPrefix remains empty
// - if absolute (if either path or baseFilePath are absolute), resolvedPathPrefix is "C:\", "/", etc.
std::string resolvedPathPrefix;
if (isAbsolutePath(path))
{
// path is absolute, we use path's prefix and ignore baseFilePath
size_t afterPrefix = path.find_first_of("\\/") + 1;
resolvedPathPrefix = path.substr(0, afterPrefix);
pathComponents = splitPath(path.substr(afterPrefix));
}
else
{
pathComponents = splitPath(path);
if (isAbsolutePath(baseFilePath))
{
// path is relative and baseFilePath is absolute, we use baseFilePath's prefix
size_t afterPrefix = baseFilePath.find_first_of("\\/") + 1;
resolvedPathPrefix = baseFilePath.substr(0, afterPrefix);
baseFilePathComponents = splitPath(baseFilePath.substr(afterPrefix));
}
else
{
// path and baseFilePath are both relative, we do not set a prefix (resolved path will be relative)
baseFilePathComponents = splitPath(baseFilePath);
}
}
// Remove filename from components
if (!baseFilePathComponents.empty())
baseFilePathComponents.pop_back();
// Resolve the path by applying pathComponents to baseFilePathComponents
int numPrependedParents = 0;
for (std::string_view component : pathComponents)
{
if (component == "..")
{
if (baseFilePathComponents.empty())
{
if (resolvedPathPrefix.empty()) // only when final resolved path will be relative
numPrependedParents++; // "../" will later be added to the beginning of the resolved path
}
else if (baseFilePathComponents.back() != "..")
{
baseFilePathComponents.pop_back(); // Resolve cases like "folder/subfolder/../../file" to "file"
}
}
else if (component != "." && !component.empty())
{
baseFilePathComponents.push_back(component);
}
}
// Join baseFilePathComponents to form the resolved path
std::string resolvedPath = resolvedPathPrefix;
// Only when resolvedPath will be relative
for (int i = 0; i < numPrependedParents; i++)
{
resolvedPath += "../";
}
for (auto iter = baseFilePathComponents.begin(); iter != baseFilePathComponents.end(); ++iter)
{
if (iter != baseFilePathComponents.begin())
resolvedPath += "/";
resolvedPath += *iter;
}
if (resolvedPath.size() > resolvedPathPrefix.size() && resolvedPath.back() == '/')
{
// Remove trailing '/' if present
resolvedPath.pop_back();
}
return resolvedPath;
}
std::optional<std::string> readFile(const std::string& name)
{
#ifdef _WIN32
@ -224,6 +367,24 @@ bool isDirectory(const std::string& path)
#endif
}
std::vector<std::string_view> splitPath(std::string_view path)
{
std::vector<std::string_view> components;
size_t pos = 0;
size_t nextPos = path.find_first_of("\\/", pos);
while (nextPos != std::string::npos)
{
components.push_back(path.substr(pos, nextPos - pos));
pos = nextPos + 1;
nextPos = path.find_first_of("\\/", pos);
}
components.push_back(path.substr(pos));
return components;
}
std::string joinPaths(const std::string& lhs, const std::string& rhs)
{
std::string result = lhs;

View File

@ -3,15 +3,24 @@
#include <optional>
#include <string>
#include <string_view>
#include <functional>
#include <vector>
std::optional<std::string> getCurrentWorkingDirectory();
std::string normalizePath(std::string_view path);
std::string resolvePath(std::string_view relativePath, std::string_view baseFilePath);
std::optional<std::string> readFile(const std::string& name);
std::optional<std::string> readStdin();
bool isAbsolutePath(std::string_view path);
bool isExplicitlyRelative(std::string_view path);
bool isDirectory(const std::string& path);
bool traverseDirectory(const std::string& path, const std::function<void(const std::string& name)>& callback);
std::vector<std::string_view> splitPath(std::string_view path);
std::string joinPaths(const std::string& lhs, const std::string& rhs);
std::optional<std::string> getParentPath(const std::string& path);

View File

@ -1,6 +1,7 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Repl.h"
#include "Luau/Common.h"
#include "lua.h"
#include "lualib.h"
@ -13,6 +14,7 @@
#include "FileUtils.h"
#include "Flags.h"
#include "Profiler.h"
#include "Require.h"
#include "isocline.h"
@ -39,6 +41,8 @@
LUAU_FASTFLAG(DebugLuauTimeTracing)
LUAU_FASTFLAGVARIABLE(LuauUpdatedRequireByStringSemantics, false)
constexpr int MaxTraversalLimit = 50;
static bool codegen = false;
@ -115,74 +119,129 @@ static int finishrequire(lua_State* L)
static int lua_require(lua_State* L)
{
std::string name = luaL_checkstring(L, 1);
std::string chunkname = "=" + name;
luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1);
// return the module from the cache
lua_getfield(L, -1, name.c_str());
if (!lua_isnil(L, -1))
if (FFlag::LuauUpdatedRequireByStringSemantics)
{
// L stack: _MODULES result
std::string name = luaL_checkstring(L, 1);
RequireResolver::ResolvedRequire resolvedRequire = RequireResolver::resolveRequire(L, std::move(name));
if (resolvedRequire.status == RequireResolver::ModuleStatus::Cached)
return finishrequire(L);
else if (resolvedRequire.status == RequireResolver::ModuleStatus::NotFound)
luaL_errorL(L, "error requiring module");
// module needs to run in a new thread, isolated from the rest
// note: we create ML on main thread so that it doesn't inherit environment of L
lua_State* GL = lua_mainthread(L);
lua_State* ML = lua_newthread(GL);
lua_xmove(GL, L, 1);
// new thread needs to have the globals sandboxed
luaL_sandboxthread(ML);
// now we can compile & run module on the new thread
std::string bytecode = Luau::compile(resolvedRequire.sourceCode, copts());
if (luau_load(ML, resolvedRequire.chunkName.c_str(), bytecode.data(), bytecode.size(), 0) == 0)
{
if (codegen)
Luau::CodeGen::compile(ML, -1);
if (coverageActive())
coverageTrack(ML, -1);
int status = lua_resume(ML, L, 0);
if (status == 0)
{
if (lua_gettop(ML) == 0)
lua_pushstring(ML, "module must return a value");
else if (!lua_istable(ML, -1) && !lua_isfunction(ML, -1))
lua_pushstring(ML, "module must return a table or function");
}
else if (status == LUA_YIELD)
{
lua_pushstring(ML, "module can not yield");
}
else if (!lua_isstring(ML, -1))
{
lua_pushstring(ML, "unknown error while running module");
}
}
// there's now a return value on top of ML; L stack: _MODULES ML
lua_xmove(ML, L, 1);
lua_pushvalue(L, -1);
lua_setfield(L, -4, resolvedRequire.absolutePath.c_str());
// L stack: _MODULES ML result
return finishrequire(L);
}
lua_pop(L, 1);
std::optional<std::string> source = readFile(name + ".luau");
if (!source)
else
{
source = readFile(name + ".lua"); // try .lua if .luau doesn't exist
std::string name = luaL_checkstring(L, 1);
std::string chunkname = "=" + name;
luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1);
// return the module from the cache
lua_getfield(L, -1, name.c_str());
if (!lua_isnil(L, -1))
{
// L stack: _MODULES result
return finishrequire(L);
}
lua_pop(L, 1);
std::optional<std::string> source = readFile(name + ".luau");
if (!source)
luaL_argerrorL(L, 1, ("error loading " + name).c_str()); // if neither .luau nor .lua exist, we have an error
{
source = readFile(name + ".lua"); // try .lua if .luau doesn't exist
if (!source)
luaL_argerrorL(L, 1, ("error loading " + name).c_str()); // if neither .luau nor .lua exist, we have an error
}
// module needs to run in a new thread, isolated from the rest
// note: we create ML on main thread so that it doesn't inherit environment of L
lua_State* GL = lua_mainthread(L);
lua_State* ML = lua_newthread(GL);
lua_xmove(GL, L, 1);
// new thread needs to have the globals sandboxed
luaL_sandboxthread(ML);
// now we can compile & run module on the new thread
std::string bytecode = Luau::compile(*source, copts());
if (luau_load(ML, chunkname.c_str(), bytecode.data(), bytecode.size(), 0) == 0)
{
if (codegen)
Luau::CodeGen::compile(ML, -1);
if (coverageActive())
coverageTrack(ML, -1);
int status = lua_resume(ML, L, 0);
if (status == 0)
{
if (lua_gettop(ML) == 0)
lua_pushstring(ML, "module must return a value");
else if (!lua_istable(ML, -1) && !lua_isfunction(ML, -1))
lua_pushstring(ML, "module must return a table or function");
}
else if (status == LUA_YIELD)
{
lua_pushstring(ML, "module can not yield");
}
else if (!lua_isstring(ML, -1))
{
lua_pushstring(ML, "unknown error while running module");
}
}
// there's now a return value on top of ML; L stack: _MODULES ML
lua_xmove(ML, L, 1);
lua_pushvalue(L, -1);
lua_setfield(L, -4, name.c_str());
// L stack: _MODULES ML result
return finishrequire(L);
}
// module needs to run in a new thread, isolated from the rest
// note: we create ML on main thread so that it doesn't inherit environment of L
lua_State* GL = lua_mainthread(L);
lua_State* ML = lua_newthread(GL);
lua_xmove(GL, L, 1);
// new thread needs to have the globals sandboxed
luaL_sandboxthread(ML);
// now we can compile & run module on the new thread
std::string bytecode = Luau::compile(*source, copts());
if (luau_load(ML, chunkname.c_str(), bytecode.data(), bytecode.size(), 0) == 0)
{
if (codegen)
Luau::CodeGen::compile(ML, -1);
if (coverageActive())
coverageTrack(ML, -1);
int status = lua_resume(ML, L, 0);
if (status == 0)
{
if (lua_gettop(ML) == 0)
lua_pushstring(ML, "module must return a value");
else if (!lua_istable(ML, -1) && !lua_isfunction(ML, -1))
lua_pushstring(ML, "module must return a table or function");
}
else if (status == LUA_YIELD)
{
lua_pushstring(ML, "module can not yield");
}
else if (!lua_isstring(ML, -1))
{
lua_pushstring(ML, "unknown error while running module");
}
}
// there's now a return value on top of ML; L stack: _MODULES ML
lua_xmove(ML, L, 1);
lua_pushvalue(L, -1);
lua_setfield(L, -4, name.c_str());
// L stack: _MODULES ML result
return finishrequire(L);
}
static int lua_collectgarbage(lua_State* L)

290
CLI/Require.cpp Normal file
View File

@ -0,0 +1,290 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Require.h"
#include "FileUtils.h"
#include "Luau/Common.h"
#include <algorithm>
#include <array>
#include <utility>
RequireResolver::RequireResolver(lua_State* L, std::string path)
: pathToResolve(std::move(path))
, L(L)
{
lua_Debug ar;
lua_getinfo(L, 1, "s", &ar);
sourceChunkname = ar.source;
if (!isRequireAllowed(sourceChunkname))
luaL_errorL(L, "require is not supported in this context");
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);
}
}
[[nodiscard]] RequireResolver::ResolvedRequire RequireResolver::resolveRequire(lua_State* L, std::string path)
{
RequireResolver resolver(L, std::move(path));
ModuleStatus status = resolver.findModule();
if (status != ModuleStatus::FileRead)
return ResolvedRequire{status};
else
return ResolvedRequire{status, std::move(resolver.chunkname), std::move(resolver.absolutePath), std::move(resolver.sourceCode)};
}
RequireResolver::ModuleStatus RequireResolver::findModule()
{
resolveAndStoreDefaultPaths();
// Put _MODULES table on stack for checking and saving to the cache
luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1);
RequireResolver::ModuleStatus moduleStatus = findModuleImpl();
if (moduleStatus != RequireResolver::ModuleStatus::NotFound)
return moduleStatus;
if (!shouldSearchPathsArray())
return moduleStatus;
if (!isConfigFullyResolved)
parseNextConfig();
// Index-based iteration because std::iterator may be invalidated if config.paths is reallocated
for (size_t i = 0; i < config.paths.size(); ++i)
{
// "placeholder" acts as a requiring file in the relevant directory
std::optional<std::string> absolutePathOpt = resolvePath(pathToResolve, joinPaths(config.paths[i], "placeholder"));
if (!absolutePathOpt)
luaL_errorL(L, "error requiring module");
chunkname = *absolutePathOpt;
absolutePath = *absolutePathOpt;
moduleStatus = findModuleImpl();
if (moduleStatus != RequireResolver::ModuleStatus::NotFound)
return moduleStatus;
// Before finishing the loop, parse more config files if there are any
if (i == config.paths.size() - 1 && !isConfigFullyResolved)
parseNextConfig(); // could reallocate config.paths when paths are parsed and added
}
return RequireResolver::ModuleStatus::NotFound;
}
RequireResolver::ModuleStatus RequireResolver::findModuleImpl()
{
static const std::array<const char*, 4> possibleSuffixes = {".luau", ".lua", "/init.luau", "/init.lua"};
size_t unsuffixedAbsolutePathSize = absolutePath.size();
for (const char* possibleSuffix : possibleSuffixes)
{
absolutePath += possibleSuffix;
// Check cache for module
lua_getfield(L, -1, absolutePath.c_str());
if (!lua_isnil(L, -1))
{
return ModuleStatus::Cached;
}
lua_pop(L, 1);
// Try to read the matching file
std::optional<std::string> source = readFile(absolutePath);
if (source)
{
chunkname = "=" + chunkname + possibleSuffix;
sourceCode = *source;
return ModuleStatus::FileRead;
}
absolutePath.resize(unsuffixedAbsolutePathSize); // truncate to remove suffix
}
return ModuleStatus::NotFound;
}
bool RequireResolver::isRequireAllowed(std::string_view sourceChunkname)
{
LUAU_ASSERT(!sourceChunkname.empty());
return (sourceChunkname[0] == '=' || sourceChunkname[0] == '@');
}
bool RequireResolver::shouldSearchPathsArray()
{
return !isAbsolutePath(pathToResolve) && !isExplicitlyRelative(pathToResolve);
}
void RequireResolver::resolveAndStoreDefaultPaths()
{
if (!isAbsolutePath(pathToResolve))
{
std::string chunknameContext = getRequiringContextRelative();
std::optional<std::string> absolutePathContext = getRequiringContextAbsolute();
if (!absolutePathContext)
luaL_errorL(L, "error requiring module");
// resolvePath automatically sanitizes/normalizes the paths
std::optional<std::string> chunknameOpt = resolvePath(pathToResolve, chunknameContext);
std::optional<std::string> absolutePathOpt = resolvePath(pathToResolve, *absolutePathContext);
if (!chunknameOpt || !absolutePathOpt)
luaL_errorL(L, "error requiring module");
chunkname = std::move(*chunknameOpt);
absolutePath = std::move(*absolutePathOpt);
}
else
{
// Here we must explicitly sanitize, as the path is taken as is
std::optional<std::string> sanitizedPath = normalizePath(pathToResolve);
if (!sanitizedPath)
luaL_errorL(L, "error requiring module");
chunkname = *sanitizedPath;
absolutePath = std::move(*sanitizedPath);
}
}
std::optional<std::string> RequireResolver::getRequiringContextAbsolute()
{
std::string requiringFile;
if (isAbsolutePath(sourceChunkname.substr(1)))
{
// We already have an absolute path for the requiring file
requiringFile = sourceChunkname.substr(1);
}
else
{
// Requiring file's stored path is relative to the CWD, must make absolute
std::optional<std::string> cwd = getCurrentWorkingDirectory();
if (!cwd)
return std::nullopt;
if (sourceChunkname.substr(1) == "stdin")
{
// Require statement is being executed from REPL input prompt
// The requiring context is the pseudo-file "stdin" in the CWD
requiringFile = joinPaths(*cwd, "stdin");
}
else
{
// Require statement is being executed in a file, must resolve relative to CWD
std::optional<std::string> requiringFileOpt = resolvePath(sourceChunkname.substr(1), joinPaths(*cwd, "stdin"));
if (!requiringFileOpt)
return std::nullopt;
requiringFile = *requiringFileOpt;
}
}
std::replace(requiringFile.begin(), requiringFile.end(), '\\', '/');
return requiringFile;
}
std::string RequireResolver::getRequiringContextRelative()
{
std::string baseFilePath;
if (sourceChunkname.substr(1) != "stdin")
baseFilePath = sourceChunkname.substr(1);
return baseFilePath;
}
void RequireResolver::substituteAliasIfPresent(std::string& path)
{
std::string potentialAlias = path.substr(0, path.find_first_of("\\/"));
// Not worth searching when potentialAlias cannot be an alias
if (!Luau::isValidAlias(potentialAlias))
return;
std::optional<std::string> alias = getAlias(potentialAlias);
if (alias)
{
path = *alias + path.substr(potentialAlias.size());
}
}
std::optional<std::string> RequireResolver::getAlias(std::string alias)
{
std::transform(alias.begin(), alias.end(), alias.begin(), [](unsigned char c) {
return ('A' <= c && c <= 'Z') ? (c + ('a' - 'A')) : c;
});
while (!config.aliases.count(alias) && !isConfigFullyResolved)
{
parseNextConfig();
}
if (!config.aliases.count(alias) && isConfigFullyResolved)
return std::nullopt; // could not find alias
return resolvePath(config.aliases[alias], joinPaths(lastSearchedDir, Luau::kConfigName));
}
void RequireResolver::parseNextConfig()
{
if (isConfigFullyResolved)
return; // no config files left to parse
std::optional<std::string> directory;
if (lastSearchedDir.empty())
{
std::optional<std::string> requiringFile = getRequiringContextAbsolute();
if (!requiringFile)
luaL_errorL(L, "error requiring module");
directory = getParentPath(*requiringFile);
}
else
directory = getParentPath(lastSearchedDir);
if (directory)
{
lastSearchedDir = *directory;
parseConfigInDirectory(*directory);
}
else
isConfigFullyResolved = true;
}
void RequireResolver::parseConfigInDirectory(const std::string& directory)
{
std::string configPath = joinPaths(directory, Luau::kConfigName);
size_t numPaths = config.paths.size();
if (std::optional<std::string> contents = readFile(configPath))
{
std::optional<std::string> error = Luau::parseConfig(*contents, config);
if (error)
luaL_errorL(L, "error parsing %s (%s)", configPath.c_str(), (*error).c_str());
}
// Resolve any newly obtained relative paths in "paths" in relation to configPath
for (auto it = config.paths.begin() + numPaths; it != config.paths.end(); ++it)
{
if (!isAbsolutePath(*it))
{
if (std::optional<std::string> resolvedPath = resolvePath(*it, configPath))
*it = std::move(*resolvedPath);
else
luaL_errorL(L, "error requiring module");
}
}
}

62
CLI/Require.h Normal file
View File

@ -0,0 +1,62 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "lua.h"
#include "lualib.h"
#include "Luau/Config.h"
#include <string>
#include <string_view>
class RequireResolver
{
public:
std::string chunkname;
std::string absolutePath;
std::string sourceCode;
enum class ModuleStatus
{
Cached,
FileRead,
NotFound
};
struct ResolvedRequire
{
ModuleStatus status;
std::string chunkName;
std::string absolutePath;
std::string sourceCode;
};
[[nodiscard]] ResolvedRequire static resolveRequire(lua_State* L, std::string path);
private:
std::string pathToResolve;
std::string_view sourceChunkname;
RequireResolver(lua_State* L, std::string path);
ModuleStatus findModule();
lua_State* L;
Luau::Config config;
std::string lastSearchedDir;
bool isConfigFullyResolved = false;
bool isRequireAllowed(std::string_view sourceChunkname);
bool shouldSearchPathsArray();
void resolveAndStoreDefaultPaths();
ModuleStatus findModuleImpl();
std::optional<std::string> getRequiringContextAbsolute();
std::string getRequiringContextRelative();
void substituteAliasIfPresent(std::string& path);
std::optional<std::string> getAlias(std::string alias);
void parseNextConfig();
void parseConfigInDirectory(const std::string& path);
};

View File

@ -193,7 +193,7 @@ if(LUAU_BUILD_CLI)
target_include_directories(Luau.Repl.CLI PRIVATE extern extern/isocline/include)
target_link_libraries(Luau.Repl.CLI PRIVATE Luau.Compiler Luau.CodeGen Luau.VM isocline)
target_link_libraries(Luau.Repl.CLI PRIVATE Luau.Compiler Luau.Config Luau.CodeGen Luau.VM isocline)
if(UNIX)
find_library(LIBPTHREAD pthread)
@ -230,7 +230,7 @@ if(LUAU_BUILD_TESTS)
target_compile_options(Luau.CLI.Test PRIVATE ${LUAU_OPTIONS})
target_include_directories(Luau.CLI.Test PRIVATE extern CLI)
target_link_libraries(Luau.CLI.Test PRIVATE Luau.Compiler Luau.CodeGen Luau.VM isocline)
target_link_libraries(Luau.CLI.Test PRIVATE Luau.Compiler Luau.Config Luau.CodeGen Luau.VM isocline)
if(UNIX)
find_library(LIBPTHREAD pthread)
if (LIBPTHREAD)
@ -254,6 +254,8 @@ if(LUAU_BUILD_WEB)
target_link_options(Luau.Web PRIVATE -sSINGLE_FILE=1)
endif()
add_subdirectory(fuzz)
# validate dependencies for internal libraries
foreach(LIB Luau.Ast Luau.Compiler Luau.Config Luau.Analysis Luau.CodeGen Luau.VM)
if(TARGET ${LIB})

47
CMakePresets.json Normal file
View File

@ -0,0 +1,47 @@
{
"version": 6,
"configurePresets": [
{
"name": "fuzz",
"displayName": "Fuzz",
"description": "Configures required fuzzer settings.",
"binaryDir": "build",
"condition": {
"type": "anyOf",
"conditions": [
{
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Darwin"
},
{
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Linux"
}
]
},
"cacheVariables": {
"CMAKE_OSX_ARCHITECTURES": "x86_64",
"CMAKE_BUILD_TYPE": "Release",
"CMAKE_CXX_STANDARD": "17",
"CMAKE_CXX_EXTENSIONS": false
},
"warnings": {
"dev": false
}
}
],
"buildPresets": [
{
"name": "fuzz-proto",
"displayName": "Protobuf Fuzzer",
"description": "Builds the protobuf-based fuzzer and transpiler tools.",
"configurePreset": "fuzz",
"targets": [
"Luau.Fuzz.Proto",
"Luau.Fuzz.ProtoTest"
]
}
]
}

View File

@ -0,0 +1,21 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "Luau/Common.h"
#include <vector>
#include <stdint.h>
namespace Luau
{
namespace CodeGen
{
struct IrFunction;
void buildBytecodeBlocks(IrFunction& function, const std::vector<uint8_t>& jumpTargets);
void analyzeBytecodeTypes(IrFunction& function);
} // namespace CodeGen
} // namespace Luau

View File

@ -7,6 +7,8 @@
#include <string>
#include <vector>
#include <stdint.h>
struct lua_State;
struct Proto;

View File

@ -3,6 +3,7 @@
#include <algorithm>
#include <string>
#include <vector>
#include <stddef.h>
#include <stdint.h>
@ -101,6 +102,15 @@ struct BlockLinearizationStats
}
};
struct FunctionStats
{
std::string name;
int line = -1;
unsigned bcodeCount = 0;
unsigned irCount = 0;
unsigned asmCount = 0;
};
struct LoweringStats
{
unsigned totalFunctions = 0;
@ -117,6 +127,9 @@ struct LoweringStats
BlockLinearizationStats blockLinearizationStats;
bool collectFunctionStats = false;
std::vector<FunctionStats> functions;
LoweringStats operator+(const LoweringStats& other) const
{
LoweringStats result(*this);
@ -137,6 +150,8 @@ struct LoweringStats
this->regAllocErrors += that.regAllocErrors;
this->loweringErrors += that.loweringErrors;
this->blockLinearizationStats += that.blockLinearizationStats;
if (this->collectFunctionStats)
this->functions.insert(this->functions.end(), that.functions.begin(), that.functions.end());
return *this;
}
};

View File

@ -78,7 +78,13 @@ struct IrBuilder
std::vector<uint32_t> instIndexToBlock; // Block index at the bytecode instruction
std::vector<IrOp> loopStepStack;
struct LoopInfo
{
IrOp step;
int startpc = 0;
};
std::vector<LoopInfo> numericLoopStack;
// Similar to BytecodeBuilder, duplicate constants are removed used the same method
struct ConstantKey

View File

@ -1,6 +1,7 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "Luau/Bytecode.h"
#include "Luau/IrAnalysis.h"
#include "Luau/Label.h"
#include "Luau/RegisterX64.h"
@ -12,6 +13,8 @@
#include <stdint.h>
#include <string.h>
LUAU_FASTFLAG(LuauKeepVmapLinear2)
struct Proto;
namespace Luau
@ -930,18 +933,37 @@ struct BytecodeMapping
uint32_t asmLocation;
};
struct BytecodeBlock
{
// 'start' and 'finish' define an inclusive range of instructions which belong to the block
int startpc = -1;
int finishpc = -1;
};
struct BytecodeTypes
{
uint8_t result = LBC_TYPE_ANY;
uint8_t a = LBC_TYPE_ANY;
uint8_t b = LBC_TYPE_ANY;
uint8_t c = LBC_TYPE_ANY;
};
struct IrFunction
{
std::vector<IrBlock> blocks;
std::vector<IrInst> instructions;
std::vector<IrConst> constants;
std::vector<BytecodeBlock> bcBlocks;
std::vector<BytecodeTypes> bcTypes;
std::vector<BytecodeMapping> bcMapping;
uint32_t entryBlock = 0;
uint32_t entryLocation = 0;
// For each instruction, an operand that can be used to recompute the value
std::vector<IrOp> valueRestoreOps;
std::vector<uint32_t> validRestoreOpBlocks;
uint32_t validRestoreOpBlockIdx = 0;
Proto* proto = nullptr;
@ -1086,22 +1108,53 @@ struct IrFunction
if (instIdx >= valueRestoreOps.size())
return {};
const IrBlock& block = blocks[validRestoreOpBlockIdx];
// When spilled, values can only reference restore operands in the current block
if (limitToCurrentBlock)
if (FFlag::LuauKeepVmapLinear2)
{
if (instIdx < block.start || instIdx > block.finish)
return {};
}
// When spilled, values can only reference restore operands in the current block chain
if (limitToCurrentBlock)
{
for (uint32_t blockIdx : validRestoreOpBlocks)
{
const IrBlock& block = blocks[blockIdx];
return valueRestoreOps[instIdx];
if (instIdx >= block.start && instIdx <= block.finish)
return valueRestoreOps[instIdx];
}
return {};
}
return valueRestoreOps[instIdx];
}
else
{
const IrBlock& block = blocks[validRestoreOpBlockIdx];
// When spilled, values can only reference restore operands in the current block
if (limitToCurrentBlock)
{
if (instIdx < block.start || instIdx > block.finish)
return {};
}
return valueRestoreOps[instIdx];
}
}
IrOp findRestoreOp(const IrInst& inst, bool limitToCurrentBlock) const
{
return findRestoreOp(getInstIndex(inst), limitToCurrentBlock);
}
BytecodeTypes getBytecodeTypesAt(int pcpos) const
{
LUAU_ASSERT(pcpos >= 0);
if (size_t(pcpos) < bcTypes.size())
return bcTypes[pcpos];
return BytecodeTypes();
}
};
inline IrCondition conditionOp(IrOp op)

View File

@ -29,6 +29,7 @@ void toString(IrToStringContext& ctx, const IrBlock& block, uint32_t index); //
void toString(IrToStringContext& ctx, IrOp op);
void toString(std::string& result, IrConst constant);
void toString(std::string& result, const BytecodeTypes& bcTypes);
void toStringDetailed(IrToStringContext& ctx, const IrBlock& block, uint32_t blockIdx, const IrInst& inst, uint32_t instIdx, bool includeUseInfo);
void toStringDetailed(IrToStringContext& ctx, const IrBlock& block, uint32_t index, bool includeUseInfo); // Block title

View File

@ -41,6 +41,11 @@ static void visitVmRegDefsUses(T& visitor, IrFunction& function, const IrInst& i
break;
// A <- B, C
case IrCmd::DO_ARITH:
visitor.maybeUse(inst.b); // Argument can also be a VmConst
visitor.maybeUse(inst.c); // Argument can also be a VmConst
visitor.def(inst.a);
break;
case IrCmd::GET_TABLE:
visitor.use(inst.b);
visitor.maybeUse(inst.c); // Argument can also be a VmConst

View File

@ -0,0 +1,884 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/BytecodeAnalysis.h"
#include "Luau/BytecodeUtils.h"
#include "Luau/IrData.h"
#include "Luau/IrUtils.h"
#include "lobject.h"
namespace Luau
{
namespace CodeGen
{
static bool hasTypedParameters(Proto* proto)
{
return proto->typeinfo && proto->numparams != 0;
}
static uint8_t getBytecodeConstantTag(Proto* proto, unsigned ki)
{
TValue protok = proto->k[ki];
switch (protok.tt)
{
case LUA_TNIL:
return LBC_TYPE_NIL;
case LUA_TBOOLEAN:
return LBC_TYPE_BOOLEAN;
case LUA_TLIGHTUSERDATA:
return LBC_TYPE_USERDATA;
case LUA_TNUMBER:
return LBC_TYPE_NUMBER;
case LUA_TVECTOR:
return LBC_TYPE_VECTOR;
case LUA_TSTRING:
return LBC_TYPE_STRING;
case LUA_TTABLE:
return LBC_TYPE_TABLE;
case LUA_TFUNCTION:
return LBC_TYPE_FUNCTION;
case LUA_TUSERDATA:
return LBC_TYPE_USERDATA;
case LUA_TTHREAD:
return LBC_TYPE_THREAD;
case LUA_TBUFFER:
return LBC_TYPE_BUFFER;
}
return LBC_TYPE_ANY;
}
static void applyBuiltinCall(int bfid, BytecodeTypes& types)
{
switch (bfid)
{
case LBF_NONE:
case LBF_ASSERT:
types.result = LBC_TYPE_ANY;
break;
case LBF_MATH_ABS:
case LBF_MATH_ACOS:
case LBF_MATH_ASIN:
types.result = LBC_TYPE_NUMBER;
types.a = LBC_TYPE_NUMBER;
break;
case LBF_MATH_ATAN2:
types.result = LBC_TYPE_NUMBER;
types.a = LBC_TYPE_NUMBER;
types.b = LBC_TYPE_NUMBER;
break;
case LBF_MATH_ATAN:
case LBF_MATH_CEIL:
case LBF_MATH_COSH:
case LBF_MATH_COS:
case LBF_MATH_DEG:
case LBF_MATH_EXP:
case LBF_MATH_FLOOR:
types.result = LBC_TYPE_NUMBER;
types.a = LBC_TYPE_NUMBER;
break;
case LBF_MATH_FMOD:
types.result = LBC_TYPE_NUMBER;
types.a = LBC_TYPE_NUMBER;
types.b = LBC_TYPE_NUMBER;
break;
case LBF_MATH_FREXP:
types.result = LBC_TYPE_NUMBER;
types.a = LBC_TYPE_NUMBER;
break;
case LBF_MATH_LDEXP:
types.result = LBC_TYPE_NUMBER;
types.a = LBC_TYPE_NUMBER;
types.b = LBC_TYPE_NUMBER;
break;
case LBF_MATH_LOG10:
types.result = LBC_TYPE_NUMBER;
types.a = LBC_TYPE_NUMBER;
break;
case LBF_MATH_LOG:
types.result = LBC_TYPE_NUMBER;
types.a = LBC_TYPE_NUMBER;
types.b = LBC_TYPE_NUMBER; // We can mark optional arguments
break;
case LBF_MATH_MAX:
case LBF_MATH_MIN:
types.result = LBC_TYPE_NUMBER;
types.a = LBC_TYPE_NUMBER;
types.b = LBC_TYPE_NUMBER;
types.c = LBC_TYPE_NUMBER; // We can mark optional arguments
break;
case LBF_MATH_MODF:
types.result = LBC_TYPE_NUMBER;
types.a = LBC_TYPE_NUMBER;
break;
case LBF_MATH_POW:
types.result = LBC_TYPE_NUMBER;
types.a = LBC_TYPE_NUMBER;
types.b = LBC_TYPE_NUMBER;
break;
case LBF_MATH_RAD:
case LBF_MATH_SINH:
case LBF_MATH_SIN:
case LBF_MATH_SQRT:
case LBF_MATH_TANH:
case LBF_MATH_TAN:
types.result = LBC_TYPE_NUMBER;
types.a = LBC_TYPE_NUMBER;
break;
case LBF_BIT32_ARSHIFT:
types.result = LBC_TYPE_NUMBER;
types.a = LBC_TYPE_NUMBER;
types.b = LBC_TYPE_NUMBER;
break;
case LBF_BIT32_BAND:
types.result = LBC_TYPE_NUMBER;
types.a = LBC_TYPE_NUMBER;
types.b = LBC_TYPE_NUMBER;
types.c = LBC_TYPE_NUMBER; // We can mark optional arguments
break;
case LBF_BIT32_BNOT:
types.result = LBC_TYPE_NUMBER;
types.a = LBC_TYPE_NUMBER;
break;
case LBF_BIT32_BOR:
case LBF_BIT32_BXOR:
case LBF_BIT32_BTEST:
case LBF_BIT32_EXTRACT:
types.result = LBC_TYPE_NUMBER;
types.a = LBC_TYPE_NUMBER;
types.b = LBC_TYPE_NUMBER;
types.c = LBC_TYPE_NUMBER; // We can mark optional arguments
break;
case LBF_BIT32_LROTATE:
case LBF_BIT32_LSHIFT:
types.result = LBC_TYPE_NUMBER;
types.a = LBC_TYPE_NUMBER;
types.b = LBC_TYPE_NUMBER;
break;
case LBF_BIT32_REPLACE:
types.result = LBC_TYPE_NUMBER;
types.a = LBC_TYPE_NUMBER;
types.b = LBC_TYPE_NUMBER;
types.c = LBC_TYPE_NUMBER; // We can mark optional arguments
break;
case LBF_BIT32_RROTATE:
case LBF_BIT32_RSHIFT:
types.result = LBC_TYPE_NUMBER;
types.a = LBC_TYPE_NUMBER;
types.b = LBC_TYPE_NUMBER;
break;
case LBF_TYPE:
types.result = LBC_TYPE_STRING;
break;
case LBF_STRING_BYTE:
types.result = LBC_TYPE_NUMBER;
types.a = LBC_TYPE_STRING;
types.b = LBC_TYPE_NUMBER;
break;
case LBF_STRING_CHAR:
types.result = LBC_TYPE_STRING;
// We can mark optional arguments
types.a = LBC_TYPE_NUMBER;
types.b = LBC_TYPE_NUMBER;
types.c = LBC_TYPE_NUMBER;
break;
case LBF_STRING_LEN:
types.result = LBC_TYPE_NUMBER;
types.a = LBC_TYPE_STRING;
break;
case LBF_TYPEOF:
types.result = LBC_TYPE_STRING;
break;
case LBF_STRING_SUB:
types.result = LBC_TYPE_STRING;
types.a = LBC_TYPE_STRING;
types.b = LBC_TYPE_NUMBER;
types.c = LBC_TYPE_NUMBER;
break;
case LBF_MATH_CLAMP:
types.result = LBC_TYPE_NUMBER;
types.a = LBC_TYPE_NUMBER;
types.b = LBC_TYPE_NUMBER;
types.c = LBC_TYPE_NUMBER;
break;
case LBF_MATH_SIGN:
types.result = LBC_TYPE_NUMBER;
types.a = LBC_TYPE_NUMBER;
break;
case LBF_MATH_ROUND:
types.result = LBC_TYPE_NUMBER;
types.a = LBC_TYPE_NUMBER;
break;
case LBF_RAWGET:
types.result = LBC_TYPE_ANY;
types.a = LBC_TYPE_TABLE;
break;
case LBF_RAWEQUAL:
types.result = LBC_TYPE_BOOLEAN;
break;
case LBF_TABLE_UNPACK:
types.result = LBC_TYPE_ANY;
types.a = LBC_TYPE_TABLE;
types.b = LBC_TYPE_NUMBER; // We can mark optional arguments
break;
case LBF_VECTOR:
types.result = LBC_TYPE_VECTOR;
types.a = LBC_TYPE_NUMBER;
types.b = LBC_TYPE_NUMBER;
types.c = LBC_TYPE_NUMBER;
break;
case LBF_BIT32_COUNTLZ:
case LBF_BIT32_COUNTRZ:
types.result = LBC_TYPE_NUMBER;
types.a = LBC_TYPE_NUMBER;
break;
case LBF_SELECT_VARARG:
types.result = LBC_TYPE_ANY;
break;
case LBF_RAWLEN:
types.result = LBC_TYPE_NUMBER;
break;
case LBF_BIT32_EXTRACTK:
types.result = LBC_TYPE_NUMBER;
types.a = LBC_TYPE_NUMBER;
types.b = LBC_TYPE_NUMBER;
break;
case LBF_GETMETATABLE:
types.result = LBC_TYPE_TABLE;
break;
case LBF_TONUMBER:
types.result = LBC_TYPE_NUMBER;
break;
case LBF_TOSTRING:
types.result = LBC_TYPE_STRING;
break;
case LBF_BIT32_BYTESWAP:
types.result = LBC_TYPE_NUMBER;
types.a = LBC_TYPE_NUMBER;
break;
case LBF_BUFFER_READI8:
case LBF_BUFFER_READU8:
types.result = LBC_TYPE_NUMBER;
types.a = LBC_TYPE_BUFFER;
types.b = LBC_TYPE_NUMBER;
break;
case LBF_BUFFER_WRITEU8:
types.result = LBC_TYPE_NIL;
types.a = LBC_TYPE_BUFFER;
types.b = LBC_TYPE_NUMBER;
types.c = LBC_TYPE_NUMBER;
break;
case LBF_BUFFER_READI16:
case LBF_BUFFER_READU16:
types.result = LBC_TYPE_NUMBER;
types.a = LBC_TYPE_BUFFER;
types.b = LBC_TYPE_NUMBER;
break;
case LBF_BUFFER_WRITEU16:
types.result = LBC_TYPE_NIL;
types.a = LBC_TYPE_BUFFER;
types.b = LBC_TYPE_NUMBER;
types.c = LBC_TYPE_NUMBER;
break;
case LBF_BUFFER_READI32:
case LBF_BUFFER_READU32:
types.result = LBC_TYPE_NUMBER;
types.a = LBC_TYPE_BUFFER;
types.b = LBC_TYPE_NUMBER;
break;
case LBF_BUFFER_WRITEU32:
types.result = LBC_TYPE_NIL;
types.a = LBC_TYPE_BUFFER;
types.b = LBC_TYPE_NUMBER;
types.c = LBC_TYPE_NUMBER;
break;
case LBF_BUFFER_READF32:
types.result = LBC_TYPE_NUMBER;
types.a = LBC_TYPE_BUFFER;
types.b = LBC_TYPE_NUMBER;
break;
case LBF_BUFFER_WRITEF32:
types.result = LBC_TYPE_NIL;
types.a = LBC_TYPE_BUFFER;
types.b = LBC_TYPE_NUMBER;
types.c = LBC_TYPE_NUMBER;
break;
case LBF_BUFFER_READF64:
types.result = LBC_TYPE_NUMBER;
types.a = LBC_TYPE_BUFFER;
types.b = LBC_TYPE_NUMBER;
break;
case LBF_BUFFER_WRITEF64:
types.result = LBC_TYPE_NIL;
types.a = LBC_TYPE_BUFFER;
types.b = LBC_TYPE_NUMBER;
types.c = LBC_TYPE_NUMBER;
break;
case LBF_TABLE_INSERT:
types.result = LBC_TYPE_NIL;
types.a = LBC_TYPE_TABLE;
break;
case LBF_RAWSET:
types.result = LBC_TYPE_ANY;
types.a = LBC_TYPE_TABLE;
break;
case LBF_SETMETATABLE:
types.result = LBC_TYPE_TABLE;
types.a = LBC_TYPE_TABLE;
types.b = LBC_TYPE_TABLE;
break;
}
}
void buildBytecodeBlocks(IrFunction& function, const std::vector<uint8_t>& jumpTargets)
{
Proto* proto = function.proto;
LUAU_ASSERT(proto);
std::vector<BytecodeBlock>& bcBlocks = function.bcBlocks;
// Using the same jump targets, create VM bytecode basic blocks
bcBlocks.push_back(BytecodeBlock{0, -1});
int previ = 0;
for (int i = 0; i < proto->sizecode;)
{
const Instruction* pc = &proto->code[i];
LuauOpcode op = LuauOpcode(LUAU_INSN_OP(*pc));
int nexti = i + getOpLength(op);
// If instruction is a jump target, begin new block starting from it
if (i != 0 && jumpTargets[i])
{
bcBlocks.back().finishpc = previ;
bcBlocks.push_back(BytecodeBlock{i, -1});
}
int target = getJumpTarget(*pc, uint32_t(i));
// Implicit fallthroughs terminate the block and might start a new one
if (target >= 0 && !isFastCall(op))
{
bcBlocks.back().finishpc = i;
// Start a new block if there was no explicit jump for the fallthrough
if (!jumpTargets[nexti])
bcBlocks.push_back(BytecodeBlock{nexti, -1});
}
// Returns just terminate the block
else if (op == LOP_RETURN)
{
bcBlocks.back().finishpc = i;
}
previ = i;
i = nexti;
LUAU_ASSERT(i <= proto->sizecode);
}
}
void analyzeBytecodeTypes(IrFunction& function)
{
Proto* proto = function.proto;
LUAU_ASSERT(proto);
// Setup our current knowledge of type tags based on arguments
uint8_t regTags[256];
memset(regTags, LBC_TYPE_ANY, 256);
function.bcTypes.resize(proto->sizecode);
// Now that we have VM basic blocks, we can attempt to track register type tags locally
for (const BytecodeBlock& block : function.bcBlocks)
{
LUAU_ASSERT(block.startpc != -1);
LUAU_ASSERT(block.finishpc != -1);
// At the block start, reset or knowledge to the starting state
// In the future we might be able to propagate some info between the blocks as well
if (hasTypedParameters(proto))
{
for (int i = 0; i < proto->numparams; ++i)
{
uint8_t et = proto->typeinfo[2 + i];
// TODO: if argument is optional, this might force a VM exit unnecessarily
regTags[i] = et & ~LBC_TYPE_OPTIONAL_BIT;
}
}
for (int i = proto->numparams; i < proto->maxstacksize; ++i)
regTags[i] = LBC_TYPE_ANY;
for (int i = block.startpc; i <= block.finishpc;)
{
const Instruction* pc = &proto->code[i];
LuauOpcode op = LuauOpcode(LUAU_INSN_OP(*pc));
BytecodeTypes& bcType = function.bcTypes[i];
switch (op)
{
case LOP_NOP:
break;
case LOP_LOADNIL:
{
int ra = LUAU_INSN_A(*pc);
regTags[ra] = LBC_TYPE_NIL;
bcType.result = regTags[ra];
break;
}
case LOP_LOADB:
{
int ra = LUAU_INSN_A(*pc);
regTags[ra] = LBC_TYPE_BOOLEAN;
bcType.result = regTags[ra];
break;
}
case LOP_LOADN:
{
int ra = LUAU_INSN_A(*pc);
regTags[ra] = LBC_TYPE_NUMBER;
bcType.result = regTags[ra];
break;
}
case LOP_LOADK:
{
int ra = LUAU_INSN_A(*pc);
int kb = LUAU_INSN_D(*pc);
bcType.a = getBytecodeConstantTag(proto, kb);
regTags[ra] = bcType.a;
bcType.result = regTags[ra];
break;
}
case LOP_LOADKX:
{
int ra = LUAU_INSN_A(*pc);
int kb = int(pc[1]);
bcType.a = getBytecodeConstantTag(proto, kb);
regTags[ra] = bcType.a;
bcType.result = regTags[ra];
break;
}
case LOP_MOVE:
{
int ra = LUAU_INSN_A(*pc);
int rb = LUAU_INSN_B(*pc);
bcType.a = regTags[rb];
regTags[ra] = regTags[rb];
bcType.result = regTags[ra];
break;
}
case LOP_GETTABLE:
{
int rb = LUAU_INSN_B(*pc);
int rc = LUAU_INSN_C(*pc);
bcType.a = regTags[rb];
bcType.b = regTags[rc];
break;
}
case LOP_SETTABLE:
{
int rb = LUAU_INSN_B(*pc);
int rc = LUAU_INSN_C(*pc);
bcType.a = regTags[rb];
bcType.b = regTags[rc];
break;
}
case LOP_GETTABLEKS:
{
int ra = LUAU_INSN_A(*pc);
int rb = LUAU_INSN_B(*pc);
uint32_t kc = pc[1];
bcType.a = regTags[rb];
bcType.b = getBytecodeConstantTag(proto, kc);
regTags[ra] = LBC_TYPE_ANY;
// Assuming that vector component is being indexed
// TODO: check what key is used
if (bcType.a == LBC_TYPE_VECTOR)
regTags[ra] = LBC_TYPE_NUMBER;
bcType.result = regTags[ra];
break;
}
case LOP_SETTABLEKS:
{
int rb = LUAU_INSN_B(*pc);
bcType.a = regTags[rb];
bcType.b = LBC_TYPE_STRING;
break;
}
case LOP_GETTABLEN:
case LOP_SETTABLEN:
{
int rb = LUAU_INSN_B(*pc);
bcType.a = regTags[rb];
bcType.b = LBC_TYPE_NUMBER;
break;
}
case LOP_ADD:
case LOP_SUB:
{
int ra = LUAU_INSN_A(*pc);
int rb = LUAU_INSN_B(*pc);
int rc = LUAU_INSN_C(*pc);
bcType.a = regTags[rb];
bcType.b = regTags[rc];
regTags[ra] = LBC_TYPE_ANY;
if (bcType.a == LBC_TYPE_NUMBER && bcType.b == LBC_TYPE_NUMBER)
regTags[ra] = LBC_TYPE_NUMBER;
else if (bcType.a == LBC_TYPE_VECTOR && bcType.b == LBC_TYPE_VECTOR)
regTags[ra] = LBC_TYPE_VECTOR;
bcType.result = regTags[ra];
break;
}
case LOP_MUL:
case LOP_DIV:
case LOP_IDIV:
{
int ra = LUAU_INSN_A(*pc);
int rb = LUAU_INSN_B(*pc);
int rc = LUAU_INSN_C(*pc);
bcType.a = regTags[rb];
bcType.b = regTags[rc];
regTags[ra] = LBC_TYPE_ANY;
if (bcType.a == LBC_TYPE_NUMBER)
{
if (bcType.b == LBC_TYPE_NUMBER)
regTags[ra] = LBC_TYPE_NUMBER;
else if (bcType.b == LBC_TYPE_VECTOR)
regTags[ra] = LBC_TYPE_VECTOR;
}
else if (bcType.a == LBC_TYPE_VECTOR)
{
if (bcType.b == LBC_TYPE_NUMBER || bcType.b == LBC_TYPE_VECTOR)
regTags[ra] = LBC_TYPE_VECTOR;
}
bcType.result = regTags[ra];
break;
}
case LOP_MOD:
case LOP_POW:
{
int ra = LUAU_INSN_A(*pc);
int rb = LUAU_INSN_B(*pc);
int rc = LUAU_INSN_C(*pc);
bcType.a = regTags[rb];
bcType.b = regTags[rc];
regTags[ra] = LBC_TYPE_ANY;
if (bcType.a == LBC_TYPE_NUMBER && bcType.b == LBC_TYPE_NUMBER)
regTags[ra] = LBC_TYPE_NUMBER;
bcType.result = regTags[ra];
break;
}
case LOP_ADDK:
case LOP_SUBK:
{
int ra = LUAU_INSN_A(*pc);
int rb = LUAU_INSN_B(*pc);
int kc = LUAU_INSN_C(*pc);
bcType.a = regTags[rb];
bcType.b = getBytecodeConstantTag(proto, kc);
regTags[ra] = LBC_TYPE_ANY;
if (bcType.a == LBC_TYPE_NUMBER && bcType.b == LBC_TYPE_NUMBER)
regTags[ra] = LBC_TYPE_NUMBER;
else if (bcType.a == LBC_TYPE_VECTOR && bcType.b == LBC_TYPE_VECTOR)
regTags[ra] = LBC_TYPE_VECTOR;
bcType.result = regTags[ra];
break;
}
case LOP_MULK:
case LOP_DIVK:
case LOP_IDIVK:
{
int ra = LUAU_INSN_A(*pc);
int rb = LUAU_INSN_B(*pc);
int kc = LUAU_INSN_C(*pc);
bcType.a = regTags[rb];
bcType.b = getBytecodeConstantTag(proto, kc);
regTags[ra] = LBC_TYPE_ANY;
if (bcType.a == LBC_TYPE_NUMBER)
{
if (bcType.b == LBC_TYPE_NUMBER)
regTags[ra] = LBC_TYPE_NUMBER;
else if (bcType.b == LBC_TYPE_VECTOR)
regTags[ra] = LBC_TYPE_VECTOR;
}
else if (bcType.a == LBC_TYPE_VECTOR)
{
if (bcType.b == LBC_TYPE_NUMBER || bcType.b == LBC_TYPE_VECTOR)
regTags[ra] = LBC_TYPE_VECTOR;
}
bcType.result = regTags[ra];
break;
}
case LOP_MODK:
case LOP_POWK:
{
int ra = LUAU_INSN_A(*pc);
int rb = LUAU_INSN_B(*pc);
int kc = LUAU_INSN_C(*pc);
bcType.a = regTags[rb];
bcType.b = getBytecodeConstantTag(proto, kc);
regTags[ra] = LBC_TYPE_ANY;
if (bcType.a == LBC_TYPE_NUMBER && bcType.b == LBC_TYPE_NUMBER)
regTags[ra] = LBC_TYPE_NUMBER;
bcType.result = regTags[ra];
break;
}
case LOP_SUBRK:
{
int ra = LUAU_INSN_A(*pc);
int kb = LUAU_INSN_B(*pc);
int rc = LUAU_INSN_C(*pc);
bcType.a = getBytecodeConstantTag(proto, kb);
bcType.b = regTags[rc];
regTags[ra] = LBC_TYPE_ANY;
if (bcType.a == LBC_TYPE_NUMBER && bcType.b == LBC_TYPE_NUMBER)
regTags[ra] = LBC_TYPE_NUMBER;
else if (bcType.a == LBC_TYPE_VECTOR && bcType.b == LBC_TYPE_VECTOR)
regTags[ra] = LBC_TYPE_VECTOR;
bcType.result = regTags[ra];
break;
}
case LOP_DIVRK:
{
int ra = LUAU_INSN_A(*pc);
int rb = LUAU_INSN_B(*pc);
int kc = LUAU_INSN_C(*pc);
bcType.a = regTags[rb];
bcType.b = getBytecodeConstantTag(proto, kc);
regTags[ra] = LBC_TYPE_ANY;
if (bcType.a == LBC_TYPE_NUMBER)
{
if (bcType.b == LBC_TYPE_NUMBER)
regTags[ra] = LBC_TYPE_NUMBER;
else if (bcType.b == LBC_TYPE_VECTOR)
regTags[ra] = LBC_TYPE_VECTOR;
}
else if (bcType.a == LBC_TYPE_VECTOR)
{
if (bcType.b == LBC_TYPE_NUMBER || bcType.b == LBC_TYPE_VECTOR)
regTags[ra] = LBC_TYPE_VECTOR;
}
bcType.result = regTags[ra];
break;
}
case LOP_NOT:
{
int ra = LUAU_INSN_A(*pc);
int rb = LUAU_INSN_B(*pc);
bcType.a = regTags[rb];
regTags[ra] = LBC_TYPE_BOOLEAN;
bcType.result = regTags[ra];
break;
}
case LOP_MINUS:
{
int ra = LUAU_INSN_A(*pc);
int rb = LUAU_INSN_B(*pc);
bcType.a = regTags[rb];
regTags[ra] = LBC_TYPE_ANY;
if (bcType.a == LBC_TYPE_NUMBER)
regTags[ra] = LBC_TYPE_NUMBER;
else if (bcType.a == LBC_TYPE_VECTOR)
regTags[ra] = LBC_TYPE_VECTOR;
bcType.result = regTags[ra];
break;
}
case LOP_LENGTH:
{
int ra = LUAU_INSN_A(*pc);
int rb = LUAU_INSN_B(*pc);
bcType.a = regTags[rb];
regTags[ra] = LBC_TYPE_NUMBER; // Even if it's a custom __len, it's ok to assume a sane result
bcType.result = regTags[ra];
break;
}
case LOP_NEWTABLE:
case LOP_DUPTABLE:
{
int ra = LUAU_INSN_A(*pc);
regTags[ra] = LBC_TYPE_TABLE;
bcType.result = regTags[ra];
break;
}
case LOP_FASTCALL:
{
int bfid = LUAU_INSN_A(*pc);
int skip = LUAU_INSN_C(*pc);
Instruction call = pc[skip + 1];
LUAU_ASSERT(LUAU_INSN_OP(call) == LOP_CALL);
int ra = LUAU_INSN_A(call);
applyBuiltinCall(bfid, bcType);
regTags[ra + 1] = bcType.a;
regTags[ra + 2] = bcType.b;
regTags[ra + 3] = bcType.c;
regTags[ra] = bcType.result;
break;
}
case LOP_FASTCALL1:
case LOP_FASTCALL2K:
{
int bfid = LUAU_INSN_A(*pc);
int skip = LUAU_INSN_C(*pc);
Instruction call = pc[skip + 1];
LUAU_ASSERT(LUAU_INSN_OP(call) == LOP_CALL);
int ra = LUAU_INSN_A(call);
applyBuiltinCall(bfid, bcType);
regTags[LUAU_INSN_B(*pc)] = bcType.a;
regTags[ra] = bcType.result;
break;
}
case LOP_FASTCALL2:
{
int bfid = LUAU_INSN_A(*pc);
int skip = LUAU_INSN_C(*pc);
Instruction call = pc[skip + 1];
LUAU_ASSERT(LUAU_INSN_OP(call) == LOP_CALL);
int ra = LUAU_INSN_A(call);
applyBuiltinCall(bfid, bcType);
regTags[LUAU_INSN_B(*pc)] = bcType.a;
regTags[int(pc[1])] = bcType.b;
regTags[ra] = bcType.result;
break;
}
case LOP_FORNPREP:
{
int ra = LUAU_INSN_A(*pc);
regTags[ra] = LBC_TYPE_NUMBER;
regTags[ra + 1] = LBC_TYPE_NUMBER;
regTags[ra + 2] = LBC_TYPE_NUMBER;
break;
}
case LOP_FORNLOOP:
{
int ra = LUAU_INSN_A(*pc);
// These types are established by LOP_FORNPREP and we reinforce that here
regTags[ra] = LBC_TYPE_NUMBER;
regTags[ra + 1] = LBC_TYPE_NUMBER;
regTags[ra + 2] = LBC_TYPE_NUMBER;
break;
}
case LOP_CONCAT:
{
int ra = LUAU_INSN_A(*pc);
regTags[ra] = LBC_TYPE_STRING;
bcType.result = regTags[ra];
break;
}
case LOP_NEWCLOSURE:
case LOP_DUPCLOSURE:
{
int ra = LUAU_INSN_A(*pc);
regTags[ra] = LBC_TYPE_FUNCTION;
bcType.result = regTags[ra];
break;
}
case LOP_GETGLOBAL:
case LOP_SETGLOBAL:
case LOP_CALL:
case LOP_RETURN:
case LOP_JUMP:
case LOP_JUMPBACK:
case LOP_JUMPIF:
case LOP_JUMPIFNOT:
case LOP_JUMPIFEQ:
case LOP_JUMPIFLE:
case LOP_JUMPIFLT:
case LOP_JUMPIFNOTEQ:
case LOP_JUMPIFNOTLE:
case LOP_JUMPIFNOTLT:
case LOP_JUMPX:
case LOP_JUMPXEQKNIL:
case LOP_JUMPXEQKB:
case LOP_JUMPXEQKN:
case LOP_JUMPXEQKS:
case LOP_SETLIST:
case LOP_GETUPVAL:
case LOP_SETUPVAL:
case LOP_CLOSEUPVALS:
case LOP_FORGLOOP:
case LOP_FORGPREP_NEXT:
case LOP_FORGPREP_INEXT:
case LOP_AND:
case LOP_ANDK:
case LOP_OR:
case LOP_ORK:
case LOP_COVERAGE:
case LOP_GETIMPORT:
case LOP_CAPTURE:
case LOP_NAMECALL:
case LOP_PREPVARARGS:
case LOP_GETVARARGS:
case LOP_FORGPREP:
break;
default:
LUAU_ASSERT(!"Unknown instruction");
}
i += getOpLength(op);
}
}
}
} // namespace CodeGen
} // namespace Luau

View File

@ -1,5 +1,6 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/CodeGen.h"
#include "Luau/BytecodeUtils.h"
#include "CodeGenLower.h"
@ -42,6 +43,17 @@ static void logFunctionHeader(AssemblyBuilder& build, Proto* proto)
build.logAppend("\n");
}
unsigned getInstructionCount(const Instruction* insns, const unsigned size)
{
unsigned count = 0;
for (unsigned i = 0; i < size;)
{
++count;
i += Luau::getOpLength(LuauOpcode(LUAU_INSN_OP(insns[i])));
}
return count;
}
template<typename AssemblyBuilder>
static std::string getAssemblyImpl(AssemblyBuilder& build, const TValue* func, AssemblyOptions options, LoweringStats* stats)
{
@ -53,7 +65,11 @@ static std::string getAssemblyImpl(AssemblyBuilder& build, const TValue* func, A
std::vector<Proto*> protos;
gatherFunctions(protos, root, options.flags);
protos.erase(std::remove_if(protos.begin(), protos.end(), [](Proto* p) { return p == nullptr; }), protos.end());
protos.erase(std::remove_if(protos.begin(), protos.end(),
[](Proto* p) {
return p == nullptr;
}),
protos.end());
if (stats)
stats->totalFunctions += unsigned(protos.size());
@ -77,6 +93,7 @@ static std::string getAssemblyImpl(AssemblyBuilder& build, const TValue* func, A
{
IrBuilder ir;
ir.buildFunctionIr(p);
unsigned asmCount = build.getCodeSize();
if (options.includeAssembly || options.includeIr)
logFunctionHeader(build, p);
@ -86,9 +103,24 @@ static std::string getAssemblyImpl(AssemblyBuilder& build, const TValue* func, A
if (build.logText)
build.logAppend("; skipping (can't lower)\n");
asmCount = 0;
if (stats)
stats->skippedFunctions += 1;
}
else
{
asmCount = build.getCodeSize() - asmCount;
}
if (stats && stats->collectFunctionStats)
{
const char* name = p->debugname ? getstr(p->debugname) : "";
int line = p->linedefined;
unsigned bcodeCount = getInstructionCount(p->code, p->sizecode);
unsigned irCount = unsigned(ir.function.instructions.size());
stats->functions.push_back({name, line, bcodeCount, irCount, asmCount});
}
if (build.logText)
build.logAppend("\n");

View File

@ -26,6 +26,7 @@ LUAU_FASTFLAG(DebugCodegenSkipNumbering)
LUAU_FASTINT(CodegenHeuristicsInstructionLimit)
LUAU_FASTINT(CodegenHeuristicsBlockLimit)
LUAU_FASTINT(CodegenHeuristicsBlockInstructionLimit)
LUAU_FASTFLAG(LuauKeepVmapLinear2)
namespace Luau
{
@ -112,8 +113,16 @@ inline bool lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction&
toStringDetailed(ctx, block, blockIndex, /* includeUseInfo */ true);
}
// Values can only reference restore operands in the current block
function.validRestoreOpBlockIdx = blockIndex;
if (FFlag::LuauKeepVmapLinear2)
{
// Values can only reference restore operands in the current block chain
function.validRestoreOpBlocks.push_back(blockIndex);
}
else
{
// Values can only reference restore operands in the current block
function.validRestoreOpBlockIdx = blockIndex;
}
build.setLabel(block.label);
@ -139,6 +148,15 @@ inline bool lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction&
if (outputEnabled && options.annotator && bcLocation != ~0u)
{
options.annotator(options.annotatorContext, build.text, bytecodeid, bcLocation);
// If available, report inferred register tags
BytecodeTypes bcTypes = function.getBytecodeTypesAt(bcLocation);
if (bcTypes.result != LBC_TYPE_ANY || bcTypes.a != LBC_TYPE_ANY || bcTypes.b != LBC_TYPE_ANY || bcTypes.c != LBC_TYPE_ANY)
{
toString(ctx.result, bcTypes);
build.logAppend("\n");
}
}
// If bytecode needs the location of this instruction for jumps, record it
@ -190,6 +208,9 @@ inline bool lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction&
if (options.includeIr)
build.logAppend("#\n");
if (FFlag::LuauKeepVmapLinear2 && block.expectedNextBlock == ~0u)
function.validRestoreOpBlocks.clear();
}
if (!seenFallback)

View File

@ -426,7 +426,7 @@ const Instruction* executeGETTABLEKS(lua_State* L, const Instruction* pc, StkId
if (unsigned(ic) < LUA_VECTOR_SIZE && name[1] == '\0')
{
const float* v = rb->value.v; // silences ubsan when indexing v[]
const float* v = vvalue(rb); // silences ubsan when indexing v[]
setnvalue(ra, v[ic]);
return pc;
}

View File

@ -148,12 +148,12 @@ void convertNumberToIndexOrJump(AssemblyBuilderX64& build, RegisterX64 tmp, Regi
build.jcc(ConditionX64::NotZero, label);
}
void callArithHelper(IrRegAllocX64& regs, AssemblyBuilderX64& build, int ra, int rb, OperandX64 c, TMS tm)
void callArithHelper(IrRegAllocX64& regs, AssemblyBuilderX64& build, int ra, OperandX64 b, OperandX64 c, TMS tm)
{
IrCallWrapperX64 callWrap(regs, build);
callWrap.addArgument(SizeX64::qword, rState);
callWrap.addArgument(SizeX64::qword, luauRegAddress(ra));
callWrap.addArgument(SizeX64::qword, luauRegAddress(rb));
callWrap.addArgument(SizeX64::qword, b);
callWrap.addArgument(SizeX64::qword, c);
callWrap.addArgument(SizeX64::dword, tm);
callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaV_doarith)]);

View File

@ -200,7 +200,7 @@ ConditionX64 getConditionInt(IrCondition cond);
void getTableNodeAtCachedSlot(AssemblyBuilderX64& build, RegisterX64 tmp, RegisterX64 node, RegisterX64 table, int pcpos);
void convertNumberToIndexOrJump(AssemblyBuilderX64& build, RegisterX64 tmp, RegisterX64 numd, RegisterX64 numi, Label& label);
void callArithHelper(IrRegAllocX64& regs, AssemblyBuilderX64& build, int ra, int rb, OperandX64 c, TMS tm);
void callArithHelper(IrRegAllocX64& regs, AssemblyBuilderX64& build, int ra, OperandX64 b, OperandX64 c, TMS tm);
void callLengthHelper(IrRegAllocX64& regs, AssemblyBuilderX64& build, int ra, int rb);
void callGetTable(IrRegAllocX64& regs, AssemblyBuilderX64& build, int rb, OperandX64 c, int ra);
void callSetTable(IrRegAllocX64& regs, AssemblyBuilderX64& build, int rb, OperandX64 c, int ra);

View File

@ -8,6 +8,7 @@
#include "lobject.h"
#include <algorithm>
#include <bitset>
#include <stddef.h>

View File

@ -2,6 +2,7 @@
#include "Luau/IrBuilder.h"
#include "Luau/Bytecode.h"
#include "Luau/BytecodeAnalysis.h"
#include "Luau/BytecodeUtils.h"
#include "Luau/IrData.h"
#include "Luau/IrUtils.h"
@ -12,6 +13,8 @@
#include <string.h>
LUAU_FASTFLAGVARIABLE(LuauCodegenBytecodeInfer, false)
namespace Luau
{
namespace CodeGen
@ -119,6 +122,10 @@ void IrBuilder::buildFunctionIr(Proto* proto)
// Rebuild original control flow blocks
rebuildBytecodeBasicBlocks(proto);
// Infer register tags in bytecode
if (FFlag::LuauCodegenBytecodeInfer)
analyzeBytecodeTypes(function);
function.bcMapping.resize(proto->sizecode, {~0u, ~0u});
if (generateTypeChecks)
@ -152,7 +159,7 @@ void IrBuilder::buildFunctionIr(Proto* proto)
// Numeric for loops require additional processing to maintain loop stack
// Notably, this must be performed even when the block is dead so that we maintain the pairing FORNPREP-FORNLOOP
if (op == LOP_FORNPREP)
beforeInstForNPrep(*this, pc);
beforeInstForNPrep(*this, pc, i);
// We skip dead bytecode instructions when they appear after block was already terminated
if (!inTerminatedBlock)
@ -212,7 +219,6 @@ void IrBuilder::rebuildBytecodeBasicBlocks(Proto* proto)
LUAU_ASSERT(i <= proto->sizecode);
}
// Bytecode blocks are created at bytecode jump targets and the start of a function
jumpTargets[0] = true;
@ -224,6 +230,9 @@ void IrBuilder::rebuildBytecodeBasicBlocks(Proto* proto)
instIndexToBlock[i] = b.index;
}
}
if (FFlag::LuauCodegenBytecodeInfer)
buildBytecodeBlocks(function, jumpTargets);
}
void IrBuilder::translateInst(LuauOpcode op, const Instruction* pc, int i)
@ -381,6 +390,12 @@ void IrBuilder::translateInst(LuauOpcode op, const Instruction* pc, int i)
case LOP_POWK:
translateInstBinaryK(*this, pc, i, TM_POW);
break;
case LOP_SUBRK:
translateInstBinaryRK(*this, pc, i, TM_SUB);
break;
case LOP_DIVRK:
translateInstBinaryRK(*this, pc, i, TM_DIV);
break;
case LOP_NOT:
translateInstNot(*this, pc);
break;

View File

@ -432,7 +432,10 @@ void toString(IrToStringContext& ctx, IrOp op)
append(ctx.result, "U%d", vmUpvalueOp(op));
break;
case IrOpKind::VmExit:
append(ctx.result, "exit(%d)", vmExitOp(op));
if (vmExitOp(op) == kVmExitEntryGuardPc)
append(ctx.result, "exit(entry)");
else
append(ctx.result, "exit(%d)", vmExitOp(op));
break;
}
}
@ -459,6 +462,47 @@ void toString(std::string& result, IrConst constant)
}
}
const char* getBytecodeTypeName(uint8_t type)
{
switch (type)
{
case LBC_TYPE_NIL:
return "nil";
case LBC_TYPE_BOOLEAN:
return "boolean";
case LBC_TYPE_NUMBER:
return "number";
case LBC_TYPE_STRING:
return "string";
case LBC_TYPE_TABLE:
return "table";
case LBC_TYPE_FUNCTION:
return "function";
case LBC_TYPE_THREAD:
return "thread";
case LBC_TYPE_USERDATA:
return "userdata";
case LBC_TYPE_VECTOR:
return "vector";
case LBC_TYPE_BUFFER:
return "buffer";
case LBC_TYPE_ANY:
return "any";
}
LUAU_ASSERT(!"Unhandled type in getBytecodeTypeName");
return nullptr;
}
void toString(std::string& result, const BytecodeTypes& bcTypes)
{
if (bcTypes.c != LBC_TYPE_ANY)
append(result, "%s <- %s, %s, %s", getBytecodeTypeName(bcTypes.result), getBytecodeTypeName(bcTypes.a), getBytecodeTypeName(bcTypes.b),
getBytecodeTypeName(bcTypes.c));
else
append(result, "%s <- %s, %s", getBytecodeTypeName(bcTypes.result), getBytecodeTypeName(bcTypes.a), getBytecodeTypeName(bcTypes.b));
}
static void appendBlockSet(IrToStringContext& ctx, BlockIteratorWrapper blocks)
{
bool comma = false;

View File

@ -1067,7 +1067,11 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next)
regs.spill(build, index);
build.mov(x0, rState);
build.add(x1, rBase, uint16_t(vmRegOp(inst.a) * sizeof(TValue)));
build.add(x2, rBase, uint16_t(vmRegOp(inst.b) * sizeof(TValue)));
if (inst.b.kind == IrOpKind::VmConst)
emitAddOffset(build, x2, rConstants, vmConstOp(inst.b) * sizeof(TValue));
else
build.add(x2, rBase, uint16_t(vmRegOp(inst.b) * sizeof(TValue)));
if (inst.c.kind == IrOpKind::VmConst)
emitAddOffset(build, x3, rConstants, vmConstOp(inst.c) * sizeof(TValue));

View File

@ -962,11 +962,12 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next)
break;
}
case IrCmd::DO_ARITH:
if (inst.c.kind == IrOpKind::VmReg)
callArithHelper(regs, build, vmRegOp(inst.a), vmRegOp(inst.b), luauRegAddress(vmRegOp(inst.c)), TMS(intOp(inst.d)));
else
callArithHelper(regs, build, vmRegOp(inst.a), vmRegOp(inst.b), luauConstantAddress(vmConstOp(inst.c)), TMS(intOp(inst.d)));
{
OperandX64 opb = inst.b.kind == IrOpKind::VmReg ? luauRegAddress(vmRegOp(inst.b)) : luauConstantAddress(vmConstOp(inst.b));
OperandX64 opc = inst.c.kind == IrOpKind::VmReg ? luauRegAddress(vmRegOp(inst.c)) : luauConstantAddress(vmConstOp(inst.c));
callArithHelper(regs, build, vmRegOp(inst.a), opb, opc, TMS(intOp(inst.d)));
break;
}
case IrCmd::DO_LEN:
callLengthHelper(regs, build, vmRegOp(inst.a), vmRegOp(inst.b));
break;
@ -1911,7 +1912,7 @@ void IrLoweringX64::finishBlock(const IrBlock& curr, const IrBlock& next)
{
// If we have spills remaining, we have to immediately lower the successor block
for (uint32_t predIdx : predecessors(function.cfg, function.getBlockIndex(next)))
LUAU_ASSERT(predIdx == function.getBlockIndex(curr));
LUAU_ASSERT(predIdx == function.getBlockIndex(curr) || function.blocks[predIdx].kind == IrBlockKind::Dead);
// And the next block cannot be a join block in cfg
LUAU_ASSERT(next.useCount == 1);

View File

@ -9,7 +9,6 @@
#include <math.h>
LUAU_FASTFLAGVARIABLE(LuauBufferTranslateIr, false)
LUAU_FASTFLAGVARIABLE(LuauImproveInsertIr, false)
// TODO: when nresults is less than our actual result count, we can skip computing/writing unused results
@ -691,34 +690,24 @@ static BuiltinImplResult translateBuiltinTableInsert(IrBuilder& build, int npara
IrOp setnum = build.inst(IrCmd::TABLE_SETNUM, table, pos);
if (FFlag::LuauImproveInsertIr)
if (args.kind == IrOpKind::Constant)
{
if (args.kind == IrOpKind::Constant)
{
LUAU_ASSERT(build.function.constOp(args).kind == IrConstKind::Double);
LUAU_ASSERT(build.function.constOp(args).kind == IrConstKind::Double);
// No barrier necessary since numbers aren't collectable
build.inst(IrCmd::STORE_DOUBLE, setnum, args);
build.inst(IrCmd::STORE_TAG, setnum, build.constTag(LUA_TNUMBER));
}
else
{
IrOp va = build.inst(IrCmd::LOAD_TVALUE, args);
build.inst(IrCmd::STORE_TVALUE, setnum, va);
// Compiler only generates FASTCALL*K for source-level constants, so dynamic imports are not affected
LUAU_ASSERT(build.function.proto);
IrOp argstag = args.kind == IrOpKind::VmConst ? build.constTag(build.function.proto->k[vmConstOp(args)].tt) : build.undef();
build.inst(IrCmd::BARRIER_TABLE_FORWARD, table, args, argstag);
}
// No barrier necessary since numbers aren't collectable
build.inst(IrCmd::STORE_DOUBLE, setnum, args);
build.inst(IrCmd::STORE_TAG, setnum, build.constTag(LUA_TNUMBER));
}
else
{
IrOp va = build.inst(IrCmd::LOAD_TVALUE, args);
build.inst(IrCmd::STORE_TVALUE, setnum, va);
build.inst(IrCmd::BARRIER_TABLE_FORWARD, table, args, build.undef());
// Compiler only generates FASTCALL*K for source-level constants, so dynamic imports are not affected
LUAU_ASSERT(build.function.proto);
IrOp argstag = args.kind == IrOpKind::VmConst ? build.constTag(build.function.proto->k[vmConstOp(args)].tt) : build.undef();
build.inst(IrCmd::BARRIER_TABLE_FORWARD, table, args, argstag);
}
return {BuiltinImplType::Full, 0};

View File

@ -12,9 +12,8 @@
#include "lstate.h"
#include "ltm.h"
LUAU_FASTFLAGVARIABLE(LuauLowerAltLoopForn, false)
LUAU_FASTFLAG(LuauImproveInsertIr)
LUAU_FASTFLAGVARIABLE(LuauFullLoopLuserdata, false)
LUAU_FASTFLAGVARIABLE(LuauLoopInterruptFix, false)
namespace Luau
{
@ -44,6 +43,14 @@ struct FallbackStreamScope
IrOp next;
};
static IrOp getInitializedFallback(IrBuilder& build, IrOp& fallback)
{
if (fallback.kind == IrOpKind::None)
fallback = build.block(IrBlockKind::Fallback);
return fallback;
}
void translateInstLoadNil(IrBuilder& build, const Instruction* pc)
{
int ra = LUAU_INSN_A(*pc);
@ -327,25 +334,43 @@ void translateInstJumpxEqS(IrBuilder& build, const Instruction* pc, int pcpos)
build.beginBlock(next);
}
static void translateInstBinaryNumeric(IrBuilder& build, int ra, int rb, int rc, IrOp opc, int pcpos, TMS tm)
static void translateInstBinaryNumeric(IrBuilder& build, int ra, int rb, int rc, IrOp opb, IrOp opc, int pcpos, TMS tm)
{
IrOp fallback = build.block(IrBlockKind::Fallback);
IrOp fallback;
BytecodeTypes bcTypes = build.function.getBytecodeTypesAt(pcpos);
// fast-path: number
IrOp tb = build.inst(IrCmd::LOAD_TAG, build.vmReg(rb));
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TNUMBER), fallback);
if (rc != -1 && rc != rb) // TODO: optimization should handle second check, but we'll test it later
if (rb != -1)
{
IrOp tc = build.inst(IrCmd::LOAD_TAG, build.vmReg(rc));
build.inst(IrCmd::CHECK_TAG, tc, build.constTag(LUA_TNUMBER), fallback);
IrOp tb = build.inst(IrCmd::LOAD_TAG, build.vmReg(rb));
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TNUMBER),
bcTypes.a == LBC_TYPE_NUMBER ? build.vmExit(pcpos) : getInitializedFallback(build, fallback));
}
IrOp vb = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(rb));
IrOp vc;
if (rc != -1 && rc != rb)
{
IrOp tc = build.inst(IrCmd::LOAD_TAG, build.vmReg(rc));
build.inst(IrCmd::CHECK_TAG, tc, build.constTag(LUA_TNUMBER),
bcTypes.b == LBC_TYPE_NUMBER ? build.vmExit(pcpos) : getInitializedFallback(build, fallback));
}
IrOp vb, vc;
IrOp result;
if (opb.kind == IrOpKind::VmConst)
{
LUAU_ASSERT(build.function.proto);
TValue protok = build.function.proto->k[vmConstOp(opb)];
LUAU_ASSERT(protok.tt == LUA_TNUMBER);
vb = build.constDouble(protok.value.n);
}
else
{
vb = build.inst(IrCmd::LOAD_DOUBLE, opb);
}
if (opc.kind == IrOpKind::VmConst)
{
LUAU_ASSERT(build.function.proto);
@ -405,22 +430,33 @@ static void translateInstBinaryNumeric(IrBuilder& build, int ra, int rb, int rc,
if (ra != rb && ra != rc) // TODO: optimization should handle second check, but we'll test this later
build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TNUMBER));
IrOp next = build.blockAtInst(pcpos + 1);
FallbackStreamScope scope(build, fallback, next);
if (fallback.kind != IrOpKind::None)
{
IrOp next = build.blockAtInst(pcpos + 1);
FallbackStreamScope scope(build, fallback, next);
build.inst(IrCmd::SET_SAVEDPC, build.constUint(pcpos + 1));
build.inst(IrCmd::DO_ARITH, build.vmReg(ra), build.vmReg(rb), opc, build.constInt(tm));
build.inst(IrCmd::JUMP, next);
build.inst(IrCmd::SET_SAVEDPC, build.constUint(pcpos + 1));
build.inst(IrCmd::DO_ARITH, build.vmReg(ra), opb, opc, build.constInt(tm));
build.inst(IrCmd::JUMP, next);
}
}
void translateInstBinary(IrBuilder& build, const Instruction* pc, int pcpos, TMS tm)
{
translateInstBinaryNumeric(build, LUAU_INSN_A(*pc), LUAU_INSN_B(*pc), LUAU_INSN_C(*pc), build.vmReg(LUAU_INSN_C(*pc)), pcpos, tm);
translateInstBinaryNumeric(
build, LUAU_INSN_A(*pc), LUAU_INSN_B(*pc), LUAU_INSN_C(*pc), build.vmReg(LUAU_INSN_B(*pc)), build.vmReg(LUAU_INSN_C(*pc)), pcpos, tm);
}
void translateInstBinaryK(IrBuilder& build, const Instruction* pc, int pcpos, TMS tm)
{
translateInstBinaryNumeric(build, LUAU_INSN_A(*pc), LUAU_INSN_B(*pc), -1, build.vmConst(LUAU_INSN_C(*pc)), pcpos, tm);
translateInstBinaryNumeric(
build, LUAU_INSN_A(*pc), LUAU_INSN_B(*pc), -1, build.vmReg(LUAU_INSN_B(*pc)), build.vmConst(LUAU_INSN_C(*pc)), pcpos, tm);
}
void translateInstBinaryRK(IrBuilder& build, const Instruction* pc, int pcpos, TMS tm)
{
translateInstBinaryNumeric(
build, LUAU_INSN_A(*pc), -1, LUAU_INSN_C(*pc), build.vmConst(LUAU_INSN_B(*pc)), build.vmReg(LUAU_INSN_C(*pc)), pcpos, tm);
}
void translateInstNot(IrBuilder& build, const Instruction* pc)
@ -439,13 +475,15 @@ void translateInstNot(IrBuilder& build, const Instruction* pc)
void translateInstMinus(IrBuilder& build, const Instruction* pc, int pcpos)
{
IrOp fallback;
BytecodeTypes bcTypes = build.function.getBytecodeTypesAt(pcpos);
int ra = LUAU_INSN_A(*pc);
int rb = LUAU_INSN_B(*pc);
IrOp fallback = build.block(IrBlockKind::Fallback);
IrOp tb = build.inst(IrCmd::LOAD_TAG, build.vmReg(rb));
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TNUMBER), fallback);
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TNUMBER),
bcTypes.a == LBC_TYPE_NUMBER ? build.vmExit(pcpos) : getInitializedFallback(build, fallback));
// fast-path: number
IrOp vb = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(rb));
@ -456,23 +494,29 @@ void translateInstMinus(IrBuilder& build, const Instruction* pc, int pcpos)
if (ra != rb)
build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TNUMBER));
IrOp next = build.blockAtInst(pcpos + 1);
FallbackStreamScope scope(build, fallback, next);
if (fallback.kind != IrOpKind::None)
{
IrOp next = build.blockAtInst(pcpos + 1);
FallbackStreamScope scope(build, fallback, next);
build.inst(IrCmd::SET_SAVEDPC, build.constUint(pcpos + 1));
build.inst(IrCmd::DO_ARITH, build.vmReg(LUAU_INSN_A(*pc)), build.vmReg(LUAU_INSN_B(*pc)), build.vmReg(LUAU_INSN_B(*pc)), build.constInt(TM_UNM));
build.inst(IrCmd::JUMP, next);
build.inst(IrCmd::SET_SAVEDPC, build.constUint(pcpos + 1));
build.inst(
IrCmd::DO_ARITH, build.vmReg(LUAU_INSN_A(*pc)), build.vmReg(LUAU_INSN_B(*pc)), build.vmReg(LUAU_INSN_B(*pc)), build.constInt(TM_UNM));
build.inst(IrCmd::JUMP, next);
}
}
void translateInstLength(IrBuilder& build, const Instruction* pc, int pcpos)
{
BytecodeTypes bcTypes = build.function.getBytecodeTypesAt(pcpos);
int ra = LUAU_INSN_A(*pc);
int rb = LUAU_INSN_B(*pc);
IrOp fallback = build.block(IrBlockKind::Fallback);
IrOp tb = build.inst(IrCmd::LOAD_TAG, build.vmReg(rb));
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TTABLE), fallback);
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TTABLE), bcTypes.a == LBC_TYPE_TABLE ? build.vmExit(pcpos) : fallback);
// fast-path: table without __len
IrOp vb = build.inst(IrCmd::LOAD_POINTER, build.vmReg(rb));
@ -562,7 +606,7 @@ IrOp translateFastCallN(IrBuilder& build, const Instruction* pc, int pcpos, bool
IrOp builtinArgs = args;
if (customArgs.kind == IrOpKind::VmConst && (FFlag::LuauImproveInsertIr || bfid != LBF_TABLE_INSERT))
if (customArgs.kind == IrOpKind::VmConst)
{
LUAU_ASSERT(build.function.proto);
TValue protok = build.function.proto->k[vmConstOp(customArgs)];
@ -633,18 +677,18 @@ static IrOp getLoopStepK(IrBuilder& build, int ra)
return build.undef();
}
void beforeInstForNPrep(IrBuilder& build, const Instruction* pc)
void beforeInstForNPrep(IrBuilder& build, const Instruction* pc, int pcpos)
{
int ra = LUAU_INSN_A(*pc);
IrOp stepK = getLoopStepK(build, ra);
build.loopStepStack.push_back(stepK);
build.numericLoopStack.push_back({stepK, pcpos + 1});
}
void afterInstForNLoop(IrBuilder& build, const Instruction* pc)
{
LUAU_ASSERT(!build.loopStepStack.empty());
build.loopStepStack.pop_back();
LUAU_ASSERT(!build.numericLoopStack.empty());
build.numericLoopStack.pop_back();
}
void translateInstForNPrep(IrBuilder& build, const Instruction* pc, int pcpos)
@ -654,8 +698,8 @@ void translateInstForNPrep(IrBuilder& build, const Instruction* pc, int pcpos)
IrOp loopStart = build.blockAtInst(pcpos + getOpLength(LuauOpcode(LUAU_INSN_OP(*pc))));
IrOp loopExit = build.blockAtInst(getJumpTarget(*pc, pcpos));
LUAU_ASSERT(!build.loopStepStack.empty());
IrOp stepK = build.loopStepStack.back();
LUAU_ASSERT(!build.numericLoopStack.empty());
IrOp stepK = build.numericLoopStack.back().step;
// When loop parameters are not numbers, VM tries to perform type coercion from string and raises an exception if that fails
// Performing that fallback in native code increases code size and complicates CFG, obscuring the values when they are constant
@ -673,35 +717,9 @@ void translateInstForNPrep(IrBuilder& build, const Instruction* pc, int pcpos)
IrOp tagStep = build.inst(IrCmd::LOAD_TAG, build.vmReg(ra + 1));
build.inst(IrCmd::CHECK_TAG, tagStep, build.constTag(LUA_TNUMBER), build.vmExit(pcpos));
if (FFlag::LuauLowerAltLoopForn)
{
IrOp step = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(ra + 1));
IrOp step = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(ra + 1));
build.inst(IrCmd::JUMP_FORN_LOOP_COND, idx, limit, step, loopStart, loopExit);
}
else
{
IrOp direct = build.block(IrBlockKind::Internal);
IrOp reverse = build.block(IrBlockKind::Internal);
IrOp zero = build.constDouble(0.0);
IrOp step = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(ra + 1));
// step > 0
// note: equivalent to 0 < step, but lowers into one instruction on both X64 and A64
build.inst(IrCmd::JUMP_CMP_NUM, step, zero, build.cond(IrCondition::Greater), direct, reverse);
// Condition to start the loop: step > 0 ? idx <= limit : limit <= idx
// We invert the condition so that loopStart is the fallthrough (false) label
// step > 0 is false, check limit <= idx
build.beginBlock(reverse);
build.inst(IrCmd::JUMP_CMP_NUM, limit, idx, build.cond(IrCondition::NotLessEqual), loopExit, loopStart);
// step > 0 is true, check idx <= limit
build.beginBlock(direct);
build.inst(IrCmd::JUMP_CMP_NUM, idx, limit, build.cond(IrCondition::NotLessEqual), loopExit, loopStart);
}
build.inst(IrCmd::JUMP_FORN_LOOP_COND, idx, limit, step, loopStart, loopExit);
}
else
{
@ -729,17 +747,32 @@ void translateInstForNLoop(IrBuilder& build, const Instruction* pc, int pcpos)
{
int ra = LUAU_INSN_A(*pc);
IrOp loopRepeat = build.blockAtInst(getJumpTarget(*pc, pcpos));
int repeatJumpTarget = getJumpTarget(*pc, pcpos);
IrOp loopRepeat = build.blockAtInst(repeatJumpTarget);
IrOp loopExit = build.blockAtInst(pcpos + getOpLength(LuauOpcode(LUAU_INSN_OP(*pc))));
// normally, the interrupt is placed at the beginning of the loop body by FORNPREP translation
// however, there are rare contrived cases where FORNLOOP ends up jumping to itself without an interrupt placed
// we detect this by checking if loopRepeat has any instructions (it should normally start with INTERRUPT) and emit a failsafe INTERRUPT if not
if (build.function.blockOp(loopRepeat).start == build.function.instructions.size())
build.inst(IrCmd::INTERRUPT, build.constUint(pcpos));
LUAU_ASSERT(!build.numericLoopStack.empty());
IrBuilder::LoopInfo loopInfo = build.numericLoopStack.back();
LUAU_ASSERT(!build.loopStepStack.empty());
IrOp stepK = build.loopStepStack.back();
if (FFlag::LuauLoopInterruptFix)
{
// normally, the interrupt is placed at the beginning of the loop body by FORNPREP translation
// however, there are rare cases where FORNLOOP might not jump directly to the first loop instruction
// we detect this by checking the starting instruction of the loop body from loop information stack
if (repeatJumpTarget != loopInfo.startpc)
build.inst(IrCmd::INTERRUPT, build.constUint(pcpos));
}
else
{
// normally, the interrupt is placed at the beginning of the loop body by FORNPREP translation
// however, there are rare contrived cases where FORNLOOP ends up jumping to itself without an interrupt placed
// we detect this by checking if loopRepeat has any instructions (it should normally start with INTERRUPT) and emit a failsafe INTERRUPT if
// not
if (build.function.blockOp(loopRepeat).start == build.function.instructions.size())
build.inst(IrCmd::INTERRUPT, build.constUint(pcpos));
}
IrOp stepK = loopInfo.step;
IrOp limit = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(ra + 0));
IrOp step = stepK.kind == IrOpKind::Undef ? build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(ra + 1)) : stepK;
@ -750,31 +783,7 @@ void translateInstForNLoop(IrBuilder& build, const Instruction* pc, int pcpos)
if (stepK.kind == IrOpKind::Undef)
{
if (FFlag::LuauLowerAltLoopForn)
{
build.inst(IrCmd::JUMP_FORN_LOOP_COND, idx, limit, step, loopRepeat, loopExit);
}
else
{
IrOp direct = build.block(IrBlockKind::Internal);
IrOp reverse = build.block(IrBlockKind::Internal);
IrOp zero = build.constDouble(0.0);
// step > 0
// note: equivalent to 0 < step, but lowers into one instruction on both X64 and A64
build.inst(IrCmd::JUMP_CMP_NUM, step, zero, build.cond(IrCondition::Greater), direct, reverse);
// Condition to continue the loop: step > 0 ? idx <= limit : limit <= idx
// step > 0 is false, check limit <= idx
build.beginBlock(reverse);
build.inst(IrCmd::JUMP_CMP_NUM, limit, idx, build.cond(IrCondition::LessEqual), loopRepeat, loopExit);
// step > 0 is true, check idx <= limit
build.beginBlock(direct);
build.inst(IrCmd::JUMP_CMP_NUM, idx, limit, build.cond(IrCondition::LessEqual), loopRepeat, loopExit);
}
build.inst(IrCmd::JUMP_FORN_LOOP_COND, idx, limit, step, loopRepeat, loopExit);
}
else
{
@ -913,9 +922,10 @@ void translateInstGetTableN(IrBuilder& build, const Instruction* pc, int pcpos)
int c = LUAU_INSN_C(*pc);
IrOp fallback = build.block(IrBlockKind::Fallback);
BytecodeTypes bcTypes = build.function.getBytecodeTypesAt(pcpos);
IrOp tb = build.inst(IrCmd::LOAD_TAG, build.vmReg(rb));
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TTABLE), fallback);
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TTABLE), bcTypes.a == LBC_TYPE_TABLE ? build.vmExit(pcpos) : fallback);
IrOp vb = build.inst(IrCmd::LOAD_POINTER, build.vmReg(rb));
@ -942,9 +952,10 @@ void translateInstSetTableN(IrBuilder& build, const Instruction* pc, int pcpos)
int c = LUAU_INSN_C(*pc);
IrOp fallback = build.block(IrBlockKind::Fallback);
BytecodeTypes bcTypes = build.function.getBytecodeTypesAt(pcpos);
IrOp tb = build.inst(IrCmd::LOAD_TAG, build.vmReg(rb));
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TTABLE), fallback);
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TTABLE), bcTypes.a == LBC_TYPE_TABLE ? build.vmExit(pcpos) : fallback);
IrOp vb = build.inst(IrCmd::LOAD_POINTER, build.vmReg(rb));
@ -974,11 +985,12 @@ void translateInstGetTable(IrBuilder& build, const Instruction* pc, int pcpos)
int rc = LUAU_INSN_C(*pc);
IrOp fallback = build.block(IrBlockKind::Fallback);
BytecodeTypes bcTypes = build.function.getBytecodeTypesAt(pcpos);
IrOp tb = build.inst(IrCmd::LOAD_TAG, build.vmReg(rb));
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TTABLE), fallback);
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TTABLE), bcTypes.a == LBC_TYPE_TABLE ? build.vmExit(pcpos) : fallback);
IrOp tc = build.inst(IrCmd::LOAD_TAG, build.vmReg(rc));
build.inst(IrCmd::CHECK_TAG, tc, build.constTag(LUA_TNUMBER), fallback);
build.inst(IrCmd::CHECK_TAG, tc, build.constTag(LUA_TNUMBER), bcTypes.b == LBC_TYPE_NUMBER ? build.vmExit(pcpos) : fallback);
// fast-path: table with a number index
IrOp vb = build.inst(IrCmd::LOAD_POINTER, build.vmReg(rb));
@ -1011,11 +1023,12 @@ void translateInstSetTable(IrBuilder& build, const Instruction* pc, int pcpos)
int rc = LUAU_INSN_C(*pc);
IrOp fallback = build.block(IrBlockKind::Fallback);
BytecodeTypes bcTypes = build.function.getBytecodeTypesAt(pcpos);
IrOp tb = build.inst(IrCmd::LOAD_TAG, build.vmReg(rb));
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TTABLE), fallback);
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TTABLE), bcTypes.a == LBC_TYPE_TABLE ? build.vmExit(pcpos) : fallback);
IrOp tc = build.inst(IrCmd::LOAD_TAG, build.vmReg(rc));
build.inst(IrCmd::CHECK_TAG, tc, build.constTag(LUA_TNUMBER), fallback);
build.inst(IrCmd::CHECK_TAG, tc, build.constTag(LUA_TNUMBER), bcTypes.b == LBC_TYPE_NUMBER ? build.vmExit(pcpos) : fallback);
// fast-path: table with a number index
IrOp vb = build.inst(IrCmd::LOAD_POINTER, build.vmReg(rb));
@ -1080,9 +1093,10 @@ void translateInstGetTableKS(IrBuilder& build, const Instruction* pc, int pcpos)
uint32_t aux = pc[1];
IrOp fallback = build.block(IrBlockKind::Fallback);
BytecodeTypes bcTypes = build.function.getBytecodeTypesAt(pcpos);
IrOp tb = build.inst(IrCmd::LOAD_TAG, build.vmReg(rb));
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TTABLE), fallback);
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TTABLE), bcTypes.a == LBC_TYPE_TABLE ? build.vmExit(pcpos) : fallback);
IrOp vb = build.inst(IrCmd::LOAD_POINTER, build.vmReg(rb));
@ -1107,9 +1121,10 @@ void translateInstSetTableKS(IrBuilder& build, const Instruction* pc, int pcpos)
uint32_t aux = pc[1];
IrOp fallback = build.block(IrBlockKind::Fallback);
BytecodeTypes bcTypes = build.function.getBytecodeTypesAt(pcpos);
IrOp tb = build.inst(IrCmd::LOAD_TAG, build.vmReg(rb));
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TTABLE), fallback);
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TTABLE), bcTypes.a == LBC_TYPE_TABLE ? build.vmExit(pcpos) : fallback);
IrOp vb = build.inst(IrCmd::LOAD_POINTER, build.vmReg(rb));

View File

@ -35,6 +35,7 @@ void translateInstJumpxEqN(IrBuilder& build, const Instruction* pc, int pcpos);
void translateInstJumpxEqS(IrBuilder& build, const Instruction* pc, int pcpos);
void translateInstBinary(IrBuilder& build, const Instruction* pc, int pcpos, TMS tm);
void translateInstBinaryK(IrBuilder& build, const Instruction* pc, int pcpos, TMS tm);
void translateInstBinaryRK(IrBuilder& build, const Instruction* pc, int pcpos, TMS tm);
void translateInstNot(IrBuilder& build, const Instruction* pc);
void translateInstMinus(IrBuilder& build, const Instruction* pc, int pcpos);
void translateInstLength(IrBuilder& build, const Instruction* pc, int pcpos);
@ -65,7 +66,7 @@ void translateInstAndX(IrBuilder& build, const Instruction* pc, int pcpos, IrOp
void translateInstOrX(IrBuilder& build, const Instruction* pc, int pcpos, IrOp c);
void translateInstNewClosure(IrBuilder& build, const Instruction* pc, int pcpos);
void beforeInstForNPrep(IrBuilder& build, const Instruction* pc);
void beforeInstForNPrep(IrBuilder& build, const Instruction* pc, int pcpos);
void afterInstForNLoop(IrBuilder& build, const Instruction* pc);
} // namespace CodeGen

View File

@ -8,6 +8,8 @@
#include "lua.h"
#include <limits.h>
#include <array>
#include <utility>
#include <vector>
@ -15,9 +17,9 @@
LUAU_FASTINTVARIABLE(LuauCodeGenMinLinearBlockPath, 3)
LUAU_FASTINTVARIABLE(LuauCodeGenReuseSlotLimit, 64)
LUAU_FASTFLAGVARIABLE(DebugLuauAbortingChecks, false)
LUAU_FASTFLAGVARIABLE(LuauReuseArrSlots2, false)
LUAU_FASTFLAG(LuauLowerAltLoopForn)
LUAU_FASTFLAGVARIABLE(LuauCodeGenFixByteLower, false)
LUAU_FASTFLAGVARIABLE(LuauKeepVmapLinear2, false)
LUAU_FASTFLAGVARIABLE(LuauReuseBufferChecks, false)
namespace Luau
{
@ -194,12 +196,19 @@ struct ConstPropState
checkArraySizeCache.clear();
}
void invalidateHeapBufferData()
{
checkBufferLenCache.clear();
}
void invalidateHeap()
{
for (int i = 0; i <= maxReg; ++i)
invalidateHeap(regs[i]);
invalidateHeapTableData();
// Buffer length checks are not invalidated since buffer size is immutable
}
void invalidateHeap(RegisterInfo& reg)
@ -408,6 +417,7 @@ struct ConstPropState
invalidateValuePropagation();
invalidateHeapTableData();
invalidateHeapBufferData();
}
IrFunction& function;
@ -435,6 +445,8 @@ struct ConstPropState
std::vector<uint32_t> getArrAddrCache;
std::vector<uint32_t> checkArraySizeCache; // Additionally, fallback block argument might be different
std::vector<uint32_t> checkBufferLenCache; // Additionally, fallback block argument might be different
};
static void handleBuiltinEffects(ConstPropState& state, LuauBuiltinFunction bfid, uint32_t firstReturnReg, int nresults)
@ -677,7 +689,21 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
if (inst.a.kind == IrOpKind::VmReg || inst.a.kind == IrOpKind::Inst)
{
if (inst.a.kind == IrOpKind::VmReg)
{
if (FFlag::LuauReuseBufferChecks && inst.b.kind == IrOpKind::Inst)
{
if (uint32_t* prevIdx = state.getPreviousVersionedLoadIndex(IrCmd::LOAD_TVALUE, inst.a))
{
if (*prevIdx == inst.b.index)
{
kill(function, inst);
break;
}
}
}
state.invalidate(inst.a);
}
uint8_t tag = state.tryGetTag(inst.b);
IrOp value = state.tryGetValue(inst.b);
@ -921,8 +947,65 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
}
break;
case IrCmd::CHECK_BUFFER_LEN:
// TODO: remove duplicate checks and extend earlier check bound when possible
{
if (!FFlag::LuauReuseBufferChecks)
break;
std::optional<int> bufferOffset = function.asIntOp(inst.b.kind == IrOpKind::Constant ? inst.b : state.tryGetValue(inst.b));
int accessSize = function.intOp(inst.c);
LUAU_ASSERT(accessSize > 0);
if (bufferOffset)
{
// Negative offsets and offsets overflowing signed integer will jump to fallback, no need to keep the check
if (*bufferOffset < 0 || unsigned(*bufferOffset) + unsigned(accessSize) >= unsigned(INT_MAX))
{
replace(function, block, index, {IrCmd::JUMP, inst.d});
break;
}
}
for (uint32_t prevIdx : state.checkBufferLenCache)
{
IrInst& prev = function.instructions[prevIdx];
if (prev.a != inst.a || prev.c != inst.c)
continue;
if (prev.b == inst.b)
{
if (FFlag::DebugLuauAbortingChecks)
replace(function, inst.d, build.undef());
else
kill(function, inst);
return; // Break out from both the loop and the switch
}
else if (inst.b.kind == IrOpKind::Constant && prev.b.kind == IrOpKind::Constant)
{
// If arguments are different constants, we can check if a larger bound was already tested or if the previous bound can be raised
int currBound = function.intOp(inst.b);
int prevBound = function.intOp(prev.b);
// Negative and overflowing constant offsets should already be replaced with unconditional jumps to a fallback
LUAU_ASSERT(currBound >= 0);
LUAU_ASSERT(prevBound >= 0);
if (unsigned(currBound) >= unsigned(prevBound))
replace(function, prev.b, inst.b);
if (FFlag::DebugLuauAbortingChecks)
replace(function, inst.d, build.undef());
else
kill(function, inst);
return; // Break out from both the loop and the switch
}
}
if (int(state.checkBufferLenCache.size()) < FInt::LuauCodeGenReuseSlotLimit)
state.checkBufferLenCache.push_back(index);
break;
}
case IrCmd::BUFFER_READI8:
case IrCmd::BUFFER_READU8:
case IrCmd::BUFFER_WRITEI8:
@ -969,9 +1052,6 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
case IrCmd::LOAD_ENV:
break;
case IrCmd::GET_ARR_ADDR:
if (!FFlag::LuauReuseArrSlots2)
break;
for (uint32_t prevIdx : state.getArrAddrCache)
{
const IrInst& prev = function.instructions[prevIdx];
@ -1039,9 +1119,6 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
case IrCmd::DUP_TABLE:
break;
case IrCmd::TRY_NUM_TO_INDEX:
if (!FFlag::LuauReuseArrSlots2)
break;
for (uint32_t prevIdx : state.tryNumToIndexCache)
{
const IrInst& prev = function.instructions[prevIdx];
@ -1079,7 +1156,7 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
std::optional<int> arrayIndex = function.asIntOp(inst.b.kind == IrOpKind::Constant ? inst.b : state.tryGetValue(inst.b));
// Negative offsets will jump to fallback, no need to keep the check
if (FFlag::LuauReuseArrSlots2 && arrayIndex && *arrayIndex < 0)
if (arrayIndex && *arrayIndex < 0)
{
replace(function, block, index, {IrCmd::JUMP, inst.c});
break;
@ -1105,9 +1182,6 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
}
}
if (!FFlag::LuauReuseArrSlots2)
break;
for (uint32_t prevIdx : state.checkArraySizeCache)
{
const IrInst& prev = function.instructions[prevIdx];
@ -1220,6 +1294,7 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
// TODO: this can be relaxed when x64 emitInstSetList becomes aware of register allocator
state.invalidateValuePropagation();
state.invalidateHeapTableData();
state.invalidateHeapBufferData();
break;
case IrCmd::CALL:
state.invalidateRegistersFrom(vmRegOp(inst.a));
@ -1236,6 +1311,7 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
// TODO: this can be relaxed when x64 emitInstForGLoop becomes aware of register allocator
state.invalidateValuePropagation();
state.invalidateHeapTableData();
state.invalidateHeapBufferData();
break;
case IrCmd::FORGLOOP_FALLBACK:
state.invalidateRegistersFrom(vmRegOp(inst.a) + 2); // Rn and Rn+1 are not modified
@ -1299,11 +1375,15 @@ static void constPropInBlock(IrBuilder& build, IrBlock& block, ConstPropState& s
constPropInInst(state, build, function, block, inst, index);
}
// Value numbering and load/store propagation is not performed between blocks
state.invalidateValuePropagation();
if (!FFlag::LuauKeepVmapLinear2)
{
// Value numbering and load/store propagation is not performed between blocks
state.invalidateValuePropagation();
// Same for table slot data propagation
state.invalidateHeapTableData();
// Same for table and buffer data propagation
state.invalidateHeapTableData();
state.invalidateHeapBufferData();
}
}
static void constPropInBlockChain(IrBuilder& build, std::vector<uint8_t>& visited, IrBlock* block, ConstPropState& state)
@ -1323,6 +1403,16 @@ static void constPropInBlockChain(IrBuilder& build, std::vector<uint8_t>& visite
constPropInBlock(build, *block, state);
if (FFlag::LuauKeepVmapLinear2)
{
// Value numbering and load/store propagation is not performed between blocks
state.invalidateValuePropagation();
// Same for table and buffer data propagation
state.invalidateHeapTableData();
state.invalidateHeapBufferData();
}
// Blocks in a chain are guaranteed to follow each other
// We force that by giving all blocks the same sorting key, but consecutive chain keys
block->sortkey = startSortkey;
@ -1341,7 +1431,7 @@ static void constPropInBlockChain(IrBuilder& build, std::vector<uint8_t>& visite
if (target.useCount == 1 && !visited[targetIdx] && target.kind != IrBlockKind::Fallback)
{
if (FFlag::LuauLowerAltLoopForn && getLiveOutValueCount(function, target) != 0)
if (getLiveOutValueCount(function, target) != 0)
break;
// Make sure block ordering guarantee is checked at lowering time

View File

@ -45,6 +45,7 @@
// Version 2: Adds Proto::linedefined. Supported until 0.544.
// Version 3: Adds FORGPREP/JUMPXEQK* and enhances AUX encoding for FORGLOOP. Removes FORGLOOP_NEXT/INEXT and JUMPIFEQK/JUMPIFNOTEQK. Currently supported.
// Version 4: Adds Proto::flags, typeinfo, and floor division opcodes IDIV/IDIVK. Currently supported.
// Version 5: Adds SUBRK/DIVRK and vector constants. Currently supported.
// Bytecode opcode, part of the instruction header
enum LuauOpcode
@ -70,7 +71,7 @@ enum LuauOpcode
// D: value (-32768..32767)
LOP_LOADN,
// LOADK: sets register to an entry from the constant table from the proto (number/string)
// LOADK: sets register to an entry from the constant table from the proto (number/vector/string)
// A: target register
// D: constant table index (0..32767)
LOP_LOADK,
@ -218,7 +219,7 @@ enum LuauOpcode
// ADDK, SUBK, MULK, DIVK, MODK, POWK: compute arithmetic operation between the source register and a constant and put the result into target register
// A: target register
// B: source register
// C: constant table index (0..255)
// C: constant table index (0..255); must refer to a number
LOP_ADDK,
LOP_SUBK,
LOP_MULK,
@ -347,9 +348,12 @@ enum LuauOpcode
// B: source register (for VAL/REF) or upvalue index (for UPVAL/UPREF)
LOP_CAPTURE,
// removed in v3
LOP_DEP_JUMPIFEQK,
LOP_DEP_JUMPIFNOTEQK,
// SUBRK, DIVRK: compute arithmetic operation between the constant and a source register and put the result into target register
// A: target register
// B: source register
// C: constant table index (0..255); must refer to a number
LOP_SUBRK,
LOP_DIVRK,
// FASTCALL1: perform a fast call of a built-in function using 1 register argument
// A: builtin function id (see LuauBuiltinFunction)
@ -426,7 +430,7 @@ enum LuauBytecodeTag
{
// Bytecode version; runtime supports [MIN, MAX], compiler emits TARGET by default but may emit a higher version when flags are enabled
LBC_VERSION_MIN = 3,
LBC_VERSION_MAX = 4,
LBC_VERSION_MAX = 5,
LBC_VERSION_TARGET = 4,
// Type encoding version
LBC_TYPE_VERSION = 1,
@ -438,6 +442,7 @@ enum LuauBytecodeTag
LBC_CONSTANT_IMPORT,
LBC_CONSTANT_TABLE,
LBC_CONSTANT_CLOSURE,
LBC_CONSTANT_VECTOR,
};
// Type table tags

View File

@ -54,6 +54,7 @@ public:
int32_t addConstantNil();
int32_t addConstantBoolean(bool value);
int32_t addConstantNumber(double value);
int32_t addConstantVector(float x, float y, float z, float w);
int32_t addConstantString(StringRef value);
int32_t addImport(uint32_t iid);
int32_t addConstantTable(const TableShape& shape);
@ -146,6 +147,7 @@ private:
Type_Nil,
Type_Boolean,
Type_Number,
Type_Vector,
Type_String,
Type_Import,
Type_Table,
@ -157,6 +159,7 @@ private:
{
bool valueBoolean;
double valueNumber;
float valueVector[4];
unsigned int valueString; // index into string table
uint32_t valueImport; // 10-10-10-2 encoded import id
uint32_t valueTable; // index into tableShapes[]
@ -167,12 +170,14 @@ private:
struct ConstantKey
{
Constant::Type type;
// Note: this stores value* from Constant; when type is Number_Double, this stores the same bits as double does but in uint64_t.
// Note: this stores value* from Constant; when type is Type_Number, this stores the same bits as double does but in uint64_t.
// For Type_Vector, x and y are stored in 'value' and z and w are stored in 'extra'.
uint64_t value;
uint64_t extra = 0;
bool operator==(const ConstantKey& key) const
{
return type == key.type && value == key.value;
return type == key.type && value == key.value && extra == key.extra;
}
};

View File

@ -5,6 +5,8 @@
#include <math.h>
LUAU_FASTFLAGVARIABLE(LuauVectorLiterals, false)
namespace Luau
{
namespace Compile
@ -32,6 +34,16 @@ static Constant cnum(double v)
return res;
}
static Constant cvector(double x, double y, double z, double w)
{
Constant res = {Constant::Type_Vector};
res.valueVector[0] = (float)x;
res.valueVector[1] = (float)y;
res.valueVector[2] = (float)z;
res.valueVector[3] = (float)w;
return res;
}
static Constant cstring(const char* v)
{
Constant res = {Constant::Type_String};
@ -55,6 +67,9 @@ static Constant ctype(const Constant& c)
case Constant::Type_Number:
return cstring("number");
case Constant::Type_Vector:
return cstring("vector");
case Constant::Type_String:
return cstring("string");
@ -456,6 +471,17 @@ Constant foldBuiltin(int bfid, const Constant* args, size_t count)
if (count == 1 && args[0].type == Constant::Type_Number)
return cnum(round(args[0].valueNumber));
break;
case LBF_VECTOR:
if (FFlag::LuauVectorLiterals && count >= 3 && args[0].type == Constant::Type_Number && args[1].type == Constant::Type_Number &&
args[2].type == Constant::Type_Number)
{
if (count == 3)
return cvector(args[0].valueNumber, args[1].valueNumber, args[2].valueNumber, 0.0);
else if (count == 4 && args[3].type == Constant::Type_Number)
return cvector(args[0].valueNumber, args[1].valueNumber, args[2].valueNumber, args[3].valueNumber);
}
break;
}
return cvar();

View File

@ -7,6 +7,8 @@
#include <algorithm>
#include <string.h>
LUAU_FASTFLAG(LuauVectorLiterals)
LUAU_FASTFLAG(LuauCompileRevK)
namespace Luau
{
@ -41,6 +43,11 @@ static void writeInt(std::string& ss, int value)
ss.append(reinterpret_cast<const char*>(&value), sizeof(value));
}
static void writeFloat(std::string& ss, float value)
{
ss.append(reinterpret_cast<const char*>(&value), sizeof(value));
}
static void writeDouble(std::string& ss, double value)
{
ss.append(reinterpret_cast<const char*>(&value), sizeof(value));
@ -146,23 +153,43 @@ size_t BytecodeBuilder::StringRefHash::operator()(const StringRef& v) const
size_t BytecodeBuilder::ConstantKeyHash::operator()(const ConstantKey& key) const
{
// finalizer from MurmurHash64B
const uint32_t m = 0x5bd1e995;
if (key.type == Constant::Type_Vector)
{
uint32_t i[4];
static_assert(sizeof(key.value) + sizeof(key.extra) == sizeof(i), "Expecting vector to have four 32-bit components");
memcpy(i, &key.value, sizeof(i));
uint32_t h1 = uint32_t(key.value);
uint32_t h2 = uint32_t(key.value >> 32) ^ (key.type * m);
// scramble bits to make sure that integer coordinates have entropy in lower bits
i[0] ^= i[0] >> 17;
i[1] ^= i[1] >> 17;
i[2] ^= i[2] >> 17;
i[3] ^= i[3] >> 17;
h1 ^= h2 >> 18;
h1 *= m;
h2 ^= h1 >> 22;
h2 *= m;
h1 ^= h2 >> 17;
h1 *= m;
h2 ^= h1 >> 19;
h2 *= m;
// Optimized Spatial Hashing for Collision Detection of Deformable Objects
uint32_t h = (i[0] * 73856093) ^ (i[1] * 19349663) ^ (i[2] * 83492791) ^ (i[3] * 39916801);
// ... truncated to 32-bit output (normally hash is equal to (uint64_t(h1) << 32) | h2, but we only really need the lower 32-bit half)
return size_t(h2);
return size_t(h);
}
else
{
// finalizer from MurmurHash64B
const uint32_t m = 0x5bd1e995;
uint32_t h1 = uint32_t(key.value);
uint32_t h2 = uint32_t(key.value >> 32) ^ (key.type * m);
h1 ^= h2 >> 18;
h1 *= m;
h2 ^= h1 >> 22;
h2 *= m;
h1 ^= h2 >> 17;
h1 *= m;
h2 ^= h1 >> 19;
h2 *= m;
// ... truncated to 32-bit output (normally hash is equal to (uint64_t(h1) << 32) | h2, but we only really need the lower 32-bit half)
return size_t(h2);
}
}
size_t BytecodeBuilder::TableShapeHash::operator()(const TableShape& v) const
@ -330,6 +357,25 @@ int32_t BytecodeBuilder::addConstantNumber(double value)
return addConstant(k, c);
}
int32_t BytecodeBuilder::addConstantVector(float x, float y, float z, float w)
{
Constant c = {Constant::Type_Vector};
c.valueVector[0] = x;
c.valueVector[1] = y;
c.valueVector[2] = z;
c.valueVector[3] = w;
ConstantKey k = {Constant::Type_Vector};
static_assert(
sizeof(k.value) == sizeof(x) + sizeof(y) && sizeof(k.extra) == sizeof(z) + sizeof(w), "Expecting vector to have four 32-bit components");
memcpy(&k.value, &x, sizeof(x));
memcpy((char*)&k.value + sizeof(x), &y, sizeof(y));
memcpy(&k.extra, &z, sizeof(z));
memcpy((char*)&k.extra + sizeof(z), &w, sizeof(w));
return addConstant(k, c);
}
int32_t BytecodeBuilder::addConstantString(StringRef value)
{
unsigned int index = addStringTableEntry(value);
@ -647,6 +693,14 @@ void BytecodeBuilder::writeFunction(std::string& ss, uint32_t id, uint8_t flags)
writeDouble(ss, c.valueNumber);
break;
case Constant::Type_Vector:
writeByte(ss, LBC_CONSTANT_VECTOR);
writeFloat(ss, c.valueVector[0]);
writeFloat(ss, c.valueVector[1]);
writeFloat(ss, c.valueVector[2]);
writeFloat(ss, c.valueVector[3]);
break;
case Constant::Type_String:
writeByte(ss, LBC_CONSTANT_STRING);
writeVarInt(ss, c.valueString);
@ -1071,7 +1125,7 @@ std::string BytecodeBuilder::getError(const std::string& message)
uint8_t BytecodeBuilder::getVersion()
{
// This function usually returns LBC_VERSION_TARGET but may sometimes return a higher number (within LBC_VERSION_MIN/MAX) under fast flags
return LBC_VERSION_TARGET;
return (FFlag::LuauVectorLiterals || FFlag::LuauCompileRevK) ? 5 : LBC_VERSION_TARGET;
}
uint8_t BytecodeBuilder::getTypeEncodingVersion()
@ -1299,6 +1353,13 @@ void BytecodeBuilder::validateInstructions() const
VCONST(LUAU_INSN_C(insn), Number);
break;
case LOP_SUBRK:
case LOP_DIVRK:
VREG(LUAU_INSN_A(insn));
VCONST(LUAU_INSN_B(insn), Number);
VREG(LUAU_INSN_C(insn));
break;
case LOP_AND:
case LOP_OR:
VREG(LUAU_INSN_A(insn));
@ -1635,6 +1696,13 @@ void BytecodeBuilder::dumpConstant(std::string& result, int k) const
case Constant::Type_Number:
formatAppend(result, "%.17g", data.valueNumber);
break;
case Constant::Type_Vector:
// 3-vectors is the most common configuration, so truncate to three components if possible
if (data.valueVector[3] == 0.0)
formatAppend(result, "%.9g, %.9g, %.9g", data.valueVector[0], data.valueVector[1], data.valueVector[2]);
else
formatAppend(result, "%.9g, %.9g, %.9g, %.9g", data.valueVector[0], data.valueVector[1], data.valueVector[2], data.valueVector[3]);
break;
case Constant::Type_String:
{
const StringRef& str = debugStrings[data.valueString - 1];
@ -1914,6 +1982,18 @@ void BytecodeBuilder::dumpInstruction(const uint32_t* code, std::string& result,
result.append("]\n");
break;
case LOP_SUBRK:
formatAppend(result, "SUBRK R%d K%d [", LUAU_INSN_A(insn), LUAU_INSN_B(insn));
dumpConstant(result, LUAU_INSN_B(insn));
formatAppend(result, "] R%d\n", LUAU_INSN_C(insn));
break;
case LOP_DIVRK:
formatAppend(result, "DIVRK R%d K%d [", LUAU_INSN_A(insn), LUAU_INSN_B(insn));
dumpConstant(result, LUAU_INSN_B(insn));
formatAppend(result, "] R%d\n", LUAU_INSN_C(insn));
break;
case LOP_AND:
formatAppend(result, "AND R%d R%d R%d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn));
break;

View File

@ -26,8 +26,7 @@ LUAU_FASTINTVARIABLE(LuauCompileInlineThreshold, 25)
LUAU_FASTINTVARIABLE(LuauCompileInlineThresholdMaxBoost, 300)
LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5)
LUAU_FASTFLAGVARIABLE(LuauCompileSideEffects, false)
LUAU_FASTFLAGVARIABLE(LuauCompileDeadIf, false)
LUAU_FASTFLAGVARIABLE(LuauCompileRevK, false)
namespace Luau
{
@ -1094,6 +1093,13 @@ struct Compiler
return cv && cv->type != Constant::Type_Unknown && !cv->isTruthful();
}
bool isConstantVector(AstExpr* node)
{
const Constant* cv = constants.find(node);
return cv && cv->type == Constant::Type_Vector;
}
Constant getConstant(AstExpr* node)
{
const Constant* cv = constants.find(node);
@ -1117,6 +1123,10 @@ struct Compiler
std::swap(left, right);
}
// disable fast path for vectors because supporting it would require a new opcode
if (operandIsConstant && isConstantVector(right))
operandIsConstant = false;
uint8_t rl = compileExprAuto(left, rs);
if (isEq && operandIsConstant)
@ -1226,6 +1236,10 @@ struct Compiler
cid = bytecode.addConstantNumber(c->valueNumber);
break;
case Constant::Type_Vector:
cid = bytecode.addConstantVector(c->valueVector[0], c->valueVector[1], c->valueVector[2], c->valueVector[3]);
break;
case Constant::Type_String:
cid = bytecode.addConstantString(sref(c->getString()));
break;
@ -1501,6 +1515,20 @@ struct Compiler
}
else
{
if (FFlag::LuauCompileRevK && (expr->op == AstExprBinary::Sub || expr->op == AstExprBinary::Div))
{
int32_t lc = getConstantNumber(expr->left);
if (lc >= 0 && lc <= 255)
{
uint8_t rr = compileExprAuto(expr->right, rs);
LuauOpcode op = (expr->op == AstExprBinary::Sub) ? LOP_SUBRK : LOP_DIVRK;
bytecode.emitABC(op, target, uint8_t(lc), uint8_t(rr));
return;
}
}
uint8_t rl = compileExprAuto(expr->left, rs);
uint8_t rr = compileExprAuto(expr->right, rs);
@ -2052,6 +2080,13 @@ struct Compiler
}
break;
case Constant::Type_Vector:
{
int32_t cid = bytecode.addConstantVector(cv->valueVector[0], cv->valueVector[1], cv->valueVector[2], cv->valueVector[3]);
emitLoadK(target, cid);
}
break;
case Constant::Type_String:
{
int32_t cid = bytecode.addConstantString(sref(cv->getString()));
@ -2207,16 +2242,13 @@ struct Compiler
void compileExprSide(AstExpr* node)
{
if (FFlag::LuauCompileSideEffects)
{
// Optimization: some expressions never carry side effects so we don't need to emit any code
if (node->is<AstExprLocal>() || node->is<AstExprGlobal>() || node->is<AstExprVarargs>() || node->is<AstExprFunction>() || isConstant(node))
return;
// Optimization: some expressions never carry side effects so we don't need to emit any code
if (node->is<AstExprLocal>() || node->is<AstExprGlobal>() || node->is<AstExprVarargs>() || node->is<AstExprFunction>() || isConstant(node))
return;
// note: the remark is omitted for calls as it's fairly noisy due to inlining
if (!node->is<AstExprCall>())
bytecode.addDebugRemark("expression only compiled for side effects");
}
// note: the remark is omitted for calls as it's fairly noisy due to inlining
if (!node->is<AstExprCall>())
bytecode.addDebugRemark("expression only compiled for side effects");
RegScope rsi(this);
compileExprAuto(node, rsi);
@ -2506,15 +2538,12 @@ struct Compiler
}
// Optimization: condition is always false but isn't a constant => we only need the else body and condition's side effects
if (FFlag::LuauCompileDeadIf)
if (AstExprBinary* cand = stat->condition->as<AstExprBinary>(); cand && cand->op == AstExprBinary::And && isConstantFalse(cand->right))
{
if (AstExprBinary* cand = stat->condition->as<AstExprBinary>(); cand && cand->op == AstExprBinary::And && isConstantFalse(cand->right))
{
compileExprSide(cand->left);
if (stat->elsebody)
compileStat(stat->elsebody);
return;
}
compileExprSide(cand->left);
if (stat->elsebody)
compileStat(stat->elsebody);
return;
}
// Optimization: body is a "break" statement with no "else" => we can directly break out of the loop in "then" case

View File

@ -26,6 +26,10 @@ static bool constantsEqual(const Constant& la, const Constant& ra)
case Constant::Type_Number:
return ra.type == Constant::Type_Number && la.valueNumber == ra.valueNumber;
case Constant::Type_Vector:
return ra.type == Constant::Type_Vector && la.valueVector[0] == ra.valueVector[0] && la.valueVector[1] == ra.valueVector[1] &&
la.valueVector[2] == ra.valueVector[2] && la.valueVector[3] == ra.valueVector[3];
case Constant::Type_String:
return ra.type == Constant::Type_String && la.stringLength == ra.stringLength && memcmp(la.valueString, ra.valueString, la.stringLength) == 0;

View File

@ -16,6 +16,7 @@ struct Constant
Type_Nil,
Type_Boolean,
Type_Number,
Type_Vector,
Type_String,
};
@ -26,6 +27,7 @@ struct Constant
{
bool valueBoolean;
double valueNumber;
float valueVector[4];
const char* valueString = nullptr; // length stored in stringLength
};

View File

@ -4,8 +4,9 @@
#include "Luau/LinterConfig.h"
#include "Luau/ParseOptions.h"
#include <string>
#include <optional>
#include <string>
#include <unordered_map>
#include <vector>
namespace Luau
@ -30,6 +31,9 @@ struct Config
bool typeErrors = true;
std::vector<std::string> globals;
std::vector<std::string> paths;
std::unordered_map<std::string, std::string> aliases;
};
struct ConfigResolver
@ -50,6 +54,8 @@ std::optional<std::string> parseModeString(Mode& mode, const std::string& modeSt
std::optional<std::string> parseLintRuleString(
LintOptions& enabledLints, LintOptions& fatalLints, const std::string& warningName, const std::string& value, bool compat = false);
bool isValidAlias(const std::string& alias);
std::optional<std::string> parseConfig(const std::string& contents, Config& config, bool compat = false);
} // namespace Luau

View File

@ -3,6 +3,8 @@
#include "Luau/Lexer.h"
#include "Luau/StringUtils.h"
#include <algorithm>
#include <unordered_map>
namespace Luau
{
@ -107,6 +109,42 @@ Error parseLintRuleString(LintOptions& enabledLints, LintOptions& fatalLints, co
return std::nullopt;
}
bool isValidAlias(const std::string& alias)
{
if (alias.empty())
return false;
bool aliasIsNotAPath = alias != "." && alias != ".." && alias.find_first_of("\\/") == std::string::npos;
if (!aliasIsNotAPath)
return false;
for (char ch : alias)
{
bool isupper = 'A' <= ch && ch <= 'Z';
bool islower = 'a' <= ch && ch <= 'z';
bool isdigit = '0' <= ch && ch <= '9';
if (!isupper && !islower && !isdigit && ch != '-' && ch != '_' && ch != '.')
return false;
}
return true;
}
Error parseAlias(std::unordered_map<std::string, std::string>& aliases, std::string aliasKey, const std::string& aliasValue)
{
if (!isValidAlias(aliasKey))
return Error{"Invalid alias " + aliasKey};
std::transform(aliasKey.begin(), aliasKey.end(), aliasKey.begin(), [](unsigned char c) {
return ('A' <= c && c <= 'Z') ? (c + ('a' - 'A')) : c;
});
if (!aliases.count(aliasKey))
aliases[std::move(aliasKey)] = aliasValue;
return std::nullopt;
}
static void next(Lexer& lexer)
{
lexer.next();
@ -253,6 +291,13 @@ Error parseConfig(const std::string& contents, Config& config, bool compat)
config.globals.push_back(value);
return std::nullopt;
}
else if (keys.size() == 1 && keys[0] == "paths")
{
config.paths.push_back(value);
return std::nullopt;
}
else if (keys.size() == 2 && keys[0] == "aliases")
return parseAlias(config.aliases, keys[1], value);
else if (compat && keys.size() == 2 && keys[0] == "language" && keys[1] == "mode")
return parseModeString(config.mode, value, compat);
else

View File

@ -38,11 +38,11 @@ ISOCLINE_SOURCES=extern/isocline/src/isocline.c
ISOCLINE_OBJECTS=$(ISOCLINE_SOURCES:%=$(BUILD)/%.o)
ISOCLINE_TARGET=$(BUILD)/libisocline.a
TESTS_SOURCES=$(wildcard tests/*.cpp) CLI/FileUtils.cpp CLI/Flags.cpp CLI/Profiler.cpp CLI/Coverage.cpp CLI/Repl.cpp
TESTS_SOURCES=$(wildcard tests/*.cpp) CLI/FileUtils.cpp CLI/Flags.cpp CLI/Profiler.cpp CLI/Coverage.cpp CLI/Repl.cpp CLI/Require.cpp
TESTS_OBJECTS=$(TESTS_SOURCES:%=$(BUILD)/%.o)
TESTS_TARGET=$(BUILD)/luau-tests
REPL_CLI_SOURCES=CLI/FileUtils.cpp CLI/Flags.cpp CLI/Profiler.cpp CLI/Coverage.cpp CLI/Repl.cpp CLI/ReplEntry.cpp
REPL_CLI_SOURCES=CLI/FileUtils.cpp CLI/Flags.cpp CLI/Profiler.cpp CLI/Coverage.cpp CLI/Repl.cpp CLI/ReplEntry.cpp CLI/Require.cpp
REPL_CLI_OBJECTS=$(REPL_CLI_SOURCES:%=$(BUILD)/%.o)
REPL_CLI_TARGET=$(BUILD)/luau
@ -219,7 +219,7 @@ luau-tests: $(TESTS_TARGET)
# executable targets
$(TESTS_TARGET): $(TESTS_OBJECTS) $(ANALYSIS_TARGET) $(COMPILER_TARGET) $(CONFIG_TARGET) $(AST_TARGET) $(CODEGEN_TARGET) $(VM_TARGET) $(ISOCLINE_TARGET)
$(REPL_CLI_TARGET): $(REPL_CLI_OBJECTS) $(COMPILER_TARGET) $(AST_TARGET) $(CODEGEN_TARGET) $(VM_TARGET) $(ISOCLINE_TARGET)
$(REPL_CLI_TARGET): $(REPL_CLI_OBJECTS) $(COMPILER_TARGET) $(CONFIG_TARGET) $(AST_TARGET) $(CODEGEN_TARGET) $(VM_TARGET) $(ISOCLINE_TARGET)
$(ANALYZE_CLI_TARGET): $(ANALYZE_CLI_OBJECTS) $(ANALYSIS_TARGET) $(AST_TARGET) $(CONFIG_TARGET)
$(COMPILE_CLI_TARGET): $(COMPILE_CLI_OBJECTS) $(COMPILER_TARGET) $(AST_TARGET) $(CODEGEN_TARGET) $(VM_TARGET)
$(BYTECODE_CLI_TARGET): $(BYTECODE_CLI_OBJECTS) $(COMPILER_TARGET) $(AST_TARGET) $(CODEGEN_TARGET) $(VM_TARGET)

View File

@ -92,6 +92,7 @@ target_sources(Luau.CodeGen PRIVATE
CodeGen/include/Luau/UnwindBuilder.h
CodeGen/include/Luau/UnwindBuilderDwarf2.h
CodeGen/include/Luau/UnwindBuilderWin.h
CodeGen/include/Luau/BytecodeAnalysis.h
CodeGen/include/Luau/BytecodeSummary.h
CodeGen/include/luacodegen.h
@ -125,6 +126,7 @@ target_sources(Luau.CodeGen PRIVATE
CodeGen/src/OptimizeFinalX64.cpp
CodeGen/src/UnwindBuilderDwarf2.cpp
CodeGen/src/UnwindBuilderWin.cpp
CodeGen/src/BytecodeAnalysis.cpp
CodeGen/src/BytecodeSummary.cpp
CodeGen/src/BitUtils.h
@ -349,7 +351,8 @@ if(TARGET Luau.Repl.CLI)
CLI/Profiler.h
CLI/Profiler.cpp
CLI/Repl.cpp
CLI/ReplEntry.cpp)
CLI/ReplEntry.cpp
CLI/Require.cpp)
endif()
if(TARGET Luau.Analyze.CLI)
@ -455,7 +458,7 @@ if(TARGET Luau.UnitTest)
tests/TypeInfer.tables.test.cpp
tests/TypeInfer.test.cpp
tests/TypeInfer.tryUnify.test.cpp
tests/TypeInfer.typePacks.cpp
tests/TypeInfer.typePacks.test.cpp
tests/TypeInfer.typestates.test.cpp
tests/TypeInfer.unionTypes.test.cpp
tests/TypeInfer.unknownnever.test.cpp
@ -489,10 +492,12 @@ if(TARGET Luau.CLI.Test)
CLI/Profiler.h
CLI/Profiler.cpp
CLI/Repl.cpp
CLI/Require.cpp
tests/RegisterCallbacks.h
tests/RegisterCallbacks.cpp
tests/Repl.test.cpp
tests/RequireByString.test.cpp
tests/main.cpp)
endif()

View File

@ -101,7 +101,7 @@
VM_DISPATCH_OP(LOP_FORGLOOP), VM_DISPATCH_OP(LOP_FORGPREP_INEXT), VM_DISPATCH_OP(LOP_DEP_FORGLOOP_INEXT), VM_DISPATCH_OP(LOP_FORGPREP_NEXT), \
VM_DISPATCH_OP(LOP_NATIVECALL), VM_DISPATCH_OP(LOP_GETVARARGS), VM_DISPATCH_OP(LOP_DUPCLOSURE), VM_DISPATCH_OP(LOP_PREPVARARGS), \
VM_DISPATCH_OP(LOP_LOADKX), VM_DISPATCH_OP(LOP_JUMPX), VM_DISPATCH_OP(LOP_FASTCALL), VM_DISPATCH_OP(LOP_COVERAGE), \
VM_DISPATCH_OP(LOP_CAPTURE), VM_DISPATCH_OP(LOP_DEP_JUMPIFEQK), VM_DISPATCH_OP(LOP_DEP_JUMPIFNOTEQK), VM_DISPATCH_OP(LOP_FASTCALL1), \
VM_DISPATCH_OP(LOP_CAPTURE), VM_DISPATCH_OP(LOP_SUBRK), VM_DISPATCH_OP(LOP_DIVRK), VM_DISPATCH_OP(LOP_FASTCALL1), \
VM_DISPATCH_OP(LOP_FASTCALL2), VM_DISPATCH_OP(LOP_FASTCALL2K), VM_DISPATCH_OP(LOP_FORGPREP), VM_DISPATCH_OP(LOP_JUMPXEQKNIL), \
VM_DISPATCH_OP(LOP_JUMPXEQKB), VM_DISPATCH_OP(LOP_JUMPXEQKN), VM_DISPATCH_OP(LOP_JUMPXEQKS), VM_DISPATCH_OP(LOP_IDIV), \
VM_DISPATCH_OP(LOP_IDIVK),
@ -521,7 +521,7 @@ reentry:
if (unsigned(ic) < LUA_VECTOR_SIZE && name[1] == '\0')
{
const float* v = rb->value.v; // silences ubsan when indexing v[]
const float* v = vvalue(rb); // silences ubsan when indexing v[]
setnvalue(ra, v[ic]);
VM_NEXT();
}
@ -1464,8 +1464,8 @@ reentry:
}
else if (ttisvector(rb) && ttisvector(rc))
{
const float* vb = rb->value.v;
const float* vc = rc->value.v;
const float* vb = vvalue(rb);
const float* vc = vvalue(rc);
setvvalue(ra, vb[0] + vc[0], vb[1] + vc[1], vb[2] + vc[2], vb[3] + vc[3]);
VM_NEXT();
}
@ -1510,8 +1510,8 @@ reentry:
}
else if (ttisvector(rb) && ttisvector(rc))
{
const float* vb = rb->value.v;
const float* vc = rc->value.v;
const float* vb = vvalue(rb);
const float* vc = vvalue(rc);
setvvalue(ra, vb[0] - vc[0], vb[1] - vc[1], vb[2] - vc[2], vb[3] - vc[3]);
VM_NEXT();
}
@ -1556,22 +1556,22 @@ reentry:
}
else if (ttisvector(rb) && ttisnumber(rc))
{
const float* vb = rb->value.v;
const float* vb = vvalue(rb);
float vc = cast_to(float, nvalue(rc));
setvvalue(ra, vb[0] * vc, vb[1] * vc, vb[2] * vc, vb[3] * vc);
VM_NEXT();
}
else if (ttisvector(rb) && ttisvector(rc))
{
const float* vb = rb->value.v;
const float* vc = rc->value.v;
const float* vb = vvalue(rb);
const float* vc = vvalue(rc);
setvvalue(ra, vb[0] * vc[0], vb[1] * vc[1], vb[2] * vc[2], vb[3] * vc[3]);
VM_NEXT();
}
else if (ttisnumber(rb) && ttisvector(rc))
{
float vb = cast_to(float, nvalue(rb));
const float* vc = rc->value.v;
const float* vc = vvalue(rc);
setvvalue(ra, vb * vc[0], vb * vc[1], vb * vc[2], vb * vc[3]);
VM_NEXT();
}
@ -1617,22 +1617,22 @@ reentry:
}
else if (ttisvector(rb) && ttisnumber(rc))
{
const float* vb = rb->value.v;
const float* vb = vvalue(rb);
float vc = cast_to(float, nvalue(rc));
setvvalue(ra, vb[0] / vc, vb[1] / vc, vb[2] / vc, vb[3] / vc);
VM_NEXT();
}
else if (ttisvector(rb) && ttisvector(rc))
{
const float* vb = rb->value.v;
const float* vc = rc->value.v;
const float* vb = vvalue(rb);
const float* vc = vvalue(rc);
setvvalue(ra, vb[0] / vc[0], vb[1] / vc[1], vb[2] / vc[2], vb[3] / vc[3]);
VM_NEXT();
}
else if (ttisnumber(rb) && ttisvector(rc))
{
float vb = cast_to(float, nvalue(rb));
const float* vc = rc->value.v;
const float* vc = vvalue(rc);
setvvalue(ra, vb / vc[0], vb / vc[1], vb / vc[2], vb / vc[3]);
VM_NEXT();
}
@ -1812,7 +1812,7 @@ reentry:
}
else if (ttisvector(rb))
{
const float* vb = rb->value.v;
const float* vb = vvalue(rb);
float vc = cast_to(float, nvalue(kv));
setvvalue(ra, vb[0] * vc, vb[1] * vc, vb[2] * vc, vb[3] * vc);
VM_NEXT();
@ -1858,9 +1858,9 @@ reentry:
}
else if (ttisvector(rb))
{
const float* vb = rb->value.v;
float vc = cast_to(float, nvalue(kv));
setvvalue(ra, vb[0] / vc, vb[1] / vc, vb[2] / vc, vb[3] / vc);
const float* vb = vvalue(rb);
float nc = cast_to(float, nvalue(kv));
setvvalue(ra, vb[0] / nc, vb[1] / nc, vb[2] / nc, vb[3] / nc);
VM_NEXT();
}
else
@ -2071,7 +2071,7 @@ reentry:
}
else if (ttisvector(rb))
{
const float* vb = rb->value.v;
const float* vb = vvalue(rb);
setvvalue(ra, -vb[0], -vb[1], -vb[2], -vb[3]);
VM_NEXT();
}
@ -2697,16 +2697,53 @@ reentry:
LUAU_UNREACHABLE();
}
VM_CASE(LOP_DEP_JUMPIFEQK)
VM_CASE(LOP_SUBRK)
{
LUAU_ASSERT(!"Unsupported deprecated opcode");
LUAU_UNREACHABLE();
Instruction insn = *pc++;
StkId ra = VM_REG(LUAU_INSN_A(insn));
TValue* kv = VM_KV(LUAU_INSN_B(insn));
StkId rc = VM_REG(LUAU_INSN_C(insn));
// fast-path
if (ttisnumber(rc))
{
setnvalue(ra, nvalue(kv) - nvalue(rc));
VM_NEXT();
}
else
{
// slow-path, may invoke C/Lua via metamethods
VM_PROTECT(luaV_doarith(L, ra, kv, rc, TM_SUB));
VM_NEXT();
}
}
VM_CASE(LOP_DEP_JUMPIFNOTEQK)
VM_CASE(LOP_DIVRK)
{
LUAU_ASSERT(!"Unsupported deprecated opcode");
LUAU_UNREACHABLE();
Instruction insn = *pc++;
StkId ra = VM_REG(LUAU_INSN_A(insn));
TValue* kv = VM_KV(LUAU_INSN_B(insn));
StkId rc = VM_REG(LUAU_INSN_C(insn));
// fast-path
if (LUAU_LIKELY(ttisnumber(rc)))
{
setnvalue(ra, nvalue(kv) / nvalue(rc));
VM_NEXT();
}
else if (ttisvector(rc))
{
float nb = cast_to(float, nvalue(kv));
const float* vc = vvalue(rc);
setvvalue(ra, nb / vc[0], nb / vc[1], nb / vc[2], nb / vc[3]);
VM_NEXT();
}
else
{
// slow-path, may invoke C/Lua via metamethods
VM_PROTECT(luaV_doarith(L, ra, kv, rc, TM_DIV));
VM_NEXT();
}
}
VM_CASE(LOP_FASTCALL1)

View File

@ -287,6 +287,17 @@ int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size
break;
}
case LBC_CONSTANT_VECTOR:
{
float x = read<float>(data, size, offset);
float y = read<float>(data, size, offset);
float z = read<float>(data, size, offset);
float w = read<float>(data, size, offset);
(void)w;
setvvalue(&p->k[j], x, y, z, w);
break;
}
case LBC_CONSTANT_STRING:
{
TString* v = readString(strings, data, size, offset);

View File

@ -46,7 +46,7 @@ int luaV_tostring(lua_State* L, StkId obj)
const float* luaV_tovector(const TValue* obj)
{
if (ttisvector(obj))
return obj->value.v;
return vvalue(obj);
return nullptr;
}

105
fuzz/CMakeLists.txt Normal file
View File

@ -0,0 +1,105 @@
# This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
cmake_minimum_required(VERSION 3.26)
include(FetchContent)
cmake_policy(SET CMP0054 NEW)
cmake_policy(SET CMP0058 NEW)
cmake_policy(SET CMP0074 NEW)
cmake_policy(SET CMP0077 NEW)
cmake_policy(SET CMP0091 NEW)
if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
message(WARNING "Building the Luau fuzzer requires Clang to be used. AppleClang is not sufficient.")
return()
endif()
if(NOT CMAKE_OSX_ARCHITECTURES STREQUAL "x86_64")
message(WARNING "Building the Luau fuzzer for ARM64 is currently unsupported.")
return()
endif()
# protobuf / std integer types vary based on platform; disable sign-compare
# warnings for portability.
set(FUZZ_COMPILE_OPTIONS ${LUAU_OPTIONS} -fsanitize=address,fuzzer -g2 -Wno-sign-compare)
set(FUZZ_LINK_OPTIONS ${LUAU_OPTIONS} -fsanitize=address,fuzzer)
FetchContent_Declare(
ProtobufMutator
GIT_REPOSITORY https://github.com/google/libprotobuf-mutator
GIT_TAG 212a7be1eb08e7f9c79732d2aab9b2097085d936
# libprotobuf-mutator unconditionally configures its examples, but this
# doesn't actually work with how we're building Protobuf from source. This
# patch disables configuration of the examples.
PATCH_COMMAND
git apply
--reverse
--check
--ignore-space-change
--ignore-whitespace
"${CMAKE_CURRENT_SOURCE_DIR}/libprotobuf-mutator-patch.patch"
||
git apply
--ignore-space-change
--ignore-whitespace
"${CMAKE_CURRENT_SOURCE_DIR}/libprotobuf-mutator-patch.patch"
)
FetchContent_Declare(
Protobuf
GIT_REPOSITORY https://github.com/protocolbuffers/protobuf.git
# Needs to match the Protobuf version that libprotobuf-mutator is written for, roughly.
GIT_TAG v22.3
GIT_SHALLOW ON
# libprotobuf-mutator will need to be able to find this at configuration
# time.
OVERRIDE_FIND_PACKAGE
)
set(protobuf_BUILD_TESTS OFF)
set(protobuf_BUILD_SHARED_LIBS OFF)
# libprotobuf-mutator relies on older module support.
set(protobuf_MODULE_COMPATIBLE ON)
find_package(Protobuf CONFIG REQUIRED)
# libprotobuf-mutator happily ignores CMP0077 because of its minimum version
# requirement. To override that, we set the policy default here.
set(CMAKE_POLICY_DEFAULT_CMP0077 NEW)
set(LIB_PROTO_MUTATOR_TESTING OFF)
FetchContent_MakeAvailable(ProtobufMutator)
# This patches around the fact that find_package isn't going to set the right
# values for libprotobuf-mutator to link against protobuf libraries.
target_link_libraries(protobuf-mutator-libfuzzer protobuf::libprotobuf)
target_link_libraries(protobuf-mutator protobuf::libprotobuf)
set(LUAU_PB_DIR ${CMAKE_CURRENT_BINARY_DIR}/protobuf)
set(LUAU_PB_SOURCES ${LUAU_PB_DIR}/luau.pb.cc ${LUAU_PB_DIR}/luau.pb.h)
add_custom_command(
OUTPUT ${LUAU_PB_SOURCES}
COMMAND ${CMAKE_COMMAND} -E make_directory ${LUAU_PB_DIR}
COMMAND $<TARGET_FILE:protobuf::protoc> ${CMAKE_CURRENT_SOURCE_DIR}/luau.proto --proto_path=${CMAKE_CURRENT_SOURCE_DIR} --cpp_out=${LUAU_PB_DIR}
DEPENDS protobuf::protoc ${CMAKE_CURRENT_SOURCE_DIR}/luau.proto
)
add_executable(Luau.Fuzz.Proto)
target_compile_options(Luau.Fuzz.Proto PRIVATE ${FUZZ_COMPILE_OPTIONS})
target_link_options(Luau.Fuzz.Proto PRIVATE ${FUZZ_LINK_OPTIONS})
target_compile_features(Luau.Fuzz.Proto PRIVATE cxx_std_17)
target_include_directories(Luau.Fuzz.Proto PRIVATE ${LUAU_PB_DIR} ${protobufmutator_SOURCE_DIR})
target_sources(Luau.Fuzz.Proto PRIVATE ${LUAU_PB_SOURCES} proto.cpp protoprint.cpp)
target_link_libraries(Luau.Fuzz.Proto PRIVATE protobuf::libprotobuf protobuf-mutator-libfuzzer protobuf-mutator Luau.Analysis Luau.Compiler Luau.Ast Luau.Config Luau.VM Luau.CodeGen)
set_target_properties(Luau.Fuzz.Proto PROPERTIES CXX_STANDARD_REQUIRED ON CXX_EXTENSIONS OFF OUTPUT_NAME fuzz-proto)
add_executable(Luau.Fuzz.ProtoTest)
target_compile_options(Luau.Fuzz.ProtoTest PRIVATE ${FUZZ_COMPILE_OPTIONS})
target_link_options(Luau.Fuzz.ProtoTest PRIVATE ${FUZZ_LINK_OPTIONS})
target_compile_features(Luau.Fuzz.ProtoTest PRIVATE cxx_std_17)
target_include_directories(Luau.Fuzz.ProtoTest PRIVATE ${LUAU_PB_DIR} ${protobufmutator_SOURCE_DIR})
target_sources(Luau.Fuzz.ProtoTest PRIVATE ${LUAU_PB_SOURCES} prototest.cpp protoprint.cpp)
target_link_libraries(Luau.Fuzz.ProtoTest PRIVATE protobuf::libprotobuf protobuf-mutator-libfuzzer protobuf-mutator)
set_target_properties(Luau.Fuzz.ProtoTest PROPERTIES CXX_STANDARD_REQUIRED ON CXX_EXTENSIONS OFF OUTPUT_NAME fuzz-prototest)

View File

@ -0,0 +1,12 @@
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 4805c82..9f0df5c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -149,7 +149,6 @@ add_subdirectory(src)
if (NOT "${LIB_PROTO_MUTATOR_FUZZER_LIBRARIES}" STREQUAL "" OR
NOT "${FUZZING_FLAGS}" STREQUAL "")
- add_subdirectory(examples EXCLUDE_FROM_ALL)
endif()
install(EXPORT libprotobuf-mutatorTargets FILE libprotobuf-mutatorTargets.cmake

View File

@ -11,12 +11,14 @@
using namespace Luau;
LUAU_FASTFLAG(LuauClipExtraHasEndProps);
struct JsonEncoderFixture
{
Allocator allocator;
AstNameTable names{allocator};
ScopedFastFlag sff{"LuauClipExtraHasEndProps", true};
ScopedFastFlag sff{FFlag::LuauClipExtraHasEndProps, true};
ParseResult parse(std::string_view src)
{
@ -93,7 +95,7 @@ TEST_CASE("basic_escaping")
TEST_CASE("encode_AstStatBlock")
{
ScopedFastFlag sff{"LuauClipExtraHasEndProps", true};
ScopedFastFlag sff{FFlag::LuauClipExtraHasEndProps, true};
AstLocal astlocal{AstName{"a_local"}, Location(), nullptr, 0, 0, nullptr};
AstLocal* astlocalarray[] = {&astlocal};

View File

@ -15,6 +15,8 @@
LUAU_FASTFLAG(LuauTraceTypesInNonstrictMode2)
LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel)
LUAU_FASTFLAG(LuauAutocompleteStringLiteralBounds);
LUAU_FASTFLAG(LuauAutocompleteDoEnd);
using namespace Luau;
@ -978,7 +980,7 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_end_with_lambda")
TEST_CASE_FIXTURE(ACFixture, "autocomplete_end_of_do_block")
{
ScopedFastFlag sff{"LuauAutocompleteDoEnd", true};
ScopedFastFlag sff{FFlag::LuauAutocompleteDoEnd, true};
check("do @1");
@ -3105,7 +3107,7 @@ TEST_CASE_FIXTURE(ACFixture, "string_singleton_as_table_key")
// https://github.com/Roblox/luau/issues/858
TEST_CASE_FIXTURE(ACFixture, "string_singleton_in_if_statement")
{
ScopedFastFlag sff{"LuauAutocompleteStringLiteralBounds", true};
ScopedFastFlag sff{FFlag::LuauAutocompleteStringLiteralBounds, true};
check(R"(
--!strict

View File

@ -15,14 +15,28 @@ namespace Luau
std::string rep(const std::string& s, size_t n);
}
LUAU_FASTFLAG(LuauVectorLiterals)
LUAU_FASTFLAG(LuauCompileRevK)
LUAU_FASTINT(LuauCompileInlineDepth)
LUAU_FASTINT(LuauCompileInlineThreshold)
LUAU_FASTINT(LuauCompileInlineThresholdMaxBoost)
LUAU_FASTINT(LuauCompileLoopUnrollThreshold)
LUAU_FASTINT(LuauCompileLoopUnrollThresholdMaxBoost)
LUAU_FASTINT(LuauRecursionLimit)
using namespace Luau;
static std::string compileFunction(const char* source, uint32_t id, int optimizationLevel = 1)
static std::string compileFunction(const char* source, uint32_t id, int optimizationLevel = 1, bool enableVectors = false)
{
Luau::BytecodeBuilder bcb;
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code);
Luau::CompileOptions options;
options.optimizationLevel = optimizationLevel;
if (enableVectors)
{
options.vectorLib = "Vector3";
options.vectorCtor = "new";
}
Luau::compileOrThrow(bcb, source, options);
return bcb.dumpFunction(id);
@ -1168,6 +1182,8 @@ RETURN R0 1
TEST_CASE("AndOrChainCodegen")
{
ScopedFastFlag sff(FFlag::LuauCompileRevK, true);
const char* source = R"(
return
(1 - verticalGradientTurbulence < waterLevel + .015 and Enum.Material.Sand)
@ -1176,23 +1192,22 @@ TEST_CASE("AndOrChainCodegen")
)";
CHECK_EQ("\n" + compileFunction0(source), R"(
LOADN R2 1
GETIMPORT R3 1 [verticalGradientTurbulence]
SUB R1 R2 R3
GETIMPORT R3 4 [waterLevel]
ADDK R2 R3 K2 [0.014999999999999999]
GETIMPORT R2 2 [verticalGradientTurbulence]
SUBRK R1 K0 [1] R2
GETIMPORT R3 5 [waterLevel]
ADDK R2 R3 K3 [0.014999999999999999]
JUMPIFNOTLT R1 R2 L0
GETIMPORT R0 8 [Enum.Material.Sand]
GETIMPORT R0 9 [Enum.Material.Sand]
JUMPIF R0 L2
L0: GETIMPORT R1 10 [sandbank]
L0: GETIMPORT R1 11 [sandbank]
LOADN R2 0
JUMPIFNOTLT R2 R1 L1
GETIMPORT R1 10 [sandbank]
GETIMPORT R1 11 [sandbank]
LOADN R2 1
JUMPIFNOTLT R1 R2 L1
GETIMPORT R0 8 [Enum.Material.Sand]
GETIMPORT R0 9 [Enum.Material.Sand]
JUMPIF R0 L2
L1: GETIMPORT R0 12 [Enum.Material.Sandstone]
L1: GETIMPORT R0 13 [Enum.Material.Sandstone]
L2: RETURN R0 1
)");
}
@ -1980,7 +1995,7 @@ RETURN R0 0
TEST_CASE("LoopContinueCorrectlyHandlesImplicitConstantAfterUnroll")
{
ScopedFastInt sfi("LuauCompileLoopUnrollThreshold", 200);
ScopedFastInt sfi(FInt::LuauCompileLoopUnrollThreshold, 200);
// access to implicit constant that depends on the unrolled loop constant is still invalid even though we can constant-propagate it
try
@ -2091,6 +2106,8 @@ RETURN R0 0
TEST_CASE("AndOrOptimizations")
{
ScopedFastFlag sff(FFlag::LuauCompileRevK, true);
// the OR/ORK optimization triggers for cutoff since lhs is simple
CHECK_EQ("\n" + compileFunction(R"(
local function advancedRidgedFilter(value, cutoff)
@ -2103,17 +2120,15 @@ end
R"(
ORK R2 R1 K0 [0.5]
SUB R0 R0 R2
LOADN R4 1
LOADN R8 0
JUMPIFNOTLT R0 R8 L0
MINUS R7 R0
JUMPIF R7 L1
L0: MOVE R7 R0
L1: MULK R6 R7 K1 [1]
LOADN R8 1
SUB R7 R8 R2
DIV R5 R6 R7
SUB R3 R4 R5
LOADN R7 0
JUMPIFNOTLT R0 R7 L0
MINUS R6 R0
JUMPIF R6 L1
L0: MOVE R6 R0
L1: MULK R5 R6 K1 [1]
SUBRK R6 K1 [1] R2
DIV R4 R5 R6
SUBRK R3 K1 [1] R4
RETURN R3 1
)");
@ -2126,9 +2141,8 @@ end
0),
R"(
LOADB R2 0
LOADK R4 K0 [0.5]
MULK R5 R1 K1 [0.40000000000000002]
SUB R3 R4 R5
MULK R4 R1 K1 [0.40000000000000002]
SUBRK R3 K0 [0.5] R4
JUMPIFNOTLT R3 R0 L1
LOADK R4 K0 [0.5]
MULK R5 R1 K1 [0.40000000000000002]
@ -2148,9 +2162,8 @@ end
0),
R"(
LOADB R2 1
LOADK R4 K0 [0.5]
MULK R5 R1 K1 [0.40000000000000002]
SUB R3 R4 R5
MULK R4 R1 K1 [0.40000000000000002]
SUBRK R3 K0 [0.5] R4
JUMPIFLT R0 R3 L1
LOADK R4 K0 [0.5]
MULK R5 R1 K1 [0.40000000000000002]
@ -2293,9 +2306,9 @@ TEST_CASE("RecursionParse")
// The test forcibly pushes the stack limit during compilation; in NoOpt, the stack consumption is much larger so we need to reduce the limit to
// not overflow the C stack. When ASAN is enabled, stack consumption increases even more.
#if defined(LUAU_ENABLE_ASAN)
ScopedFastInt flag("LuauRecursionLimit", 200);
ScopedFastInt flag(FInt::LuauRecursionLimit, 200);
#elif defined(_NOOPT) || defined(_DEBUG)
ScopedFastInt flag("LuauRecursionLimit", 300);
ScopedFastInt flag(FInt::LuauRecursionLimit, 300);
#endif
Luau::BytecodeBuilder bcb;
@ -4475,6 +4488,41 @@ L0: RETURN R0 -1
)");
}
TEST_CASE("VectorLiterals")
{
ScopedFastFlag sff(FFlag::LuauVectorLiterals, true);
CHECK_EQ("\n" + compileFunction("return Vector3.new(1, 2, 3)", 0, 2, /*enableVectors*/ true), R"(
LOADK R0 K0 [1, 2, 3]
RETURN R0 1
)");
CHECK_EQ("\n" + compileFunction("print(Vector3.new(1, 2, 3))", 0, 2, /*enableVectors*/ true), R"(
GETIMPORT R0 1 [print]
LOADK R1 K2 [1, 2, 3]
CALL R0 1 0
RETURN R0 0
)");
CHECK_EQ("\n" + compileFunction("print(Vector3.new(1, 2, 3, 4))", 0, 2, /*enableVectors*/ true), R"(
GETIMPORT R0 1 [print]
LOADK R1 K2 [1, 2, 3, 4]
CALL R0 1 0
RETURN R0 0
)");
CHECK_EQ("\n" + compileFunction("return Vector3.new(0, 0, 0), Vector3.new(-0, 0, 0)", 0, 2, /*enableVectors*/ true), R"(
LOADK R0 K0 [0, 0, 0]
LOADK R1 K1 [-0, 0, 0]
RETURN R0 2
)");
CHECK_EQ("\n" + compileFunction("return type(Vector3.new(0, 0, 0))", 0, 2, /*enableVectors*/ true), R"(
LOADK R0 K0 ['vector']
RETURN R0 1
)");
}
TEST_CASE("TypeAssertion")
{
// validate that type assertions work with the compiler and that the code inside type assertion isn't evaluated
@ -4754,8 +4802,8 @@ L1: RETURN R0 0
TEST_CASE("LoopUnrollControlFlow")
{
ScopedFastInt sfis[] = {
{"LuauCompileLoopUnrollThreshold", 50},
{"LuauCompileLoopUnrollThresholdMaxBoost", 300},
{FInt::LuauCompileLoopUnrollThreshold, 50},
{FInt::LuauCompileLoopUnrollThresholdMaxBoost, 300},
};
// break jumps to the end
@ -4891,8 +4939,8 @@ RETURN R0 0
TEST_CASE("LoopUnrollCost")
{
ScopedFastInt sfis[] = {
{"LuauCompileLoopUnrollThreshold", 25},
{"LuauCompileLoopUnrollThresholdMaxBoost", 300},
{FInt::LuauCompileLoopUnrollThreshold, 25},
{FInt::LuauCompileLoopUnrollThresholdMaxBoost, 300},
};
// loops with short body
@ -5069,8 +5117,8 @@ L1: RETURN R0 0
TEST_CASE("LoopUnrollCostBuiltins")
{
ScopedFastInt sfis[] = {
{"LuauCompileLoopUnrollThreshold", 25},
{"LuauCompileLoopUnrollThresholdMaxBoost", 300},
{FInt::LuauCompileLoopUnrollThreshold, 25},
{FInt::LuauCompileLoopUnrollThresholdMaxBoost, 300},
};
// this loop uses builtins and is close to the cost budget so it's important that we model builtins as cheaper than regular calls
@ -5947,9 +5995,9 @@ RETURN R3 1
TEST_CASE("InlineThresholds")
{
ScopedFastInt sfis[] = {
{"LuauCompileInlineThreshold", 25},
{"LuauCompileInlineThresholdMaxBoost", 300},
{"LuauCompileInlineDepth", 2},
{FInt::LuauCompileInlineThreshold, 25},
{FInt::LuauCompileInlineThresholdMaxBoost, 300},
{FInt::LuauCompileInlineDepth, 2},
};
// this function has enormous register pressure (50 regs) so we choose not to inline it
@ -7686,8 +7734,6 @@ RETURN R1 1
TEST_CASE("SideEffects")
{
ScopedFastFlag sff("LuauCompileSideEffects", true);
// we do not evaluate expressions in some cases when we know they can't carry side effects
CHECK_EQ("\n" + compileFunction0(R"(
local x = 5, print
@ -7739,9 +7785,6 @@ RETURN R0 0
TEST_CASE("IfElimination")
{
ScopedFastFlag sff1("LuauCompileDeadIf", true);
ScopedFastFlag sff2("LuauCompileSideEffects", true);
// if the left hand side of a condition is constant, it constant folds and we don't emit the branch
CHECK_EQ("\n" + compileFunction0("local a = false if a and b then b() end"), R"(
RETURN R0 0
@ -7807,4 +7850,32 @@ RETURN R0 1
)");
}
TEST_CASE("ArithRevK")
{
ScopedFastFlag sff(FFlag::LuauCompileRevK, true);
// - and / have special optimized form for reverse constants; in the future, + and * will likely get compiled to ADDK/MULK
// other operators are not important enough to optimize reverse constant forms for
CHECK_EQ("\n" + compileFunction0(R"(
local x: number = unknown
return 2 + x, 2 - x, 2 * x, 2 / x, 2 % x, 2 // x, 2 ^ x
)"),
R"(
GETIMPORT R0 1 [unknown]
LOADN R2 2
ADD R1 R2 R0
SUBRK R2 K2 [2] R0
LOADN R4 2
MUL R3 R4 R0
DIVRK R4 K2 [2] R0
LOADN R6 2
MOD R5 R6 R0
LOADN R7 2
IDIV R6 R7 R0
LOADN R8 2
POW R7 R8 R0
RETURN R1 7
)");
}
TEST_SUITE_END();

View File

@ -26,6 +26,14 @@ extern bool verbose;
extern bool codegen;
extern int optimizationLevel;
LUAU_FASTFLAG(LuauBit32Byteswap);
LUAU_FASTFLAG(LuauBufferBetterMsg);
LUAU_FASTFLAG(LuauBufferDefinitions);
LUAU_FASTFLAG(LuauCodeGenFixByteLower);
LUAU_FASTFLAG(LuauCompileBufferAnnotation);
LUAU_FASTFLAG(LuauLoopInterruptFix);
LUAU_DYNAMIC_FASTFLAG(LuauStricterUtf8);
static lua_CompileOptions defaultOptions()
{
lua_CompileOptions copts = {};
@ -288,7 +296,9 @@ static std::vector<Luau::CodeGen::FunctionBytecodeSummary> analyzeFile(const cha
std::unique_ptr<lua_State, void (*)(lua_State*)> globalState(luaL_newstate(), lua_close);
lua_State* L = globalState.get();
LUAU_ASSERT(luau_load(L, "source", bytecode.data(), bytecode.size(), 0) == 0);
int result = luau_load(L, "source", bytecode.data(), bytecode.size(), 0);
REQUIRE(result == 0);
return Luau::CodeGen::summarizeBytecode(L, -1, nestingLimit);
}
@ -312,8 +322,8 @@ TEST_CASE("Basic")
TEST_CASE("Buffers")
{
ScopedFastFlag luauBufferBetterMsg{"LuauBufferBetterMsg", true};
ScopedFastFlag luauCodeGenFixByteLower{"LuauCodeGenFixByteLower", true};
ScopedFastFlag luauBufferBetterMsg{FFlag::LuauBufferBetterMsg, true};
ScopedFastFlag luauCodeGenFixByteLower{FFlag::LuauCodeGenFixByteLower, true};
runConformance("buffers.lua");
}
@ -429,13 +439,13 @@ TEST_CASE("GC")
TEST_CASE("Bitwise")
{
ScopedFastFlag sffs{"LuauBit32Byteswap", true};
ScopedFastFlag sffs{FFlag::LuauBit32Byteswap, true};
runConformance("bitwise.lua");
}
TEST_CASE("UTF8")
{
ScopedFastFlag sff("LuauStricterUtf8", true);
ScopedFastFlag sff(DFFlag::LuauStricterUtf8, true);
runConformance("utf8.lua");
}
@ -580,7 +590,7 @@ static void populateRTTI(lua_State* L, Luau::TypeId type)
TEST_CASE("Types")
{
ScopedFastFlag luauBufferDefinitions{"LuauBufferDefinitions", true};
ScopedFastFlag luauBufferDefinitions{FFlag::LuauBufferDefinitions, true};
runConformance("types.lua", [](lua_State* L) {
Luau::NullModuleResolver moduleResolver;
@ -1528,6 +1538,8 @@ TEST_CASE("GCDump")
TEST_CASE("Interrupt")
{
ScopedFastFlag luauLoopInterruptFix{FFlag::LuauLoopInterruptFix, true};
lua_CompileOptions copts = defaultOptions();
copts.optimizationLevel = 1; // disable loop unrolling to get fixed expected hit results
@ -1586,12 +1598,12 @@ TEST_CASE("Interrupt")
if (gc >= 0)
return;
CHECK(index < 10);
if (++index == 10)
CHECK(index < 11);
if (++index == 11)
lua_yield(L, 0);
};
for (int test = 1; test <= 9; ++test)
for (int test = 1; test <= 10; ++test)
{
lua_State* T = lua_newthread(L);
@ -1601,7 +1613,7 @@ TEST_CASE("Interrupt")
index = 0;
int status = lua_resume(T, nullptr, 0);
CHECK(status == LUA_YIELD);
CHECK(index == 10);
CHECK(index == 11);
// abandon the thread
lua_pop(L, 1);
@ -1913,8 +1925,6 @@ TEST_CASE("SafeEnv")
TEST_CASE("Native")
{
ScopedFastFlag luauLowerAltLoopForn{"LuauLowerAltLoopForn", true};
runConformance("native.lua");
}
@ -1924,7 +1934,7 @@ TEST_CASE("NativeTypeAnnotations")
if (!codegen || !luau_codegen_supported())
return;
ScopedFastFlag luauCompileBufferAnnotation{"LuauCompileBufferAnnotation", true};
ScopedFastFlag luauCompileBufferAnnotation{FFlag::LuauCompileBufferAnnotation, true};
lua_CompileOptions copts = defaultOptions();
copts.vectorCtor = "vector";

View File

@ -1,5 +1,8 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "ConstraintGeneratorFixture.h"
#include "ScopedFlags.h"
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
namespace Luau
{
@ -7,7 +10,7 @@ namespace Luau
ConstraintGeneratorFixture::ConstraintGeneratorFixture()
: Fixture()
, mainModule(new Module)
, forceTheFlag{"DebugLuauDeferredConstraintResolution", true}
, forceTheFlag{FFlag::DebugLuauDeferredConstraintResolution, true}
{
mainModule->name = "MainModule";
mainModule->humanReadableName = "MainModule";

View File

@ -10,10 +10,12 @@
using namespace Luau;
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
struct DataFlowGraphFixture
{
// Only needed to fix the operator== reflexivity of an empty Symbol.
ScopedFastFlag dcr{"DebugLuauDeferredConstraintResolution", true};
ScopedFastFlag dcr{FFlag::DebugLuauDeferredConstraintResolution, true};
InternalErrorReporter handle;

View File

@ -590,7 +590,7 @@ TEST_CASE_FIXTURE(DifferFixture, "equal_table_cyclic_diamonds_unraveled")
TEST_CASE_FIXTURE(DifferFixture, "equal_function_cyclic")
{
// Old solver does not correctly infer function typepacks
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
CheckResult result = check(R"(
function foo()
@ -611,7 +611,7 @@ TEST_CASE_FIXTURE(DifferFixture, "equal_function_cyclic")
TEST_CASE_FIXTURE(DifferFixture, "equal_function_table_cyclic")
{
// Old solver does not correctly infer function typepacks
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
CheckResult result = check(R"(
function foo()
@ -638,7 +638,7 @@ TEST_CASE_FIXTURE(DifferFixture, "equal_function_table_cyclic")
TEST_CASE_FIXTURE(DifferFixture, "function_table_self_referential_cyclic")
{
// Old solver does not correctly infer function typepacks
// ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
// ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
CheckResult result = check(R"(
function foo()
@ -685,7 +685,7 @@ TEST_CASE_FIXTURE(DifferFixture, "equal_union_cyclic")
TEST_CASE_FIXTURE(DifferFixture, "equal_intersection_cyclic")
{
// Old solver does not correctly refine test types
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
CheckResult result = check(R"(
function foo1(x: number)
@ -883,7 +883,7 @@ TEST_CASE_FIXTURE(DifferFixture, "intersection_tables_missing_left")
TEST_CASE_FIXTURE(DifferFixture, "equal_function")
{
// Old solver does not correctly infer function typepacks
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
CheckResult result = check(R"(
function foo(x: number)
@ -901,7 +901,7 @@ TEST_CASE_FIXTURE(DifferFixture, "equal_function")
TEST_CASE_FIXTURE(DifferFixture, "equal_function_inferred_ret_length")
{
// Old solver does not correctly infer function typepacks
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
CheckResult result = check(R"(
function bar(x: number, y: string)
@ -925,7 +925,7 @@ TEST_CASE_FIXTURE(DifferFixture, "equal_function_inferred_ret_length")
TEST_CASE_FIXTURE(DifferFixture, "equal_function_inferred_ret_length_2")
{
// Old solver does not correctly infer function typepacks
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
CheckResult result = check(R"(
function bar(x: number, y: string)
@ -946,7 +946,7 @@ TEST_CASE_FIXTURE(DifferFixture, "equal_function_inferred_ret_length_2")
TEST_CASE_FIXTURE(DifferFixture, "function_arg_normal")
{
// Old solver does not correctly infer function typepacks
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
CheckResult result = check(R"(
function foo(x: number, y: number, z: number)
@ -965,7 +965,7 @@ TEST_CASE_FIXTURE(DifferFixture, "function_arg_normal")
TEST_CASE_FIXTURE(DifferFixture, "function_arg_normal_2")
{
// Old solver does not correctly infer function typepacks
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
CheckResult result = check(R"(
function foo(x: number, y: number, z: string)
@ -984,7 +984,7 @@ TEST_CASE_FIXTURE(DifferFixture, "function_arg_normal_2")
TEST_CASE_FIXTURE(DifferFixture, "function_ret_normal")
{
// Old solver does not correctly infer function typepacks
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
CheckResult result = check(R"(
function foo(x: number, y: number, z: string)
@ -1003,7 +1003,7 @@ TEST_CASE_FIXTURE(DifferFixture, "function_ret_normal")
TEST_CASE_FIXTURE(DifferFixture, "function_arg_length")
{
// Old solver does not correctly infer function typepacks
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
CheckResult result = check(R"(
function foo(x: number, y: number)
@ -1022,7 +1022,7 @@ TEST_CASE_FIXTURE(DifferFixture, "function_arg_length")
TEST_CASE_FIXTURE(DifferFixture, "function_arg_length_2")
{
// Old solver does not correctly infer function typepacks
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
CheckResult result = check(R"(
function foo(x: number, y: string, z: number)
@ -1041,7 +1041,7 @@ TEST_CASE_FIXTURE(DifferFixture, "function_arg_length_2")
TEST_CASE_FIXTURE(DifferFixture, "function_arg_length_none")
{
// Old solver does not correctly infer function typepacks
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
CheckResult result = check(R"(
function foo()
@ -1060,7 +1060,7 @@ TEST_CASE_FIXTURE(DifferFixture, "function_arg_length_none")
TEST_CASE_FIXTURE(DifferFixture, "function_arg_length_none_2")
{
// Old solver does not correctly infer function typepacks
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
CheckResult result = check(R"(
function foo(x: number)
@ -1079,7 +1079,7 @@ TEST_CASE_FIXTURE(DifferFixture, "function_arg_length_none_2")
TEST_CASE_FIXTURE(DifferFixture, "function_ret_length")
{
// Old solver does not correctly infer function typepacks
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
CheckResult result = check(R"(
function foo(x: number, y: number)
@ -1098,7 +1098,7 @@ TEST_CASE_FIXTURE(DifferFixture, "function_ret_length")
TEST_CASE_FIXTURE(DifferFixture, "function_ret_length_2")
{
// Old solver does not correctly infer function typepacks
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
CheckResult result = check(R"(
function foo(x: number, y: string, z: number)
@ -1117,7 +1117,7 @@ TEST_CASE_FIXTURE(DifferFixture, "function_ret_length_2")
TEST_CASE_FIXTURE(DifferFixture, "function_ret_length_none")
{
// Old solver does not correctly infer function typepacks
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
CheckResult result = check(R"(
function foo(x: number, y: string)
@ -1136,7 +1136,7 @@ TEST_CASE_FIXTURE(DifferFixture, "function_ret_length_none")
TEST_CASE_FIXTURE(DifferFixture, "function_ret_length_none_2")
{
// Old solver does not correctly infer function typepacks
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
CheckResult result = check(R"(
function foo()
@ -1155,7 +1155,7 @@ TEST_CASE_FIXTURE(DifferFixture, "function_ret_length_none_2")
TEST_CASE_FIXTURE(DifferFixture, "function_variadic_arg_normal")
{
// Old solver does not correctly infer function typepacks
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
CheckResult result = check(R"(
function foo(x: number, y: string, ...: number)
@ -1174,7 +1174,7 @@ TEST_CASE_FIXTURE(DifferFixture, "function_variadic_arg_normal")
TEST_CASE_FIXTURE(DifferFixture, "function_variadic_arg_missing")
{
// Old solver does not correctly infer function typepacks
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
CheckResult result = check(R"(
function foo(x: number, y: string, ...: number)
@ -1193,7 +1193,7 @@ TEST_CASE_FIXTURE(DifferFixture, "function_variadic_arg_missing")
TEST_CASE_FIXTURE(DifferFixture, "function_variadic_arg_missing_2")
{
// Old solver does not correctly infer function typepacks
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
CheckResult result = check(R"(
function foo(x: number, y: string)
@ -1212,7 +1212,7 @@ TEST_CASE_FIXTURE(DifferFixture, "function_variadic_arg_missing_2")
TEST_CASE_FIXTURE(DifferFixture, "function_variadic_oversaturation")
{
// Old solver does not correctly infer function typepacks
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
CheckResult result = check(R"(
-- allowed to be oversaturated
@ -1231,7 +1231,7 @@ TEST_CASE_FIXTURE(DifferFixture, "function_variadic_oversaturation")
TEST_CASE_FIXTURE(DifferFixture, "function_variadic_oversaturation_2")
{
// Old solver does not correctly infer function typepacks
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
CheckResult result = check(R"(
-- must not be oversaturated
@ -1250,7 +1250,7 @@ TEST_CASE_FIXTURE(DifferFixture, "function_variadic_oversaturation_2")
TEST_CASE_FIXTURE(DifferFixture, "generic")
{
// Old solver does not correctly infer function typepacks
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
CheckResult result = check(R"(
function foo(x, y)
@ -1269,7 +1269,7 @@ TEST_CASE_FIXTURE(DifferFixture, "generic")
TEST_CASE_FIXTURE(DifferFixture, "generic_one_vs_two")
{
// Old solver does not correctly infer function typepacks
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
CheckResult result = check(R"(
function foo<X>(x: X, y: X)
@ -1288,7 +1288,7 @@ TEST_CASE_FIXTURE(DifferFixture, "generic_one_vs_two")
TEST_CASE_FIXTURE(DifferFixture, "generic_three_or_three")
{
// Old solver does not correctly infer function typepacks
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
CheckResult result = check(R"(
function foo<X, Y>(x: X, y: X, z: Y)
@ -1329,7 +1329,7 @@ TEST_CASE_FIXTURE(DifferFixtureWithBuiltins, "equal_metatable")
TEST_CASE_FIXTURE(DifferFixtureWithBuiltins, "metatable_normal")
{
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", false};
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
local metaFoo = {

View File

@ -6,6 +6,8 @@
using namespace Luau;
LUAU_FASTFLAG(LuauStacklessTypeClone3);
TEST_SUITE_BEGIN("ErrorTests");
TEST_CASE("TypeError_code_should_return_nonzero_code")
@ -17,7 +19,7 @@ TEST_CASE("TypeError_code_should_return_nonzero_code")
TEST_CASE_FIXTURE(BuiltinsFixture, "metatable_names_show_instead_of_tables")
{
frontend.options.retainFullTypeGraphs = false;
ScopedFastFlag sff{"LuauStacklessTypeClone3", true};
ScopedFastFlag sff{FFlag::LuauStacklessTypeClone3, true};
CheckResult result = check(R"(
--!strict
local Account = {}

View File

@ -22,6 +22,7 @@
static const char* mainModuleName = "MainModule";
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
LUAU_FASTFLAG(DebugLuauFreezeArena);
extern std::optional<unsigned> randomSeed; // tests/main.cpp
@ -136,7 +137,7 @@ const Config& TestConfigResolver::getConfig(const ModuleName& name) const
}
Fixture::Fixture(bool freeze, bool prepareAutocomplete)
: sff_DebugLuauFreezeArena("DebugLuauFreezeArena", freeze)
: sff_DebugLuauFreezeArena(FFlag::DebugLuauFreezeArena, freeze)
, frontend(&fileResolver, &configResolver,
{/* retainFullTypeGraphs= */ true, /* forAutocomplete */ false, /* runLintChecks */ false, /* randomConstraintResolutionSeed */ randomSeed})
, builtinTypes(frontend.builtinTypes)

View File

@ -22,6 +22,8 @@
#include <unordered_map>
#include <optional>
LUAU_FASTFLAG(LuauBufferTypeck);
namespace Luau
{
@ -99,7 +101,7 @@ struct Fixture
ScopedFastFlag sff_DebugLuauFreezeArena;
ScopedFastFlag luauBufferTypeck{"LuauBufferTypeck", true};
ScopedFastFlag luauBufferTypeck{FFlag::LuauBufferTypeck, true};
TestFileResolver fileResolver;
TestConfigResolver configResolver;

View File

@ -13,6 +13,8 @@
using namespace Luau;
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
LUAU_FASTFLAG(DebugLuauFreezeArena);
LUAU_FASTFLAG(CorrectEarlyReturnInMarkDirty);
namespace
{
@ -1112,7 +1114,7 @@ a:b() -- this should error, since A doesn't define a:b()
TEST_CASE("no_use_after_free_with_type_fun_instantiation")
{
// This flag forces this test to crash if there's a UAF in this code.
ScopedFastFlag sff_DebugLuauFreezeArena("DebugLuauFreezeArena", true);
ScopedFastFlag sff_DebugLuauFreezeArena(FFlag::DebugLuauFreezeArena, true);
FrontendFixture fix;
@ -1252,7 +1254,7 @@ TEST_CASE_FIXTURE(FrontendFixture, "parse_only")
TEST_CASE_FIXTURE(FrontendFixture, "markdirty_early_return")
{
ScopedFastFlag fflag("CorrectEarlyReturnInMarkDirty", true);
ScopedFastFlag fflag(FFlag::CorrectEarlyReturnInMarkDirty, true);
constexpr char moduleName[] = "game/Gui/Modules/A";
fileResolver.source[moduleName] = R"(

View File

@ -13,6 +13,8 @@
using namespace Luau::CodeGen;
LUAU_FASTFLAG(LuauReuseBufferChecks);
class IrBuilderFixture
{
public:
@ -2058,8 +2060,6 @@ bb_fallback_1:
TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateArrayElemChecksSameIndex")
{
ScopedFastFlag luauReuseHashSlots{"LuauReuseArrSlots2", true};
IrOp block = build.block(IrBlockKind::Internal);
IrOp fallback = build.block(IrBlockKind::Fallback);
@ -2115,8 +2115,6 @@ bb_fallback_1:
TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateArrayElemChecksSameValue")
{
ScopedFastFlag luauReuseHashSlots{"LuauReuseArrSlots2", true};
IrOp block = build.block(IrBlockKind::Internal);
IrOp fallback = build.block(IrBlockKind::Fallback);
@ -2180,8 +2178,6 @@ bb_fallback_1:
TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateArrayElemChecksLowerIndex")
{
ScopedFastFlag luauReuseHashSlots{"LuauReuseArrSlots2", true};
IrOp block = build.block(IrBlockKind::Internal);
IrOp fallback = build.block(IrBlockKind::Fallback);
@ -2236,8 +2232,6 @@ bb_fallback_1:
TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateArrayElemChecksInvalidations")
{
ScopedFastFlag luauReuseHashSlots{"LuauReuseArrSlots2", true};
IrOp block = build.block(IrBlockKind::Internal);
IrOp fallback = build.block(IrBlockKind::Fallback);
@ -2296,8 +2290,6 @@ bb_fallback_1:
TEST_CASE_FIXTURE(IrBuilderFixture, "ArrayElemChecksNegativeIndex")
{
ScopedFastFlag luauReuseHashSlots{"LuauReuseArrSlots2", true};
IrOp block = build.block(IrBlockKind::Internal);
IrOp fallback = build.block(IrBlockKind::Fallback);
@ -2343,6 +2335,111 @@ bb_fallback_1:
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateBufferLengthChecks")
{
ScopedFastFlag luauReuseBufferChecks{FFlag::LuauReuseBufferChecks, true};
IrOp block = build.block(IrBlockKind::Internal);
IrOp fallback = build.block(IrBlockKind::Fallback);
build.beginBlock(block);
IrOp sourceBuf = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(0));
build.inst(IrCmd::STORE_TVALUE, build.vmReg(2), sourceBuf);
IrOp buffer1 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(2));
build.inst(IrCmd::CHECK_BUFFER_LEN, buffer1, build.constInt(12), build.constInt(4), fallback);
build.inst(IrCmd::BUFFER_WRITEI32, buffer1, build.constInt(12), build.constInt(32));
// Now with lower index, should be removed
build.inst(IrCmd::STORE_TVALUE, build.vmReg(2), sourceBuf);
IrOp buffer2 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(2));
build.inst(IrCmd::CHECK_BUFFER_LEN, buffer2, build.constInt(8), build.constInt(4), fallback);
build.inst(IrCmd::BUFFER_WRITEI32, buffer2, build.constInt(8), build.constInt(30));
// Now with higher index, should raise the initial check bound
build.inst(IrCmd::STORE_TVALUE, build.vmReg(2), sourceBuf);
IrOp buffer3 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(2));
build.inst(IrCmd::CHECK_BUFFER_LEN, buffer3, build.constInt(16), build.constInt(4), fallback);
build.inst(IrCmd::BUFFER_WRITEI32, buffer3, build.constInt(16), build.constInt(60));
// Now with different access size, should not reuse previous checks (can be improved in the future)
build.inst(IrCmd::CHECK_BUFFER_LEN, buffer3, build.constInt(16), build.constInt(2), fallback);
build.inst(IrCmd::BUFFER_WRITEI16, buffer3, build.constInt(16), build.constInt(55));
// Now with same, but unknown index value
IrOp index = build.inst(IrCmd::LOAD_INT, build.vmReg(1));
build.inst(IrCmd::CHECK_BUFFER_LEN, buffer3, index, build.constInt(2), fallback);
build.inst(IrCmd::BUFFER_WRITEI16, buffer3, index, build.constInt(1));
build.inst(IrCmd::CHECK_BUFFER_LEN, buffer3, index, build.constInt(2), fallback);
build.inst(IrCmd::BUFFER_WRITEI16, buffer3, index, build.constInt(2));
build.inst(IrCmd::RETURN, build.vmReg(1), build.constUint(1));
build.beginBlock(fallback);
build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1));
updateUseCounts(build.function);
constPropInBlockChains(build, true);
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"(
bb_0:
%0 = LOAD_TVALUE R0
STORE_TVALUE R2, %0
%2 = LOAD_POINTER R2
CHECK_BUFFER_LEN %2, 16i, 4i, bb_fallback_1
BUFFER_WRITEI32 %2, 12i, 32i
BUFFER_WRITEI32 %2, 8i, 30i
BUFFER_WRITEI32 %2, 16i, 60i
CHECK_BUFFER_LEN %2, 16i, 2i, bb_fallback_1
BUFFER_WRITEI16 %2, 16i, 55i
%15 = LOAD_INT R1
CHECK_BUFFER_LEN %2, %15, 2i, bb_fallback_1
BUFFER_WRITEI16 %2, %15, 1i
BUFFER_WRITEI16 %2, %15, 2i
RETURN R1, 1u
bb_fallback_1:
RETURN R0, 1u
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "BufferLenghtChecksNegativeIndex")
{
ScopedFastFlag luauReuseBufferChecks{FFlag::LuauReuseBufferChecks, true};
IrOp block = build.block(IrBlockKind::Internal);
IrOp fallback = build.block(IrBlockKind::Fallback);
build.beginBlock(block);
IrOp sourceBuf = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(0));
build.inst(IrCmd::STORE_TVALUE, build.vmReg(2), sourceBuf);
IrOp buffer1 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(2));
build.inst(IrCmd::CHECK_BUFFER_LEN, buffer1, build.constInt(-4), build.constInt(4), fallback);
build.inst(IrCmd::BUFFER_WRITEI32, buffer1, build.constInt(-4), build.constInt(32));
build.inst(IrCmd::RETURN, build.vmReg(1), build.constUint(1));
build.beginBlock(fallback);
build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1));
updateUseCounts(build.function);
constPropInBlockChains(build, true);
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"(
bb_0:
%0 = LOAD_TVALUE R0
STORE_TVALUE R2, %0
JUMP bb_fallback_1
bb_fallback_1:
RETURN R0, 1u
)");
}
TEST_SUITE_END();
TEST_SUITE_BEGIN("Analysis");

View File

@ -1829,8 +1829,6 @@ local _ = 0x10000000000000000
TEST_CASE_FIXTURE(Fixture, "IntegerParsingDecimalImprecise")
{
ScopedFastFlag sff("LuauParseImpreciseNumber", true);
LintResult result = lint(R"(
local _ = 10000000000000000000000000000000000000000000000000000000000000000
local _ = 10000000000000001
@ -1867,8 +1865,6 @@ local _ = -9223372036854775808
TEST_CASE_FIXTURE(Fixture, "IntegerParsingHexImprecise")
{
ScopedFastFlag sff("LuauParseImpreciseNumber", true);
LintResult result = lint(R"(
local _ = 0x1234567812345678

View File

@ -15,6 +15,9 @@ using namespace Luau;
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
LUAU_FASTFLAG(LuauStacklessTypeClone3)
LUAU_FASTFLAG(DebugLuauFreezeArena);
LUAU_FASTINT(LuauTypeCloneIterationLimit);
LUAU_FASTINT(LuauTypeCloneRecursionLimit);
TEST_SUITE_BEGIN("ModuleTests");
@ -112,7 +115,7 @@ TEST_CASE_FIXTURE(Fixture, "deepClone_cyclic_table")
// not, but it's tangental to the core purpose of this test.
ScopedFastFlag sff[] = {
{"DebugLuauDeferredConstraintResolution", false},
{FFlag::DebugLuauDeferredConstraintResolution, false},
};
CheckResult result = check(R"(
@ -270,7 +273,7 @@ TEST_CASE_FIXTURE(Fixture, "clone_class")
TEST_CASE_FIXTURE(Fixture, "clone_free_types")
{
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", false};
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
TypeArena arena;
TypeId freeTy = freshType(NotNull{&arena}, builtinTypes, nullptr);
@ -336,8 +339,8 @@ TEST_CASE_FIXTURE(Fixture, "clone_recursion_limit")
int limit = 400;
#endif
ScopedFastFlag sff{"LuauStacklessTypeClone3", false};
ScopedFastInt luauTypeCloneRecursionLimit{"LuauTypeCloneRecursionLimit", limit};
ScopedFastFlag sff{FFlag::LuauStacklessTypeClone3, false};
ScopedFastInt luauTypeCloneRecursionLimit{FInt::LuauTypeCloneRecursionLimit, limit};
TypeArena src;
@ -360,8 +363,8 @@ TEST_CASE_FIXTURE(Fixture, "clone_recursion_limit")
TEST_CASE_FIXTURE(Fixture, "clone_iteration_limit")
{
ScopedFastFlag sff{"LuauStacklessTypeClone3", true};
ScopedFastInt sfi{"LuauTypeCloneIterationLimit", 500};
ScopedFastFlag sff{FFlag::LuauStacklessTypeClone3, true};
ScopedFastInt sfi{FInt::LuauTypeCloneIterationLimit, 500};
TypeArena src;
@ -530,7 +533,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "clone_table_bound_to_table_bound_to_table")
TEST_CASE_FIXTURE(BuiltinsFixture, "clone_a_bound_type_to_a_persistent_type")
{
ScopedFastFlag sff{"LuauStacklessTypeClone3", true};
ScopedFastFlag sff{FFlag::LuauStacklessTypeClone3, true};
TypeArena arena;
@ -546,7 +549,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "clone_a_bound_type_to_a_persistent_type")
TEST_CASE_FIXTURE(BuiltinsFixture, "clone_a_bound_typepack_to_a_persistent_typepack")
{
ScopedFastFlag sff{"LuauStacklessTypeClone3", true};
ScopedFastFlag sff{FFlag::LuauStacklessTypeClone3, true};
TypeArena arena;

View File

@ -3,33 +3,73 @@
#include "Fixture.h"
#include "Luau/Common.h"
#include "Luau/Ast.h"
#include "Luau/Common.h"
#include "Luau/IostreamHelpers.h"
#include "Luau/ModuleResolver.h"
#include "Luau/VisitType.h"
#include "ScopedFlags.h"
#include "doctest.h"
#include <iostream>
using namespace Luau;
#define NONSTRICT_REQUIRE_CHECKED_ERR(index, name, result) \
LUAU_FASTFLAG(LuauCheckedFunctionSyntax);
#define NONSTRICT_REQUIRE_ERR_AT_POS(pos, result, idx) \
do \
{ \
REQUIRE(index < result.errors.size()); \
auto err##index = get<CheckedFunctionCallError>(result.errors[index]); \
REQUIRE(err##index != nullptr); \
CHECK_EQ((err##index)->checkedFunctionName, name); \
auto pos_ = (pos); \
bool foundErr = false; \
int index = 0; \
for (const auto& err : result.errors) \
{ \
if (err.location.begin == pos_) \
{ \
foundErr = true; \
break; \
} \
index++; \
} \
REQUIRE_MESSAGE(foundErr, "Expected error at " << pos_); \
idx = index; \
} while (false)
#define NONSTRICT_REQUIRE_CHECKED_ERR(pos, name, result) \
do \
{ \
int errIndex; \
NONSTRICT_REQUIRE_ERR_AT_POS(pos, result, errIndex); \
auto err = get<CheckedFunctionCallError>(result.errors[errIndex]); \
REQUIRE(err != nullptr); \
CHECK_EQ(err->checkedFunctionName, name); \
} while (false)
#define NONSTRICT_REQUIRE_FUNC_DEFINITION_ERR(pos, argname, result) \
do \
{ \
int errIndex; \
NONSTRICT_REQUIRE_ERR_AT_POS(pos, result, errIndex); \
auto err = get<NonStrictFunctionDefinitionError>(result.errors[errIndex]); \
REQUIRE(err != nullptr); \
CHECK_EQ(err->argument, argname); \
} while (false)
struct NonStrictTypeCheckerFixture : Fixture
{
NonStrictTypeCheckerFixture()
{
registerHiddenTypes(&frontend);
}
CheckResult checkNonStrict(const std::string& code)
{
ScopedFastFlag flags[] = {
{"LuauCheckedFunctionSyntax", true},
{"DebugLuauDeferredConstraintResolution", true},
{FFlag::LuauCheckedFunctionSyntax, true},
{FFlag::DebugLuauDeferredConstraintResolution, true},
};
LoadDefinitionFileResult res = loadDefinition(definitions);
LUAU_ASSERT(res.success);
@ -40,19 +80,41 @@ struct NonStrictTypeCheckerFixture : Fixture
declare function @checked abs(n: number): number
declare function @checked lower(s: string): string
declare function cond() : boolean
declare function @checked contrived(n : Not<number>) : number
)BUILTIN_SRC";
};
TEST_SUITE_BEGIN("NonStrictTypeCheckerTest");
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "simple_negation_caching_example")
{
CheckResult result = checkNonStrict(R"(
local x = 3
abs(x)
abs(x)
)");
LUAU_REQUIRE_NO_ERRORS(result);
result = checkNonStrict(R"(
local x = 3
contrived(x)
contrived(x)
)");
LUAU_REQUIRE_ERROR_COUNT(2, result);
NONSTRICT_REQUIRE_CHECKED_ERR(Position(2, 10), "contrived", result);
NONSTRICT_REQUIRE_CHECKED_ERR(Position(3, 10), "contrived", result);
}
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "simple_non_strict_failure")
{
CheckResult result = checkNonStrict(R"BUILTIN_SRC(
abs("hi")
)BUILTIN_SRC");
LUAU_REQUIRE_ERROR_COUNT(1, result);
NONSTRICT_REQUIRE_CHECKED_ERR(0, "abs", result);
NONSTRICT_REQUIRE_CHECKED_ERR(Position(1, 4), "abs", result);
}
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "nested_function_calls_constant")
@ -63,7 +125,7 @@ abs(lower(x))
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
NONSTRICT_REQUIRE_CHECKED_ERR(0, "abs", result);
NONSTRICT_REQUIRE_CHECKED_ERR(Position(2, 4), "abs", result);
}
@ -79,8 +141,8 @@ end
)");
LUAU_REQUIRE_ERROR_COUNT(2, result);
NONSTRICT_REQUIRE_CHECKED_ERR(0, "abs", result);
NONSTRICT_REQUIRE_CHECKED_ERR(1, "lower", result);
NONSTRICT_REQUIRE_CHECKED_ERR(Position(3, 8), "abs", result);
NONSTRICT_REQUIRE_CHECKED_ERR(Position(5, 10), "lower", result);
}
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "if_then_else_warns_nil_branches")
@ -95,8 +157,8 @@ end
)");
LUAU_REQUIRE_ERROR_COUNT(2, result);
NONSTRICT_REQUIRE_CHECKED_ERR(0, "abs", result);
NONSTRICT_REQUIRE_CHECKED_ERR(1, "lower", result);
NONSTRICT_REQUIRE_CHECKED_ERR(Position(3, 8), "abs", result);
NONSTRICT_REQUIRE_CHECKED_ERR(Position(5, 10), "lower", result);
}
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "if_then_else_doesnt_warn_else_branch")
@ -111,7 +173,7 @@ end
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
NONSTRICT_REQUIRE_CHECKED_ERR(0, "abs", result);
NONSTRICT_REQUIRE_CHECKED_ERR(Position(3, 8), "abs", result);
}
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "if_then_no_else")
@ -129,13 +191,13 @@ end
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "if_then_no_else_err_in_cond")
{
CheckResult result = checkNonStrict(R"(
local x : string
local x : string = ""
if abs(x) then
lower(x)
end
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
NONSTRICT_REQUIRE_CHECKED_ERR(0, "abs", result);
NONSTRICT_REQUIRE_CHECKED_ERR(Position(2, 7), "abs", result);
}
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "if_then_else_expr_should_warn")
@ -145,8 +207,8 @@ local x : never
local y = if cond() then abs(x) else lower(x)
)");
LUAU_REQUIRE_ERROR_COUNT(2, result);
NONSTRICT_REQUIRE_CHECKED_ERR(0, "abs", result);
NONSTRICT_REQUIRE_CHECKED_ERR(1, "lower", result);
NONSTRICT_REQUIRE_CHECKED_ERR(Position(2, 29), "abs", result);
NONSTRICT_REQUIRE_CHECKED_ERR(Position(2, 43), "lower", result);
}
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "if_then_else_expr_doesnt_warn_else_branch")
@ -156,21 +218,7 @@ local x : string = "hi"
local y = if cond() then abs(x) else lower(x)
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
NONSTRICT_REQUIRE_CHECKED_ERR(0, "abs", result);
}
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "sequencing_errors")
{
CheckResult result = checkNonStrict(R"(
function f(x)
abs(x)
lower(x)
end
)");
LUAU_REQUIRE_ERROR_COUNT(2, result);
NONSTRICT_REQUIRE_CHECKED_ERR(0, "abs", result);
NONSTRICT_REQUIRE_CHECKED_ERR(1, "lower", result);
NONSTRICT_REQUIRE_CHECKED_ERR(Position(2, 29), "abs", result);
}
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "sequencing_if_checked_call")
@ -185,10 +233,10 @@ end
lower(x)
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
NONSTRICT_REQUIRE_CHECKED_ERR(0, "lower", result);
NONSTRICT_REQUIRE_CHECKED_ERR(Position(7, 6), "lower", result);
}
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "sequencing_unrelated_checked_calls")
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "function_def_unrelated_checked_calls")
{
CheckResult result = checkNonStrict(R"(
function h(x, y)
@ -200,5 +248,143 @@ end
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "function_def_basic_no_errors")
{
CheckResult result = checkNonStrict(R"(
function f(x)
abs(x)
end
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "function_def_basic_errors")
{
CheckResult result = checkNonStrict(R"(
function f(x : string)
abs(x)
end
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
NONSTRICT_REQUIRE_CHECKED_ERR(Position(2, 8), "abs", result);
}
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "function_def_failure")
{
CheckResult result = checkNonStrict(R"(
function f(x)
abs(lower(x))
end
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
NONSTRICT_REQUIRE_CHECKED_ERR(Position(2, 8), "abs", result);
}
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "function_def_sequencing_errors")
{
CheckResult result = checkNonStrict(R"(
function f(x)
abs(x)
lower(x)
end
)");
LUAU_REQUIRE_ERROR_COUNT(3, result);
NONSTRICT_REQUIRE_CHECKED_ERR(Position(2, 8), "abs", result);
NONSTRICT_REQUIRE_CHECKED_ERR(Position(3, 10), "lower", result);
NONSTRICT_REQUIRE_FUNC_DEFINITION_ERR(Position(1, 11), "x", result);
}
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "local_fn_produces_error")
{
CheckResult result = checkNonStrict(R"(
local x = 5
local function y() lower(x) end
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
NONSTRICT_REQUIRE_CHECKED_ERR(Position(2, 25), "lower", result);
}
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "fn_expr_produces_error")
{
CheckResult result = checkNonStrict(R"(
local x = 5
local y = function() lower(x) end
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
NONSTRICT_REQUIRE_CHECKED_ERR(Position(2, 27), "lower", result);
}
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "function_def_if_warns_never")
{
CheckResult result = checkNonStrict(R"(
function f(x)
if cond() then
abs(x)
else
lower(x)
end
end
)");
LUAU_REQUIRE_ERROR_COUNT(2, result);
NONSTRICT_REQUIRE_CHECKED_ERR(Position(3, 12), "abs", result);
NONSTRICT_REQUIRE_CHECKED_ERR(Position(5, 14), "lower", result);
}
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "function_def_if_no_else")
{
CheckResult result = checkNonStrict(R"(
function f(x)
if cond() then
abs(x)
end
end
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "function_def_if_assignment_errors")
{
CheckResult result = checkNonStrict(R"(
function f(x)
if cond() then
x = 5
else
x = nil
end
lower(x)
end
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
NONSTRICT_REQUIRE_CHECKED_ERR(Position(7, 10), "lower", result);
}
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "function_def_if_assignment_no_errors")
{
CheckResult result = checkNonStrict(R"(
function f(x : string | number)
if cond() then
x = 5
else
x = "hi"
end
abs(x)
end
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "local_only_one_warning")
{
CheckResult result = checkNonStrict(R"(
local x = 5
lower(x)
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
NONSTRICT_REQUIRE_CHECKED_ERR(Position(2, 6), "lower", result);
}
TEST_SUITE_END();

View File

@ -11,6 +11,7 @@
#include "Luau/BuiltinDefinitions.h"
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
LUAU_FASTFLAG(LuauTransitiveSubtyping);
using namespace Luau;
@ -32,7 +33,7 @@ struct IsSubtypeFixture : Fixture
bool isConsistentSubtype(TypeId a, TypeId b)
{
// any test that is testing isConsistentSubtype is testing the old solver exclusively!
ScopedFastFlag noDcr{"DebugLuauDeferredConstraintResolution", false};
ScopedFastFlag noDcr{FFlag::DebugLuauDeferredConstraintResolution, false};
Location location;
ModulePtr module = getMainModule();
@ -182,7 +183,7 @@ TEST_CASE_FIXTURE(IsSubtypeFixture, "table_with_union_prop")
TEST_CASE_FIXTURE(IsSubtypeFixture, "table_with_any_prop")
{
ScopedFastFlag sffs[] = {
{"LuauTransitiveSubtyping", true},
{FFlag::LuauTransitiveSubtyping, true},
};
check(R"(
@ -243,7 +244,7 @@ TEST_CASE_FIXTURE(IsSubtypeFixture, "union_and_intersection")
TEST_CASE_FIXTURE(IsSubtypeFixture, "tables")
{
ScopedFastFlag sffs[] = {
{"LuauTransitiveSubtyping", true},
{FFlag::LuauTransitiveSubtyping, true},
};
check(R"(
@ -398,7 +399,7 @@ TEST_CASE_FIXTURE(IsSubtypeFixture, "metatable" * doctest::expected_failures{1})
TEST_CASE_FIXTURE(IsSubtypeFixture, "any_is_unknown_union_error")
{
ScopedFastFlag sffs[] = {
{"LuauTransitiveSubtyping", true},
{FFlag::LuauTransitiveSubtyping, true},
};
check(R"(
@ -418,7 +419,7 @@ TEST_CASE_FIXTURE(IsSubtypeFixture, "any_is_unknown_union_error")
TEST_CASE_FIXTURE(IsSubtypeFixture, "any_intersect_T_is_T")
{
ScopedFastFlag sffs[] = {
{"LuauTransitiveSubtyping", true},
{FFlag::LuauTransitiveSubtyping, true},
};
check(R"(
@ -440,7 +441,7 @@ TEST_CASE_FIXTURE(IsSubtypeFixture, "any_intersect_T_is_T")
TEST_CASE_FIXTURE(IsSubtypeFixture, "error_suppression")
{
ScopedFastFlag sffs[] = {
{"LuauTransitiveSubtyping", true},
{FFlag::LuauTransitiveSubtyping, true},
};
check("");

View File

@ -11,6 +11,13 @@
using namespace Luau;
LUAU_FASTFLAG(LuauCheckedFunctionSyntax);
LUAU_FASTFLAG(LuauLexerLookaheadRemembersBraceType);
LUAU_FASTINT(LuauRecursionLimit);
LUAU_FASTINT(LuauTypeLengthLimit);
LUAU_FASTINT(LuauParseErrorLimit);
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
namespace
{
@ -1156,7 +1163,7 @@ until false
TEST_CASE_FIXTURE(Fixture, "parse_nesting_based_end_detection_local_function")
{
ScopedFastFlag sff[] = {
{"DebugLuauDeferredConstraintResolution", false},
{FFlag::DebugLuauDeferredConstraintResolution, false},
};
try
@ -1192,7 +1199,7 @@ end
TEST_CASE_FIXTURE(Fixture, "parse_nesting_based_end_detection_failsafe_earlier")
{
ScopedFastFlag sff[] = {
{"DebugLuauDeferredConstraintResolution", false},
{FFlag::DebugLuauDeferredConstraintResolution, false},
};
try
@ -1317,7 +1324,7 @@ end
TEST_CASE_FIXTURE(Fixture, "parse_error_with_too_many_nested_type_group")
{
ScopedFastInt sfis{"LuauRecursionLimit", 10};
ScopedFastInt sfis{FInt::LuauRecursionLimit, 10};
matchParseError(
"function f(): ((((((((((Fail)))))))))) end", "Exceeded allowed recursion depth; simplify your type annotation to make the code compile");
@ -1336,7 +1343,7 @@ TEST_CASE_FIXTURE(Fixture, "parse_error_with_too_many_nested_type_group")
TEST_CASE_FIXTURE(Fixture, "can_parse_complex_unions_successfully")
{
ScopedFastInt sfis[] = {{"LuauRecursionLimit", 10}, {"LuauTypeLengthLimit", 10}};
ScopedFastInt sfis[] = {{FInt::LuauRecursionLimit, 10}, {FInt::LuauTypeLengthLimit, 10}};
parse(R"(
local f:
@ -1367,7 +1374,7 @@ local f: a? | b? | c? | d? | e? | f? | g? | h?
TEST_CASE_FIXTURE(Fixture, "parse_error_with_too_many_nested_if_statements")
{
ScopedFastInt sfis{"LuauRecursionLimit", 10};
ScopedFastInt sfis{FInt::LuauRecursionLimit, 10};
matchParseErrorPrefix(
"function f() if true then if true then if true then if true then if true then if true then if true then if true then if true "
@ -1377,7 +1384,7 @@ TEST_CASE_FIXTURE(Fixture, "parse_error_with_too_many_nested_if_statements")
TEST_CASE_FIXTURE(Fixture, "parse_error_with_too_many_changed_elseif_statements")
{
ScopedFastInt sfis{"LuauRecursionLimit", 10};
ScopedFastInt sfis{FInt::LuauRecursionLimit, 10};
matchParseErrorPrefix(
"function f() if false then elseif false then elseif false then elseif false then elseif false then elseif false then elseif "
@ -1387,7 +1394,7 @@ TEST_CASE_FIXTURE(Fixture, "parse_error_with_too_many_changed_elseif_statements"
TEST_CASE_FIXTURE(Fixture, "parse_error_with_too_many_nested_ifelse_expressions1")
{
ScopedFastInt sfis{"LuauRecursionLimit", 10};
ScopedFastInt sfis{FInt::LuauRecursionLimit, 10};
matchParseError("function f() return if true then 1 elseif true then 2 elseif true then 3 elseif true then 4 elseif true then 5 elseif true then "
"6 elseif true then 7 elseif true then 8 elseif true then 9 elseif true then 10 else 11 end",
@ -1396,7 +1403,7 @@ TEST_CASE_FIXTURE(Fixture, "parse_error_with_too_many_nested_ifelse_expressions1
TEST_CASE_FIXTURE(Fixture, "parse_error_with_too_many_nested_ifelse_expressions2")
{
ScopedFastInt sfis{"LuauRecursionLimit", 10};
ScopedFastInt sfis{FInt::LuauRecursionLimit, 10};
matchParseError(
"function f() return if if if if if if if if if if true then false else true then false else true then false else true then false else true "
@ -2411,7 +2418,7 @@ local a : { [string] : number, [number] : string, count: number }
TEST_CASE_FIXTURE(Fixture, "recovery_error_limit_1")
{
ScopedFastInt luauParseErrorLimit("LuauParseErrorLimit", 1);
ScopedFastInt luauParseErrorLimit(FInt::LuauParseErrorLimit, 1);
try
{
@ -2427,7 +2434,7 @@ TEST_CASE_FIXTURE(Fixture, "recovery_error_limit_1")
TEST_CASE_FIXTURE(Fixture, "recovery_error_limit_2")
{
ScopedFastInt luauParseErrorLimit("LuauParseErrorLimit", 2);
ScopedFastInt luauParseErrorLimit(FInt::LuauParseErrorLimit, 2);
try
{
@ -2491,7 +2498,7 @@ TEST_CASE_FIXTURE(Fixture, "recovery_of_parenthesized_expressions")
};
ScopedFastFlag sff[] = {
{"DebugLuauDeferredConstraintResolution", false},
{FFlag::DebugLuauDeferredConstraintResolution, false},
};
checkRecovery("function foo(a, b. c) return a + b end", "function foo(a, b) return a + b end", 1);
@ -2725,7 +2732,7 @@ TEST_CASE_FIXTURE(Fixture, "AstName_comparison")
TEST_CASE_FIXTURE(Fixture, "generic_type_list_recovery")
{
ScopedFastFlag sff[] = {
{"DebugLuauDeferredConstraintResolution", false},
{FFlag::DebugLuauDeferredConstraintResolution, false},
};
try
@ -3020,7 +3027,7 @@ TEST_CASE_FIXTURE(Fixture, "do_block_with_no_end")
TEST_CASE_FIXTURE(Fixture, "parse_interpolated_string_with_lookahead_involved")
{
ScopedFastFlag sff{"LuauLexerLookaheadRemembersBraceType", true};
ScopedFastFlag sff{FFlag::LuauLexerLookaheadRemembersBraceType, true};
ParseResult result = tryParse(R"(
local x = `{ {y} }`
@ -3031,7 +3038,7 @@ TEST_CASE_FIXTURE(Fixture, "parse_interpolated_string_with_lookahead_involved")
TEST_CASE_FIXTURE(Fixture, "parse_interpolated_string_with_lookahead_involved2")
{
ScopedFastFlag sff{"LuauLexerLookaheadRemembersBraceType", true};
ScopedFastFlag sff{FFlag::LuauLexerLookaheadRemembersBraceType, true};
ParseResult result = tryParse(R"(
local x = `{ { y{} } }`
@ -3044,7 +3051,7 @@ TEST_CASE_FIXTURE(Fixture, "parse_top_level_checked_fn")
{
ParseOptions opts;
opts.allowDeclarationSyntax = true;
ScopedFastFlag sff{"LuauCheckedFunctionSyntax", true};
ScopedFastFlag sff{FFlag::LuauCheckedFunctionSyntax, true};
std::string src = R"BUILTIN_SRC(
declare function @checked abs(n: number): number
@ -3064,7 +3071,7 @@ TEST_CASE_FIXTURE(Fixture, "parse_declared_table_checked_member")
{
ParseOptions opts;
opts.allowDeclarationSyntax = true;
ScopedFastFlag sff{"LuauCheckedFunctionSyntax", true};
ScopedFastFlag sff{FFlag::LuauCheckedFunctionSyntax, true};
const std::string src = R"BUILTIN_SRC(
declare math : {
@ -3092,7 +3099,7 @@ TEST_CASE_FIXTURE(Fixture, "parse_checked_outside_decl_fails")
{
ParseOptions opts;
opts.allowDeclarationSyntax = true;
ScopedFastFlag sff{"LuauCheckedFunctionSyntax", true};
ScopedFastFlag sff{FFlag::LuauCheckedFunctionSyntax, true};
ParseResult pr = tryParse(R"(
local @checked = 3
@ -3106,7 +3113,7 @@ TEST_CASE_FIXTURE(Fixture, "parse_checked_in_and_out_of_decl_fails")
{
ParseOptions opts;
opts.allowDeclarationSyntax = true;
ScopedFastFlag sff{"LuauCheckedFunctionSyntax", true};
ScopedFastFlag sff{FFlag::LuauCheckedFunctionSyntax, true};
auto pr = tryParse(R"(
local @checked = 3
@ -3122,7 +3129,7 @@ TEST_CASE_FIXTURE(Fixture, "parse_checked_as_function_name_fails")
{
ParseOptions opts;
opts.allowDeclarationSyntax = true;
ScopedFastFlag sff{"LuauCheckedFunctionSyntax", true};
ScopedFastFlag sff{FFlag::LuauCheckedFunctionSyntax, true};
auto pr = tryParse(R"(
function @checked(x: number) : number
@ -3136,7 +3143,7 @@ TEST_CASE_FIXTURE(Fixture, "cannot_use_@_as_variable_name")
{
ParseOptions opts;
opts.allowDeclarationSyntax = true;
ScopedFastFlag sff{"LuauCheckedFunctionSyntax", true};
ScopedFastFlag sff{FFlag::LuauCheckedFunctionSyntax, true};
auto pr = tryParse(R"(
local @blah = 3

View File

@ -0,0 +1,391 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Common.h"
#include "ScopedFlags.h"
#include "lua.h"
#include "lualib.h"
#include "Repl.h"
#include "FileUtils.h"
#include "doctest.h"
#include <algorithm>
#include <initializer_list>
#include <memory>
LUAU_FASTFLAG(LuauUpdatedRequireByStringSemantics)
class ReplWithPathFixture
{
public:
ReplWithPathFixture()
: luaState(luaL_newstate(), lua_close)
{
L = luaState.get();
setupState(L);
luaL_sandboxthread(L);
runCode(L, prettyPrintSource);
}
// Returns all of the output captured from the pretty printer
std::string getCapturedOutput()
{
lua_getglobal(L, "capturedoutput");
const char* str = lua_tolstring(L, -1, nullptr);
std::string result(str);
lua_pop(L, 1);
return result;
}
enum class PathType
{
Absolute,
Relative
};
std::string getLuauDirectory(PathType type)
{
std::string luauDirRel = ".";
std::string luauDirAbs;
std::optional<std::string> cwd = getCurrentWorkingDirectory();
REQUIRE_MESSAGE(cwd, "Error getting Luau path");
std::replace((*cwd).begin(), (*cwd).end(), '\\', '/');
luauDirAbs = *cwd;
for (int i = 0; i < 20; ++i)
{
if (isDirectory(luauDirAbs + "/Luau/tests") || isDirectory(luauDirAbs + "/Client/Luau/tests"))
{
if (isDirectory(luauDirAbs + "/Client/Luau/tests"))
{
luauDirRel += "/Client";
luauDirAbs += "/Client";
}
luauDirRel += "/Luau";
luauDirAbs += "/Luau";
if (type == PathType::Relative)
return luauDirRel;
if (type == PathType::Absolute)
return luauDirAbs;
}
luauDirRel += "/..";
std::optional<std::string> parentPath = getParentPath(luauDirAbs);
REQUIRE_MESSAGE(parentPath, "Error getting Luau path");
luauDirAbs = *parentPath;
}
// Could not find the directory
REQUIRE_MESSAGE(false, "Error getting Luau path");
return {};
}
void runProtectedRequire(const std::string& path)
{
runCode(L, "return pcall(function() return require(\"" + path + "\") end)");
}
void assertOutputContainsAll(const std::initializer_list<std::string>& list)
{
const std::string capturedOutput = getCapturedOutput();
for (const std::string& elem : list)
{
CHECK_MESSAGE(capturedOutput.find(elem) != std::string::npos, "Captured output: ", capturedOutput);
}
}
lua_State* L;
private:
std::unique_ptr<lua_State, void (*)(lua_State*)> luaState;
// This is a simplistic and incomplete pretty printer.
// It is included here to test that the pretty printer hook is being called.
// More elaborate tests to ensure correct output can be added if we introduce
// a more feature rich pretty printer.
std::string prettyPrintSource = R"(
-- Accumulate pretty printer output in `capturedoutput`
capturedoutput = ""
function arraytostring(arr)
local strings = {}
table.foreachi(arr, function(k,v) table.insert(strings, pptostring(v)) end )
return "{" .. table.concat(strings, ", ") .. "}"
end
function pptostring(x)
if type(x) == "table" then
-- Just assume array-like tables for now.
return arraytostring(x)
elseif type(x) == "string" then
return '"' .. x .. '"'
else
return tostring(x)
end
end
-- Note: Instead of calling print, the pretty printer just stores the output
-- in `capturedoutput` so we can check for the correct results.
function _PRETTYPRINT(...)
local args = table.pack(...)
local strings = {}
for i=1, args.n do
local item = args[i]
local str = pptostring(item, customoptions)
if i == 1 then
capturedoutput = capturedoutput .. str
else
capturedoutput = capturedoutput .. "\t" .. str
end
end
end
)";
};
TEST_SUITE_BEGIN("RequireByStringTests");
TEST_CASE("PathResolution")
{
#ifdef _WIN32
std::string prefix = "C:/";
#else
std::string prefix = "/";
#endif
ScopedFastFlag sff{FFlag::LuauUpdatedRequireByStringSemantics, true};
CHECK(resolvePath(prefix + "Users/modules/module.luau", "") == prefix + "Users/modules/module.luau");
CHECK(resolvePath(prefix + "Users/modules/module.luau", "a/string/that/should/be/ignored") == prefix + "Users/modules/module.luau");
CHECK(resolvePath(prefix + "Users/modules/module.luau", "./a/string/that/should/be/ignored") == prefix + "Users/modules/module.luau");
CHECK(resolvePath(prefix + "Users/modules/module.luau", "/a/string/that/should/be/ignored") == prefix + "Users/modules/module.luau");
CHECK(resolvePath(prefix + "Users/modules/module.luau", "/Users/modules") == prefix + "Users/modules/module.luau");
CHECK(resolvePath("../module", "") == "../module");
CHECK(resolvePath("../../module", "") == "../../module");
CHECK(resolvePath("../module/..", "") == "..");
CHECK(resolvePath("../module/../..", "") == "../..");
CHECK(resolvePath("../dependency", prefix + "Users/modules/module.luau") == prefix + "Users/dependency");
CHECK(resolvePath("../dependency/", prefix + "Users/modules/module.luau") == prefix + "Users/dependency");
CHECK(resolvePath("../../../../../Users/dependency", prefix + "Users/modules/module.luau") == prefix + "Users/dependency");
CHECK(resolvePath("../..", prefix + "Users/modules/module.luau") == prefix);
}
TEST_CASE("PathNormalization")
{
#ifdef _WIN32
std::string prefix = "C:/";
#else
std::string prefix = "/";
#endif
ScopedFastFlag sff{FFlag::LuauUpdatedRequireByStringSemantics, true};
// Relative path
std::optional<std::string> result = normalizePath("../../modules/module");
CHECK(result);
std::string normalized = *result;
std::vector<std::string> variants = {
"./.././.././modules/./module/", "placeholder/../../../modules/module", "../placeholder/placeholder2/../../../modules/module"};
for (const std::string& variant : variants)
{
result = normalizePath(variant);
CHECK(result);
CHECK(normalized == *result);
}
// Absolute path
result = normalizePath(prefix + "Users/modules/module");
CHECK(result);
normalized = *result;
variants = {"Users/Users/Users/.././.././modules/./module/", "placeholder/../Users/..//Users/modules/module",
"Users/../placeholder/placeholder2/../../Users/modules/module"};
for (const std::string& variant : variants)
{
result = normalizePath(prefix + variant);
CHECK(result);
CHECK(normalized == *result);
}
}
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireSimpleRelativePath")
{
ScopedFastFlag sff{FFlag::LuauUpdatedRequireByStringSemantics, true};
std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/dependency";
runProtectedRequire(path);
assertOutputContainsAll({"true", "result from dependency"});
}
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireRelativeToRequiringFile")
{
ScopedFastFlag sff{FFlag::LuauUpdatedRequireByStringSemantics, true};
std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/module";
runProtectedRequire(path);
assertOutputContainsAll({"true", "result from dependency", "required into module"});
}
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireLua")
{
ScopedFastFlag sff{FFlag::LuauUpdatedRequireByStringSemantics, true};
std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/lua_dependency";
runProtectedRequire(path);
assertOutputContainsAll({"true", "result from lua_dependency"});
}
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireInitLuau")
{
ScopedFastFlag sff{FFlag::LuauUpdatedRequireByStringSemantics, true};
std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/luau";
runProtectedRequire(path);
assertOutputContainsAll({"true", "result from init.luau"});
}
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireInitLua")
{
ScopedFastFlag sff{FFlag::LuauUpdatedRequireByStringSemantics, true};
std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/lua";
runProtectedRequire(path);
assertOutputContainsAll({"true", "result from init.lua"});
}
TEST_CASE_FIXTURE(ReplWithPathFixture, "CheckCacheAfterRequireLuau")
{
ScopedFastFlag sff{FFlag::LuauUpdatedRequireByStringSemantics, true};
std::string relativePath = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/module";
std::string absolutePath = getLuauDirectory(PathType::Absolute) + "/tests/require/without_config/module";
luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1);
lua_getfield(L, -1, (absolutePath + ".luau").c_str());
REQUIRE_MESSAGE(lua_isnil(L, -1), "Cache already contained module result");
runProtectedRequire(relativePath);
assertOutputContainsAll({"true", "result from dependency", "required into module"});
// Check cache for the absolute path as a cache key
luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1);
lua_getfield(L, -1, (absolutePath + ".luau").c_str());
REQUIRE_FALSE_MESSAGE(lua_isnil(L, -1), "Cache did not contain module result");
}
TEST_CASE_FIXTURE(ReplWithPathFixture, "CheckCacheAfterRequireLua")
{
ScopedFastFlag sff{FFlag::LuauUpdatedRequireByStringSemantics, true};
std::string relativePath = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/lua_dependency";
std::string absolutePath = getLuauDirectory(PathType::Absolute) + "/tests/require/without_config/lua_dependency";
luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1);
lua_getfield(L, -1, (absolutePath + ".luau").c_str());
REQUIRE_MESSAGE(lua_isnil(L, -1), "Cache already contained module result");
runProtectedRequire(relativePath);
assertOutputContainsAll({"true", "result from lua_dependency"});
// Check cache for the absolute path as a cache key
luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1);
lua_getfield(L, -1, (absolutePath + ".lua").c_str());
REQUIRE_FALSE_MESSAGE(lua_isnil(L, -1), "Cache did not contain module result");
}
TEST_CASE_FIXTURE(ReplWithPathFixture, "CheckCacheAfterRequireInitLuau")
{
ScopedFastFlag sff{FFlag::LuauUpdatedRequireByStringSemantics, true};
std::string relativePath = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/luau";
std::string absolutePath = getLuauDirectory(PathType::Absolute) + "/tests/require/without_config/luau";
luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1);
lua_getfield(L, -1, (absolutePath + "/init.luau").c_str());
REQUIRE_MESSAGE(lua_isnil(L, -1), "Cache already contained module result");
runProtectedRequire(relativePath);
assertOutputContainsAll({"true", "result from init.luau"});
// Check cache for the absolute path as a cache key
luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1);
lua_getfield(L, -1, (absolutePath + "/init.luau").c_str());
REQUIRE_FALSE_MESSAGE(lua_isnil(L, -1), "Cache did not contain module result");
}
TEST_CASE_FIXTURE(ReplWithPathFixture, "CheckCacheAfterRequireInitLua")
{
ScopedFastFlag sff{FFlag::LuauUpdatedRequireByStringSemantics, true};
std::string relativePath = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/lua";
std::string absolutePath = getLuauDirectory(PathType::Absolute) + "/tests/require/without_config/lua";
luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1);
lua_getfield(L, -1, (absolutePath + "/init.lua").c_str());
REQUIRE_MESSAGE(lua_isnil(L, -1), "Cache already contained module result");
runProtectedRequire(relativePath);
assertOutputContainsAll({"true", "result from init.lua"});
// Check cache for the absolute path as a cache key
luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1);
lua_getfield(L, -1, (absolutePath + "/init.lua").c_str());
REQUIRE_FALSE_MESSAGE(lua_isnil(L, -1), "Cache did not contain module result");
}
TEST_CASE_FIXTURE(ReplWithPathFixture, "LoadStringRelative")
{
ScopedFastFlag sff{FFlag::LuauUpdatedRequireByStringSemantics, true};
runCode(L, "return pcall(function() return loadstring(\"require('a/relative/path')\")() end)");
assertOutputContainsAll({"false", "require is not supported in this context"});
}
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireAbsolutePath")
{
ScopedFastFlag sff{FFlag::LuauUpdatedRequireByStringSemantics, true};
#ifdef _WIN32
std::string absolutePath = "C:/an/absolute/path";
#else
std::string absolutePath = "/an/absolute/path";
#endif
runProtectedRequire(absolutePath);
assertOutputContainsAll({"false", "cannot require an absolute path"});
}
TEST_CASE_FIXTURE(ReplWithPathFixture, "PathsArrayRelativePath")
{
ScopedFastFlag sff{FFlag::LuauUpdatedRequireByStringSemantics, true};
std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/with_config/src/requirer";
runProtectedRequire(path);
assertOutputContainsAll({"true", "result from library"});
}
TEST_CASE_FIXTURE(ReplWithPathFixture, "PathsArrayExplicitlyRelativePath")
{
ScopedFastFlag sff{FFlag::LuauUpdatedRequireByStringSemantics, true};
std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/with_config/src/fail_requirer";
runProtectedRequire(path);
assertOutputContainsAll({"false", "error requiring module"});
}
TEST_CASE_FIXTURE(ReplWithPathFixture, "PathsArrayFromParent")
{
ScopedFastFlag sff{FFlag::LuauUpdatedRequireByStringSemantics, true};
std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/with_config/src/global_library_requirer";
runProtectedRequire(path);
assertOutputContainsAll({"true", "result from global_library"});
}
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequirePathWithAlias")
{
ScopedFastFlag sff{FFlag::LuauUpdatedRequireByStringSemantics, true};
std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/with_config/src/alias_requirer";
runProtectedRequire(path);
assertOutputContainsAll({"true", "result from dependency"});
}
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequirePathWithParentAlias")
{
ScopedFastFlag sff{FFlag::LuauUpdatedRequireByStringSemantics, true};
std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/with_config/src/parent_alias_requirer";
runProtectedRequire(path);
assertOutputContainsAll({"true", "result from other_dependency"});
}
TEST_SUITE_END();

View File

@ -13,12 +13,17 @@
#include "doctest.h"
#include <algorithm>
using namespace Luau;
LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
struct LimitFixture : BuiltinsFixture
{
#if defined(_NOOPT) || defined(_DEBUG)
ScopedFastInt LuauTypeInferRecursionLimit{"LuauTypeInferRecursionLimit", 100};
ScopedFastInt LuauTypeInferRecursionLimit{FInt::LuauTypeInferRecursionLimit, 100};
#endif
};
@ -36,7 +41,7 @@ TEST_SUITE_BEGIN("RuntimeLimits");
TEST_CASE_FIXTURE(LimitFixture, "typescript_port_of_Result_type")
{
ScopedFastFlag sff[] = {
{"DebugLuauDeferredConstraintResolution", false},
{FFlag::DebugLuauDeferredConstraintResolution, false},
};
constexpr const char* src = R"LUA(

View File

@ -13,18 +13,11 @@ private:
T oldValue = T();
public:
ScopedFValue(const char* name, T newValue)
ScopedFValue(Luau::FValue<T>& fvalue, T newValue)
{
for (Luau::FValue<T>* v = Luau::FValue<T>::list; v; v = v->next)
if (strcmp(v->name, name) == 0)
{
value = v;
oldValue = v->value;
v->value = newValue;
break;
}
LUAU_ASSERT(value);
value = &fvalue;
oldValue = fvalue.value;
fvalue.value = newValue;
}
ScopedFValue(const ScopedFValue&) = delete;

View File

@ -8,6 +8,8 @@
using namespace Luau;
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
namespace
{
@ -59,7 +61,7 @@ struct SimplifyFixture : Fixture
TypeId anotherChildClassTy = nullptr;
TypeId unrelatedClassTy = nullptr;
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
SimplifyFixture()
{

View File

@ -24,6 +24,8 @@ std::ostream& operator<<(std::ostream& lhs, const SubtypingVariance& variance)
{
case SubtypingVariance::Covariant:
return lhs << "covariant";
case SubtypingVariance::Contravariant:
return lhs << "contravariant";
case SubtypingVariance::Invariant:
return lhs << "invariant";
case SubtypingVariance::Invalid:
@ -643,12 +645,7 @@ TEST_CASE_FIXTURE(SubtypeFixture, "(number) -> () <!: <T>(T) -> ()")
TEST_CASE_FIXTURE(SubtypeFixture, "<T>() -> (T, T) <!: () -> (string, number)")
{
TypeId nothingToTwoTs = arena.addType(FunctionType{
{genericT},
{},
builtinTypes->emptyTypePack,
arena.addTypePack({genericT, genericT})
});
TypeId nothingToTwoTs = arena.addType(FunctionType{{genericT}, {}, builtinTypes->emptyTypePack, arena.addTypePack({genericT, genericT})});
TypeId nothingToStringAndNumber = fn({}, {builtinTypes->stringType, builtinTypes->numberType});
@ -790,7 +787,7 @@ TEST_IS_NOT_SUBTYPE(negate(builtinTypes->neverType), builtinTypes->stringType);
TEST_IS_SUBTYPE(negate(builtinTypes->unknownType), builtinTypes->stringType);
TEST_IS_NOT_SUBTYPE(negate(builtinTypes->anyType), builtinTypes->stringType);
TEST_IS_SUBTYPE(negate(meet(builtinTypes->neverType, builtinTypes->unknownType)), builtinTypes->stringType);
TEST_IS_NOT_SUBTYPE(negate(join(builtinTypes->neverType, builtinTypes->unknownType)), builtinTypes->stringType);
TEST_IS_SUBTYPE(negate(join(builtinTypes->neverType, builtinTypes->unknownType)), builtinTypes->stringType);
// Negated supertypes: never/unknown/any/error
TEST_IS_SUBTYPE(builtinTypes->stringType, negate(builtinTypes->neverType));
@ -812,7 +809,7 @@ TEST_IS_SUBTYPE(builtinTypes->booleanType, negate(meet(builtinTypes->stringType,
TEST_IS_SUBTYPE(builtinTypes->trueType, negate(meet(builtinTypes->booleanType, builtinTypes->numberType)));
TEST_IS_SUBTYPE(rootClass, negate(meet(builtinTypes->classType, childClass)));
TEST_IS_SUBTYPE(childClass, negate(meet(builtinTypes->classType, builtinTypes->numberType)));
TEST_IS_NOT_SUBTYPE(builtinTypes->unknownType, negate(meet(builtinTypes->classType, builtinTypes->numberType)));
TEST_IS_SUBTYPE(builtinTypes->unknownType, negate(meet(builtinTypes->classType, builtinTypes->numberType)));
TEST_IS_NOT_SUBTYPE(str("foo"), negate(meet(builtinTypes->stringType, negate(str("bar")))));
// Negated supertypes: tables and metatables
@ -1041,6 +1038,16 @@ TEST_CASE_FIXTURE(SubtypeFixture, "(string | number) & (\"a\" | true) <: { lower
CHECK_IS_SUBTYPE(base, tableWithLower);
}
TEST_CASE_FIXTURE(SubtypeFixture, "number <: ~~number")
{
CHECK_IS_SUBTYPE(builtinTypes->numberType, negate(negate(builtinTypes->numberType)));
}
TEST_CASE_FIXTURE(SubtypeFixture, "~~number <: number")
{
CHECK_IS_SUBTYPE(negate(negate(builtinTypes->numberType)), builtinTypes->numberType);
}
/*
* Within the scope to which a generic belongs, that generic ought to be treated
* as its bounds.
@ -1075,11 +1082,15 @@ TEST_IS_NOT_SUBTYPE(tbl({}), idx(builtinTypes->numberType, builtinTypes->numberT
TEST_IS_NOT_SUBTYPE(tbl({{"X", builtinTypes->numberType}}), idx(builtinTypes->numberType, builtinTypes->numberType));
TEST_IS_NOT_SUBTYPE(idx(builtinTypes->numberType, builtinTypes->numberType), tbl({{"X", builtinTypes->numberType}}));
TEST_IS_NOT_SUBTYPE(idx(join(builtinTypes->numberType, builtinTypes->stringType), builtinTypes->numberType), idx(builtinTypes->numberType, builtinTypes->numberType));
TEST_IS_NOT_SUBTYPE(idx(builtinTypes->numberType, builtinTypes->numberType), idx(join(builtinTypes->numberType, builtinTypes->stringType), builtinTypes->numberType));
TEST_IS_NOT_SUBTYPE(
idx(join(builtinTypes->numberType, builtinTypes->stringType), builtinTypes->numberType), idx(builtinTypes->numberType, builtinTypes->numberType));
TEST_IS_NOT_SUBTYPE(
idx(builtinTypes->numberType, builtinTypes->numberType), idx(join(builtinTypes->numberType, builtinTypes->stringType), builtinTypes->numberType));
TEST_IS_NOT_SUBTYPE(idx(builtinTypes->numberType, join(builtinTypes->stringType, builtinTypes->numberType)), idx(builtinTypes->numberType, builtinTypes->numberType));
TEST_IS_NOT_SUBTYPE(idx(builtinTypes->numberType, builtinTypes->numberType), idx(builtinTypes->numberType, join(builtinTypes->stringType, builtinTypes->numberType)));
TEST_IS_NOT_SUBTYPE(
idx(builtinTypes->numberType, join(builtinTypes->stringType, builtinTypes->numberType)), idx(builtinTypes->numberType, builtinTypes->numberType));
TEST_IS_NOT_SUBTYPE(
idx(builtinTypes->numberType, builtinTypes->numberType), idx(builtinTypes->numberType, join(builtinTypes->stringType, builtinTypes->numberType)));
TEST_IS_NOT_SUBTYPE(tbl({{"X", builtinTypes->numberType}}), idx(builtinTypes->stringType, builtinTypes->numberType));
TEST_IS_SUBTYPE(idx(builtinTypes->stringType, builtinTypes->numberType), tbl({{"X", builtinTypes->numberType}}));
@ -1176,6 +1187,7 @@ TEST_CASE_FIXTURE(SubtypeFixture, "fn_arguments")
CHECK(result.reasoning == std::vector{SubtypingReasoning{
/* subPath */ TypePath::PathBuilder().args().index(0).build(),
/* superPath */ TypePath::PathBuilder().args().index(0).build(),
/* variance */ SubtypingVariance::Contravariant,
}});
}
@ -1189,6 +1201,7 @@ TEST_CASE_FIXTURE(SubtypeFixture, "fn_arguments_tail")
CHECK(result.reasoning == std::vector{SubtypingReasoning{
/* subPath */ TypePath::PathBuilder().args().tail().variadic().build(),
/* superPath */ TypePath::PathBuilder().args().tail().variadic().build(),
/* variance */ SubtypingVariance::Contravariant,
}});
}

View File

@ -8,6 +8,8 @@
using namespace Luau;
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
TEST_SUITE_BEGIN("SymbolTests");
TEST_CASE("equality_and_hashing_of_globals")
@ -66,7 +68,7 @@ TEST_CASE("equality_and_hashing_of_locals")
TEST_CASE("equality_of_empty_symbols")
{
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
std::string s1 = "name";
std::string s2 = "name";

View File

@ -289,7 +289,7 @@ n3 [label="TableType 3"];
TEST_CASE_FIXTURE(Fixture, "free")
{
ScopedFastFlag sff[] = {
{"DebugLuauDeferredConstraintResolution", false},
{FFlag::DebugLuauDeferredConstraintResolution, false},
};
Type type{TypeVariant{FreeType{TypeLevel{0, 0}}}};
@ -305,7 +305,7 @@ n1 [label="FreeType 1"];
TEST_CASE_FIXTURE(Fixture, "free_with_constraints")
{
ScopedFastFlag sff[] = {
{"DebugLuauDeferredConstraintResolution", true},
{FFlag::DebugLuauDeferredConstraintResolution, true},
};
Type type{TypeVariant{FreeType{nullptr, builtinTypes->numberType, builtinTypes->optionalNumberType}}};

View File

@ -12,6 +12,8 @@ using namespace Luau;
LUAU_FASTFLAG(LuauRecursiveTypeParameterRestriction);
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
LUAU_FASTFLAG(LuauCheckedFunctionSyntax);
LUAU_FASTFLAG(DebugLuauSharedSelf);
TEST_SUITE_BEGIN("ToString");
@ -236,7 +238,7 @@ TEST_CASE_FIXTURE(Fixture, "functions_are_always_parenthesized_in_unions_or_inte
TEST_CASE_FIXTURE(Fixture, "simple_intersections_printed_on_one_line")
{
ScopedFastFlag sff{"LuauToStringSimpleCompositeTypesSingleLine", true};
ScopedFastFlag sff{FFlag::LuauToStringSimpleCompositeTypesSingleLine, true};
CheckResult result = check(R"(
local a: string & number
)");
@ -249,7 +251,7 @@ TEST_CASE_FIXTURE(Fixture, "simple_intersections_printed_on_one_line")
TEST_CASE_FIXTURE(Fixture, "complex_intersections_printed_on_multiple_lines")
{
ScopedFastFlag sff{"LuauToStringSimpleCompositeTypesSingleLine", true};
ScopedFastFlag sff{FFlag::LuauToStringSimpleCompositeTypesSingleLine, true};
CheckResult result = check(R"(
local a: string & number & boolean
)");
@ -268,7 +270,7 @@ TEST_CASE_FIXTURE(Fixture, "complex_intersections_printed_on_multiple_lines")
TEST_CASE_FIXTURE(Fixture, "overloaded_functions_always_printed_on_multiple_lines")
{
ScopedFastFlag sff{"LuauToStringSimpleCompositeTypesSingleLine", true};
ScopedFastFlag sff{FFlag::LuauToStringSimpleCompositeTypesSingleLine, true};
CheckResult result = check(R"(
local a: ((string) -> string) & ((number) -> number)
)");
@ -285,7 +287,7 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_always_printed_on_multiple_line
TEST_CASE_FIXTURE(Fixture, "simple_unions_printed_on_one_line")
{
ScopedFastFlag sff{"LuauToStringSimpleCompositeTypesSingleLine", true};
ScopedFastFlag sff{FFlag::LuauToStringSimpleCompositeTypesSingleLine, true};
CheckResult result = check(R"(
local a: number | boolean
)");
@ -298,7 +300,7 @@ TEST_CASE_FIXTURE(Fixture, "simple_unions_printed_on_one_line")
TEST_CASE_FIXTURE(Fixture, "complex_unions_printed_on_multiple_lines")
{
ScopedFastFlag sff{"LuauToStringSimpleCompositeTypesSingleLine", true};
ScopedFastFlag sff{FFlag::LuauToStringSimpleCompositeTypesSingleLine, true};
CheckResult result = check(R"(
local a: string | number | boolean
)");
@ -565,7 +567,7 @@ TEST_CASE_FIXTURE(Fixture, "toStringDetailed")
TEST_CASE_FIXTURE(BuiltinsFixture, "toStringDetailed2")
{
ScopedFastFlag sff[] = {
{"DebugLuauSharedSelf", true},
{FFlag::DebugLuauSharedSelf, true},
};
CheckResult result = check(R"(
@ -865,7 +867,7 @@ TEST_CASE_FIXTURE(Fixture, "pick_distinct_names_for_mixed_explicit_and_implicit_
TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_include_self_param")
{
ScopedFastFlag sff[]{
{"DebugLuauSharedSelf", true},
{FFlag::DebugLuauSharedSelf, true},
};
CheckResult result = check(R"(
@ -887,7 +889,7 @@ TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_include_self_param")
TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_hide_self_param")
{
ScopedFastFlag sff[]{
{"DebugLuauSharedSelf", true},
{FFlag::DebugLuauSharedSelf, true},
};
CheckResult result = check(R"(
@ -965,8 +967,8 @@ Type 'string' could not be converted into 'number' in an invariant context)";
TEST_CASE_FIXTURE(Fixture, "checked_fn_toString")
{
ScopedFastFlag flags[] = {
{"LuauCheckedFunctionSyntax", true},
{"DebugLuauDeferredConstraintResolution", true},
{FFlag::LuauCheckedFunctionSyntax, true},
{FFlag::DebugLuauDeferredConstraintResolution, true},
};
auto _result = loadDefinition(R"(

View File

@ -12,6 +12,8 @@
using namespace Luau;
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
struct TxnLogFixture
{
TxnLog log{/*useScopes*/ true};
@ -33,7 +35,7 @@ TEST_SUITE_BEGIN("TxnLog");
TEST_CASE_FIXTURE(TxnLogFixture, "colliding_union_incoming_type_has_greater_scope")
{
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
log.replace(c, BoundType{a});
log2.replace(a, BoundType{c});
@ -66,7 +68,7 @@ TEST_CASE_FIXTURE(TxnLogFixture, "colliding_union_incoming_type_has_greater_scop
TEST_CASE_FIXTURE(TxnLogFixture, "colliding_union_incoming_type_has_lesser_scope")
{
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
log.replace(a, BoundType{c});
log2.replace(c, BoundType{a});
@ -99,7 +101,7 @@ TEST_CASE_FIXTURE(TxnLogFixture, "colliding_union_incoming_type_has_lesser_scope
TEST_CASE_FIXTURE(TxnLogFixture, "colliding_coincident_logs_do_not_create_degenerate_unions")
{
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
log.replace(a, BoundType{b});
log2.replace(a, BoundType{b});

View File

@ -8,6 +8,7 @@
using namespace Luau;
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
LUAU_FASTFLAG(DebugLuauSharedSelf);
TEST_SUITE_BEGIN("TypeAliases");
@ -188,7 +189,7 @@ TEST_CASE_FIXTURE(Fixture, "mutually_recursive_aliases")
TEST_CASE_FIXTURE(Fixture, "generic_aliases")
{
ScopedFastFlag sff[] = {
{"DebugLuauDeferredConstraintResolution", true},
{FFlag::DebugLuauDeferredConstraintResolution, true},
};
CheckResult result = check(R"(
type T<a> = { v: a }
@ -206,7 +207,7 @@ TEST_CASE_FIXTURE(Fixture, "generic_aliases")
TEST_CASE_FIXTURE(Fixture, "dependent_generic_aliases")
{
ScopedFastFlag sff[] = {
{"DebugLuauDeferredConstraintResolution", true},
{FFlag::DebugLuauDeferredConstraintResolution, true},
};
CheckResult result = check(R"(
@ -334,7 +335,7 @@ TEST_CASE_FIXTURE(Fixture, "stringify_type_alias_of_recursive_template_table_typ
TEST_CASE_FIXTURE(Fixture, "cli_38393_recursive_intersection_oom")
{
ScopedFastFlag sff[] = {
{"DebugLuauDeferredConstraintResolution", false},
{FFlag::DebugLuauDeferredConstraintResolution, false},
}; // FIXME
CheckResult result = check(R"(
@ -820,7 +821,7 @@ TEST_CASE_FIXTURE(Fixture, "forward_declared_alias_is_not_clobbered_by_prior_uni
TEST_CASE_FIXTURE(Fixture, "forward_declared_alias_is_not_clobbered_by_prior_unification_with_any_2")
{
ScopedFastFlag sff[] = {
{"DebugLuauSharedSelf", true},
{FFlag::DebugLuauSharedSelf, true},
};
CheckResult result = check(R"(

View File

@ -8,6 +8,7 @@
#include "doctest.h"
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
LUAU_FASTFLAG(DebugLuauMagicTypes);
using namespace Luau;
@ -77,7 +78,7 @@ TEST_CASE_FIXTURE(Fixture, "assignment_cannot_transform_a_table_property_type")
TEST_CASE_FIXTURE(Fixture, "assignments_to_unannotated_parameters_can_transform_the_type")
{
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
CheckResult result = check(R"(
function f(x)
@ -93,7 +94,7 @@ TEST_CASE_FIXTURE(Fixture, "assignments_to_unannotated_parameters_can_transform_
TEST_CASE_FIXTURE(Fixture, "assignments_to_annotated_parameters_are_checked")
{
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
CheckResult result = check(R"(
function f(x: string)
@ -768,7 +769,7 @@ int AssertionCatcher::tripped;
TEST_CASE_FIXTURE(Fixture, "luau_ice_triggers_an_ice_exception_with_flag")
{
ScopedFastFlag sffs{"DebugLuauMagicTypes", true};
ScopedFastFlag sffs{FFlag::DebugLuauMagicTypes, true};
AssertionCatcher ac;
@ -782,7 +783,7 @@ TEST_CASE_FIXTURE(Fixture, "luau_ice_triggers_an_ice_exception_with_flag")
TEST_CASE_FIXTURE(Fixture, "luau_ice_triggers_an_ice_exception_with_flag_handler")
{
ScopedFastFlag sffs{"DebugLuauMagicTypes", true};
ScopedFastFlag sffs{FFlag::DebugLuauMagicTypes, true};
bool caught = false;
@ -800,7 +801,7 @@ TEST_CASE_FIXTURE(Fixture, "luau_ice_triggers_an_ice_exception_with_flag_handler
TEST_CASE_FIXTURE(Fixture, "luau_ice_is_not_special_without_the_flag")
{
ScopedFastFlag sffs{"DebugLuauMagicTypes", false};
ScopedFastFlag sffs{FFlag::DebugLuauMagicTypes, false};
// We only care that this does not throw
check(R"(
@ -816,7 +817,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "luau_print_is_magic_if_the_flag_is_set")
output.push_back(s);
});
ScopedFastFlag sffs{"DebugLuauMagicTypes", true};
ScopedFastFlag sffs{FFlag::DebugLuauMagicTypes, true};
CheckResult result = check(R"(
local a: _luau_print<typeof(math.abs)>
@ -829,7 +830,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "luau_print_is_magic_if_the_flag_is_set")
TEST_CASE_FIXTURE(Fixture, "luau_print_is_not_special_without_the_flag")
{
ScopedFastFlag sffs{"DebugLuauMagicTypes", false};
ScopedFastFlag sffs{FFlag::DebugLuauMagicTypes, false};
CheckResult result = check(R"(
local a: _luau_print<number>
@ -840,7 +841,7 @@ TEST_CASE_FIXTURE(Fixture, "luau_print_is_not_special_without_the_flag")
TEST_CASE_FIXTURE(Fixture, "luau_print_incomplete")
{
ScopedFastFlag sffs{"DebugLuauMagicTypes", true};
ScopedFastFlag sffs{FFlag::DebugLuauMagicTypes, true};
CheckResult result = check(R"(
local a: _luau_print

View File

@ -9,6 +9,7 @@
using namespace Luau;
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
LUAU_FASTFLAG(LuauAlwaysCommitInferencesOfFunctionCalls);
TEST_SUITE_BEGIN("BuiltinTests");
@ -133,7 +134,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "sort_with_predicate")
TEST_CASE_FIXTURE(BuiltinsFixture, "sort_with_bad_predicate")
{
ScopedFastFlag sff[] = {
{"LuauAlwaysCommitInferencesOfFunctionCalls", true},
{FFlag::LuauAlwaysCommitInferencesOfFunctionCalls, true},
};
CheckResult result = check(R"(

View File

@ -6,11 +6,14 @@
using namespace Luau;
LUAU_FASTFLAG(LuauTinyControlFlowAnalysis);
LUAU_FASTFLAG(LuauLoopControlFlowAnalysis);
TEST_SUITE_BEGIN("ControlFlowAnalysis");
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return")
{
ScopedFastFlag sff{"LuauTinyControlFlowAnalysis", true};
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
CheckResult result = check(R"(
local function f(x: string?)
@ -28,10 +31,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return")
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break")
{
ScopedFastFlag flags[] = {
{"LuauTinyControlFlowAnalysis", true},
{"LuauLoopControlFlowAnalysis", true}
};
ScopedFastFlag flags[] = {{FFlag::LuauTinyControlFlowAnalysis, true}, {FFlag::LuauLoopControlFlowAnalysis, true}};
CheckResult result = check(R"(
local function f(x: {{value: string?}})
@ -51,10 +51,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break")
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue")
{
ScopedFastFlag flags[] = {
{"LuauTinyControlFlowAnalysis", true},
{"LuauLoopControlFlowAnalysis", true}
};
ScopedFastFlag flags[] = {{FFlag::LuauTinyControlFlowAnalysis, true}, {FFlag::LuauLoopControlFlowAnalysis, true}};
CheckResult result = check(R"(
local function f(x: {{value: string?}})
@ -74,7 +71,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue")
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_not_y_return")
{
ScopedFastFlag sff{"LuauTinyControlFlowAnalysis", true};
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
CheckResult result = check(R"(
local function f(x: string?, y: string?)
@ -96,10 +93,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_not_y_return")
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_elif_not_y_break")
{
ScopedFastFlag flags[] = {
{"LuauTinyControlFlowAnalysis", true},
{"LuauLoopControlFlowAnalysis", true}
};
ScopedFastFlag flags[] = {{FFlag::LuauTinyControlFlowAnalysis, true}, {FFlag::LuauLoopControlFlowAnalysis, true}};
CheckResult result = check(R"(
local function f(x: {{value: string?}}, y: {{value: string?}})
@ -124,10 +118,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_elif_not_y_break")
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_elif_not_y_continue")
{
ScopedFastFlag flags[] = {
{"LuauTinyControlFlowAnalysis", true},
{"LuauLoopControlFlowAnalysis", true}
};
ScopedFastFlag flags[] = {{FFlag::LuauTinyControlFlowAnalysis, true}, {FFlag::LuauLoopControlFlowAnalysis, true}};
CheckResult result = check(R"(
local function f(x: {{value: string?}}, y: {{value: string?}})
@ -152,10 +143,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_elif_not_y_continue")
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_not_y_break")
{
ScopedFastFlag flags[] = {
{"LuauTinyControlFlowAnalysis", true},
{"LuauLoopControlFlowAnalysis", true}
};
ScopedFastFlag flags[] = {{FFlag::LuauTinyControlFlowAnalysis, true}, {FFlag::LuauLoopControlFlowAnalysis, true}};
CheckResult result = check(R"(
local function f(x: {{value: string?}}, y: {{value: string?}})
@ -180,10 +168,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_not_y_break")
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_elif_not_y_continue")
{
ScopedFastFlag flags[] = {
{"LuauTinyControlFlowAnalysis", true},
{"LuauLoopControlFlowAnalysis", true}
};
ScopedFastFlag flags[] = {{FFlag::LuauTinyControlFlowAnalysis, true}, {FFlag::LuauLoopControlFlowAnalysis, true}};
CheckResult result = check(R"(
local function f(x: {{value: string?}}, y: {{value: string?}})
@ -208,7 +193,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_elif_not_y_continue")
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_rand_return_elif_not_y_return")
{
ScopedFastFlag sff{"LuauTinyControlFlowAnalysis", true};
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
CheckResult result = check(R"(
local function f(x: string?, y: string?)
@ -232,10 +217,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_rand_return_elif_not_y_
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_elif_rand_break_elif_not_y_break")
{
ScopedFastFlag flags[] = {
{"LuauTinyControlFlowAnalysis", true},
{"LuauLoopControlFlowAnalysis", true}
};
ScopedFastFlag flags[] = {{FFlag::LuauTinyControlFlowAnalysis, true}, {FFlag::LuauLoopControlFlowAnalysis, true}};
CheckResult result = check(R"(
local function f(x: {{value: string?}}, y: {{value: string?}})
@ -262,10 +244,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_elif_rand_break_elif_not_y_br
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_elif_rand_continue_elif_not_y_continue")
{
ScopedFastFlag flags[] = {
{"LuauTinyControlFlowAnalysis", true},
{"LuauLoopControlFlowAnalysis", true}
};
ScopedFastFlag flags[] = {{FFlag::LuauTinyControlFlowAnalysis, true}, {FFlag::LuauLoopControlFlowAnalysis, true}};
CheckResult result = check(R"(
local function f(x: {{value: string?}}, y: {{value: string?}})
@ -292,7 +271,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_elif_rand_continue_elif_no
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_not_rand_return_elif_not_y_fallthrough")
{
ScopedFastFlag sff{"LuauTinyControlFlowAnalysis", true};
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
CheckResult result = check(R"(
local function f(x: string?, y: string?)
@ -316,10 +295,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_not_rand_return_elif_no
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_elif_rand_break_elif_not_y_fallthrough")
{
ScopedFastFlag flags[] = {
{"LuauTinyControlFlowAnalysis", true},
{"LuauLoopControlFlowAnalysis", true}
};
ScopedFastFlag flags[] = {{FFlag::LuauTinyControlFlowAnalysis, true}, {FFlag::LuauLoopControlFlowAnalysis, true}};
CheckResult result = check(R"(
local function f(x: {{value: string?}}, y: {{value: string?}})
@ -346,10 +322,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_elif_rand_break_elif_not_y_fa
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_elif_rand_continue_elif_not_y_fallthrough")
{
ScopedFastFlag flags[] = {
{"LuauTinyControlFlowAnalysis", true},
{"LuauLoopControlFlowAnalysis", true}
};
ScopedFastFlag flags[] = {{FFlag::LuauTinyControlFlowAnalysis, true}, {FFlag::LuauLoopControlFlowAnalysis, true}};
CheckResult result = check(R"(
local function f(x: {{value: string?}}, y: {{value: string?}})
@ -376,7 +349,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_elif_rand_continue_elif_no
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_not_y_fallthrough_elif_not_z_return")
{
ScopedFastFlag sff{"LuauTinyControlFlowAnalysis", true};
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
CheckResult result = check(R"(
local function f(x: string?, y: string?, z: string?)
@ -402,10 +375,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_not_y_fallthrough_elif_
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_elif_not_y_fallthrough_elif_not_z_break")
{
ScopedFastFlag flags[] = {
{"LuauTinyControlFlowAnalysis", true},
{"LuauLoopControlFlowAnalysis", true}
};
ScopedFastFlag flags[] = {{FFlag::LuauTinyControlFlowAnalysis, true}, {FFlag::LuauLoopControlFlowAnalysis, true}};
CheckResult result = check(R"(
local function f(x: {{value: string?}}, y: {{value: string?}}, z: {{value: string?}})
@ -435,10 +405,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_elif_not_y_fallthrough_elif_n
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_elif_not_y_fallthrough_elif_not_z_continue")
{
ScopedFastFlag flags[] = {
{"LuauTinyControlFlowAnalysis", true},
{"LuauLoopControlFlowAnalysis", true}
};
ScopedFastFlag flags[] = {{FFlag::LuauTinyControlFlowAnalysis, true}, {FFlag::LuauLoopControlFlowAnalysis, true}};
CheckResult result = check(R"(
local function f(x: {{value: string?}}, y: {{value: string?}}, z: {{value: string?}})
@ -468,10 +435,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_elif_not_y_fallthrough_eli
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_elif_not_y_throw_elif_not_z_fallthrough")
{
ScopedFastFlag flags[] = {
{"LuauTinyControlFlowAnalysis", true},
{"LuauLoopControlFlowAnalysis", true}
};
ScopedFastFlag flags[] = {{FFlag::LuauTinyControlFlowAnalysis, true}, {FFlag::LuauLoopControlFlowAnalysis, true}};
CheckResult result = check(R"(
local function f(x: {{value: string?}}, y: {{value: string?}}, z: {{value: string?}})
@ -501,10 +465,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_elif_not_y_throw_elif_not_
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_not_y_fallthrough_elif_not_z_break")
{
ScopedFastFlag flags[] = {
{"LuauTinyControlFlowAnalysis", true},
{"LuauLoopControlFlowAnalysis", true}
};
ScopedFastFlag flags[] = {{FFlag::LuauTinyControlFlowAnalysis, true}, {FFlag::LuauLoopControlFlowAnalysis, true}};
CheckResult result = check(R"(
local function f(x: {{value: string?}}, y: {{value: string?}}, z: {{value: string?}})
@ -534,7 +495,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_not_y_fallthrough_elif_
TEST_CASE_FIXTURE(BuiltinsFixture, "do_if_not_x_return")
{
ScopedFastFlag sff{"LuauTinyControlFlowAnalysis", true};
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
CheckResult result = check(R"(
local function f(x: string?)
@ -554,10 +515,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "do_if_not_x_return")
TEST_CASE_FIXTURE(BuiltinsFixture, "for_record_do_if_not_x_break")
{
ScopedFastFlag flags[] = {
{"LuauTinyControlFlowAnalysis", true},
{"LuauLoopControlFlowAnalysis", true}
};
ScopedFastFlag flags[] = {{FFlag::LuauTinyControlFlowAnalysis, true}, {FFlag::LuauLoopControlFlowAnalysis, true}};
CheckResult result = check(R"(
local function f(x: {{value: string?}})
@ -579,10 +537,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "for_record_do_if_not_x_break")
TEST_CASE_FIXTURE(BuiltinsFixture, "for_record_do_if_not_x_continue")
{
ScopedFastFlag flags[] = {
{"LuauTinyControlFlowAnalysis", true},
{"LuauLoopControlFlowAnalysis", true}
};
ScopedFastFlag flags[] = {{FFlag::LuauTinyControlFlowAnalysis, true}, {FFlag::LuauLoopControlFlowAnalysis, true}};
CheckResult result = check(R"(
local function f(x: {{value: string?}})
@ -604,7 +559,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "for_record_do_if_not_x_continue")
TEST_CASE_FIXTURE(BuiltinsFixture, "early_return_in_a_loop_which_isnt_guaranteed_to_run_first")
{
ScopedFastFlag sff{"LuauTinyControlFlowAnalysis", true};
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
CheckResult result = check(R"(
local function f(x: string?)
@ -627,7 +582,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "early_return_in_a_loop_which_isnt_guaranteed
TEST_CASE_FIXTURE(BuiltinsFixture, "early_return_in_a_loop_which_is_guaranteed_to_run_first")
{
ScopedFastFlag sff{"LuauTinyControlFlowAnalysis", true};
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
CheckResult result = check(R"(
local function f(x: string?)
@ -650,7 +605,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "early_return_in_a_loop_which_is_guaranteed_t
TEST_CASE_FIXTURE(BuiltinsFixture, "early_return_in_a_loop_which_is_guaranteed_to_run_first_2")
{
ScopedFastFlag sff{"LuauTinyControlFlowAnalysis", true};
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
CheckResult result = check(R"(
local function f(x: string?)
@ -673,7 +628,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "early_return_in_a_loop_which_is_guaranteed_t
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_then_error")
{
ScopedFastFlag sff{"LuauTinyControlFlowAnalysis", true};
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
CheckResult result = check(R"(
local function f(x: string?)
@ -691,7 +646,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_then_error")
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_then_assert_false")
{
ScopedFastFlag sff{"LuauTinyControlFlowAnalysis", true};
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
CheckResult result = check(R"(
local function f(x: string?)
@ -709,7 +664,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_then_assert_false")
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_if_not_y_return")
{
ScopedFastFlag sff{"LuauTinyControlFlowAnalysis", true};
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
CheckResult result = check(R"(
local function f(x: string?, y: string?)
@ -733,10 +688,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_if_not_y_return")
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_if_not_y_break")
{
ScopedFastFlag flags[] = {
{"LuauTinyControlFlowAnalysis", true},
{"LuauLoopControlFlowAnalysis", true}
};
ScopedFastFlag flags[] = {{FFlag::LuauTinyControlFlowAnalysis, true}, {FFlag::LuauLoopControlFlowAnalysis, true}};
CheckResult result = check(R"(
local function f(x: {{value: string?}}, y: {{value: string?}})
@ -763,10 +715,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_if_not_y_break")
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_if_not_y_continue")
{
ScopedFastFlag flags[] = {
{"LuauTinyControlFlowAnalysis", true},
{"LuauLoopControlFlowAnalysis", true}
};
ScopedFastFlag flags[] = {{FFlag::LuauTinyControlFlowAnalysis, true}, {FFlag::LuauLoopControlFlowAnalysis, true}};
CheckResult result = check(R"(
local function f(x: {{value: string?}}, y: {{value: string?}})
@ -793,10 +742,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_if_not_y_continue")
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_if_not_y_throw")
{
ScopedFastFlag flags[] = {
{"LuauTinyControlFlowAnalysis", true},
{"LuauLoopControlFlowAnalysis", true}
};
ScopedFastFlag flags[] = {{FFlag::LuauTinyControlFlowAnalysis, true}, {FFlag::LuauLoopControlFlowAnalysis, true}};
CheckResult result = check(R"(
local function f(x: {{value: string?}}, y: {{value: string?}})
@ -823,10 +769,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_if_not_y_throw")
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_if_not_y_continue")
{
ScopedFastFlag flags[] = {
{"LuauTinyControlFlowAnalysis", true},
{"LuauLoopControlFlowAnalysis", true}
};
ScopedFastFlag flags[] = {{FFlag::LuauTinyControlFlowAnalysis, true}, {FFlag::LuauLoopControlFlowAnalysis, true}};
CheckResult result = check(R"(
local function f(x: {{value: string?}}, y: {{value: string?}})
@ -853,7 +796,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_if_not_y_continue")
TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_does_not_leak_out")
{
ScopedFastFlag sff{"LuauTinyControlFlowAnalysis", true};
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
CheckResult result = check(R"(
local function f(x: string?)
@ -876,10 +819,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_does_not_leak_out")
TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_does_not_leak_out_breaking")
{
ScopedFastFlag flags[] = {
{"LuauTinyControlFlowAnalysis", true},
{"LuauLoopControlFlowAnalysis", true}
};
ScopedFastFlag flags[] = {{FFlag::LuauTinyControlFlowAnalysis, true}, {FFlag::LuauLoopControlFlowAnalysis, true}};
CheckResult result = check(R"(
local function f(x: {{value: string?}})
@ -904,10 +844,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_does_not_leak_out_breaking")
TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_does_not_leak_out_continuing")
{
ScopedFastFlag flags[] = {
{"LuauTinyControlFlowAnalysis", true},
{"LuauLoopControlFlowAnalysis", true}
};
ScopedFastFlag flags[] = {{FFlag::LuauTinyControlFlowAnalysis, true}, {FFlag::LuauLoopControlFlowAnalysis, true}};
CheckResult result = check(R"(
local function f(x: {{value: string?}})
@ -932,7 +869,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_does_not_leak_out_continuing")
TEST_CASE_FIXTURE(BuiltinsFixture, "prototyping_and_visiting_alias_has_the_same_scope")
{
ScopedFastFlag sff{"LuauTinyControlFlowAnalysis", true};
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
// In CG, we walk the block to prototype aliases. We then visit the block in-order, which will resolve the prototype to a real type.
// That second walk assumes that the name occurs in the same `Scope` that the prototype walk had. If we arbitrarily change scope midway
@ -958,10 +895,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "prototyping_and_visiting_alias_has_the_same_
TEST_CASE_FIXTURE(BuiltinsFixture, "prototyping_and_visiting_alias_has_the_same_scope_breaking")
{
ScopedFastFlag flags[] = {
{"LuauTinyControlFlowAnalysis", true},
{"LuauLoopControlFlowAnalysis", true}
};
ScopedFastFlag flags[] = {{FFlag::LuauTinyControlFlowAnalysis, true}, {FFlag::LuauLoopControlFlowAnalysis, true}};
CheckResult result = check(R"(
local function f(x: {{value: string?}})
@ -986,10 +920,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "prototyping_and_visiting_alias_has_the_same_
TEST_CASE_FIXTURE(BuiltinsFixture, "prototyping_and_visiting_alias_has_the_same_scope_continuing")
{
ScopedFastFlag flags[] = {
{"LuauTinyControlFlowAnalysis", true},
{"LuauLoopControlFlowAnalysis", true}
};
ScopedFastFlag flags[] = {{FFlag::LuauTinyControlFlowAnalysis, true}, {FFlag::LuauLoopControlFlowAnalysis, true}};
CheckResult result = check(R"(
local function f(x: {{value: string?}})
@ -1014,7 +945,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "prototyping_and_visiting_alias_has_the_same_
TEST_CASE_FIXTURE(BuiltinsFixture, "tagged_unions")
{
ScopedFastFlag sff{"LuauTinyControlFlowAnalysis", true};
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
CheckResult result = check(R"(
type Ok<T> = { tag: "ok", value: T }
@ -1049,10 +980,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tagged_unions")
TEST_CASE_FIXTURE(BuiltinsFixture, "tagged_unions_breaking")
{
ScopedFastFlag flags[] = {
{"LuauTinyControlFlowAnalysis", true},
{"LuauLoopControlFlowAnalysis", true}
};
ScopedFastFlag flags[] = {{FFlag::LuauTinyControlFlowAnalysis, true}, {FFlag::LuauLoopControlFlowAnalysis, true}};
CheckResult result = check(R"(
type Ok<T> = { tag: "ok", value: T }
@ -1085,10 +1013,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tagged_unions_breaking")
TEST_CASE_FIXTURE(BuiltinsFixture, "tagged_unions_continuing")
{
ScopedFastFlag flags[] = {
{"LuauTinyControlFlowAnalysis", true},
{"LuauLoopControlFlowAnalysis", true}
};
ScopedFastFlag flags[] = {{FFlag::LuauTinyControlFlowAnalysis, true}, {FFlag::LuauLoopControlFlowAnalysis, true}};
CheckResult result = check(R"(
type Ok<T> = { tag: "ok", value: T }
@ -1121,7 +1046,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tagged_unions_continuing")
TEST_CASE_FIXTURE(BuiltinsFixture, "do_assert_x")
{
ScopedFastFlag sff{"LuauTinyControlFlowAnalysis", true};
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
CheckResult result = check(R"(
local function f(x: string?)

View File

@ -13,6 +13,7 @@ using namespace Luau;
using std::nullopt;
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
LUAU_FASTFLAG(LuauAlwaysCommitInferencesOfFunctionCalls);
TEST_SUITE_BEGIN("TypeInferClasses");
@ -368,7 +369,7 @@ b.X = 2 -- real Vector2.X is also read-only
TEST_CASE_FIXTURE(ClassFixture, "detailed_class_unification_error")
{
ScopedFastFlag sff[] = {
{"LuauAlwaysCommitInferencesOfFunctionCalls", true},
{FFlag::LuauAlwaysCommitInferencesOfFunctionCalls, true},
};
CheckResult result = check(R"(
local function foo(v)

View File

@ -7,6 +7,8 @@
#include "doctest.h"
LUAU_FASTFLAG(LuauDefinitionFileSetModuleName)
using namespace Luau;
TEST_SUITE_BEGIN("DefinitionTests");
@ -441,4 +443,27 @@ TEST_CASE_FIXTURE(Fixture, "class_definitions_reference_other_classes")
REQUIRE(result.success);
}
TEST_CASE_FIXTURE(Fixture, "definition_file_has_source_module_name_set")
{
ScopedFastFlag sff{FFlag::LuauDefinitionFileSetModuleName, true};
LoadDefinitionFileResult result = loadDefinition(R"(
declare class Foo
end
)");
REQUIRE(result.success);
CHECK_EQ(result.sourceModule.name, "@test");
CHECK_EQ(result.sourceModule.humanReadableName, "@test");
std::optional<TypeFun> fooTy = frontend.globals.globalScope->lookupType("Foo");
REQUIRE(fooTy);
const ClassType* ctv = get<ClassType>(fooTy->type);
REQUIRE(ctv);
CHECK_EQ(ctv->definitionModuleName, "@test");
}
TEST_SUITE_END();

View File

@ -16,6 +16,8 @@ using namespace Luau;
LUAU_FASTFLAG(LuauInstantiateInSubtyping);
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
LUAU_FASTFLAG(LuauAlwaysCommitInferencesOfFunctionCalls);
LUAU_FASTINT(LuauTarjanChildLimit);
TEST_SUITE_BEGIN("TypeInferFunctions");
@ -1911,7 +1913,7 @@ end
TEST_CASE_FIXTURE(BuiltinsFixture, "dont_assert_when_the_tarjan_limit_is_exceeded_during_generalization")
{
ScopedFastInt sfi{"LuauTarjanChildLimit", 2};
ScopedFastInt sfi{FInt::LuauTarjanChildLimit, 2};
if (!FFlag::DebugLuauDeferredConstraintResolution)
return;
@ -1995,7 +1997,7 @@ TEST_CASE_FIXTURE(Fixture, "function_exprs_are_generalized_at_signature_scope_no
TEST_CASE_FIXTURE(BuiltinsFixture, "param_1_and_2_both_takes_the_same_generic_but_their_arguments_are_incompatible")
{
ScopedFastFlag sff[] = {
{"LuauAlwaysCommitInferencesOfFunctionCalls", true},
{FFlag::LuauAlwaysCommitInferencesOfFunctionCalls, true},
};
CheckResult result = check(R"(
@ -2050,7 +2052,7 @@ Table type '{ x: number }' not compatible with type 'vec2' because the former is
TEST_CASE_FIXTURE(BuiltinsFixture, "param_1_and_2_both_takes_the_same_generic_but_their_arguments_are_incompatible_2")
{
ScopedFastFlag sff{"LuauAlwaysCommitInferencesOfFunctionCalls", true};
ScopedFastFlag sff{FFlag::LuauAlwaysCommitInferencesOfFunctionCalls, true};
CheckResult result = check(R"(
local function f<a>(x: a, y: a): a
@ -2103,7 +2105,7 @@ TEST_CASE_FIXTURE(Fixture, "generic_packs_are_not_variadic")
if (!FFlag::DebugLuauDeferredConstraintResolution)
return;
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
CheckResult result = check(R"(
local function apply<a, b..., c...>(f: (a, b...) -> c..., x: a)

View File

@ -11,6 +11,7 @@
LUAU_FASTFLAG(LuauInstantiateInSubtyping);
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
LUAU_FASTFLAG(DebugLuauSharedSelf);
using namespace Luau;
@ -274,7 +275,7 @@ TEST_CASE_FIXTURE(Fixture, "infer_nested_generic_function")
TEST_CASE_FIXTURE(Fixture, "infer_generic_methods")
{
ScopedFastFlag sff{"DebugLuauSharedSelf", true};
ScopedFastFlag sff{FFlag::DebugLuauSharedSelf, true};
CheckResult result = check(R"(
local x = {}
@ -1260,7 +1261,7 @@ end
TEST_CASE_FIXTURE(BuiltinsFixture, "higher_rank_polymorphism_should_not_accept_instantiated_arguments")
{
ScopedFastFlag sffs[] = {
{"LuauInstantiateInSubtyping", true},
{FFlag::LuauInstantiateInSubtyping, true},
};
CheckResult result = check(R"(

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