Sync to upstream/release/631 (#1299)

### What's new

* Added lint warning for using redundant `@native` attributes on
functions inside a `--!native` module
* Improved typechecking speed in old solver for modules with large types

### New Solver

* Fixed the length type function sealing the table prematurely
* Fixed crashes caused by general table indexing expressions

### VM

* Added support for a specialized 3-argument fast-call instruction to
improve performance of `vector` constructor, `buffer` writes and a few
`bit32` methods

---

### Internal Contributors

Co-authored-by: Aaron Weiss <aaronweiss@roblox.com>
Co-authored-by: Andy Friesen <afriesen@roblox.com>
Co-authored-by: Aviral Goel <agoel@roblox.com>
Co-authored-by: Vighnesh Vijay <vvijay@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>

---------

Co-authored-by: Aaron Weiss <aaronweiss@roblox.com>
Co-authored-by: Alexander McCord <amccord@roblox.com>
Co-authored-by: Andy Friesen <afriesen@roblox.com>
Co-authored-by: Vighnesh <vvijay@roblox.com>
Co-authored-by: Aviral Goel <agoel@roblox.com>
Co-authored-by: David Cope <dcope@roblox.com>
Co-authored-by: Lily Brown <lbrown@roblox.com>
This commit is contained in:
vegorov-rbx 2024-06-20 16:37:55 -07:00 committed by GitHub
parent 7d4033071a
commit caee04d82d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
63 changed files with 1433 additions and 1160 deletions

View file

@ -300,7 +300,7 @@ public:
* @returns a non-free type that generalizes the argument, or `std::nullopt` if one
* does not exist
*/
std::optional<TypeId> generalizeFreeType(NotNull<Scope> scope, TypeId type);
std::optional<TypeId> generalizeFreeType(NotNull<Scope> scope, TypeId type, bool avoidSealingTables = false);
/**
* Checks the existing set of constraints to see if there exist any that contain

View file

@ -8,6 +8,6 @@
namespace Luau
{
std::optional<TypeId> generalize(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, NotNull<Scope> scope, NotNull<DenseHashSet<TypeId>> bakedTypes, TypeId ty);
std::optional<TypeId> generalize(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, NotNull<Scope> scope,
NotNull<DenseHashSet<TypeId>> bakedTypes, TypeId ty, /* avoid sealing tables*/ bool avoidSealingTables = false);
}

View file

@ -27,12 +27,16 @@ struct ReplaceGenerics : Substitution
{
}
void resetState(const TxnLog* log, TypeArena* arena, NotNull<BuiltinTypes> builtinTypes, TypeLevel level, Scope* scope,
const std::vector<TypeId>& generics, const std::vector<TypePackId>& genericPacks);
NotNull<BuiltinTypes> builtinTypes;
TypeLevel level;
Scope* scope;
std::vector<TypeId> generics;
std::vector<TypePackId> genericPacks;
bool ignoreChildren(TypeId ty) override;
bool isDirty(TypeId ty) override;
bool isDirty(TypePackId tp) override;
@ -48,13 +52,19 @@ struct Instantiation : Substitution
, builtinTypes(builtinTypes)
, level(level)
, scope(scope)
, reusableReplaceGenerics(log, arena, builtinTypes, level, scope, {}, {})
{
}
void resetState(const TxnLog* log, TypeArena* arena, NotNull<BuiltinTypes> builtinTypes, TypeLevel level, Scope* scope);
NotNull<BuiltinTypes> builtinTypes;
TypeLevel level;
Scope* scope;
ReplaceGenerics reusableReplaceGenerics;
bool ignoreChildren(TypeId ty) override;
bool isDirty(TypeId ty) override;
bool isDirty(TypePackId tp) override;

View file

@ -134,7 +134,8 @@ struct Tarjan
TarjanResult visitRoot(TypeId ty);
TarjanResult visitRoot(TypePackId ty);
void clearTarjan();
// Used to reuse the object for a new operation
void clearTarjan(const TxnLog* log);
// Get/set the dirty bit for an index (grows the vector if needed)
bool getDirty(int index);
@ -212,6 +213,8 @@ public:
std::optional<TypeId> substitute(TypeId ty);
std::optional<TypePackId> substitute(TypePackId tp);
void resetState(const TxnLog* log, TypeArena* arena);
TypeId replace(TypeId ty);
TypePackId replace(TypePackId tp);

View file

@ -82,8 +82,8 @@ struct TypeFamilyReductionResult
};
template<typename T>
using ReducerFunction = std::function<TypeFamilyReductionResult<T>(T, const std::vector<TypeId>&, const std::vector<TypePackId>&,
NotNull<TypeFamilyContext>)>;
using ReducerFunction =
std::function<TypeFamilyReductionResult<T>(T, const std::vector<TypeId>&, const std::vector<TypePackId>&, NotNull<TypeFamilyContext>)>;
/// Represents a type function that may be applied to map a series of types and
/// type packs to a single output type.

View file

@ -4,6 +4,7 @@
#include "Luau/Anyification.h"
#include "Luau/ControlFlow.h"
#include "Luau/Error.h"
#include "Luau/Instantiation.h"
#include "Luau/Module.h"
#include "Luau/Predicate.h"
#include "Luau/Substitution.h"
@ -362,6 +363,8 @@ public:
UnifierSharedState unifierState;
Normalizer normalizer;
Instantiation reusableInstantiation;
std::vector<RequireCycle> requireCycles;
// Type inference limits

View file

@ -256,21 +256,44 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
TypeId tableMetaMT = arena.addType(MetatableType{tabTy, genericMT});
// getmetatable : <MT>({ @metatable MT, {+ +} }) -> MT
addGlobalBinding(globals, "getmetatable", makeFunction(arena, std::nullopt, {genericMT}, {}, {tableMetaMT}, {genericMT}), "@luau");
// clang-format off
// setmetatable<T: {}, MT>(T, MT) -> { @metatable MT, T }
addGlobalBinding(globals, "setmetatable",
arena.addType(
FunctionType{
{genericMT},
{},
arena.addTypePack(TypePack{{tabTy, genericMT}}),
arena.addTypePack(TypePack{{tableMetaMT}})
}
), "@luau"
);
// clang-format on
if (FFlag::DebugLuauDeferredConstraintResolution)
{
TypeId genericT = arena.addType(GenericType{"T"});
TypeId tMetaMT = arena.addType(MetatableType{genericT, genericMT});
// clang-format off
// setmetatable<T: {}, MT>(T, MT) -> { @metatable MT, T }
addGlobalBinding(globals, "setmetatable",
arena.addType(
FunctionType{
{genericT, genericMT},
{},
arena.addTypePack(TypePack{{genericT, genericMT}}),
arena.addTypePack(TypePack{{tMetaMT}})
}
), "@luau"
);
// clang-format on
}
else
{
// clang-format off
// setmetatable<T: {}, MT>(T, MT) -> { @metatable MT, T }
addGlobalBinding(globals, "setmetatable",
arena.addType(
FunctionType{
{genericMT},
{},
arena.addTypePack(TypePack{{tabTy, genericMT}}),
arena.addTypePack(TypePack{{tableMetaMT}})
}
), "@luau"
);
// clang-format on
}
for (const auto& pair : globals.globalScope->bindings)
{

View file

@ -787,7 +787,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocal* stat
auto uc = addConstraint(scope, statLocal->location, UnpackConstraint{valueTypes, rvaluePack});
for (TypeId t: valueTypes)
for (TypeId t : valueTypes)
getMutable<BlockedType>(t)->setOwner(uc);
}
@ -920,7 +920,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatForIn* forI
auto iterable = addConstraint(
loopScope, getLocation(forIn->values), IterableConstraint{iterator, variableTypes, forIn->values.data[0], &module->astForInNextTypes});
for (TypeId var: variableTypes)
for (TypeId var : variableTypes)
{
auto bt = getMutable<BlockedType>(var);
LUAU_ASSERT(bt);
@ -1171,7 +1171,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatAssign* ass
auto uc = addConstraint(scope, assign->location, UnpackConstraint{valueTypes, resultPack});
for (TypeId t: valueTypes)
for (TypeId t : valueTypes)
getMutable<BlockedType>(t)->setOwner(uc);
}

View file

@ -694,7 +694,7 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
}
for (TypeId ty : c.interiorTypes)
generalize(NotNull{arena}, builtinTypes, constraint->scope, generalizedTypes, ty);
generalize(NotNull{arena}, builtinTypes, constraint->scope, generalizedTypes, ty, /* avoidSealingTables */ false);
return true;
}
@ -769,13 +769,8 @@ bool ConstraintSolver::tryDispatch(const IterableConstraint& c, NotNull<const Co
{
TypeId keyTy = freshType(arena, builtinTypes, constraint->scope);
TypeId valueTy = freshType(arena, builtinTypes, constraint->scope);
TypeId tableTy = arena->addType(TableType{
TableType::Props{},
TableIndexer{keyTy, valueTy},
TypeLevel{},
constraint->scope,
TableState::Free
});
TypeId tableTy =
arena->addType(TableType{TableType::Props{}, TableIndexer{keyTy, valueTy}, TypeLevel{}, constraint->scope, TableState::Free});
unify(constraint, nextTy, tableTy);
@ -1022,13 +1017,10 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
const TableType* tfTable = getTableType(tf->type);
//clang-format off
bool needsClone =
follow(tf->type) == target ||
(tfTable != nullptr && tfTable == getTableType(target)) ||
std::any_of(typeArguments.begin(), typeArguments.end(), [&](const auto& other) {
return other == target;
}
);
bool needsClone = follow(tf->type) == target || (tfTable != nullptr && tfTable == getTableType(target)) ||
std::any_of(typeArguments.begin(), typeArguments.end(), [&](const auto& other) {
return other == target;
});
//clang-format on
// Only tables have the properties we're trying to set.
@ -1446,6 +1438,8 @@ bool ConstraintSolver::tryDispatchHasIndexer(
bind(constraint, resultType, tbl->indexer->indexResultType);
return true;
}
else if (auto mt = get<MetatableType>(follow(ft->upperBound)))
return tryDispatchHasIndexer(recursionDepth, constraint, mt->table, indexType, resultType, seen);
FreeType freeResult{ft->scope, builtinTypes->neverType, builtinTypes->unknownType};
emplace<FreeType>(constraint, resultType, freeResult);
@ -1461,11 +1455,11 @@ bool ConstraintSolver::tryDispatchHasIndexer(
if (auto indexer = tt->indexer)
{
unify(constraint, indexType, indexer->indexType);
bind(constraint, resultType, indexer->indexResultType);
return true;
}
else if (tt->state == TableState::Unsealed)
if (tt->state == TableState::Unsealed)
{
// FIXME this is greedy.
@ -2067,7 +2061,7 @@ bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const Iterabl
}
auto unpack = [&](TypeId ty) {
for (TypeId varTy: c.variables)
for (TypeId varTy : c.variables)
{
LUAU_ASSERT(get<BlockedType>(varTy));
LUAU_ASSERT(varTy != ty);
@ -2228,11 +2222,12 @@ bool ConstraintSolver::tryDispatchIterableFunction(
return true;
}
NotNull<const Constraint> ConstraintSolver::unpackAndAssign(const std::vector<TypeId> destTypes, TypePackId srcTypes, NotNull<const Constraint> constraint)
NotNull<const Constraint> ConstraintSolver::unpackAndAssign(
const std::vector<TypeId> destTypes, TypePackId srcTypes, NotNull<const Constraint> constraint)
{
auto c = pushConstraint(constraint->scope, constraint->location, UnpackConstraint{destTypes, srcTypes});
for (TypeId t: destTypes)
for (TypeId t : destTypes)
{
BlockedType* bt = getMutable<BlockedType>(t);
LUAU_ASSERT(bt);
@ -2834,7 +2829,7 @@ void ConstraintSolver::shiftReferences(TypeId source, TypeId target)
targetRefs += count;
}
std::optional<TypeId> ConstraintSolver::generalizeFreeType(NotNull<Scope> scope, TypeId type)
std::optional<TypeId> ConstraintSolver::generalizeFreeType(NotNull<Scope> scope, TypeId type, bool avoidSealingTables)
{
TypeId t = follow(type);
if (get<FreeType>(t))
@ -2849,7 +2844,7 @@ std::optional<TypeId> ConstraintSolver::generalizeFreeType(NotNull<Scope> scope,
// that until all constraint generation is complete.
}
return generalize(NotNull{arena}, builtinTypes, scope, generalizedTypes, type);
return generalize(NotNull{arena}, builtinTypes, scope, generalizedTypes, type, avoidSealingTables);
}
bool ConstraintSolver::hasUnresolvedConstraints(TypeId ty)

View file

@ -65,21 +65,15 @@ namespace Luau
{
// this list of binary operator type families is used for better stringification of type families errors
static const std::unordered_map<std::string, const char*> kBinaryOps{
{"add", "+"}, {"sub", "-"}, {"mul", "*"}, {"div", "/"}, {"idiv", "//"}, {"pow", "^"}, {"mod", "%"}, {"concat", ".."}, {"and", "and"},
{"or", "or"}, {"lt", "< or >="}, {"le", "<= or >"}, {"eq", "== or ~="}
};
static const std::unordered_map<std::string, const char*> kBinaryOps{{"add", "+"}, {"sub", "-"}, {"mul", "*"}, {"div", "/"}, {"idiv", "//"},
{"pow", "^"}, {"mod", "%"}, {"concat", ".."}, {"and", "and"}, {"or", "or"}, {"lt", "< or >="}, {"le", "<= or >"}, {"eq", "== or ~="}};
// this list of unary operator type families is used for better stringification of type families errors
static const std::unordered_map<std::string, const char*> kUnaryOps{
{"unm", "-"}, {"len", "#"}, {"not", "not"}
};
static const std::unordered_map<std::string, const char*> kUnaryOps{{"unm", "-"}, {"len", "#"}, {"not", "not"}};
// this list of type families will receive a special error indicating that the user should file a bug on the GitHub repository
// putting a type family in this list indicates that it is expected to _always_ reduce
static const std::unordered_set<std::string> kUnreachableTypeFamilies{
"refine", "singleton", "union", "intersect"
};
static const std::unordered_set<std::string> kUnreachableTypeFamilies{"refine", "singleton", "union", "intersect"};
struct ErrorConverter
{
@ -682,7 +676,7 @@ struct ErrorConverter
if (kUnreachableTypeFamilies.count(tfit->family->name))
{
return "Type family instance " + Luau::toString(e.ty) + " is uninhabited\n" +
"This is likely to be a bug, please report it at https://github.com/luau-lang/luau/issues";
"This is likely to be a bug, please report it at https://github.com/luau-lang/luau/issues";
}
// Everything should be specialized above to report a more descriptive error that hopefully does not mention "type families" explicitly.

View file

@ -24,15 +24,17 @@ struct MutatingGeneralizer : TypeOnceVisitor
std::vector<TypePackId> genericPacks;
bool isWithinFunction = false;
bool avoidSealingTables = false;
MutatingGeneralizer(NotNull<BuiltinTypes> builtinTypes, NotNull<Scope> scope, NotNull<DenseHashSet<TypeId>> cachedTypes, DenseHashMap<const void*, size_t> positiveTypes,
DenseHashMap<const void*, size_t> negativeTypes)
MutatingGeneralizer(NotNull<BuiltinTypes> builtinTypes, NotNull<Scope> scope, NotNull<DenseHashSet<TypeId>> cachedTypes,
DenseHashMap<const void*, size_t> positiveTypes, DenseHashMap<const void*, size_t> negativeTypes, bool avoidSealingTables)
: TypeOnceVisitor(/* skipBoundTypes */ true)
, builtinTypes(builtinTypes)
, scope(scope)
, cachedTypes(cachedTypes)
, positiveTypes(std::move(positiveTypes))
, negativeTypes(std::move(negativeTypes))
, avoidSealingTables(avoidSealingTables)
{
}
@ -268,7 +270,8 @@ struct MutatingGeneralizer : TypeOnceVisitor
TableType* tt = getMutable<TableType>(ty);
LUAU_ASSERT(tt);
tt->state = TableState::Sealed;
if (!avoidSealingTables)
tt->state = TableState::Sealed;
return true;
}
@ -338,31 +341,31 @@ struct FreeTypeSearcher : TypeVisitor
{
switch (polarity)
{
case Positive:
{
if (seenPositive.contains(ty))
return true;
case Positive:
{
if (seenPositive.contains(ty))
return true;
seenPositive.insert(ty);
return false;
}
case Negative:
{
if (seenNegative.contains(ty))
return true;
seenPositive.insert(ty);
return false;
}
case Negative:
{
if (seenNegative.contains(ty))
return true;
seenNegative.insert(ty);
return false;
}
case Both:
{
if (seenPositive.contains(ty) && seenNegative.contains(ty))
return true;
seenNegative.insert(ty);
return false;
}
case Both:
{
if (seenPositive.contains(ty) && seenNegative.contains(ty))
return true;
seenPositive.insert(ty);
seenNegative.insert(ty);
return false;
}
seenPositive.insert(ty);
seenNegative.insert(ty);
return false;
}
}
return false;
@ -519,7 +522,8 @@ struct TypeCacher : TypeOnceVisitor
explicit TypeCacher(NotNull<DenseHashSet<TypeId>> cachedTypes)
: TypeOnceVisitor(/* skipBoundTypes */ true)
, cachedTypes(cachedTypes)
{}
{
}
void cache(TypeId ty)
{
@ -611,7 +615,7 @@ struct TypeCacher : TypeOnceVisitor
traverse(ft.argTypes);
traverse(ft.retTypes);
for (TypeId gen: ft.generics)
for (TypeId gen : ft.generics)
traverse(gen);
bool uncacheable = false;
@ -622,7 +626,7 @@ struct TypeCacher : TypeOnceVisitor
else if (isUncacheable(ft.retTypes))
uncacheable = true;
for (TypeId argTy: ft.argTypes)
for (TypeId argTy : ft.argTypes)
{
if (isUncacheable(argTy))
{
@ -631,7 +635,7 @@ struct TypeCacher : TypeOnceVisitor
}
}
for (TypeId retTy: ft.retTypes)
for (TypeId retTy : ft.retTypes)
{
if (isUncacheable(retTy))
{
@ -640,7 +644,7 @@ struct TypeCacher : TypeOnceVisitor
}
}
for (TypeId g: ft.generics)
for (TypeId g : ft.generics)
{
if (isUncacheable(g))
{
@ -863,7 +867,8 @@ struct TypeCacher : TypeOnceVisitor
}
};
std::optional<TypeId> generalize(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, NotNull<Scope> scope, NotNull<DenseHashSet<TypeId>> cachedTypes, TypeId ty)
std::optional<TypeId> generalize(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, NotNull<Scope> scope,
NotNull<DenseHashSet<TypeId>> cachedTypes, TypeId ty, bool avoidSealingTables)
{
ty = follow(ty);
@ -876,14 +881,14 @@ std::optional<TypeId> generalize(NotNull<TypeArena> arena, NotNull<BuiltinTypes>
FreeTypeSearcher fts{scope, cachedTypes};
fts.traverse(ty);
MutatingGeneralizer gen{builtinTypes, scope, cachedTypes, std::move(fts.positiveTypes), std::move(fts.negativeTypes)};
MutatingGeneralizer gen{builtinTypes, scope, cachedTypes, std::move(fts.positiveTypes), std::move(fts.negativeTypes), avoidSealingTables};
gen.traverse(ty);
/* MutatingGeneralizer mutates types in place, so it is possible that ty has
* been transmuted to a BoundType. We must follow it again and verify that
* we are allowed to mutate it before we attach generics to it.
*/
* been transmuted to a BoundType. We must follow it again and verify that
* we are allowed to mutate it before we attach generics to it.
*/
ty = follow(ty);
if (ty->owningArena != arena || ty->persistent)

View file

@ -11,10 +11,23 @@
#include <algorithm>
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
LUAU_FASTFLAG(LuauReusableSubstitutions)
namespace Luau
{
void Instantiation::resetState(const TxnLog* log, TypeArena* arena, NotNull<BuiltinTypes> builtinTypes, TypeLevel level, Scope* scope)
{
LUAU_ASSERT(FFlag::LuauReusableSubstitutions);
Substitution::resetState(log, arena);
this->builtinTypes = builtinTypes;
this->level = level;
this->scope = scope;
}
bool Instantiation::isDirty(TypeId ty)
{
if (const FunctionType* ftv = log->getMutable<FunctionType>(ty))
@ -58,13 +71,26 @@ TypeId Instantiation::clean(TypeId ty)
clone.argNames = ftv->argNames;
TypeId result = addType(std::move(clone));
// Annoyingly, we have to do this even if there are no generics,
// to replace any generic tables.
ReplaceGenerics replaceGenerics{log, arena, builtinTypes, level, scope, ftv->generics, ftv->genericPacks};
if (FFlag::LuauReusableSubstitutions)
{
// Annoyingly, we have to do this even if there are no generics,
// to replace any generic tables.
reusableReplaceGenerics.resetState(log, arena, builtinTypes, level, scope, ftv->generics, ftv->genericPacks);
// TODO: What to do if this returns nullopt?
// We don't have access to the error-reporting machinery
result = replaceGenerics.substitute(result).value_or(result);
// TODO: What to do if this returns nullopt?
// We don't have access to the error-reporting machinery
result = reusableReplaceGenerics.substitute(result).value_or(result);
}
else
{
// Annoyingly, we have to do this even if there are no generics,
// to replace any generic tables.
ReplaceGenerics replaceGenerics{log, arena, builtinTypes, level, scope, ftv->generics, ftv->genericPacks};
// TODO: What to do if this returns nullopt?
// We don't have access to the error-reporting machinery
result = replaceGenerics.substitute(result).value_or(result);
}
asMutable(result)->documentationSymbol = ty->documentationSymbol;
return result;
@ -76,6 +102,22 @@ TypePackId Instantiation::clean(TypePackId tp)
return tp;
}
void ReplaceGenerics::resetState(const TxnLog* log, TypeArena* arena, NotNull<BuiltinTypes> builtinTypes, TypeLevel level, Scope* scope,
const std::vector<TypeId>& generics, const std::vector<TypePackId>& genericPacks)
{
LUAU_ASSERT(FFlag::LuauReusableSubstitutions);
Substitution::resetState(log, arena);
this->builtinTypes = builtinTypes;
this->level = level;
this->scope = scope;
this->generics = generics;
this->genericPacks = genericPacks;
}
bool ReplaceGenerics::ignoreChildren(TypeId ty)
{
if (const FunctionType* ftv = log->getMutable<FunctionType>(ty))

View file

@ -16,6 +16,11 @@ LUAU_FASTINTVARIABLE(LuauSuggestionDistance, 4)
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
LUAU_FASTFLAG(LuauAttributeSyntax)
LUAU_FASTFLAG(LuauAttribute)
LUAU_FASTFLAG(LuauNativeAttribute)
LUAU_FASTFLAGVARIABLE(LintRedundantNativeAttribute, false)
namespace Luau
{
@ -2922,6 +2927,64 @@ static void lintComments(LintContext& context, const std::vector<HotComment>& ho
}
}
static bool hasNativeCommentDirective(const std::vector<HotComment>& hotcomments)
{
LUAU_ASSERT(FFlag::LuauAttributeSyntax);
LUAU_ASSERT(FFlag::LuauNativeAttribute);
LUAU_ASSERT(FFlag::LintRedundantNativeAttribute);
for (const HotComment& hc : hotcomments)
{
if (hc.content.empty() || hc.content[0] == ' ' || hc.content[0] == '\t')
continue;
if (hc.header)
{
size_t space = hc.content.find_first_of(" \t");
std::string_view first = std::string_view(hc.content).substr(0, space);
if (first == "native")
return true;
}
}
return false;
}
struct LintRedundantNativeAttribute : AstVisitor
{
public:
LUAU_NOINLINE static void process(LintContext& context)
{
LUAU_ASSERT(FFlag::LuauAttributeSyntax);
LUAU_ASSERT(FFlag::LuauNativeAttribute);
LUAU_ASSERT(FFlag::LintRedundantNativeAttribute);
LintRedundantNativeAttribute pass;
pass.context = &context;
context.root->visit(&pass);
}
private:
LintContext* context;
bool visit(AstExprFunction* node) override
{
node->body->visit(this);
for (const auto attribute : node->attributes)
{
if (attribute->type == AstAttr::Type::Native)
{
emitWarning(*context, LintWarning::Code_RedundantNativeAttribute, attribute->location,
"native attribute on a function is redundant in a native module; consider removing it");
}
}
return false;
}
};
std::vector<LintWarning> lint(AstStat* root, const AstNameTable& names, const ScopePtr& env, const Module* module,
const std::vector<HotComment>& hotcomments, const LintOptions& options)
{
@ -3008,6 +3071,13 @@ std::vector<LintWarning> lint(AstStat* root, const AstNameTable& names, const Sc
if (context.warningEnabled(LintWarning::Code_ComparisonPrecedence))
LintComparisonPrecedence::process(context);
if (FFlag::LuauAttributeSyntax && FFlag::LuauNativeAttribute && FFlag::LintRedundantNativeAttribute &&
context.warningEnabled(LintWarning::Code_RedundantNativeAttribute))
{
if (hasNativeCommentDirective(hotcomments))
LintRedundantNativeAttribute::process(context);
}
std::sort(context.result.begin(), context.result.end(), WarningComparator());
return context.result;

View file

@ -11,6 +11,7 @@
LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 10000)
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
LUAU_FASTINTVARIABLE(LuauTarjanPreallocationSize, 256);
LUAU_FASTFLAG(LuauReusableSubstitutions)
namespace Luau
{
@ -146,6 +147,8 @@ static TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log, bool a
}
Tarjan::Tarjan()
: typeToIndex(nullptr, FFlag::LuauReusableSubstitutions ? FInt::LuauTarjanPreallocationSize : 0)
, packToIndex(nullptr, FFlag::LuauReusableSubstitutions ? FInt::LuauTarjanPreallocationSize : 0)
{
nodes.reserve(FInt::LuauTarjanPreallocationSize);
stack.reserve(FInt::LuauTarjanPreallocationSize);
@ -446,14 +449,31 @@ TarjanResult Tarjan::visitRoot(TypePackId tp)
return loop();
}
void Tarjan::clearTarjan()
void Tarjan::clearTarjan(const TxnLog* log)
{
typeToIndex.clear();
packToIndex.clear();
if (FFlag::LuauReusableSubstitutions)
{
typeToIndex.clear(~0u);
packToIndex.clear(~0u);
}
else
{
typeToIndex.clear();
packToIndex.clear();
}
nodes.clear();
stack.clear();
if (FFlag::LuauReusableSubstitutions)
{
childCount = 0;
// childLimit setting stays the same
this->log = log;
}
edgesTy.clear();
edgesTp.clear();
worklist.clear();
@ -528,7 +548,6 @@ Substitution::Substitution(const TxnLog* log_, TypeArena* arena)
{
log = log_;
LUAU_ASSERT(log);
LUAU_ASSERT(arena);
}
void Substitution::dontTraverseInto(TypeId ty)
@ -546,7 +565,7 @@ std::optional<TypeId> Substitution::substitute(TypeId ty)
ty = log->follow(ty);
// clear algorithm state for reentrancy
clearTarjan();
clearTarjan(log);
auto result = findDirty(ty);
if (result != TarjanResult::Ok)
@ -579,7 +598,7 @@ std::optional<TypePackId> Substitution::substitute(TypePackId tp)
tp = log->follow(tp);
// clear algorithm state for reentrancy
clearTarjan();
clearTarjan(log);
auto result = findDirty(tp);
if (result != TarjanResult::Ok)
@ -607,6 +626,23 @@ std::optional<TypePackId> Substitution::substitute(TypePackId tp)
return newTp;
}
void Substitution::resetState(const TxnLog* log, TypeArena* arena)
{
LUAU_ASSERT(FFlag::LuauReusableSubstitutions);
clearTarjan(log);
this->arena = arena;
newTypes.clear();
newPacks.clear();
replacedTypes.clear();
replacedTypePacks.clear();
noTraverseTypes.clear();
noTraverseTypePacks.clear();
}
TypeId Substitution::clone(TypeId ty)
{
return shallowClone(ty, *arena, log, /* alwaysClone */ true);

View file

@ -1540,6 +1540,24 @@ struct TypeChecker2
visitExprName(indexName->expr, indexName->location, indexName->index.value, context, builtinTypes->stringType);
}
void indexExprMetatableHelper(AstExprIndexExpr* indexExpr, const MetatableType* metaTable, TypeId exprType, TypeId indexType)
{
if (auto tt = get<TableType>(follow(metaTable->table)); tt && tt->indexer)
testIsSubtype(indexType, tt->indexer->indexType, indexExpr->index->location);
else if (auto mt = get<MetatableType>(follow(metaTable->table)))
indexExprMetatableHelper(indexExpr, mt, exprType, indexType);
else if (auto tmt = get<TableType>(follow(metaTable->metatable)); tmt && tmt->indexer)
testIsSubtype(indexType, tmt->indexer->indexType, indexExpr->index->location);
else if (auto mtmt = get<MetatableType>(follow(metaTable->metatable)))
indexExprMetatableHelper(indexExpr, mtmt, exprType, indexType);
else
{
LUAU_ASSERT(tt || get<PrimitiveType>(follow(metaTable->table)));
reportError(CannotExtendTable{exprType, CannotExtendTable::Indexer, "indexer??"}, indexExpr->location);
}
}
void visit(AstExprIndexExpr* indexExpr, ValueContext context)
{
if (auto str = indexExpr->index->as<AstExprConstantString>())
@ -1565,15 +1583,7 @@ struct TypeChecker2
}
else if (auto mt = get<MetatableType>(exprType))
{
const TableType* tt = get<TableType>(follow(mt->table));
LUAU_ASSERT(tt);
if (tt->indexer)
testIsSubtype(indexType, tt->indexer->indexType, indexExpr->index->location);
else
{
// TODO: Maybe the metatable has a suitable indexer?
reportError(CannotExtendTable{exprType, CannotExtendTable::Indexer, "indexer??"}, indexExpr->location);
}
return indexExprMetatableHelper(indexExpr, mt, exprType, indexType);
}
else if (auto cls = get<ClassType>(exprType))
{

View file

@ -344,8 +344,7 @@ struct FamilyReducer
if (tryGuessing(subject))
return;
TypeFamilyReductionResult<TypeId> result =
tfit->family->reducer(subject, tfit->typeArguments, tfit->packArguments, NotNull{&ctx});
TypeFamilyReductionResult<TypeId> result = tfit->family->reducer(subject, tfit->typeArguments, tfit->packArguments, NotNull{&ctx});
handleFamilyReduction(subject, result);
}
}
@ -369,8 +368,7 @@ struct FamilyReducer
if (tryGuessing(subject))
return;
TypeFamilyReductionResult<TypePackId> result =
tfit->family->reducer(subject, tfit->typeArguments, tfit->packArguments, NotNull{&ctx});
TypeFamilyReductionResult<TypePackId> result = tfit->family->reducer(subject, tfit->typeArguments, tfit->packArguments, NotNull{&ctx});
handleFamilyReduction(subject, result);
}
}
@ -451,8 +449,8 @@ bool isPending(TypeId ty, ConstraintSolver* solver)
}
template<typename F, typename... Args>
static std::optional<TypeFamilyReductionResult<TypeId>> tryDistributeTypeFamilyApp(F f, TypeId instance,
const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx, Args&& ...args)
static std::optional<TypeFamilyReductionResult<TypeId>> tryDistributeTypeFamilyApp(F f, TypeId instance, const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx, Args&&... args)
{
// op (a | b) (c | d) ~ (op a (c | d)) | (op b (c | d)) ~ (op a c) | (op a d) | (op b c) | (op b d)
bool uninhabited = false;
@ -527,8 +525,8 @@ static std::optional<TypeFamilyReductionResult<TypeId>> tryDistributeTypeFamilyA
return std::nullopt;
}
TypeFamilyReductionResult<TypeId> notFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
TypeFamilyReductionResult<TypeId> notFamilyFn(
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{
if (typeParams.size() != 1 || !packParams.empty())
{
@ -551,8 +549,8 @@ TypeFamilyReductionResult<TypeId> notFamilyFn(TypeId instance, const std::vector
return {ctx->builtins->booleanType, false, {}, {}};
}
TypeFamilyReductionResult<TypeId> lenFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
TypeFamilyReductionResult<TypeId> lenFamilyFn(
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{
if (typeParams.size() != 1 || !packParams.empty())
{
@ -573,7 +571,7 @@ TypeFamilyReductionResult<TypeId> lenFamilyFn(TypeId instance, const std::vector
// if the type is free but has only one remaining reference, we can generalize it to its upper bound here.
if (ctx->solver)
{
std::optional<TypeId> maybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, operandTy);
std::optional<TypeId> maybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, operandTy, /* avoidSealingTables */ true);
if (!maybeGeneralized)
return {std::nullopt, false, {operandTy}, {}};
operandTy = *maybeGeneralized;
@ -643,8 +641,8 @@ TypeFamilyReductionResult<TypeId> lenFamilyFn(TypeId instance, const std::vector
return {ctx->builtins->numberType, false, {}, {}};
}
TypeFamilyReductionResult<TypeId> unmFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
TypeFamilyReductionResult<TypeId> unmFamilyFn(
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{
if (typeParams.size() != 1 || !packParams.empty())
{
@ -846,8 +844,8 @@ TypeFamilyReductionResult<TypeId> numericBinopFamilyFn(TypeId instance, const st
return {extracted.head.front(), false, {}, {}};
}
TypeFamilyReductionResult<TypeId> addFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
TypeFamilyReductionResult<TypeId> addFamilyFn(
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{
if (typeParams.size() != 2 || !packParams.empty())
{
@ -858,8 +856,8 @@ TypeFamilyReductionResult<TypeId> addFamilyFn(TypeId instance, const std::vector
return numericBinopFamilyFn(instance, typeParams, packParams, ctx, "__add");
}
TypeFamilyReductionResult<TypeId> subFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
TypeFamilyReductionResult<TypeId> subFamilyFn(
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{
if (typeParams.size() != 2 || !packParams.empty())
{
@ -870,8 +868,8 @@ TypeFamilyReductionResult<TypeId> subFamilyFn(TypeId instance, const std::vector
return numericBinopFamilyFn(instance, typeParams, packParams, ctx, "__sub");
}
TypeFamilyReductionResult<TypeId> mulFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
TypeFamilyReductionResult<TypeId> mulFamilyFn(
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{
if (typeParams.size() != 2 || !packParams.empty())
{
@ -882,8 +880,8 @@ TypeFamilyReductionResult<TypeId> mulFamilyFn(TypeId instance, const std::vector
return numericBinopFamilyFn(instance, typeParams, packParams, ctx, "__mul");
}
TypeFamilyReductionResult<TypeId> divFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
TypeFamilyReductionResult<TypeId> divFamilyFn(
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{
if (typeParams.size() != 2 || !packParams.empty())
{
@ -894,8 +892,8 @@ TypeFamilyReductionResult<TypeId> divFamilyFn(TypeId instance, const std::vector
return numericBinopFamilyFn(instance, typeParams, packParams, ctx, "__div");
}
TypeFamilyReductionResult<TypeId> idivFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
TypeFamilyReductionResult<TypeId> idivFamilyFn(
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{
if (typeParams.size() != 2 || !packParams.empty())
{
@ -906,8 +904,8 @@ TypeFamilyReductionResult<TypeId> idivFamilyFn(TypeId instance, const std::vecto
return numericBinopFamilyFn(instance, typeParams, packParams, ctx, "__idiv");
}
TypeFamilyReductionResult<TypeId> powFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
TypeFamilyReductionResult<TypeId> powFamilyFn(
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{
if (typeParams.size() != 2 || !packParams.empty())
{
@ -918,8 +916,8 @@ TypeFamilyReductionResult<TypeId> powFamilyFn(TypeId instance, const std::vector
return numericBinopFamilyFn(instance, typeParams, packParams, ctx, "__pow");
}
TypeFamilyReductionResult<TypeId> modFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
TypeFamilyReductionResult<TypeId> modFamilyFn(
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{
if (typeParams.size() != 2 || !packParams.empty())
{
@ -930,8 +928,8 @@ TypeFamilyReductionResult<TypeId> modFamilyFn(TypeId instance, const std::vector
return numericBinopFamilyFn(instance, typeParams, packParams, ctx, "__mod");
}
TypeFamilyReductionResult<TypeId> concatFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
TypeFamilyReductionResult<TypeId> concatFamilyFn(
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{
if (typeParams.size() != 2 || !packParams.empty())
{
@ -1038,8 +1036,8 @@ TypeFamilyReductionResult<TypeId> concatFamilyFn(TypeId instance, const std::vec
return {ctx->builtins->stringType, false, {}, {}};
}
TypeFamilyReductionResult<TypeId> andFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
TypeFamilyReductionResult<TypeId> andFamilyFn(
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{
if (typeParams.size() != 2 || !packParams.empty())
{
@ -1089,8 +1087,8 @@ TypeFamilyReductionResult<TypeId> andFamilyFn(TypeId instance, const std::vector
return {overallResult.result, false, std::move(blockedTypes), {}};
}
TypeFamilyReductionResult<TypeId> orFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
TypeFamilyReductionResult<TypeId> orFamilyFn(
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{
if (typeParams.size() != 2 || !packParams.empty())
{
@ -1279,8 +1277,8 @@ static TypeFamilyReductionResult<TypeId> comparisonFamilyFn(TypeId instance, con
return {ctx->builtins->booleanType, false, {}, {}};
}
TypeFamilyReductionResult<TypeId> ltFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
TypeFamilyReductionResult<TypeId> ltFamilyFn(
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{
if (typeParams.size() != 2 || !packParams.empty())
{
@ -1291,8 +1289,8 @@ TypeFamilyReductionResult<TypeId> ltFamilyFn(TypeId instance, const std::vector<
return comparisonFamilyFn(instance, typeParams, packParams, ctx, "__lt");
}
TypeFamilyReductionResult<TypeId> leFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
TypeFamilyReductionResult<TypeId> leFamilyFn(
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{
if (typeParams.size() != 2 || !packParams.empty())
{
@ -1303,8 +1301,8 @@ TypeFamilyReductionResult<TypeId> leFamilyFn(TypeId instance, const std::vector<
return comparisonFamilyFn(instance, typeParams, packParams, ctx, "__le");
}
TypeFamilyReductionResult<TypeId> eqFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
TypeFamilyReductionResult<TypeId> eqFamilyFn(
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{
if (typeParams.size() != 2 || !packParams.empty())
{
@ -1434,8 +1432,8 @@ struct FindRefinementBlockers : TypeOnceVisitor
};
TypeFamilyReductionResult<TypeId> refineFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
TypeFamilyReductionResult<TypeId> refineFamilyFn(
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{
if (typeParams.size() != 2 || !packParams.empty())
{
@ -1519,8 +1517,8 @@ TypeFamilyReductionResult<TypeId> refineFamilyFn(TypeId instance, const std::vec
return {resultTy, false, {}, {}};
}
TypeFamilyReductionResult<TypeId> singletonFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
TypeFamilyReductionResult<TypeId> singletonFamilyFn(
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{
if (typeParams.size() != 1 || !packParams.empty())
{
@ -1556,8 +1554,8 @@ TypeFamilyReductionResult<TypeId> singletonFamilyFn(TypeId instance, const std::
return {ctx->builtins->unknownType, false, {}, {}};
}
TypeFamilyReductionResult<TypeId> unionFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
TypeFamilyReductionResult<TypeId> unionFamilyFn(
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{
if (!packParams.empty())
{
@ -1617,8 +1615,8 @@ TypeFamilyReductionResult<TypeId> unionFamilyFn(TypeId instance, const std::vect
}
TypeFamilyReductionResult<TypeId> intersectFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
TypeFamilyReductionResult<TypeId> intersectFamilyFn(
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{
if (!packParams.empty())
{
@ -1841,8 +1839,8 @@ TypeFamilyReductionResult<TypeId> keyofFamilyImpl(
return {ctx->arena->addType(UnionType{singletons}), false, {}, {}};
}
TypeFamilyReductionResult<TypeId> keyofFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
TypeFamilyReductionResult<TypeId> keyofFamilyFn(
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{
if (typeParams.size() != 1 || !packParams.empty())
{
@ -1853,8 +1851,8 @@ TypeFamilyReductionResult<TypeId> keyofFamilyFn(TypeId instance, const std::vect
return keyofFamilyImpl(typeParams, packParams, ctx, /* isRaw */ false);
}
TypeFamilyReductionResult<TypeId> rawkeyofFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
TypeFamilyReductionResult<TypeId> rawkeyofFamilyFn(
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{
if (typeParams.size() != 1 || !packParams.empty())
{

View file

@ -33,11 +33,11 @@ LUAU_FASTFLAG(LuauKnowsTheDataModel3)
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false)
LUAU_FASTFLAGVARIABLE(DebugLuauSharedSelf, false)
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAGVARIABLE(LuauMetatableInstantiationCloneCheck, false)
LUAU_FASTFLAGVARIABLE(LuauTinyControlFlowAnalysis, false)
LUAU_FASTFLAGVARIABLE(LuauAlwaysCommitInferencesOfFunctionCalls, false)
LUAU_FASTFLAGVARIABLE(LuauRemoveBadRelationalOperatorWarning, false)
LUAU_FASTFLAGVARIABLE(LuauOkWithIteratingOverTableProperties, false)
LUAU_FASTFLAGVARIABLE(LuauReusableSubstitutions, false)
namespace Luau
{
@ -214,6 +214,7 @@ TypeChecker::TypeChecker(const ScopePtr& globalScope, ModuleResolver* resolver,
, iceHandler(iceHandler)
, unifierState(iceHandler)
, normalizer(nullptr, builtinTypes, NotNull{&unifierState})
, reusableInstantiation(TxnLog::empty(), nullptr, builtinTypes, {}, nullptr)
, nilType(builtinTypes->nilType)
, numberType(builtinTypes->numberType)
, stringType(builtinTypes->stringType)
@ -4865,12 +4866,27 @@ TypeId TypeChecker::instantiate(const ScopePtr& scope, TypeId ty, Location locat
if (ftv && ftv->hasNoFreeOrGenericTypes)
return ty;
Instantiation instantiation{log, &currentModule->internalTypes, builtinTypes, scope->level, /*scope*/ nullptr};
std::optional<TypeId> instantiated;
if (instantiationChildLimit)
instantiation.childLimit = *instantiationChildLimit;
if (FFlag::LuauReusableSubstitutions)
{
reusableInstantiation.resetState(log, &currentModule->internalTypes, builtinTypes, scope->level, /*scope*/ nullptr);
if (instantiationChildLimit)
reusableInstantiation.childLimit = *instantiationChildLimit;
instantiated = reusableInstantiation.substitute(ty);
}
else
{
Instantiation instantiation{log, &currentModule->internalTypes, builtinTypes, scope->level, /*scope*/ nullptr};
if (instantiationChildLimit)
instantiation.childLimit = *instantiationChildLimit;
instantiated = instantiation.substitute(ty);
}
std::optional<TypeId> instantiated = instantiation.substitute(ty);
if (instantiated.has_value())
return *instantiated;
else
@ -5619,8 +5635,8 @@ TypeId TypeChecker::instantiateTypeFun(const ScopePtr& scope, const TypeFun& tf,
TypeId instantiated = *maybeInstantiated;
TypeId target = follow(instantiated);
const TableType* tfTable = FFlag::LuauMetatableInstantiationCloneCheck ? getTableType(tf.type) : nullptr;
bool needsClone = follow(tf.type) == target || (FFlag::LuauMetatableInstantiationCloneCheck && tfTable != nullptr && tfTable == getTableType(target));
const TableType* tfTable = getTableType(tf.type);
bool needsClone = follow(tf.type) == target || (tfTable != nullptr && tfTable == getTableType(target));
bool shouldMutate = getTableType(tf.type);
TableType* ttv = getMutableTableType(target);

View file

@ -974,8 +974,7 @@ void Unifier::tryUnifyTypeWithUnion(TypeId subTy, TypeId superTy, const UnionTyp
if (!subNorm || !superNorm)
reportError(location, NormalizationTooComplex{});
else if ((failedOptionCount == 1 || foundHeuristic) && failedOption)
tryUnifyNormalizedTypes(
subTy, superTy, *subNorm, *superNorm, "None of the union options are compatible. For example:", *failedOption);
tryUnifyNormalizedTypes(subTy, superTy, *subNorm, *superNorm, "None of the union options are compatible. For example:", *failedOption);
else
tryUnifyNormalizedTypes(subTy, superTy, *subNorm, *superNorm, "none of the union options are compatible");
}

View file

@ -479,7 +479,7 @@ bool Unifier2::unify(const FunctionType* subFn, const AnyType* superAny)
bool Unifier2::unify(const AnyType* subAny, const TableType* superTable)
{
for (const auto& [propName, prop]: superTable->props)
for (const auto& [propName, prop] : superTable->props)
{
if (prop.readTy)
unify(builtinTypes->anyType, *prop.readTy);
@ -499,7 +499,7 @@ bool Unifier2::unify(const AnyType* subAny, const TableType* superTable)
bool Unifier2::unify(const TableType* subTable, const AnyType* superAny)
{
for (const auto& [propName, prop]: subTable->props)
for (const auto& [propName, prop] : subTable->props)
{
if (prop.readTy)
unify(*prop.readTy, builtinTypes->anyType);
@ -658,31 +658,31 @@ struct FreeTypeSearcher : TypeVisitor
{
switch (polarity)
{
case Positive:
{
if (seenPositive.contains(ty))
return true;
case Positive:
{
if (seenPositive.contains(ty))
return true;
seenPositive.insert(ty);
return false;
}
case Negative:
{
if (seenNegative.contains(ty))
return true;
seenPositive.insert(ty);
return false;
}
case Negative:
{
if (seenNegative.contains(ty))
return true;
seenNegative.insert(ty);
return false;
}
case Both:
{
if (seenPositive.contains(ty) && seenNegative.contains(ty))
return true;
seenNegative.insert(ty);
return false;
}
case Both:
{
if (seenPositive.contains(ty) && seenNegative.contains(ty))
return true;
seenPositive.insert(ty);
seenNegative.insert(ty);
return false;
}
seenPositive.insert(ty);
seenNegative.insert(ty);
return false;
}
}
return false;

View file

@ -326,13 +326,12 @@ enum class IrCmd : uint8_t
// This is used to recover after calling a variadic function
ADJUST_STACK_TO_TOP,
// Execute fastcall builtin function in-place
// Execute fastcall builtin function with 1 argument in-place
// This is used for a few builtins that can have more than 1 result and cannot be represented as a regular instruction
// A: unsigned int (builtin id)
// B: Rn (result start)
// C: Rn (argument start)
// D: Rn or Kn or undef (optional second argument)
// E: int (argument count)
// F: int (result count)
// C: Rn (first argument)
// D: int (result count)
FASTCALL,
// Call the fastcall builtin function
@ -340,8 +339,9 @@ enum class IrCmd : uint8_t
// B: Rn (result start)
// C: Rn (argument start)
// D: Rn or Kn or undef (optional second argument)
// E: int (argument count or -1 to use all arguments up to stack top)
// F: int (result count or -1 to preserve all results and adjust stack top)
// E: Rn or Kn or undef (optional third argument)
// F: int (argument count or -1 to use all arguments up to stack top)
// G: int (result count or -1 to preserve all results and adjust stack top)
INVOKE_FASTCALL,
// Check that fastcall builtin function invocation was successful (negative result count jumps to fallback)

View file

@ -64,6 +64,7 @@ inline bool isFastCall(LuauOpcode op)
case LOP_FASTCALL1:
case LOP_FASTCALL2:
case LOP_FASTCALL2K:
case LOP_FASTCALL3:
return true;
default:

View file

@ -4,7 +4,7 @@
#include "Luau/Common.h"
#include "Luau/IrData.h"
LUAU_FASTFLAG(LuauCodegenRemoveDeadStores5)
LUAU_FASTFLAG(LuauCodegenFastcall3)
namespace Luau
{
@ -112,12 +112,48 @@ static void visitVmRegDefsUses(T& visitor, IrFunction& function, const IrInst& i
visitor.useRange(vmRegOp(inst.a), function.intOp(inst.b));
break;
// TODO: FASTCALL is more restrictive than INVOKE_FASTCALL; we should either determine the exact semantics, or rework it
case IrCmd::FASTCALL:
case IrCmd::INVOKE_FASTCALL:
if (int count = function.intOp(inst.e); count != -1)
if (FFlag::LuauCodegenFastcall3)
{
if (count >= 3)
visitor.use(inst.c);
if (int nresults = function.intOp(inst.d); nresults != -1)
visitor.defRange(vmRegOp(inst.b), nresults);
}
else
{
if (int count = function.intOp(inst.e); count != -1)
{
if (count >= 3)
{
CODEGEN_ASSERT(inst.d.kind == IrOpKind::VmReg && vmRegOp(inst.d) == vmRegOp(inst.c) + 1);
visitor.useRange(vmRegOp(inst.c), count);
}
else
{
if (count >= 1)
visitor.use(inst.c);
if (count >= 2)
visitor.maybeUse(inst.d); // Argument can also be a VmConst
}
}
else
{
visitor.useVarargs(vmRegOp(inst.c));
}
// Multiple return sequences (count == -1) are defined by ADJUST_STACK_TO_REG
if (int count = function.intOp(inst.f); count != -1)
visitor.defRange(vmRegOp(inst.b), count);
}
break;
case IrCmd::INVOKE_FASTCALL:
if (int count = function.intOp(FFlag::LuauCodegenFastcall3 ? inst.f : inst.e); count != -1)
{
// Only LOP_FASTCALL3 lowering is allowed to have third optional argument
if (count >= 3 && (!FFlag::LuauCodegenFastcall3 || inst.e.kind == IrOpKind::Undef))
{
CODEGEN_ASSERT(inst.d.kind == IrOpKind::VmReg && vmRegOp(inst.d) == vmRegOp(inst.c) + 1);
@ -130,6 +166,9 @@ static void visitVmRegDefsUses(T& visitor, IrFunction& function, const IrInst& i
if (count >= 2)
visitor.maybeUse(inst.d); // Argument can also be a VmConst
if (FFlag::LuauCodegenFastcall3 && count >= 3)
visitor.maybeUse(inst.e); // Argument can also be a VmConst
}
}
else
@ -138,7 +177,7 @@ static void visitVmRegDefsUses(T& visitor, IrFunction& function, const IrInst& i
}
// Multiple return sequences (count == -1) are defined by ADJUST_STACK_TO_REG
if (int count = function.intOp(inst.f); count != -1)
if (int count = function.intOp(FFlag::LuauCodegenFastcall3 ? inst.g : inst.f); count != -1)
visitor.defRange(vmRegOp(inst.b), count);
break;
case IrCmd::FORGLOOP:
@ -188,15 +227,8 @@ static void visitVmRegDefsUses(T& visitor, IrFunction& function, const IrInst& i
visitor.def(inst.b);
break;
case IrCmd::FALLBACK_FORGPREP:
if (FFlag::LuauCodegenRemoveDeadStores5)
{
// This instruction doesn't always redefine Rn, Rn+1, Rn+2, so we have to mark it as implicit use
visitor.useRange(vmRegOp(inst.b), 3);
}
else
{
visitor.use(inst.b);
}
// This instruction doesn't always redefine Rn, Rn+1, Rn+2, so we have to mark it as implicit use
visitor.useRange(vmRegOp(inst.b), 3);
visitor.defRange(vmRegOp(inst.b), 3);
break;
@ -214,12 +246,6 @@ static void visitVmRegDefsUses(T& visitor, IrFunction& function, const IrInst& i
visitor.use(inst.a);
break;
// After optimizations with DebugLuauAbortingChecks enabled, CHECK_TAG Rn, tag, block instructions are generated
case IrCmd::CHECK_TAG:
if (!FFlag::LuauCodegenRemoveDeadStores5)
visitor.maybeUse(inst.a);
break;
default:
// All instructions which reference registers have to be handled explicitly
CODEGEN_ASSERT(inst.a.kind != IrOpKind::VmReg);

View file

@ -11,30 +11,19 @@
#include <algorithm>
LUAU_FASTFLAG(LuauCodegenDirectUserdataFlow)
LUAU_FASTFLAG(LuauLoadTypeInfo) // Because new VM typeinfo load changes the format used by Codegen, same flag is used
LUAU_FASTFLAGVARIABLE(LuauCodegenTypeInfo, false) // New analysis is flagged separately
LUAU_FASTFLAGVARIABLE(LuauCodegenAnalyzeHostVectorOps, false)
LUAU_FASTFLAGVARIABLE(LuauCodegenLoadTypeUpvalCheck, false)
LUAU_FASTFLAGVARIABLE(LuauCodegenUserdataOps, false)
LUAU_FASTFLAGVARIABLE(LuauCodegenFastcall3, false)
namespace Luau
{
namespace CodeGen
{
static bool hasTypedParameters(Proto* proto)
{
CODEGEN_ASSERT(!FFlag::LuauLoadTypeInfo);
return proto->typeinfo && proto->numparams != 0;
}
template<typename T>
static T read(uint8_t* data, size_t& offset)
{
CODEGEN_ASSERT(FFlag::LuauLoadTypeInfo);
T result;
memcpy(&result, data + offset, sizeof(T));
offset += sizeof(T);
@ -44,8 +33,6 @@ static T read(uint8_t* data, size_t& offset)
static uint32_t readVarInt(uint8_t* data, size_t& offset)
{
CODEGEN_ASSERT(FFlag::LuauLoadTypeInfo);
uint32_t result = 0;
uint32_t shift = 0;
@ -63,8 +50,6 @@ static uint32_t readVarInt(uint8_t* data, size_t& offset)
void loadBytecodeTypeInfo(IrFunction& function)
{
CODEGEN_ASSERT(FFlag::LuauLoadTypeInfo);
Proto* proto = function.proto;
if (!proto)
@ -173,8 +158,6 @@ static void prepareRegTypeInfoLookups(BytecodeTypeInfo& typeInfo)
static BytecodeRegTypeInfo* findRegType(BytecodeTypeInfo& info, uint8_t reg, int pc)
{
CODEGEN_ASSERT(FFlag::LuauCodegenTypeInfo);
auto b = info.regTypes.begin() + info.regTypeOffsets[reg];
auto e = info.regTypes.begin() + info.regTypeOffsets[reg + 1];
@ -199,8 +182,6 @@ static BytecodeRegTypeInfo* findRegType(BytecodeTypeInfo& info, uint8_t reg, int
static void refineRegType(BytecodeTypeInfo& info, uint8_t reg, int pc, uint8_t ty)
{
CODEGEN_ASSERT(FFlag::LuauCodegenTypeInfo);
if (ty != LBC_TYPE_ANY)
{
if (BytecodeRegTypeInfo* regType = findRegType(info, reg, pc))
@ -219,8 +200,6 @@ static void refineRegType(BytecodeTypeInfo& info, uint8_t reg, int pc, uint8_t t
static void refineUpvalueType(BytecodeTypeInfo& info, int up, uint8_t ty)
{
CODEGEN_ASSERT(FFlag::LuauCodegenTypeInfo);
if (ty != LBC_TYPE_ANY)
{
if (size_t(up) < info.upvalueTypes.size())
@ -662,28 +641,12 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks)
// 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 (FFlag::LuauLoadTypeInfo)
for (size_t i = 0; i < bcTypeInfo.argumentTypes.size(); i++)
{
for (size_t i = 0; i < bcTypeInfo.argumentTypes.size(); i++)
{
uint8_t et = bcTypeInfo.argumentTypes[i];
uint8_t et = bcTypeInfo.argumentTypes[i];
// TODO: if argument is optional, this might force a VM exit unnecessarily
regTags[i] = et & ~LBC_TYPE_OPTIONAL_BIT;
}
}
else
{
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;
}
}
// 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)
@ -696,16 +659,13 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks)
const Instruction* pc = &proto->code[i];
LuauOpcode op = LuauOpcode(LUAU_INSN_OP(*pc));
if (FFlag::LuauCodegenTypeInfo)
// Assign known register types from local type information
// TODO: this is an expensive walk for each instruction
// TODO: it's best to lookup when register is actually used in the instruction
for (BytecodeRegTypeInfo& el : bcTypeInfo.regTypes)
{
// Assign known register types from local type information
// TODO: this is an expensive walk for each instruction
// TODO: it's best to lookup when register is actually used in the instruction
for (BytecodeRegTypeInfo& el : bcTypeInfo.regTypes)
{
if (el.type != LBC_TYPE_ANY && i >= el.startpc && i < el.endpc)
regTags[el.reg] = el.type;
}
if (el.type != LBC_TYPE_ANY && i >= el.startpc && i < el.endpc)
regTags[el.reg] = el.type;
}
BytecodeTypes& bcType = function.bcTypes[i];
@ -727,8 +687,7 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks)
regTags[ra] = LBC_TYPE_BOOLEAN;
bcType.result = regTags[ra];
if (FFlag::LuauCodegenTypeInfo)
refineRegType(bcTypeInfo, ra, i, bcType.result);
refineRegType(bcTypeInfo, ra, i, bcType.result);
break;
}
case LOP_LOADN:
@ -737,8 +696,7 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks)
regTags[ra] = LBC_TYPE_NUMBER;
bcType.result = regTags[ra];
if (FFlag::LuauCodegenTypeInfo)
refineRegType(bcTypeInfo, ra, i, bcType.result);
refineRegType(bcTypeInfo, ra, i, bcType.result);
break;
}
case LOP_LOADK:
@ -749,8 +707,7 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks)
regTags[ra] = bcType.a;
bcType.result = regTags[ra];
if (FFlag::LuauCodegenTypeInfo)
refineRegType(bcTypeInfo, ra, i, bcType.result);
refineRegType(bcTypeInfo, ra, i, bcType.result);
break;
}
case LOP_LOADKX:
@ -761,8 +718,7 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks)
regTags[ra] = bcType.a;
bcType.result = regTags[ra];
if (FFlag::LuauCodegenTypeInfo)
refineRegType(bcTypeInfo, ra, i, bcType.result);
refineRegType(bcTypeInfo, ra, i, bcType.result);
break;
}
case LOP_MOVE:
@ -773,8 +729,7 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks)
regTags[ra] = regTags[rb];
bcType.result = regTags[ra];
if (FFlag::LuauCodegenTypeInfo)
refineRegType(bcTypeInfo, ra, i, bcType.result);
refineRegType(bcTypeInfo, ra, i, bcType.result);
break;
}
case LOP_GETTABLE:
@ -1142,8 +1097,7 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks)
regTags[ra + 3] = bcType.c;
regTags[ra] = bcType.result;
if (FFlag::LuauCodegenTypeInfo)
refineRegType(bcTypeInfo, ra, i, bcType.result);
refineRegType(bcTypeInfo, ra, i, bcType.result);
break;
}
case LOP_FASTCALL1:
@ -1161,8 +1115,7 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks)
regTags[LUAU_INSN_B(*pc)] = bcType.a;
regTags[ra] = bcType.result;
if (FFlag::LuauCodegenTypeInfo)
refineRegType(bcTypeInfo, ra, i, bcType.result);
refineRegType(bcTypeInfo, ra, i, bcType.result);
break;
}
case LOP_FASTCALL2:
@ -1180,8 +1133,29 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks)
regTags[int(pc[1])] = bcType.b;
regTags[ra] = bcType.result;
if (FFlag::LuauCodegenTypeInfo)
refineRegType(bcTypeInfo, ra, i, bcType.result);
refineRegType(bcTypeInfo, ra, i, bcType.result);
break;
}
case LOP_FASTCALL3:
{
CODEGEN_ASSERT(FFlag::LuauCodegenFastcall3);
int bfid = LUAU_INSN_A(*pc);
int skip = LUAU_INSN_C(*pc);
int aux = pc[1];
Instruction call = pc[skip + 1];
CODEGEN_ASSERT(LUAU_INSN_OP(call) == LOP_CALL);
int ra = LUAU_INSN_A(call);
applyBuiltinCall(bfid, bcType);
regTags[LUAU_INSN_B(*pc)] = bcType.a;
regTags[aux & 0xff] = bcType.b;
regTags[(aux >> 8) & 0xff] = bcType.c;
regTags[ra] = bcType.result;
refineRegType(bcTypeInfo, ra, i, bcType.result);
break;
}
case LOP_FORNPREP:
@ -1192,12 +1166,9 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks)
regTags[ra + 1] = LBC_TYPE_NUMBER;
regTags[ra + 2] = LBC_TYPE_NUMBER;
if (FFlag::LuauCodegenTypeInfo)
{
refineRegType(bcTypeInfo, ra, i, regTags[ra]);
refineRegType(bcTypeInfo, ra + 1, i, regTags[ra + 1]);
refineRegType(bcTypeInfo, ra + 2, i, regTags[ra + 2]);
}
refineRegType(bcTypeInfo, ra, i, regTags[ra]);
refineRegType(bcTypeInfo, ra + 1, i, regTags[ra + 1]);
refineRegType(bcTypeInfo, ra + 2, i, regTags[ra + 2]);
break;
}
case LOP_FORNLOOP:
@ -1227,42 +1198,39 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks)
}
case LOP_NAMECALL:
{
if (FFlag::LuauCodegenDirectUserdataFlow)
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);
// While namecall might result in a callable table, we assume the function fast path
regTags[ra] = LBC_TYPE_FUNCTION;
// Namecall places source register into target + 1
regTags[ra + 1] = bcType.a;
bcType.result = LBC_TYPE_FUNCTION;
if (FFlag::LuauCodegenUserdataOps)
{
int ra = LUAU_INSN_A(*pc);
int rb = LUAU_INSN_B(*pc);
uint32_t kc = pc[1];
TString* str = gco2ts(function.proto->k[kc].value.gc);
const char* field = getstr(str);
bcType.a = regTags[rb];
bcType.b = getBytecodeConstantTag(proto, kc);
// While namecall might result in a callable table, we assume the function fast path
regTags[ra] = LBC_TYPE_FUNCTION;
// Namecall places source register into target + 1
regTags[ra + 1] = bcType.a;
bcType.result = LBC_TYPE_FUNCTION;
if (FFlag::LuauCodegenUserdataOps)
if (FFlag::LuauCodegenAnalyzeHostVectorOps && bcType.a == LBC_TYPE_VECTOR && hostHooks.vectorNamecallBytecodeType)
knownNextCallResult = LuauBytecodeType(hostHooks.vectorNamecallBytecodeType(field, str->len));
else if (isCustomUserdataBytecodeType(bcType.a) && hostHooks.userdataNamecallBytecodeType)
knownNextCallResult = LuauBytecodeType(hostHooks.userdataNamecallBytecodeType(bcType.a, field, str->len));
}
else
{
if (FFlag::LuauCodegenAnalyzeHostVectorOps && bcType.a == LBC_TYPE_VECTOR && hostHooks.vectorNamecallBytecodeType)
{
TString* str = gco2ts(function.proto->k[kc].value.gc);
const char* field = getstr(str);
if (FFlag::LuauCodegenAnalyzeHostVectorOps && bcType.a == LBC_TYPE_VECTOR && hostHooks.vectorNamecallBytecodeType)
knownNextCallResult = LuauBytecodeType(hostHooks.vectorNamecallBytecodeType(field, str->len));
else if (isCustomUserdataBytecodeType(bcType.a) && hostHooks.userdataNamecallBytecodeType)
knownNextCallResult = LuauBytecodeType(hostHooks.userdataNamecallBytecodeType(bcType.a, field, str->len));
}
else
{
if (FFlag::LuauCodegenAnalyzeHostVectorOps && bcType.a == LBC_TYPE_VECTOR && hostHooks.vectorNamecallBytecodeType)
{
TString* str = gco2ts(function.proto->k[kc].value.gc);
const char* field = getstr(str);
knownNextCallResult = LuauBytecodeType(hostHooks.vectorNamecallBytecodeType(field, str->len));
}
knownNextCallResult = LuauBytecodeType(hostHooks.vectorNamecallBytecodeType(field, str->len));
}
}
break;
@ -1282,42 +1250,35 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks)
regTags[ra] = bcType.result;
}
if (FFlag::LuauCodegenTypeInfo)
refineRegType(bcTypeInfo, ra, i, bcType.result);
refineRegType(bcTypeInfo, ra, i, bcType.result);
}
break;
}
case LOP_GETUPVAL:
{
if (FFlag::LuauCodegenTypeInfo)
int ra = LUAU_INSN_A(*pc);
int up = LUAU_INSN_B(*pc);
bcType.a = LBC_TYPE_ANY;
if (size_t(up) < bcTypeInfo.upvalueTypes.size())
{
int ra = LUAU_INSN_A(*pc);
int up = LUAU_INSN_B(*pc);
uint8_t et = bcTypeInfo.upvalueTypes[up];
bcType.a = LBC_TYPE_ANY;
if (size_t(up) < bcTypeInfo.upvalueTypes.size())
{
uint8_t et = bcTypeInfo.upvalueTypes[up];
// TODO: if argument is optional, this might force a VM exit unnecessarily
bcType.a = et & ~LBC_TYPE_OPTIONAL_BIT;
}
regTags[ra] = bcType.a;
bcType.result = regTags[ra];
// TODO: if argument is optional, this might force a VM exit unnecessarily
bcType.a = et & ~LBC_TYPE_OPTIONAL_BIT;
}
regTags[ra] = bcType.a;
bcType.result = regTags[ra];
break;
}
case LOP_SETUPVAL:
{
if (FFlag::LuauCodegenTypeInfo)
{
int ra = LUAU_INSN_A(*pc);
int up = LUAU_INSN_B(*pc);
int ra = LUAU_INSN_A(*pc);
int up = LUAU_INSN_B(*pc);
refineUpvalueType(bcTypeInfo, up, regTags[ra]);
}
refineUpvalueType(bcTypeInfo, up, regTags[ra]);
break;
}
case LOP_GETGLOBAL:

View file

@ -12,7 +12,6 @@
#include "lapi.h"
LUAU_FASTFLAG(LuauCodegenTypeInfo)
LUAU_FASTFLAG(LuauLoadUserdataInfo)
LUAU_FASTFLAG(LuauNativeAttribute)
@ -87,7 +86,6 @@ static void logFunctionHeader(AssemblyBuilder& build, Proto* proto)
template<typename AssemblyBuilder>
static void logFunctionTypes_DEPRECATED(AssemblyBuilder& build, const IrFunction& function)
{
CODEGEN_ASSERT(FFlag::LuauCodegenTypeInfo);
CODEGEN_ASSERT(!FFlag::LuauLoadUserdataInfo);
const BytecodeTypeInfo& typeInfo = function.bcTypeInfo;
@ -131,7 +129,6 @@ static void logFunctionTypes_DEPRECATED(AssemblyBuilder& build, const IrFunction
template<typename AssemblyBuilder>
static void logFunctionTypes(AssemblyBuilder& build, const IrFunction& function, const char* const* userdataTypes)
{
CODEGEN_ASSERT(FFlag::LuauCodegenTypeInfo);
CODEGEN_ASSERT(FFlag::LuauLoadUserdataInfo);
const BytecodeTypeInfo& typeInfo = function.bcTypeInfo;
@ -240,7 +237,7 @@ static std::string getAssemblyImpl(AssemblyBuilder& build, const TValue* func, A
if (options.includeAssembly || options.includeIr)
logFunctionHeader(build, p);
if (FFlag::LuauCodegenTypeInfo && options.includeIrTypes)
if (options.includeIrTypes)
{
if (FFlag::LuauLoadUserdataInfo)
logFunctionTypes(build, ir.function, options.compilationOptions.userdataTypes);

View file

@ -27,7 +27,6 @@ LUAU_FASTFLAG(DebugCodegenSkipNumbering)
LUAU_FASTINT(CodegenHeuristicsInstructionLimit)
LUAU_FASTINT(CodegenHeuristicsBlockLimit)
LUAU_FASTINT(CodegenHeuristicsBlockInstructionLimit)
LUAU_FASTFLAG(LuauCodegenRemoveDeadStores5)
LUAU_FASTFLAG(LuauLoadUserdataInfo)
LUAU_FASTFLAG(LuauNativeAttribute)
@ -347,8 +346,7 @@ inline bool lowerFunction(IrBuilder& ir, AssemblyBuilder& build, ModuleHelpers&
}
}
if (FFlag::LuauCodegenRemoveDeadStores5)
markDeadStoresInBlockChains(ir);
markDeadStoresInBlockChains(ir);
}
std::vector<uint32_t> sortedBlocks = getSortedBlockOrder(ir.function);

View file

@ -12,8 +12,6 @@
#include "lstate.h"
LUAU_FASTFLAG(LuauCodegenRemoveDeadStores5)
namespace Luau
{
namespace CodeGen
@ -29,17 +27,13 @@ static void emitBuiltinMathFrexp(IrRegAllocX64& regs, AssemblyBuilderX64& build,
callWrap.call(qword[rNativeContext + offsetof(NativeContext, libm_frexp)]);
build.vmovsd(luauRegValue(ra), xmm0);
if (FFlag::LuauCodegenRemoveDeadStores5)
build.mov(luauRegTag(ra), LUA_TNUMBER);
build.mov(luauRegTag(ra), LUA_TNUMBER);
if (nresults > 1)
{
build.vcvtsi2sd(xmm0, xmm0, dword[sTemporarySlot + 0]);
build.vmovsd(luauRegValue(ra + 1), xmm0);
if (FFlag::LuauCodegenRemoveDeadStores5)
build.mov(luauRegTag(ra + 1), LUA_TNUMBER);
build.mov(luauRegTag(ra + 1), LUA_TNUMBER);
}
}
@ -52,16 +46,12 @@ static void emitBuiltinMathModf(IrRegAllocX64& regs, AssemblyBuilderX64& build,
build.vmovsd(xmm1, qword[sTemporarySlot + 0]);
build.vmovsd(luauRegValue(ra), xmm1);
if (FFlag::LuauCodegenRemoveDeadStores5)
build.mov(luauRegTag(ra), LUA_TNUMBER);
build.mov(luauRegTag(ra), LUA_TNUMBER);
if (nresults > 1)
{
build.vmovsd(luauRegValue(ra + 1), xmm0);
if (FFlag::LuauCodegenRemoveDeadStores5)
build.mov(luauRegTag(ra + 1), LUA_TNUMBER);
build.mov(luauRegTag(ra + 1), LUA_TNUMBER);
}
}
@ -90,23 +80,21 @@ static void emitBuiltinMathSign(IrRegAllocX64& regs, AssemblyBuilderX64& build,
build.vblendvpd(tmp0.reg, tmp2.reg, build.f64x2(1, 1), tmp0.reg);
build.vmovsd(luauRegValue(ra), tmp0.reg);
if (FFlag::LuauCodegenRemoveDeadStores5)
build.mov(luauRegTag(ra), LUA_TNUMBER);
build.mov(luauRegTag(ra), LUA_TNUMBER);
}
void emitBuiltin(IrRegAllocX64& regs, AssemblyBuilderX64& build, int bfid, int ra, int arg, OperandX64 arg2, int nparams, int nresults)
void emitBuiltin(IrRegAllocX64& regs, AssemblyBuilderX64& build, int bfid, int ra, int arg, int nresults)
{
switch (bfid)
{
case LBF_MATH_FREXP:
CODEGEN_ASSERT(nparams == 1 && (nresults == 1 || nresults == 2));
CODEGEN_ASSERT(nresults == 1 || nresults == 2);
return emitBuiltinMathFrexp(regs, build, ra, arg, nresults);
case LBF_MATH_MODF:
CODEGEN_ASSERT(nparams == 1 && (nresults == 1 || nresults == 2));
CODEGEN_ASSERT(nresults == 1 || nresults == 2);
return emitBuiltinMathModf(regs, build, ra, arg, nresults);
case LBF_MATH_SIGN:
CODEGEN_ASSERT(nparams == 1 && nresults == 1);
CODEGEN_ASSERT(nresults == 1);
return emitBuiltinMathSign(regs, build, ra, arg);
default:
CODEGEN_ASSERT(!"Missing x64 lowering");

View file

@ -16,7 +16,7 @@ class AssemblyBuilderX64;
struct OperandX64;
struct IrRegAllocX64;
void emitBuiltin(IrRegAllocX64& regs, AssemblyBuilderX64& build, int bfid, int ra, int arg, OperandX64 arg2, int nparams, int nresults);
void emitBuiltin(IrRegAllocX64& regs, AssemblyBuilderX64& build, int bfid, int ra, int arg, int nresults);
} // namespace X64
} // namespace CodeGen

View file

@ -13,10 +13,10 @@
#include <string.h>
LUAU_FASTFLAG(LuauLoadTypeInfo) // Because new VM typeinfo load changes the format used by Codegen, same flag is used
LUAU_FASTFLAG(LuauCodegenAnalyzeHostVectorOps)
LUAU_FASTFLAG(LuauLoadUserdataInfo)
LUAU_FASTFLAG(LuauCodegenInstG)
LUAU_FASTFLAG(LuauCodegenFastcall3)
namespace Luau
{
@ -30,96 +30,9 @@ IrBuilder::IrBuilder(const HostIrHooks& hostHooks)
, constantMap({IrConstKind::Tag, ~0ull})
{
}
static bool hasTypedParameters_DEPRECATED(Proto* proto)
{
CODEGEN_ASSERT(!FFlag::LuauLoadTypeInfo);
return proto->typeinfo && proto->numparams != 0;
}
static void buildArgumentTypeChecks_DEPRECATED(IrBuilder& build, Proto* proto)
{
CODEGEN_ASSERT(!FFlag::LuauLoadTypeInfo);
CODEGEN_ASSERT(hasTypedParameters_DEPRECATED(proto));
for (int i = 0; i < proto->numparams; ++i)
{
uint8_t et = proto->typeinfo[2 + i];
uint8_t tag = et & ~LBC_TYPE_OPTIONAL_BIT;
uint8_t optional = et & LBC_TYPE_OPTIONAL_BIT;
if (tag == LBC_TYPE_ANY)
continue;
IrOp load = build.inst(IrCmd::LOAD_TAG, build.vmReg(i));
IrOp nextCheck;
if (optional)
{
nextCheck = build.block(IrBlockKind::Internal);
IrOp fallbackCheck = build.block(IrBlockKind::Internal);
build.inst(IrCmd::JUMP_EQ_TAG, load, build.constTag(LUA_TNIL), nextCheck, fallbackCheck);
build.beginBlock(fallbackCheck);
}
switch (tag)
{
case LBC_TYPE_NIL:
build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TNIL), build.vmExit(kVmExitEntryGuardPc));
break;
case LBC_TYPE_BOOLEAN:
build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TBOOLEAN), build.vmExit(kVmExitEntryGuardPc));
break;
case LBC_TYPE_NUMBER:
build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TNUMBER), build.vmExit(kVmExitEntryGuardPc));
break;
case LBC_TYPE_STRING:
build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TSTRING), build.vmExit(kVmExitEntryGuardPc));
break;
case LBC_TYPE_TABLE:
build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TTABLE), build.vmExit(kVmExitEntryGuardPc));
break;
case LBC_TYPE_FUNCTION:
build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TFUNCTION), build.vmExit(kVmExitEntryGuardPc));
break;
case LBC_TYPE_THREAD:
build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TTHREAD), build.vmExit(kVmExitEntryGuardPc));
break;
case LBC_TYPE_USERDATA:
build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TUSERDATA), build.vmExit(kVmExitEntryGuardPc));
break;
case LBC_TYPE_VECTOR:
build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TVECTOR), build.vmExit(kVmExitEntryGuardPc));
break;
case LBC_TYPE_BUFFER:
build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TBUFFER), build.vmExit(kVmExitEntryGuardPc));
break;
}
if (optional)
{
build.inst(IrCmd::JUMP, nextCheck);
build.beginBlock(nextCheck);
}
}
// If the last argument is optional, we can skip creating a new internal block since one will already have been created.
if (!(proto->typeinfo[2 + proto->numparams - 1] & LBC_TYPE_OPTIONAL_BIT))
{
IrOp next = build.block(IrBlockKind::Internal);
build.inst(IrCmd::JUMP, next);
build.beginBlock(next);
}
}
static bool hasTypedParameters(const BytecodeTypeInfo& typeInfo)
{
CODEGEN_ASSERT(FFlag::LuauLoadTypeInfo);
for (auto el : typeInfo.argumentTypes)
{
if (el != LBC_TYPE_ANY)
@ -131,8 +44,6 @@ static bool hasTypedParameters(const BytecodeTypeInfo& typeInfo)
static void buildArgumentTypeChecks(IrBuilder& build)
{
CODEGEN_ASSERT(FFlag::LuauLoadTypeInfo);
const BytecodeTypeInfo& typeInfo = build.function.bcTypeInfo;
CODEGEN_ASSERT(hasTypedParameters(typeInfo));
@ -228,11 +139,10 @@ void IrBuilder::buildFunctionIr(Proto* proto)
function.proto = proto;
function.variadic = proto->is_vararg != 0;
if (FFlag::LuauLoadTypeInfo)
loadBytecodeTypeInfo(function);
loadBytecodeTypeInfo(function);
// Reserve entry block
bool generateTypeChecks = FFlag::LuauLoadTypeInfo ? hasTypedParameters(function.bcTypeInfo) : hasTypedParameters_DEPRECATED(proto);
bool generateTypeChecks = hasTypedParameters(function.bcTypeInfo);
IrOp entry = generateTypeChecks ? block(IrBlockKind::Internal) : IrOp{};
// Rebuild original control flow blocks
@ -247,10 +157,7 @@ void IrBuilder::buildFunctionIr(Proto* proto)
{
beginBlock(entry);
if (FFlag::LuauLoadTypeInfo)
buildArgumentTypeChecks(*this);
else
buildArgumentTypeChecks_DEPRECATED(*this, proto);
buildArgumentTypeChecks(*this);
inst(IrCmd::JUMP, blockAtInst(0));
}
@ -544,16 +451,21 @@ void IrBuilder::translateInst(LuauOpcode op, const Instruction* pc, int i)
translateInstCloseUpvals(*this, pc);
break;
case LOP_FASTCALL:
handleFastcallFallback(translateFastCallN(*this, pc, i, false, 0, {}), pc, i);
handleFastcallFallback(translateFastCallN(*this, pc, i, false, 0, {}, {}), pc, i);
break;
case LOP_FASTCALL1:
handleFastcallFallback(translateFastCallN(*this, pc, i, true, 1, undef()), pc, i);
handleFastcallFallback(translateFastCallN(*this, pc, i, true, 1, undef(), undef()), pc, i);
break;
case LOP_FASTCALL2:
handleFastcallFallback(translateFastCallN(*this, pc, i, true, 2, vmReg(pc[1])), pc, i);
handleFastcallFallback(translateFastCallN(*this, pc, i, true, 2, vmReg(pc[1]), undef()), pc, i);
break;
case LOP_FASTCALL2K:
handleFastcallFallback(translateFastCallN(*this, pc, i, true, 2, vmConst(pc[1])), pc, i);
handleFastcallFallback(translateFastCallN(*this, pc, i, true, 2, vmConst(pc[1]), undef()), pc, i);
break;
case LOP_FASTCALL3:
CODEGEN_ASSERT(FFlag::LuauCodegenFastcall3);
handleFastcallFallback(translateFastCallN(*this, pc, i, true, 3, vmReg(pc[1] & 0xff), vmReg((pc[1] >> 8) & 0xff)), pc, i);
break;
case LOP_FORNPREP:
translateInstForNPrep(*this, pc, i);

View file

@ -11,11 +11,11 @@
#include "lstate.h"
#include "lgc.h"
LUAU_FASTFLAG(LuauCodegenRemoveDeadStores5)
LUAU_FASTFLAG(LuauCodegenSplitDoarith)
LUAU_FASTFLAG(LuauCodegenUserdataOps)
LUAU_FASTFLAGVARIABLE(LuauCodegenUserdataAlloc, false)
LUAU_FASTFLAGVARIABLE(LuauCodegenUserdataOpsFixA64, false)
LUAU_FASTFLAG(LuauCodegenFastcall3)
namespace Luau
{
@ -197,78 +197,50 @@ static void emitInvokeLibm1P(AssemblyBuilderA64& build, size_t func, int arg)
build.blr(x1);
}
static bool emitBuiltin(
AssemblyBuilderA64& build, IrFunction& function, IrRegAllocA64& regs, int bfid, int res, int arg, IrOp args, int nparams, int nresults)
static bool emitBuiltin(AssemblyBuilderA64& build, IrFunction& function, IrRegAllocA64& regs, int bfid, int res, int arg, int nresults)
{
switch (bfid)
{
case LBF_MATH_FREXP:
{
if (FFlag::LuauCodegenRemoveDeadStores5)
{
CODEGEN_ASSERT(nparams == 1 && (nresults == 1 || nresults == 2));
emitInvokeLibm1P(build, offsetof(NativeContext, libm_frexp), arg);
build.str(d0, mem(rBase, res * sizeof(TValue) + offsetof(TValue, value.n)));
CODEGEN_ASSERT(nresults == 1 || nresults == 2);
emitInvokeLibm1P(build, offsetof(NativeContext, libm_frexp), arg);
build.str(d0, mem(rBase, res * sizeof(TValue) + offsetof(TValue, value.n)));
RegisterA64 temp = regs.allocTemp(KindA64::w);
build.mov(temp, LUA_TNUMBER);
build.str(temp, mem(rBase, res * sizeof(TValue) + offsetof(TValue, tt)));
RegisterA64 temp = regs.allocTemp(KindA64::w);
build.mov(temp, LUA_TNUMBER);
build.str(temp, mem(rBase, res * sizeof(TValue) + offsetof(TValue, tt)));
if (nresults == 2)
{
build.ldr(w0, sTemporary);
build.scvtf(d1, w0);
build.str(d1, mem(rBase, (res + 1) * sizeof(TValue) + offsetof(TValue, value.n)));
build.str(temp, mem(rBase, (res + 1) * sizeof(TValue) + offsetof(TValue, tt)));
}
}
else
if (nresults == 2)
{
CODEGEN_ASSERT(nparams == 1 && (nresults == 1 || nresults == 2));
emitInvokeLibm1P(build, offsetof(NativeContext, libm_frexp), arg);
build.str(d0, mem(rBase, res * sizeof(TValue) + offsetof(TValue, value.n)));
if (nresults == 2)
{
build.ldr(w0, sTemporary);
build.scvtf(d1, w0);
build.str(d1, mem(rBase, (res + 1) * sizeof(TValue) + offsetof(TValue, value.n)));
}
build.ldr(w0, sTemporary);
build.scvtf(d1, w0);
build.str(d1, mem(rBase, (res + 1) * sizeof(TValue) + offsetof(TValue, value.n)));
build.str(temp, mem(rBase, (res + 1) * sizeof(TValue) + offsetof(TValue, tt)));
}
return true;
}
case LBF_MATH_MODF:
{
if (FFlag::LuauCodegenRemoveDeadStores5)
{
CODEGEN_ASSERT(nparams == 1 && (nresults == 1 || nresults == 2));
emitInvokeLibm1P(build, offsetof(NativeContext, libm_modf), arg);
build.ldr(d1, sTemporary);
build.str(d1, mem(rBase, res * sizeof(TValue) + offsetof(TValue, value.n)));
CODEGEN_ASSERT(nresults == 1 || nresults == 2);
emitInvokeLibm1P(build, offsetof(NativeContext, libm_modf), arg);
build.ldr(d1, sTemporary);
build.str(d1, mem(rBase, res * sizeof(TValue) + offsetof(TValue, value.n)));
RegisterA64 temp = regs.allocTemp(KindA64::w);
build.mov(temp, LUA_TNUMBER);
build.str(temp, mem(rBase, res * sizeof(TValue) + offsetof(TValue, tt)));
RegisterA64 temp = regs.allocTemp(KindA64::w);
build.mov(temp, LUA_TNUMBER);
build.str(temp, mem(rBase, res * sizeof(TValue) + offsetof(TValue, tt)));
if (nresults == 2)
{
build.str(d0, mem(rBase, (res + 1) * sizeof(TValue) + offsetof(TValue, value.n)));
build.str(temp, mem(rBase, (res + 1) * sizeof(TValue) + offsetof(TValue, tt)));
}
}
else
if (nresults == 2)
{
CODEGEN_ASSERT(nparams == 1 && (nresults == 1 || nresults == 2));
emitInvokeLibm1P(build, offsetof(NativeContext, libm_modf), arg);
build.ldr(d1, sTemporary);
build.str(d1, mem(rBase, res * sizeof(TValue) + offsetof(TValue, value.n)));
if (nresults == 2)
build.str(d0, mem(rBase, (res + 1) * sizeof(TValue) + offsetof(TValue, value.n)));
build.str(d0, mem(rBase, (res + 1) * sizeof(TValue) + offsetof(TValue, value.n)));
build.str(temp, mem(rBase, (res + 1) * sizeof(TValue) + offsetof(TValue, tt)));
}
return true;
}
case LBF_MATH_SIGN:
{
CODEGEN_ASSERT(nparams == 1 && nresults == 1);
CODEGEN_ASSERT(nresults == 1);
build.ldr(d0, mem(rBase, arg * sizeof(TValue) + offsetof(TValue, value.n)));
build.fcmpz(d0);
build.fmov(d0, 0.0);
@ -278,12 +250,10 @@ static bool emitBuiltin(
build.fcsel(d0, d1, d0, getConditionFP(IrCondition::Less));
build.str(d0, mem(rBase, res * sizeof(TValue) + offsetof(TValue, value.n)));
if (FFlag::LuauCodegenRemoveDeadStores5)
{
RegisterA64 temp = regs.allocTemp(KindA64::w);
build.mov(temp, LUA_TNUMBER);
build.str(temp, mem(rBase, res * sizeof(TValue) + offsetof(TValue, tt)));
}
RegisterA64 temp = regs.allocTemp(KindA64::w);
build.mov(temp, LUA_TNUMBER);
build.str(temp, mem(rBase, res * sizeof(TValue) + offsetof(TValue, tt)));
return true;
}
@ -1205,34 +1175,88 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next)
}
case IrCmd::FASTCALL:
regs.spill(build, index);
error |= !emitBuiltin(build, function, regs, uintOp(inst.a), vmRegOp(inst.b), vmRegOp(inst.c), inst.d, intOp(inst.e), intOp(inst.f));
if (FFlag::LuauCodegenFastcall3)
error |= !emitBuiltin(build, function, regs, uintOp(inst.a), vmRegOp(inst.b), vmRegOp(inst.c), intOp(inst.d));
else
error |= !emitBuiltin(build, function, regs, uintOp(inst.a), vmRegOp(inst.b), vmRegOp(inst.c), intOp(inst.f));
break;
case IrCmd::INVOKE_FASTCALL:
{
regs.spill(build, index);
build.mov(x0, rState);
build.add(x1, rBase, uint16_t(vmRegOp(inst.b) * sizeof(TValue)));
build.add(x2, rBase, uint16_t(vmRegOp(inst.c) * sizeof(TValue)));
build.mov(w3, intOp(inst.f)); // nresults
if (inst.d.kind == IrOpKind::VmReg)
build.add(x4, rBase, uint16_t(vmRegOp(inst.d) * sizeof(TValue)));
else if (inst.d.kind == IrOpKind::VmConst)
emitAddOffset(build, x4, rConstants, vmConstOp(inst.d) * sizeof(TValue));
else
CODEGEN_ASSERT(inst.d.kind == IrOpKind::Undef);
// nparams
if (intOp(inst.e) == LUA_MULTRET)
if (FFlag::LuauCodegenFastcall3)
{
// L->top - (ra + 1)
build.ldr(x5, mem(rState, offsetof(lua_State, top)));
build.sub(x5, x5, rBase);
build.sub(x5, x5, uint16_t((vmRegOp(inst.b) + 1) * sizeof(TValue)));
build.lsr(x5, x5, kTValueSizeLog2);
// We might need a temporary and we have to preserve it over the spill
RegisterA64 temp = regs.allocTemp(KindA64::q);
regs.spill(build, index, {temp});
build.mov(x0, rState);
build.add(x1, rBase, uint16_t(vmRegOp(inst.b) * sizeof(TValue)));
build.add(x2, rBase, uint16_t(vmRegOp(inst.c) * sizeof(TValue)));
build.mov(w3, intOp(inst.g)); // nresults
// 'E' argument can only be produced by LOP_FASTCALL3 lowering
if (inst.e.kind != IrOpKind::Undef)
{
CODEGEN_ASSERT(intOp(inst.f) == 3);
build.ldr(x4, mem(rState, offsetof(lua_State, top)));
build.ldr(temp, mem(rBase, vmRegOp(inst.d) * sizeof(TValue)));
build.str(temp, mem(x4, 0));
build.ldr(temp, mem(rBase, vmRegOp(inst.e) * sizeof(TValue)));
build.str(temp, mem(x4, sizeof(TValue)));
}
else
{
if (inst.d.kind == IrOpKind::VmReg)
build.add(x4, rBase, uint16_t(vmRegOp(inst.d) * sizeof(TValue)));
else if (inst.d.kind == IrOpKind::VmConst)
emitAddOffset(build, x4, rConstants, vmConstOp(inst.d) * sizeof(TValue));
else
CODEGEN_ASSERT(inst.d.kind == IrOpKind::Undef);
}
// nparams
if (intOp(inst.f) == LUA_MULTRET)
{
// L->top - (ra + 1)
build.ldr(x5, mem(rState, offsetof(lua_State, top)));
build.sub(x5, x5, rBase);
build.sub(x5, x5, uint16_t((vmRegOp(inst.b) + 1) * sizeof(TValue)));
build.lsr(x5, x5, kTValueSizeLog2);
}
else
build.mov(w5, intOp(inst.f));
}
else
build.mov(w5, intOp(inst.e));
{
regs.spill(build, index);
build.mov(x0, rState);
build.add(x1, rBase, uint16_t(vmRegOp(inst.b) * sizeof(TValue)));
build.add(x2, rBase, uint16_t(vmRegOp(inst.c) * sizeof(TValue)));
build.mov(w3, intOp(inst.f)); // nresults
if (inst.d.kind == IrOpKind::VmReg)
build.add(x4, rBase, uint16_t(vmRegOp(inst.d) * sizeof(TValue)));
else if (inst.d.kind == IrOpKind::VmConst)
emitAddOffset(build, x4, rConstants, vmConstOp(inst.d) * sizeof(TValue));
else
CODEGEN_ASSERT(inst.d.kind == IrOpKind::Undef);
// nparams
if (intOp(inst.e) == LUA_MULTRET)
{
// L->top - (ra + 1)
build.ldr(x5, mem(rState, offsetof(lua_State, top)));
build.sub(x5, x5, rBase);
build.sub(x5, x5, uint16_t((vmRegOp(inst.b) + 1) * sizeof(TValue)));
build.lsr(x5, x5, kTValueSizeLog2);
}
else
build.mov(w5, intOp(inst.e));
}
build.ldr(x6, mem(rNativeContext, offsetof(NativeContext, luauF_table) + uintOp(inst.a) * sizeof(luau_FastFunction)));
build.blr(x6);
@ -1443,35 +1467,14 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next)
Label fresh; // used when guard aborts execution or jumps to a VM exit
Label& fail = getTargetLabel(inst.c, fresh);
if (FFlag::LuauCodegenRemoveDeadStores5)
if (tagOp(inst.b) == 0)
{
if (tagOp(inst.b) == 0)
{
build.cbnz(regOp(inst.a), fail);
}
else
{
build.cmp(regOp(inst.a), tagOp(inst.b));
build.b(ConditionA64::NotEqual, fail);
}
build.cbnz(regOp(inst.a), fail);
}
else
{
// To support DebugLuauAbortingChecks, CHECK_TAG with VmReg has to be handled
RegisterA64 tag = inst.a.kind == IrOpKind::VmReg ? regs.allocTemp(KindA64::w) : regOp(inst.a);
if (inst.a.kind == IrOpKind::VmReg)
build.ldr(tag, mem(rBase, vmRegOp(inst.a) * sizeof(TValue) + offsetof(TValue, tt)));
if (tagOp(inst.b) == 0)
{
build.cbnz(tag, fail);
}
else
{
build.cmp(tag, tagOp(inst.b));
build.b(ConditionA64::NotEqual, fail);
}
build.cmp(regOp(inst.a), tagOp(inst.b));
build.b(ConditionA64::NotEqual, fail);
}
finalizeTargetLabel(inst.c, fresh);

View file

@ -17,6 +17,7 @@
LUAU_FASTFLAG(LuauCodegenUserdataOps)
LUAU_FASTFLAG(LuauCodegenUserdataAlloc)
LUAU_FASTFLAG(LuauCodegenFastcall3)
namespace Luau
{
@ -1008,9 +1009,10 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next)
case IrCmd::FASTCALL:
{
OperandX64 arg2 = inst.d.kind != IrOpKind::Undef ? memRegDoubleOp(inst.d) : OperandX64{0};
emitBuiltin(regs, build, uintOp(inst.a), vmRegOp(inst.b), vmRegOp(inst.c), arg2, intOp(inst.e), intOp(inst.f));
if (FFlag::LuauCodegenFastcall3)
emitBuiltin(regs, build, uintOp(inst.a), vmRegOp(inst.b), vmRegOp(inst.c), intOp(inst.d));
else
emitBuiltin(regs, build, uintOp(inst.a), vmRegOp(inst.b), vmRegOp(inst.c), intOp(inst.f));
break;
}
case IrCmd::INVOKE_FASTCALL:
@ -1018,25 +1020,49 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next)
unsigned bfid = uintOp(inst.a);
OperandX64 args = 0;
ScopedRegX64 argsAlt{regs};
if (inst.d.kind == IrOpKind::VmReg)
args = luauRegAddress(vmRegOp(inst.d));
else if (inst.d.kind == IrOpKind::VmConst)
args = luauConstantAddress(vmConstOp(inst.d));
// 'E' argument can only be produced by LOP_FASTCALL3
if (FFlag::LuauCodegenFastcall3 && inst.e.kind != IrOpKind::Undef)
{
CODEGEN_ASSERT(intOp(inst.f) == 3);
ScopedRegX64 tmp{regs, SizeX64::xmmword};
argsAlt.alloc(SizeX64::qword);
build.mov(argsAlt.reg, qword[rState + offsetof(lua_State, top)]);
build.vmovups(tmp.reg, luauReg(vmRegOp(inst.d)));
build.vmovups(xmmword[argsAlt.reg], tmp.reg);
build.vmovups(tmp.reg, luauReg(vmRegOp(inst.e)));
build.vmovups(xmmword[argsAlt.reg + sizeof(TValue)], tmp.reg);
}
else
CODEGEN_ASSERT(inst.d.kind == IrOpKind::Undef);
{
if (inst.d.kind == IrOpKind::VmReg)
args = luauRegAddress(vmRegOp(inst.d));
else if (inst.d.kind == IrOpKind::VmConst)
args = luauConstantAddress(vmConstOp(inst.d));
else
CODEGEN_ASSERT(inst.d.kind == IrOpKind::Undef);
}
int ra = vmRegOp(inst.b);
int arg = vmRegOp(inst.c);
int nparams = intOp(inst.e);
int nresults = intOp(inst.f);
int nparams = intOp(FFlag::LuauCodegenFastcall3 ? inst.f : inst.e);
int nresults = intOp(FFlag::LuauCodegenFastcall3 ? inst.g : inst.f);
IrCallWrapperX64 callWrap(regs, build, index);
callWrap.addArgument(SizeX64::qword, rState);
callWrap.addArgument(SizeX64::qword, luauRegAddress(ra));
callWrap.addArgument(SizeX64::qword, luauRegAddress(arg));
callWrap.addArgument(SizeX64::dword, nresults);
callWrap.addArgument(SizeX64::qword, args);
if (FFlag::LuauCodegenFastcall3 && inst.e.kind != IrOpKind::Undef)
callWrap.addArgument(SizeX64::qword, argsAlt);
else
callWrap.addArgument(SizeX64::qword, args);
if (nparams == LUA_MULTRET)
{

View file

@ -8,7 +8,7 @@
#include <math.h>
LUAU_FASTFLAG(LuauCodegenRemoveDeadStores5)
LUAU_FASTFLAG(LuauCodegenFastcall3)
// TODO: when nresults is less than our actual result count, we can skip computing/writing unused results
@ -46,19 +46,17 @@ static BuiltinImplResult translateBuiltinNumberToNumber(
return {BuiltinImplType::None, -1};
builtinCheckDouble(build, build.vmReg(arg), pcpos);
build.inst(IrCmd::FASTCALL, build.constUint(bfid), build.vmReg(ra), build.vmReg(arg), args, build.constInt(1), build.constInt(1));
if (!FFlag::LuauCodegenRemoveDeadStores5)
{
if (ra != arg)
build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TNUMBER));
}
if (FFlag::LuauCodegenFastcall3)
build.inst(IrCmd::FASTCALL, build.constUint(bfid), build.vmReg(ra), build.vmReg(arg), build.constInt(1));
else
build.inst(IrCmd::FASTCALL, build.constUint(bfid), build.vmReg(ra), build.vmReg(arg), args, build.constInt(1), build.constInt(1));
return {BuiltinImplType::Full, 1};
}
static BuiltinImplResult translateBuiltinNumberToNumberLibm(
IrBuilder& build, LuauBuiltinFunction bfid, int nparams, int ra, int arg, IrOp args, int nresults, int pcpos)
IrBuilder& build, LuauBuiltinFunction bfid, int nparams, int ra, int arg, int nresults, int pcpos)
{
if (nparams < 1 || nresults > 1)
return {BuiltinImplType::None, -1};
@ -109,17 +107,12 @@ static BuiltinImplResult translateBuiltinNumberTo2Number(
return {BuiltinImplType::None, -1};
builtinCheckDouble(build, build.vmReg(arg), pcpos);
build.inst(
IrCmd::FASTCALL, build.constUint(bfid), build.vmReg(ra), build.vmReg(arg), args, build.constInt(1), build.constInt(nresults == 1 ? 1 : 2));
if (!FFlag::LuauCodegenRemoveDeadStores5)
{
if (ra != arg)
build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TNUMBER));
if (nresults != 1)
build.inst(IrCmd::STORE_TAG, build.vmReg(ra + 1), build.constTag(LUA_TNUMBER));
}
if (FFlag::LuauCodegenFastcall3)
build.inst(IrCmd::FASTCALL, build.constUint(bfid), build.vmReg(ra), build.vmReg(arg), build.constInt(nresults == 1 ? 1 : 2));
else
build.inst(IrCmd::FASTCALL, build.constUint(bfid), build.vmReg(ra), build.vmReg(arg), build.undef(), build.constInt(1),
build.constInt(nresults == 1 ? 1 : 2));
return {BuiltinImplType::Full, 2};
}
@ -198,7 +191,8 @@ static BuiltinImplResult translateBuiltinMathLog(IrBuilder& build, int nparams,
return {BuiltinImplType::Full, 1};
}
static BuiltinImplResult translateBuiltinMathMinMax(IrBuilder& build, IrCmd cmd, int nparams, int ra, int arg, IrOp args, int nresults, int pcpos)
static BuiltinImplResult translateBuiltinMathMinMax(
IrBuilder& build, IrCmd cmd, int nparams, int ra, int arg, IrOp args, IrOp arg3, int nresults, int pcpos)
{
if (nparams < 2 || nparams > kMinMaxUnrolledParams || nresults > 1)
return {BuiltinImplType::None, -1};
@ -206,7 +200,10 @@ static BuiltinImplResult translateBuiltinMathMinMax(IrBuilder& build, IrCmd cmd,
builtinCheckDouble(build, build.vmReg(arg), pcpos);
builtinCheckDouble(build, args, pcpos);
for (int i = 3; i <= nparams; ++i)
if (FFlag::LuauCodegenFastcall3 && nparams >= 3)
builtinCheckDouble(build, arg3, pcpos);
for (int i = (FFlag::LuauCodegenFastcall3 ? 4 : 3); i <= nparams; ++i)
builtinCheckDouble(build, build.vmReg(vmRegOp(args) + (i - 2)), pcpos);
IrOp varg1 = builtinLoadDouble(build, build.vmReg(arg));
@ -214,7 +211,13 @@ static BuiltinImplResult translateBuiltinMathMinMax(IrBuilder& build, IrCmd cmd,
IrOp res = build.inst(cmd, varg2, varg1); // Swapped arguments are required for consistency with VM builtins
for (int i = 3; i <= nparams; ++i)
if (FFlag::LuauCodegenFastcall3 && nparams >= 3)
{
IrOp arg = builtinLoadDouble(build, arg3);
res = build.inst(cmd, arg, res);
}
for (int i = (FFlag::LuauCodegenFastcall3 ? 4 : 3); i <= nparams; ++i)
{
IrOp arg = builtinLoadDouble(build, build.vmReg(vmRegOp(args) + (i - 2)));
res = build.inst(cmd, arg, res);
@ -228,7 +231,8 @@ static BuiltinImplResult translateBuiltinMathMinMax(IrBuilder& build, IrCmd cmd,
return {BuiltinImplType::Full, 1};
}
static BuiltinImplResult translateBuiltinMathClamp(IrBuilder& build, int nparams, int ra, int arg, IrOp args, int nresults, IrOp fallback, int pcpos)
static BuiltinImplResult translateBuiltinMathClamp(
IrBuilder& build, int nparams, int ra, int arg, IrOp args, IrOp arg3, int nresults, IrOp fallback, int pcpos)
{
if (nparams < 3 || nresults > 1)
return {BuiltinImplType::None, -1};
@ -239,10 +243,10 @@ static BuiltinImplResult translateBuiltinMathClamp(IrBuilder& build, int nparams
builtinCheckDouble(build, build.vmReg(arg), pcpos);
builtinCheckDouble(build, args, pcpos);
builtinCheckDouble(build, build.vmReg(vmRegOp(args) + 1), pcpos);
builtinCheckDouble(build, FFlag::LuauCodegenFastcall3 ? arg3 : build.vmReg(vmRegOp(args) + 1), pcpos);
IrOp min = builtinLoadDouble(build, args);
IrOp max = builtinLoadDouble(build, build.vmReg(vmRegOp(args) + 1));
IrOp max = builtinLoadDouble(build, FFlag::LuauCodegenFastcall3 ? arg3 : build.vmReg(vmRegOp(args) + 1));
build.inst(IrCmd::JUMP_CMP_NUM, min, max, build.cond(IrCondition::NotLessEqual), fallback, block);
build.beginBlock(block);
@ -305,7 +309,7 @@ static BuiltinImplResult translateBuiltinTypeof(IrBuilder& build, int nparams, i
}
static BuiltinImplResult translateBuiltinBit32BinaryOp(
IrBuilder& build, IrCmd cmd, bool btest, int nparams, int ra, int arg, IrOp args, int nresults, int pcpos)
IrBuilder& build, IrCmd cmd, bool btest, int nparams, int ra, int arg, IrOp args, IrOp arg3, int nresults, int pcpos)
{
if (nparams < 2 || nparams > kBit32BinaryOpUnrolledParams || nresults > 1)
return {BuiltinImplType::None, -1};
@ -313,7 +317,10 @@ static BuiltinImplResult translateBuiltinBit32BinaryOp(
builtinCheckDouble(build, build.vmReg(arg), pcpos);
builtinCheckDouble(build, args, pcpos);
for (int i = 3; i <= nparams; ++i)
if (FFlag::LuauCodegenFastcall3 && nparams >= 3)
builtinCheckDouble(build, arg3, pcpos);
for (int i = (FFlag::LuauCodegenFastcall3 ? 4 : 3); i <= nparams; ++i)
builtinCheckDouble(build, build.vmReg(vmRegOp(args) + (i - 2)), pcpos);
IrOp va = builtinLoadDouble(build, build.vmReg(arg));
@ -324,7 +331,15 @@ static BuiltinImplResult translateBuiltinBit32BinaryOp(
IrOp res = build.inst(cmd, vaui, vbui);
for (int i = 3; i <= nparams; ++i)
if (FFlag::LuauCodegenFastcall3 && nparams >= 3)
{
IrOp vc = builtinLoadDouble(build, arg3);
IrOp arg = build.inst(IrCmd::NUM_TO_UINT, vc);
res = build.inst(cmd, res, arg);
}
for (int i = (FFlag::LuauCodegenFastcall3 ? 4 : 3); i <= nparams; ++i)
{
IrOp vc = builtinLoadDouble(build, build.vmReg(vmRegOp(args) + (i - 2)));
IrOp arg = build.inst(IrCmd::NUM_TO_UINT, vc);
@ -449,7 +464,7 @@ static BuiltinImplResult translateBuiltinBit32Rotate(IrBuilder& build, IrCmd cmd
}
static BuiltinImplResult translateBuiltinBit32Extract(
IrBuilder& build, int nparams, int ra, int arg, IrOp args, int nresults, IrOp fallback, int pcpos)
IrBuilder& build, int nparams, int ra, int arg, IrOp args, IrOp arg3, int nresults, IrOp fallback, int pcpos)
{
if (nparams < 2 || nresults > 1)
return {BuiltinImplType::None, -1};
@ -497,8 +512,8 @@ static BuiltinImplResult translateBuiltinBit32Extract(
{
IrOp f = build.inst(IrCmd::NUM_TO_INT, vb);
builtinCheckDouble(build, build.vmReg(args.index + 1), pcpos);
IrOp vc = builtinLoadDouble(build, build.vmReg(args.index + 1));
builtinCheckDouble(build, FFlag::LuauCodegenFastcall3 ? arg3 : build.vmReg(args.index + 1), pcpos);
IrOp vc = builtinLoadDouble(build, FFlag::LuauCodegenFastcall3 ? arg3 : build.vmReg(args.index + 1));
IrOp w = build.inst(IrCmd::NUM_TO_INT, vc);
IrOp block1 = build.block(IrBlockKind::Internal);
@ -587,18 +602,18 @@ static BuiltinImplResult translateBuiltinBit32Unary(IrBuilder& build, IrCmd cmd,
}
static BuiltinImplResult translateBuiltinBit32Replace(
IrBuilder& build, int nparams, int ra, int arg, IrOp args, int nresults, IrOp fallback, int pcpos)
IrBuilder& build, int nparams, int ra, int arg, IrOp args, IrOp arg3, int nresults, IrOp fallback, int pcpos)
{
if (nparams < 3 || nresults > 1)
return {BuiltinImplType::None, -1};
builtinCheckDouble(build, build.vmReg(arg), pcpos);
builtinCheckDouble(build, args, pcpos);
builtinCheckDouble(build, build.vmReg(args.index + 1), pcpos);
builtinCheckDouble(build, FFlag::LuauCodegenFastcall3 ? arg3 : build.vmReg(args.index + 1), pcpos);
IrOp va = builtinLoadDouble(build, build.vmReg(arg));
IrOp vb = builtinLoadDouble(build, args);
IrOp vc = builtinLoadDouble(build, build.vmReg(args.index + 1));
IrOp vc = builtinLoadDouble(build, FFlag::LuauCodegenFastcall3 ? arg3 : build.vmReg(args.index + 1));
IrOp n = build.inst(IrCmd::NUM_TO_UINT, va);
IrOp v = build.inst(IrCmd::NUM_TO_UINT, vb);
@ -623,8 +638,8 @@ static BuiltinImplResult translateBuiltinBit32Replace(
}
else
{
builtinCheckDouble(build, build.vmReg(args.index + 2), pcpos);
IrOp vd = builtinLoadDouble(build, build.vmReg(args.index + 2));
builtinCheckDouble(build, FFlag::LuauCodegenFastcall3 ? build.vmReg(vmRegOp(args) + 2) : build.vmReg(args.index + 2), pcpos);
IrOp vd = builtinLoadDouble(build, FFlag::LuauCodegenFastcall3 ? build.vmReg(vmRegOp(args) + 2) : build.vmReg(args.index + 2));
IrOp w = build.inst(IrCmd::NUM_TO_INT, vd);
IrOp block1 = build.block(IrBlockKind::Internal);
@ -661,7 +676,7 @@ static BuiltinImplResult translateBuiltinBit32Replace(
return {BuiltinImplType::UsesFallback, 1};
}
static BuiltinImplResult translateBuiltinVector(IrBuilder& build, int nparams, int ra, int arg, IrOp args, int nresults, int pcpos)
static BuiltinImplResult translateBuiltinVector(IrBuilder& build, int nparams, int ra, int arg, IrOp args, IrOp arg3, int nresults, int pcpos)
{
if (nparams < 3 || nresults > 1)
return {BuiltinImplType::None, -1};
@ -670,11 +685,11 @@ static BuiltinImplResult translateBuiltinVector(IrBuilder& build, int nparams, i
builtinCheckDouble(build, build.vmReg(arg), pcpos);
builtinCheckDouble(build, args, pcpos);
builtinCheckDouble(build, build.vmReg(vmRegOp(args) + 1), pcpos);
builtinCheckDouble(build, FFlag::LuauCodegenFastcall3 ? arg3 : build.vmReg(vmRegOp(args) + 1), pcpos);
IrOp x = builtinLoadDouble(build, build.vmReg(arg));
IrOp y = builtinLoadDouble(build, args);
IrOp z = builtinLoadDouble(build, build.vmReg(vmRegOp(args) + 1));
IrOp z = builtinLoadDouble(build, FFlag::LuauCodegenFastcall3 ? arg3 : build.vmReg(vmRegOp(args) + 1));
build.inst(IrCmd::STORE_VECTOR, build.vmReg(ra), x, y, z);
build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TVECTOR));
@ -736,13 +751,14 @@ static BuiltinImplResult translateBuiltinStringLen(IrBuilder& build, int nparams
return {BuiltinImplType::Full, 1};
}
static void translateBufferArgsAndCheckBounds(IrBuilder& build, int nparams, int arg, IrOp args, int size, int pcpos, IrOp& buf, IrOp& intIndex)
static void translateBufferArgsAndCheckBounds(
IrBuilder& build, int nparams, int arg, IrOp args, IrOp arg3, int size, int pcpos, IrOp& buf, IrOp& intIndex)
{
build.loadAndCheckTag(build.vmReg(arg), LUA_TBUFFER, build.vmExit(pcpos));
builtinCheckDouble(build, args, pcpos);
if (nparams == 3)
builtinCheckDouble(build, build.vmReg(vmRegOp(args) + 1), pcpos);
builtinCheckDouble(build, FFlag::LuauCodegenFastcall3 ? arg3 : build.vmReg(vmRegOp(args) + 1), pcpos);
buf = build.inst(IrCmd::LOAD_POINTER, build.vmReg(arg));
@ -753,13 +769,13 @@ static void translateBufferArgsAndCheckBounds(IrBuilder& build, int nparams, int
}
static BuiltinImplResult translateBuiltinBufferRead(
IrBuilder& build, int nparams, int ra, int arg, IrOp args, int nresults, int pcpos, IrCmd readCmd, int size, IrCmd convCmd)
IrBuilder& build, int nparams, int ra, int arg, IrOp args, IrOp arg3, int nresults, int pcpos, IrCmd readCmd, int size, IrCmd convCmd)
{
if (nparams < 2 || nresults > 1)
return {BuiltinImplType::None, -1};
IrOp buf, intIndex;
translateBufferArgsAndCheckBounds(build, nparams, arg, args, size, pcpos, buf, intIndex);
translateBufferArgsAndCheckBounds(build, nparams, arg, args, arg3, size, pcpos, buf, intIndex);
IrOp result = build.inst(readCmd, buf, intIndex);
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(ra), convCmd == IrCmd::NOP ? result : build.inst(convCmd, result));
@ -769,21 +785,22 @@ static BuiltinImplResult translateBuiltinBufferRead(
}
static BuiltinImplResult translateBuiltinBufferWrite(
IrBuilder& build, int nparams, int ra, int arg, IrOp args, int nresults, int pcpos, IrCmd writeCmd, int size, IrCmd convCmd)
IrBuilder& build, int nparams, int ra, int arg, IrOp args, IrOp arg3, int nresults, int pcpos, IrCmd writeCmd, int size, IrCmd convCmd)
{
if (nparams < 3 || nresults > 0)
return {BuiltinImplType::None, -1};
IrOp buf, intIndex;
translateBufferArgsAndCheckBounds(build, nparams, arg, args, size, pcpos, buf, intIndex);
translateBufferArgsAndCheckBounds(build, nparams, arg, args, arg3, size, pcpos, buf, intIndex);
IrOp numValue = builtinLoadDouble(build, build.vmReg(vmRegOp(args) + 1));
IrOp numValue = builtinLoadDouble(build, FFlag::LuauCodegenFastcall3 ? arg3 : build.vmReg(vmRegOp(args) + 1));
build.inst(writeCmd, buf, intIndex, convCmd == IrCmd::NOP ? numValue : build.inst(convCmd, numValue));
return {BuiltinImplType::Full, 0};
}
BuiltinImplResult translateBuiltin(IrBuilder& build, int bfid, int ra, int arg, IrOp args, int nparams, int nresults, IrOp fallback, int pcpos)
BuiltinImplResult translateBuiltin(
IrBuilder& build, int bfid, int ra, int arg, IrOp args, IrOp arg3, int nparams, int nresults, IrOp fallback, int pcpos)
{
// Builtins are not allowed to handle variadic arguments
if (nparams == LUA_MULTRET)
@ -800,11 +817,11 @@ BuiltinImplResult translateBuiltin(IrBuilder& build, int bfid, int ra, int arg,
case LBF_MATH_LOG:
return translateBuiltinMathLog(build, nparams, ra, arg, args, nresults, pcpos);
case LBF_MATH_MIN:
return translateBuiltinMathMinMax(build, IrCmd::MIN_NUM, nparams, ra, arg, args, nresults, pcpos);
return translateBuiltinMathMinMax(build, IrCmd::MIN_NUM, nparams, ra, arg, args, arg3, nresults, pcpos);
case LBF_MATH_MAX:
return translateBuiltinMathMinMax(build, IrCmd::MAX_NUM, nparams, ra, arg, args, nresults, pcpos);
return translateBuiltinMathMinMax(build, IrCmd::MAX_NUM, nparams, ra, arg, args, arg3, nresults, pcpos);
case LBF_MATH_CLAMP:
return translateBuiltinMathClamp(build, nparams, ra, arg, args, nresults, fallback, pcpos);
return translateBuiltinMathClamp(build, nparams, ra, arg, args, arg3, nresults, fallback, pcpos);
case LBF_MATH_FLOOR:
return translateBuiltinMathUnary(build, IrCmd::FLOOR_NUM, nparams, ra, arg, nresults, pcpos);
case LBF_MATH_CEIL:
@ -826,7 +843,7 @@ BuiltinImplResult translateBuiltin(IrBuilder& build, int bfid, int ra, int arg,
case LBF_MATH_TAN:
case LBF_MATH_TANH:
case LBF_MATH_LOG10:
return translateBuiltinNumberToNumberLibm(build, LuauBuiltinFunction(bfid), nparams, ra, arg, args, nresults, pcpos);
return translateBuiltinNumberToNumberLibm(build, LuauBuiltinFunction(bfid), nparams, ra, arg, nresults, pcpos);
case LBF_MATH_SIGN:
return translateBuiltinNumberToNumber(build, LuauBuiltinFunction(bfid), nparams, ra, arg, args, nresults, pcpos);
case LBF_MATH_POW:
@ -838,13 +855,13 @@ BuiltinImplResult translateBuiltin(IrBuilder& build, int bfid, int ra, int arg,
case LBF_MATH_MODF:
return translateBuiltinNumberTo2Number(build, LuauBuiltinFunction(bfid), nparams, ra, arg, args, nresults, pcpos);
case LBF_BIT32_BAND:
return translateBuiltinBit32BinaryOp(build, IrCmd::BITAND_UINT, /* btest= */ false, nparams, ra, arg, args, nresults, pcpos);
return translateBuiltinBit32BinaryOp(build, IrCmd::BITAND_UINT, /* btest= */ false, nparams, ra, arg, args, arg3, nresults, pcpos);
case LBF_BIT32_BOR:
return translateBuiltinBit32BinaryOp(build, IrCmd::BITOR_UINT, /* btest= */ false, nparams, ra, arg, args, nresults, pcpos);
return translateBuiltinBit32BinaryOp(build, IrCmd::BITOR_UINT, /* btest= */ false, nparams, ra, arg, args, arg3, nresults, pcpos);
case LBF_BIT32_BXOR:
return translateBuiltinBit32BinaryOp(build, IrCmd::BITXOR_UINT, /* btest= */ false, nparams, ra, arg, args, nresults, pcpos);
return translateBuiltinBit32BinaryOp(build, IrCmd::BITXOR_UINT, /* btest= */ false, nparams, ra, arg, args, arg3, nresults, pcpos);
case LBF_BIT32_BTEST:
return translateBuiltinBit32BinaryOp(build, IrCmd::BITAND_UINT, /* btest= */ true, nparams, ra, arg, args, nresults, pcpos);
return translateBuiltinBit32BinaryOp(build, IrCmd::BITAND_UINT, /* btest= */ true, nparams, ra, arg, args, arg3, nresults, pcpos);
case LBF_BIT32_BNOT:
return translateBuiltinBit32Bnot(build, nparams, ra, arg, args, nresults, pcpos);
case LBF_BIT32_LSHIFT:
@ -858,7 +875,7 @@ BuiltinImplResult translateBuiltin(IrBuilder& build, int bfid, int ra, int arg,
case LBF_BIT32_RROTATE:
return translateBuiltinBit32Rotate(build, IrCmd::BITRROTATE_UINT, nparams, ra, arg, args, nresults, pcpos);
case LBF_BIT32_EXTRACT:
return translateBuiltinBit32Extract(build, nparams, ra, arg, args, nresults, fallback, pcpos);
return translateBuiltinBit32Extract(build, nparams, ra, arg, args, arg3, nresults, fallback, pcpos);
case LBF_BIT32_EXTRACTK:
return translateBuiltinBit32ExtractK(build, nparams, ra, arg, args, nresults, pcpos);
case LBF_BIT32_COUNTLZ:
@ -866,13 +883,13 @@ BuiltinImplResult translateBuiltin(IrBuilder& build, int bfid, int ra, int arg,
case LBF_BIT32_COUNTRZ:
return translateBuiltinBit32Unary(build, IrCmd::BITCOUNTRZ_UINT, nparams, ra, arg, args, nresults, pcpos);
case LBF_BIT32_REPLACE:
return translateBuiltinBit32Replace(build, nparams, ra, arg, args, nresults, fallback, pcpos);
return translateBuiltinBit32Replace(build, nparams, ra, arg, args, arg3, nresults, fallback, pcpos);
case LBF_TYPE:
return translateBuiltinType(build, nparams, ra, arg, args, nresults);
case LBF_TYPEOF:
return translateBuiltinTypeof(build, nparams, ra, arg, args, nresults);
case LBF_VECTOR:
return translateBuiltinVector(build, nparams, ra, arg, args, nresults, pcpos);
return translateBuiltinVector(build, nparams, ra, arg, args, arg3, nresults, pcpos);
case LBF_TABLE_INSERT:
return translateBuiltinTableInsert(build, nparams, ra, arg, args, nresults, pcpos);
case LBF_STRING_LEN:
@ -880,31 +897,31 @@ BuiltinImplResult translateBuiltin(IrBuilder& build, int bfid, int ra, int arg,
case LBF_BIT32_BYTESWAP:
return translateBuiltinBit32Unary(build, IrCmd::BYTESWAP_UINT, nparams, ra, arg, args, nresults, pcpos);
case LBF_BUFFER_READI8:
return translateBuiltinBufferRead(build, nparams, ra, arg, args, nresults, pcpos, IrCmd::BUFFER_READI8, 1, IrCmd::INT_TO_NUM);
return translateBuiltinBufferRead(build, nparams, ra, arg, args, arg3, nresults, pcpos, IrCmd::BUFFER_READI8, 1, IrCmd::INT_TO_NUM);
case LBF_BUFFER_READU8:
return translateBuiltinBufferRead(build, nparams, ra, arg, args, nresults, pcpos, IrCmd::BUFFER_READU8, 1, IrCmd::INT_TO_NUM);
return translateBuiltinBufferRead(build, nparams, ra, arg, args, arg3, nresults, pcpos, IrCmd::BUFFER_READU8, 1, IrCmd::INT_TO_NUM);
case LBF_BUFFER_WRITEU8:
return translateBuiltinBufferWrite(build, nparams, ra, arg, args, nresults, pcpos, IrCmd::BUFFER_WRITEI8, 1, IrCmd::NUM_TO_UINT);
return translateBuiltinBufferWrite(build, nparams, ra, arg, args, arg3, nresults, pcpos, IrCmd::BUFFER_WRITEI8, 1, IrCmd::NUM_TO_UINT);
case LBF_BUFFER_READI16:
return translateBuiltinBufferRead(build, nparams, ra, arg, args, nresults, pcpos, IrCmd::BUFFER_READI16, 2, IrCmd::INT_TO_NUM);
return translateBuiltinBufferRead(build, nparams, ra, arg, args, arg3, nresults, pcpos, IrCmd::BUFFER_READI16, 2, IrCmd::INT_TO_NUM);
case LBF_BUFFER_READU16:
return translateBuiltinBufferRead(build, nparams, ra, arg, args, nresults, pcpos, IrCmd::BUFFER_READU16, 2, IrCmd::INT_TO_NUM);
return translateBuiltinBufferRead(build, nparams, ra, arg, args, arg3, nresults, pcpos, IrCmd::BUFFER_READU16, 2, IrCmd::INT_TO_NUM);
case LBF_BUFFER_WRITEU16:
return translateBuiltinBufferWrite(build, nparams, ra, arg, args, nresults, pcpos, IrCmd::BUFFER_WRITEI16, 2, IrCmd::NUM_TO_UINT);
return translateBuiltinBufferWrite(build, nparams, ra, arg, args, arg3, nresults, pcpos, IrCmd::BUFFER_WRITEI16, 2, IrCmd::NUM_TO_UINT);
case LBF_BUFFER_READI32:
return translateBuiltinBufferRead(build, nparams, ra, arg, args, nresults, pcpos, IrCmd::BUFFER_READI32, 4, IrCmd::INT_TO_NUM);
return translateBuiltinBufferRead(build, nparams, ra, arg, args, arg3, nresults, pcpos, IrCmd::BUFFER_READI32, 4, IrCmd::INT_TO_NUM);
case LBF_BUFFER_READU32:
return translateBuiltinBufferRead(build, nparams, ra, arg, args, nresults, pcpos, IrCmd::BUFFER_READI32, 4, IrCmd::UINT_TO_NUM);
return translateBuiltinBufferRead(build, nparams, ra, arg, args, arg3, nresults, pcpos, IrCmd::BUFFER_READI32, 4, IrCmd::UINT_TO_NUM);
case LBF_BUFFER_WRITEU32:
return translateBuiltinBufferWrite(build, nparams, ra, arg, args, nresults, pcpos, IrCmd::BUFFER_WRITEI32, 4, IrCmd::NUM_TO_UINT);
return translateBuiltinBufferWrite(build, nparams, ra, arg, args, arg3, nresults, pcpos, IrCmd::BUFFER_WRITEI32, 4, IrCmd::NUM_TO_UINT);
case LBF_BUFFER_READF32:
return translateBuiltinBufferRead(build, nparams, ra, arg, args, nresults, pcpos, IrCmd::BUFFER_READF32, 4, IrCmd::NOP);
return translateBuiltinBufferRead(build, nparams, ra, arg, args, arg3, nresults, pcpos, IrCmd::BUFFER_READF32, 4, IrCmd::NOP);
case LBF_BUFFER_WRITEF32:
return translateBuiltinBufferWrite(build, nparams, ra, arg, args, nresults, pcpos, IrCmd::BUFFER_WRITEF32, 4, IrCmd::NOP);
return translateBuiltinBufferWrite(build, nparams, ra, arg, args, arg3, nresults, pcpos, IrCmd::BUFFER_WRITEF32, 4, IrCmd::NOP);
case LBF_BUFFER_READF64:
return translateBuiltinBufferRead(build, nparams, ra, arg, args, nresults, pcpos, IrCmd::BUFFER_READF64, 8, IrCmd::NOP);
return translateBuiltinBufferRead(build, nparams, ra, arg, args, arg3, nresults, pcpos, IrCmd::BUFFER_READF64, 8, IrCmd::NOP);
case LBF_BUFFER_WRITEF64:
return translateBuiltinBufferWrite(build, nparams, ra, arg, args, nresults, pcpos, IrCmd::BUFFER_WRITEF64, 8, IrCmd::NOP);
return translateBuiltinBufferWrite(build, nparams, ra, arg, args, arg3, nresults, pcpos, IrCmd::BUFFER_WRITEF64, 8, IrCmd::NOP);
default:
return {BuiltinImplType::None, -1};
}

View file

@ -22,7 +22,8 @@ struct BuiltinImplResult
int actualResultCount;
};
BuiltinImplResult translateBuiltin(IrBuilder& build, int bfid, int ra, int arg, IrOp args, int nparams, int nresults, IrOp fallback, int pcpos);
BuiltinImplResult translateBuiltin(
IrBuilder& build, int bfid, int ra, int arg, IrOp args, IrOp arg3, int nparams, int nresults, IrOp fallback, int pcpos);
} // namespace CodeGen
} // namespace Luau

View file

@ -13,9 +13,9 @@
#include "lstate.h"
#include "ltm.h"
LUAU_FASTFLAGVARIABLE(LuauCodegenDirectUserdataFlow, false)
LUAU_FASTFLAG(LuauCodegenAnalyzeHostVectorOps)
LUAU_FASTFLAG(LuauCodegenUserdataOps)
LUAU_FASTFLAG(LuauCodegenFastcall3)
namespace Luau
{
@ -743,7 +743,7 @@ void translateInstCloseUpvals(IrBuilder& build, const Instruction* pc)
build.inst(IrCmd::CLOSE_UPVALS, build.vmReg(ra));
}
IrOp translateFastCallN(IrBuilder& build, const Instruction* pc, int pcpos, bool customParams, int customParamCount, IrOp customArgs)
IrOp translateFastCallN(IrBuilder& build, const Instruction* pc, int pcpos, bool customParams, int customParamCount, IrOp customArgs, IrOp customArg3)
{
LuauOpcode opcode = LuauOpcode(LUAU_INSN_OP(*pc));
int bfid = LUAU_INSN_A(*pc);
@ -769,13 +769,15 @@ IrOp translateFastCallN(IrBuilder& build, const Instruction* pc, int pcpos, bool
builtinArgs = build.constDouble(protok.value.n);
}
IrOp builtinArg3 = FFlag::LuauCodegenFastcall3 ? (customParams ? customArg3 : build.vmReg(ra + 3)) : IrOp{};
IrOp fallback = build.block(IrBlockKind::Fallback);
// In unsafe environment, instead of retrying fastcall at 'pcpos' we side-exit directly to fallback sequence
build.inst(IrCmd::CHECK_SAFE_ENV, build.vmExit(pcpos + getOpLength(opcode)));
BuiltinImplResult br =
translateBuiltin(build, LuauBuiltinFunction(bfid), ra, arg, builtinArgs, nparams, nresults, fallback, pcpos + getOpLength(opcode));
BuiltinImplResult br = translateBuiltin(
build, LuauBuiltinFunction(bfid), ra, arg, builtinArgs, builtinArg3, nparams, nresults, fallback, pcpos + getOpLength(opcode));
if (br.type != BuiltinImplType::None)
{
@ -792,6 +794,22 @@ IrOp translateFastCallN(IrBuilder& build, const Instruction* pc, int pcpos, bool
return build.undef();
}
}
else if (FFlag::LuauCodegenFastcall3)
{
IrOp arg3 = customParams ? customArg3 : build.undef();
// TODO: we can skip saving pc for some well-behaved builtins which we didn't inline
build.inst(IrCmd::SET_SAVEDPC, build.constUint(pcpos + getOpLength(opcode)));
IrOp res = build.inst(IrCmd::INVOKE_FASTCALL, build.constUint(bfid), build.vmReg(ra), build.vmReg(arg), args, arg3, build.constInt(nparams),
build.constInt(nresults));
build.inst(IrCmd::CHECK_FASTCALL_RES, res, fallback);
if (nresults == LUA_MULTRET)
build.inst(IrCmd::ADJUST_STACK_TO_REG, build.vmReg(ra), res);
else if (nparams == LUA_MULTRET)
build.inst(IrCmd::ADJUST_STACK_TO_TOP);
}
else
{
// TODO: we can skip saving pc for some well-behaved builtins which we didn't inline
@ -1277,7 +1295,7 @@ void translateInstGetTableKS(IrBuilder& build, const Instruction* pc, int pcpos)
return;
}
if (FFlag::LuauCodegenDirectUserdataFlow && (FFlag::LuauCodegenUserdataOps ? isUserdataBytecodeType(bcTypes.a) : bcTypes.a == LBC_TYPE_USERDATA))
if (FFlag::LuauCodegenUserdataOps ? isUserdataBytecodeType(bcTypes.a) : bcTypes.a == LBC_TYPE_USERDATA)
{
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TUSERDATA), build.vmExit(pcpos));
@ -1324,7 +1342,7 @@ void translateInstSetTableKS(IrBuilder& build, const Instruction* pc, int pcpos)
IrOp tb = build.inst(IrCmd::LOAD_TAG, build.vmReg(rb));
if (FFlag::LuauCodegenDirectUserdataFlow && (FFlag::LuauCodegenUserdataOps ? isUserdataBytecodeType(bcTypes.a) : bcTypes.a == LBC_TYPE_USERDATA))
if (FFlag::LuauCodegenUserdataOps ? isUserdataBytecodeType(bcTypes.a) : bcTypes.a == LBC_TYPE_USERDATA)
{
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TUSERDATA), build.vmExit(pcpos));
@ -1446,7 +1464,7 @@ bool translateInstNamecall(IrBuilder& build, const Instruction* pc, int pcpos)
BytecodeTypes bcTypes = build.function.getBytecodeTypesAt(pcpos);
if (FFlag::LuauCodegenDirectUserdataFlow && bcTypes.a == LBC_TYPE_VECTOR)
if (bcTypes.a == LBC_TYPE_VECTOR)
{
build.loadAndCheckTag(build.vmReg(rb), LUA_TVECTOR, build.vmExit(pcpos));
@ -1470,7 +1488,7 @@ bool translateInstNamecall(IrBuilder& build, const Instruction* pc, int pcpos)
return false;
}
if (FFlag::LuauCodegenDirectUserdataFlow && (FFlag::LuauCodegenUserdataOps ? isUserdataBytecodeType(bcTypes.a) : bcTypes.a == LBC_TYPE_USERDATA))
if (FFlag::LuauCodegenUserdataOps ? isUserdataBytecodeType(bcTypes.a) : bcTypes.a == LBC_TYPE_USERDATA)
{
build.loadAndCheckTag(build.vmReg(rb), LUA_TUSERDATA, build.vmExit(pcpos));
@ -1499,8 +1517,7 @@ bool translateInstNamecall(IrBuilder& build, const Instruction* pc, int pcpos)
IrOp firstFastPathSuccess = build.block(IrBlockKind::Internal);
IrOp secondFastPath = build.block(IrBlockKind::Internal);
build.loadAndCheckTag(
build.vmReg(rb), LUA_TTABLE, FFlag::LuauCodegenDirectUserdataFlow && bcTypes.a == LBC_TYPE_TABLE ? build.vmExit(pcpos) : fallback);
build.loadAndCheckTag(build.vmReg(rb), LUA_TTABLE, bcTypes.a == LBC_TYPE_TABLE ? build.vmExit(pcpos) : fallback);
IrOp table = build.inst(IrCmd::LOAD_POINTER, build.vmReg(rb));
CODEGEN_ASSERT(build.function.proto);

View file

@ -44,7 +44,8 @@ void translateInstDupTable(IrBuilder& build, const Instruction* pc, int pcpos);
void translateInstGetUpval(IrBuilder& build, const Instruction* pc, int pcpos);
void translateInstSetUpval(IrBuilder& build, const Instruction* pc, int pcpos);
void translateInstCloseUpvals(IrBuilder& build, const Instruction* pc);
IrOp translateFastCallN(IrBuilder& build, const Instruction* pc, int pcpos, bool customParams, int customParamCount, IrOp customArgs);
IrOp translateFastCallN(
IrBuilder& build, const Instruction* pc, int pcpos, bool customParams, int customParamCount, IrOp customArgs, IrOp customArg3);
void translateInstForNPrep(IrBuilder& build, const Instruction* pc, int pcpos);
void translateInstForNLoop(IrBuilder& build, const Instruction* pc, int pcpos);
void translateInstForGPrepNext(IrBuilder& build, const Instruction* pc, int pcpos);

View file

@ -3,6 +3,8 @@
#include "Luau/IrUtils.h"
LUAU_FASTFLAG(LuauCodegenFastcall3)
namespace Luau
{
namespace CodeGen
@ -44,11 +46,11 @@ void IrValueLocationTracking::beforeInstLowering(IrInst& inst)
invalidateRestoreVmRegs(vmRegOp(inst.a), -1);
break;
case IrCmd::FASTCALL:
invalidateRestoreVmRegs(vmRegOp(inst.b), function.intOp(inst.f));
invalidateRestoreVmRegs(vmRegOp(inst.b), function.intOp(FFlag::LuauCodegenFastcall3 ? inst.d : inst.f));
break;
case IrCmd::INVOKE_FASTCALL:
// Multiple return sequences (count == -1) are defined by ADJUST_STACK_TO_REG
if (int count = function.intOp(inst.f); count != -1)
if (int count = function.intOp(FFlag::LuauCodegenFastcall3 ? inst.g : inst.f); count != -1)
invalidateRestoreVmRegs(vmRegOp(inst.b), count);
break;
case IrCmd::DO_ARITH:
@ -119,7 +121,7 @@ void IrValueLocationTracking::beforeInstLowering(IrInst& inst)
break;
// These instructions read VmReg only after optimizeMemoryOperandsX64
case IrCmd::CHECK_TAG: // TODO: remove with FFlagLuauCodegenRemoveDeadStores5
case IrCmd::CHECK_TAG:
case IrCmd::CHECK_TRUTHY:
case IrCmd::ADD_NUM:
case IrCmd::SUB_NUM:

View file

@ -18,10 +18,10 @@ LUAU_FASTINTVARIABLE(LuauCodeGenMinLinearBlockPath, 3)
LUAU_FASTINTVARIABLE(LuauCodeGenReuseSlotLimit, 64)
LUAU_FASTINTVARIABLE(LuauCodeGenReuseUdataTagLimit, 64)
LUAU_FASTFLAGVARIABLE(DebugLuauAbortingChecks, false)
LUAU_FASTFLAG(LuauCodegenRemoveDeadStores5)
LUAU_FASTFLAGVARIABLE(LuauCodegenFixSplitStoreConstMismatch, false)
LUAU_FASTFLAG(LuauCodegenUserdataOps)
LUAU_FASTFLAG(LuauCodegenUserdataAlloc)
LUAU_FASTFLAG(LuauCodegenFastcall3)
namespace Luau
{
@ -621,16 +621,9 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
std::tie(activeLoadCmd, activeLoadValue) = state.getPreviousVersionedLoadForTag(value, source);
if (state.tryGetTag(source) == value)
{
if (FFlag::DebugLuauAbortingChecks && !FFlag::LuauCodegenRemoveDeadStores5)
replace(function, block, index, {IrCmd::CHECK_TAG, inst.a, inst.b, build.undef()});
else
kill(function, inst);
}
kill(function, inst);
else
{
state.saveTag(source, value);
}
}
else
{
@ -1150,39 +1143,33 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
case IrCmd::FASTCALL:
{
if (FFlag::LuauCodegenRemoveDeadStores5)
LuauBuiltinFunction bfid = LuauBuiltinFunction(function.uintOp(inst.a));
int firstReturnReg = vmRegOp(inst.b);
int nresults = function.intOp(FFlag::LuauCodegenFastcall3 ? inst.d : inst.f);
// TODO: FASTCALL is more restrictive than INVOKE_FASTCALL; we should either determine the exact semantics, or rework it
handleBuiltinEffects(state, bfid, firstReturnReg, nresults);
switch (bfid)
{
LuauBuiltinFunction bfid = LuauBuiltinFunction(function.uintOp(inst.a));
int firstReturnReg = vmRegOp(inst.b);
int nresults = function.intOp(inst.f);
case LBF_MATH_MODF:
case LBF_MATH_FREXP:
state.updateTag(IrOp{IrOpKind::VmReg, uint8_t(firstReturnReg)}, LUA_TNUMBER);
// TODO: FASTCALL is more restrictive than INVOKE_FASTCALL; we should either determine the exact semantics, or rework it
handleBuiltinEffects(state, bfid, firstReturnReg, nresults);
switch (bfid)
{
case LBF_MATH_MODF:
case LBF_MATH_FREXP:
state.updateTag(IrOp{IrOpKind::VmReg, uint8_t(firstReturnReg)}, LUA_TNUMBER);
if (nresults > 1)
state.updateTag(IrOp{IrOpKind::VmReg, uint8_t(firstReturnReg + 1)}, LUA_TNUMBER);
break;
case LBF_MATH_SIGN:
state.updateTag(IrOp{IrOpKind::VmReg, uint8_t(firstReturnReg)}, LUA_TNUMBER);
break;
default:
break;
}
}
else
{
handleBuiltinEffects(state, LuauBuiltinFunction(function.uintOp(inst.a)), vmRegOp(inst.b), function.intOp(inst.f));
if (nresults > 1)
state.updateTag(IrOp{IrOpKind::VmReg, uint8_t(firstReturnReg + 1)}, LUA_TNUMBER);
break;
case LBF_MATH_SIGN:
state.updateTag(IrOp{IrOpKind::VmReg, uint8_t(firstReturnReg)}, LUA_TNUMBER);
break;
default:
break;
}
break;
}
case IrCmd::INVOKE_FASTCALL:
handleBuiltinEffects(state, LuauBuiltinFunction(function.uintOp(inst.a)), vmRegOp(inst.b), function.intOp(inst.f));
handleBuiltinEffects(
state, LuauBuiltinFunction(function.uintOp(inst.a)), vmRegOp(inst.b), function.intOp(FFlag::LuauCodegenFastcall3 ? inst.g : inst.f));
break;
// These instructions don't have an effect on register/memory state we are tracking

View file

@ -9,7 +9,6 @@
#include "lobject.h"
LUAU_FASTFLAGVARIABLE(LuauCodegenRemoveDeadStores5, false)
LUAU_FASTFLAG(LuauCodegenUserdataOps)
// TODO: optimization can be improved by knowing which registers are live in at each VM exit

View file

@ -46,6 +46,7 @@
// 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.
// Version 6: Adds FASTCALL3. Currently supported.
// # Bytecode type information history
// Version 1: (from bytecode version 4) Type information for function signature. Currently supported.
@ -299,8 +300,13 @@ enum LuauOpcode
// A: target register (see FORGLOOP for register layout)
LOP_FORGPREP_INEXT,
// removed in v3
LOP_DEP_FORGLOOP_INEXT,
// FASTCALL3: perform a fast call of a built-in function using 3 register arguments
// A: builtin function id (see LuauBuiltinFunction)
// B: source argument register
// C: jump offset to get to following CALL
// AUX: source register 2 in least-significant byte
// AUX: source register 3 in second least-significant byte
LOP_FASTCALL3,
// FORGPREP_NEXT: prepare FORGLOOP with 2 output variables (no AUX encoding), assuming generator is luaB_next, and jump to FORGLOOP
// A: target register (see FORGLOOP for register layout)
@ -434,7 +440,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 = 5,
LBC_VERSION_MAX = 6,
LBC_VERSION_TARGET = 5,
// Type encoding version
LBC_TYPE_VERSION_DEPRECATED = 1,

View file

@ -28,6 +28,7 @@ inline int getOpLength(LuauOpcode op)
case LOP_LOADKX:
case LOP_FASTCALL2:
case LOP_FASTCALL2K:
case LOP_FASTCALL3:
case LOP_JUMPXEQKNIL:
case LOP_JUMPXEQKB:
case LOP_JUMPXEQKN:

View file

@ -120,12 +120,12 @@ public:
return *this;
}
void clear()
void clear(size_t thresholdToDestroy = 32)
{
if (count == 0)
return;
if (capacity > 32)
if (capacity > thresholdToDestroy)
{
destroy();
}
@ -583,9 +583,9 @@ public:
{
}
void clear()
void clear(size_t thresholdToDestroy = 32)
{
impl.clear();
impl.clear(thresholdToDestroy);
}
// Note: this reference is invalidated by any insert operation (i.e. operator[])

View file

@ -9,6 +9,7 @@
LUAU_FASTFLAGVARIABLE(LuauCompileTypeInfo, false)
LUAU_FASTFLAG(LuauCompileUserdataInfo)
LUAU_FASTFLAG(LuauCompileFastcall3)
namespace Luau
{
@ -113,6 +114,7 @@ inline bool isFastCall(LuauOpcode op)
case LOP_FASTCALL1:
case LOP_FASTCALL2:
case LOP_FASTCALL2K:
case LOP_FASTCALL3:
return true;
default:
@ -1241,6 +1243,9 @@ 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
if (FFlag::LuauCompileFastcall3)
return 6;
return LBC_VERSION_TARGET;
}
@ -1621,6 +1626,16 @@ void BytecodeBuilder::validateInstructions() const
VCONSTANY(insns[i + 1]);
break;
case LOP_FASTCALL3:
LUAU_ASSERT(FFlag::LuauCompileFastcall3);
VREG(LUAU_INSN_B(insn));
VJUMP(LUAU_INSN_C(insn));
LUAU_ASSERT(LUAU_INSN_OP(insns[i + 1 + LUAU_INSN_C(insn)]) == LOP_CALL);
VREG(insns[i + 1] & 0xff);
VREG((insns[i + 1] >> 8) & 0xff);
break;
case LOP_COVERAGE:
break;
@ -2235,6 +2250,13 @@ void BytecodeBuilder::dumpInstruction(const uint32_t* code, std::string& result,
code++;
break;
case LOP_FASTCALL3:
LUAU_ASSERT(FFlag::LuauCompileFastcall3);
formatAppend(result, "FASTCALL3 %d R%d R%d R%d L%d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), *code & 0xff, (*code >> 8) & 0xff, targetLabel);
code++;
break;
case LOP_COVERAGE:
formatAppend(result, "COVERAGE\n");
break;

View file

@ -29,6 +29,7 @@ LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5)
LUAU_FASTFLAG(LuauCompileTypeInfo)
LUAU_FASTFLAGVARIABLE(LuauCompileTempTypeInfo, false)
LUAU_FASTFLAGVARIABLE(LuauCompileUserdataInfo, false)
LUAU_FASTFLAGVARIABLE(LuauCompileFastcall3, false)
LUAU_FASTFLAG(LuauNativeAttribute)
@ -473,10 +474,32 @@ struct Compiler
{
LUAU_ASSERT(!expr->self);
LUAU_ASSERT(expr->args.size >= 1);
LUAU_ASSERT(expr->args.size <= 2 || (bfid == LBF_BIT32_EXTRACTK && expr->args.size == 3));
if (FFlag::LuauCompileFastcall3)
LUAU_ASSERT(expr->args.size <= 3);
else
LUAU_ASSERT(expr->args.size <= 2 || (bfid == LBF_BIT32_EXTRACTK && expr->args.size == 3));
LUAU_ASSERT(bfid == LBF_BIT32_EXTRACTK ? bfK >= 0 : bfK < 0);
LuauOpcode opc = expr->args.size == 1 ? LOP_FASTCALL1 : (bfK >= 0 || isConstant(expr->args.data[1])) ? LOP_FASTCALL2K : LOP_FASTCALL2;
LuauOpcode opc = LOP_NOP;
if (FFlag::LuauCompileFastcall3)
{
if (expr->args.size == 1)
opc = LOP_FASTCALL1;
else if (bfK >= 0 || (expr->args.size == 2 && isConstant(expr->args.data[1])))
opc = LOP_FASTCALL2K;
else if (expr->args.size == 2)
opc = LOP_FASTCALL2;
else
opc = LOP_FASTCALL3;
}
else
{
opc = expr->args.size == 1 ? LOP_FASTCALL1
: (bfK >= 0 || (expr->args.size == 2 && isConstant(expr->args.data[1]))) ? LOP_FASTCALL2K : LOP_FASTCALL2;
}
uint32_t args[3] = {};
@ -504,8 +527,16 @@ struct Compiler
size_t fastcallLabel = bytecode.emitLabel();
bytecode.emitABC(opc, uint8_t(bfid), uint8_t(args[0]), 0);
if (opc != LOP_FASTCALL1)
if (FFlag::LuauCompileFastcall3 && opc == LOP_FASTCALL3)
{
LUAU_ASSERT(bfK < 0);
bytecode.emitAux(args[1] | (args[2] << 8));
}
else if (opc != LOP_FASTCALL1)
{
bytecode.emitAux(bfK >= 0 ? bfK : args[1]);
}
// Set up a traditional Lua stack for the subsequent LOP_CALL.
// Note, as with other instructions that immediately follow FASTCALL, these are normally not executed and are used as a fallback for
@ -857,11 +888,28 @@ struct Compiler
}
}
// Optimization: for 1/2 argument fast calls use specialized opcodes
if (bfid >= 0 && expr->args.size >= 1 && expr->args.size <= 2)
unsigned maxFastcallArgs = 2;
// Fastcall with 3 arguments is only used if it can help save one or more move instructions
if (FFlag::LuauCompileFastcall3 && bfid >= 0 && expr->args.size == 3)
{
for (size_t i = 0; i < expr->args.size; ++i)
{
if (int reg = getExprLocalReg(expr->args.data[i]); reg >= 0)
{
maxFastcallArgs = 3;
break;
}
}
}
// Optimization: for 1/2/3 argument fast calls use specialized opcodes
if (bfid >= 0 && expr->args.size >= 1 && expr->args.size <= (FFlag::LuauCompileFastcall3 ? maxFastcallArgs : 2u))
{
if (!isExprMultRet(expr->args.data[expr->args.size - 1]))
{
return compileExprFastcallN(expr, target, targetCount, targetTop, multRet, regs, bfid);
}
else if (options.optimizationLevel >= 2)
{
// when a builtin is none-safe with matching arity, even if the last expression returns 0 or >1 arguments,

View file

@ -49,6 +49,7 @@ struct LintWarning
Code_CommentDirective = 26,
Code_IntegerParsing = 27,
Code_ComparisonPrecedence = 28,
Code_RedundantNativeAttribute = 29,
Code__Count
};
@ -115,6 +116,7 @@ static const char* kWarningNames[] = {
"CommentDirective",
"IntegerParsing",
"ComparisonPrecedence",
"RedundantNativeAttribute",
};
// clang-format on

View file

@ -12,8 +12,6 @@
#include <string.h>
#include <stdio.h>
LUAU_FASTFLAGVARIABLE(LuauPushErrorStackCheck, false)
static const char* getfuncname(Closure* f);
static int currentpc(lua_State* L, CallInfo* ci)
@ -332,8 +330,7 @@ l_noret luaG_runerrorL(lua_State* L, const char* fmt, ...)
vsnprintf(result, sizeof(result), fmt, argp);
va_end(argp);
if (FFlag::LuauPushErrorStackCheck)
lua_rawcheckstack(L, 1);
lua_rawcheckstack(L, 1);
pusherror(L, result);
luaD_throw(L, LUA_ERRRUN);
@ -341,8 +338,7 @@ l_noret luaG_runerrorL(lua_State* L, const char* fmt, ...)
void luaG_pusherror(lua_State* L, const char* error)
{
if (FFlag::LuauPushErrorStackCheck)
lua_rawcheckstack(L, 1);
lua_rawcheckstack(L, 1);
pusherror(L, error);
}

View file

@ -6,8 +6,6 @@
#include "lmem.h"
#include "lgc.h"
LUAU_FASTFLAGVARIABLE(LuauLoadTypeInfo, false)
Proto* luaF_newproto(lua_State* L)
{
Proto* f = luaM_newgco(L, Proto, sizeof(Proto), L->activememcat);
@ -52,9 +50,7 @@ Proto* luaF_newproto(lua_State* L)
f->linegaplog2 = 0;
f->linedefined = 0;
f->bytecodeid = 0;
if (FFlag::LuauLoadTypeInfo)
f->sizetypeinfo = 0;
f->sizetypeinfo = 0;
return f;
}
@ -178,16 +174,8 @@ void luaF_freeproto(lua_State* L, Proto* f, lua_Page* page)
if (f->execdata)
L->global->ecb.destroy(L, f);
if (FFlag::LuauLoadTypeInfo)
{
if (f->typeinfo)
luaM_freearray(L, f->typeinfo, f->sizetypeinfo, uint8_t, f->memcat);
}
else
{
if (f->typeinfo)
luaM_freearray(L, f->typeinfo, f->numparams + 2, uint8_t, f->memcat);
}
if (f->typeinfo)
luaM_freearray(L, f->typeinfo, f->sizetypeinfo, uint8_t, f->memcat);
luaM_freegco(L, f, sizeof(Proto), f->memcat, page);
}

View file

@ -14,8 +14,6 @@
#include <string.h>
LUAU_FASTFLAG(LuauLoadTypeInfo)
/*
* Luau uses an incremental non-generational non-moving mark&sweep garbage collector.
*
@ -507,16 +505,8 @@ static size_t propagatemark(global_State* g)
g->gray = p->gclist;
traverseproto(g, p);
if (FFlag::LuauLoadTypeInfo)
{
return sizeof(Proto) + sizeof(Instruction) * p->sizecode + sizeof(Proto*) * p->sizep + sizeof(TValue) * p->sizek + p->sizelineinfo +
sizeof(LocVar) * p->sizelocvars + sizeof(TString*) * p->sizeupvalues + p->sizetypeinfo;
}
else
{
return sizeof(Proto) + sizeof(Instruction) * p->sizecode + sizeof(Proto*) * p->sizep + sizeof(TValue) * p->sizek + p->sizelineinfo +
sizeof(LocVar) * p->sizelocvars + sizeof(TString*) * p->sizeupvalues;
}
return sizeof(Proto) + sizeof(Instruction) * p->sizecode + sizeof(Proto*) * p->sizep + sizeof(TValue) * p->sizek + p->sizelineinfo +
sizeof(LocVar) * p->sizelocvars + sizeof(TString*) * p->sizeupvalues + p->sizetypeinfo;
}
default:
LUAU_ASSERT(0);

View file

@ -124,11 +124,16 @@ static_assert(offsetof(Udata, data) == ABISWITCH(16, 16, 12), "size mismatch for
static_assert(sizeof(Table) == ABISWITCH(48, 32, 32), "size mismatch for table header");
static_assert(offsetof(Buffer, data) == ABISWITCH(8, 8, 8), "size mismatch for buffer header");
LUAU_FASTFLAGVARIABLE(LuauExtendedSizeClasses, false)
const size_t kSizeClasses = LUA_SIZECLASSES;
const size_t kMaxSmallSize_DEPRECATED = 512; // TODO: remove with FFlagLuauExtendedSizeClasses
// Controls the number of entries in SizeClassConfig and define the maximum possible paged allocation size
// Modifications require updates the SizeClassConfig initialization
const size_t kMaxSmallSize = 1024;
// Effective limit on object size to use paged allocation
// Can be modified without additional changes to code, provided it is smaller or equal to kMaxSmallSize
const size_t kMaxSmallSizeUsed = 1024;
const size_t kLargePageThreshold = 512; // larger pages are used for objects larger than this size to fit more of them into a page
// constant factor to reduce our page sizes by, to increase the chances that pages we allocate will
@ -187,8 +192,7 @@ struct SizeClassConfig
const SizeClassConfig kSizeClassConfig;
// size class for a block of size sz; returns -1 for size=0 because empty allocations take no space
#define sizeclass(sz) \
(size_t((sz)-1) < (FFlag::LuauExtendedSizeClasses ? kMaxSmallSize : kMaxSmallSize_DEPRECATED) ? kSizeClassConfig.classForSize[sz] : -1)
#define sizeclass(sz) (size_t((sz)-1) < kMaxSmallSizeUsed ? kSizeClassConfig.classForSize[sz] : -1)
// metadata for a block is stored in the first pointer of the block
#define metadata(block) (*(void**)(block))
@ -275,34 +279,18 @@ static lua_Page* newpage(lua_State* L, lua_Page** pageset, int pageSize, int blo
// if it is inlined, then the compiler may determine those functions are "too big" to be profitably inlined, which results in reduced performance
LUAU_NOINLINE static lua_Page* newclasspage(lua_State* L, lua_Page** freepageset, lua_Page** pageset, uint8_t sizeClass, bool storeMetadata)
{
if (FFlag::LuauExtendedSizeClasses)
{
int sizeOfClass = kSizeClassConfig.sizeOfClass[sizeClass];
int pageSize = sizeOfClass > int(kLargePageThreshold) ? kLargePageSize : kSmallPageSize;
int blockSize = sizeOfClass + (storeMetadata ? kBlockHeader : 0);
int blockCount = (pageSize - offsetof(lua_Page, data)) / blockSize;
int sizeOfClass = kSizeClassConfig.sizeOfClass[sizeClass];
int pageSize = sizeOfClass > int(kLargePageThreshold) ? kLargePageSize : kSmallPageSize;
int blockSize = sizeOfClass + (storeMetadata ? kBlockHeader : 0);
int blockCount = (pageSize - offsetof(lua_Page, data)) / blockSize;
lua_Page* page = newpage(L, pageset, pageSize, blockSize, blockCount);
lua_Page* page = newpage(L, pageset, pageSize, blockSize, blockCount);
// prepend a page to page freelist (which is empty because we only ever allocate a new page when it is!)
LUAU_ASSERT(!freepageset[sizeClass]);
freepageset[sizeClass] = page;
// prepend a page to page freelist (which is empty because we only ever allocate a new page when it is!)
LUAU_ASSERT(!freepageset[sizeClass]);
freepageset[sizeClass] = page;
return page;
}
else
{
int blockSize = kSizeClassConfig.sizeOfClass[sizeClass] + (storeMetadata ? kBlockHeader : 0);
int blockCount = (kSmallPageSize - offsetof(lua_Page, data)) / blockSize;
lua_Page* page = newpage(L, pageset, kSmallPageSize, blockSize, blockCount);
// prepend a page to page freelist (which is empty because we only ever allocate a new page when it is!)
LUAU_ASSERT(!freepageset[sizeClass]);
freepageset[sizeClass] = page;
return page;
}
return page;
}
static void freepage(lua_State* L, lua_Page** pageset, lua_Page* page)

View file

@ -11,9 +11,6 @@
#include "ldebug.h"
#include "lvm.h"
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauFastTableMaxn, false)
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauFasterConcat, false)
static int foreachi(lua_State* L)
{
luaL_checktype(L, 1, LUA_TTABLE);
@ -56,41 +53,24 @@ static int maxn(lua_State* L)
double max = 0;
luaL_checktype(L, 1, LUA_TTABLE);
if (DFFlag::LuauFastTableMaxn)
Table* t = hvalue(L->base);
for (int i = 0; i < t->sizearray; i++)
{
Table* t = hvalue(L->base);
for (int i = 0; i < t->sizearray; i++)
{
if (!ttisnil(&t->array[i]))
max = i + 1;
}
for (int i = 0; i < sizenode(t); i++)
{
LuaNode* n = gnode(t, i);
if (!ttisnil(gval(n)) && ttisnumber(gkey(n)))
{
double v = nvalue(gkey(n));
if (v > max)
max = v;
}
}
if (!ttisnil(&t->array[i]))
max = i + 1;
}
else
for (int i = 0; i < sizenode(t); i++)
{
lua_pushnil(L); // first key
while (lua_next(L, 1))
LuaNode* n = gnode(t, i);
if (!ttisnil(gval(n)) && ttisnumber(gkey(n)))
{
lua_pop(L, 1); // remove value
if (lua_type(L, -1) == LUA_TNUMBER)
{
double v = lua_tonumber(L, -1);
if (v > max)
max = v;
}
double v = nvalue(gkey(n));
if (v > max)
max = v;
}
}
@ -251,7 +231,7 @@ static int tmove(lua_State* L)
static void addfield(lua_State* L, luaL_Strbuf* b, int i, Table* t)
{
if (DFFlag::LuauFasterConcat && t && unsigned(i - 1) < unsigned(t->sizearray) && ttisstring(&t->array[i - 1]))
if (t && unsigned(i - 1) < unsigned(t->sizearray) && ttisstring(&t->array[i - 1]))
{
TString* ts = tsvalue(&t->array[i - 1]);
luaL_addlstring(b, getstr(ts), ts->len);
@ -273,14 +253,14 @@ static int tconcat(lua_State* L)
int i = luaL_optinteger(L, 3, 1);
int last = luaL_opt(L, luaL_checkinteger, 4, lua_objlen(L, 1));
Table* t = DFFlag::LuauFasterConcat ? hvalue(L->base) : NULL;
Table* t = hvalue(L->base);
luaL_Strbuf b;
luaL_buffinit(L, &b);
for (; i < last; i++)
{
addfield(L, &b, i, t);
if (!DFFlag::LuauFasterConcat || lsep != 0)
if (lsep != 0)
luaL_addlstring(&b, sep, lsep);
}
if (i == last) // add last value (if interval was not empty)

View file

@ -98,7 +98,7 @@ LUAU_FASTFLAGVARIABLE(LuauVmSplitDoarith, false)
VM_DISPATCH_OP(LOP_POWK), VM_DISPATCH_OP(LOP_AND), VM_DISPATCH_OP(LOP_OR), VM_DISPATCH_OP(LOP_ANDK), VM_DISPATCH_OP(LOP_ORK), \
VM_DISPATCH_OP(LOP_CONCAT), VM_DISPATCH_OP(LOP_NOT), VM_DISPATCH_OP(LOP_MINUS), VM_DISPATCH_OP(LOP_LENGTH), VM_DISPATCH_OP(LOP_NEWTABLE), \
VM_DISPATCH_OP(LOP_DUPTABLE), VM_DISPATCH_OP(LOP_SETLIST), VM_DISPATCH_OP(LOP_FORNPREP), VM_DISPATCH_OP(LOP_FORNLOOP), \
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_FORGLOOP), VM_DISPATCH_OP(LOP_FORGPREP_INEXT), VM_DISPATCH_OP(LOP_FASTCALL3), 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_SUBRK), VM_DISPATCH_OP(LOP_DIVRK), VM_DISPATCH_OP(LOP_FASTCALL1), \
@ -2539,12 +2539,6 @@ reentry:
VM_NEXT();
}
VM_CASE(LOP_DEP_FORGLOOP_INEXT)
{
LUAU_ASSERT(!"Unsupported deprecated opcode");
LUAU_UNREACHABLE();
}
VM_CASE(LOP_FORGPREP_NEXT)
{
Instruction insn = *pc++;
@ -3013,6 +3007,60 @@ reentry:
}
}
VM_CASE(LOP_FASTCALL3)
{
Instruction insn = *pc++;
int bfid = LUAU_INSN_A(insn);
int skip = LUAU_INSN_C(insn) - 1;
uint32_t aux = *pc++;
TValue* arg1 = VM_REG(LUAU_INSN_B(insn));
TValue* arg2 = VM_REG(aux & 0xff);
TValue* arg3 = VM_REG((aux >> 8) & 0xff);
LUAU_ASSERT(unsigned(pc - cl->l.p->code + skip) < unsigned(cl->l.p->sizecode));
Instruction call = pc[skip];
LUAU_ASSERT(LUAU_INSN_OP(call) == LOP_CALL);
StkId ra = VM_REG(LUAU_INSN_A(call));
int nparams = 3;
int nresults = LUAU_INSN_C(call) - 1;
luau_FastFunction f = luauF_table[bfid];
LUAU_ASSERT(f);
if (cl->env->safeenv)
{
VM_PROTECT_PC(); // f may fail due to OOM
setobj2s(L, L->top, arg2);
setobj2s(L, L->top + 1, arg3);
int n = f(L, ra, arg1, nresults, L->top, nparams);
if (n >= 0)
{
if (nresults == LUA_MULTRET)
L->top = ra + n;
pc += skip + 1; // skip instructions that compute function as well as CALL
LUAU_ASSERT(unsigned(pc - cl->l.p->code) < unsigned(cl->l.p->sizecode));
VM_NEXT();
}
else
{
// continue execution through the fallback code
VM_NEXT();
}
}
else
{
// continue execution through the fallback code
VM_NEXT();
}
}
VM_CASE(LOP_BREAK)
{
LUAU_ASSERT(cl->l.p->debuginsn);

View file

@ -13,7 +13,6 @@
#include <string.h>
LUAU_FASTFLAG(LuauLoadTypeInfo)
LUAU_FASTFLAGVARIABLE(LuauLoadUserdataInfo, false)
// TODO: RAII deallocation doesn't work for longjmp builds if a memory error happens
@ -357,70 +356,11 @@ int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size
{
p->flags = read<uint8_t>(data, size, offset);
if (FFlag::LuauLoadTypeInfo)
{
if (typesversion == 1)
{
uint32_t typesize = readVarInt(data, size, offset);
if (typesize)
{
uint8_t* types = (uint8_t*)data + offset;
LUAU_ASSERT(typesize == unsigned(2 + p->numparams));
LUAU_ASSERT(types[0] == LBC_TYPE_FUNCTION);
LUAU_ASSERT(types[1] == p->numparams);
// transform v1 into v2 format
int headersize = typesize > 127 ? 4 : 3;
p->typeinfo = luaM_newarray(L, headersize + typesize, uint8_t, p->memcat);
p->sizetypeinfo = headersize + typesize;
if (headersize == 4)
{
p->typeinfo[0] = (typesize & 127) | (1 << 7);
p->typeinfo[1] = typesize >> 7;
p->typeinfo[2] = 0;
p->typeinfo[3] = 0;
}
else
{
p->typeinfo[0] = uint8_t(typesize);
p->typeinfo[1] = 0;
p->typeinfo[2] = 0;
}
memcpy(p->typeinfo + headersize, types, typesize);
}
offset += typesize;
}
else if (typesversion == 2 || (FFlag::LuauLoadUserdataInfo && typesversion == 3))
{
uint32_t typesize = readVarInt(data, size, offset);
if (typesize)
{
uint8_t* types = (uint8_t*)data + offset;
p->typeinfo = luaM_newarray(L, typesize, uint8_t, p->memcat);
p->sizetypeinfo = typesize;
memcpy(p->typeinfo, types, typesize);
offset += typesize;
if (FFlag::LuauLoadUserdataInfo && typesversion == 3)
{
remapUserdataTypes((char*)(uint8_t*)p->typeinfo, p->sizetypeinfo, userdataRemapping, userdataTypeLimit);
}
}
}
}
else
if (typesversion == 1)
{
uint32_t typesize = readVarInt(data, size, offset);
if (typesize && typesversion == LBC_TYPE_VERSION_DEPRECATED)
if (typesize)
{
uint8_t* types = (uint8_t*)data + offset;
@ -428,12 +368,50 @@ int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size
LUAU_ASSERT(types[0] == LBC_TYPE_FUNCTION);
LUAU_ASSERT(types[1] == p->numparams);
p->typeinfo = luaM_newarray(L, typesize, uint8_t, p->memcat);
memcpy(p->typeinfo, types, typesize);
// transform v1 into v2 format
int headersize = typesize > 127 ? 4 : 3;
p->typeinfo = luaM_newarray(L, headersize + typesize, uint8_t, p->memcat);
p->sizetypeinfo = headersize + typesize;
if (headersize == 4)
{
p->typeinfo[0] = (typesize & 127) | (1 << 7);
p->typeinfo[1] = typesize >> 7;
p->typeinfo[2] = 0;
p->typeinfo[3] = 0;
}
else
{
p->typeinfo[0] = uint8_t(typesize);
p->typeinfo[1] = 0;
p->typeinfo[2] = 0;
}
memcpy(p->typeinfo + headersize, types, typesize);
}
offset += typesize;
}
else if (typesversion == 2 || (FFlag::LuauLoadUserdataInfo && typesversion == 3))
{
uint32_t typesize = readVarInt(data, size, offset);
if (typesize)
{
uint8_t* types = (uint8_t*)data + offset;
p->typeinfo = luaM_newarray(L, typesize, uint8_t, p->memcat);
p->sizetypeinfo = typesize;
memcpy(p->typeinfo, types, typesize);
offset += typesize;
if (FFlag::LuauLoadUserdataInfo && typesversion == 3)
{
remapUserdataTypes((char*)(uint8_t*)p->typeinfo, p->sizetypeinfo, userdataRemapping, userdataTypeLimit);
}
}
}
}
const int sizecode = readVarInt(data, size, offset);

View file

@ -25,6 +25,7 @@ LUAU_FASTINT(LuauRecursionLimit)
LUAU_FASTFLAG(LuauCompileTypeInfo)
LUAU_FASTFLAG(LuauCompileTempTypeInfo)
LUAU_FASTFLAG(LuauCompileUserdataInfo)
LUAU_FASTFLAG(LuauCompileFastcall3)
using namespace Luau;
@ -3486,6 +3487,33 @@ RETURN R1 -1
)");
}
TEST_CASE("Fastcall3")
{
ScopedFastFlag luauCompileFastcall3{FFlag::LuauCompileFastcall3, true};
CHECK_EQ("\n" + compileFunction0(R"(
local a, b, c = ...
return math.min(a, b, c) + math.clamp(a, b, c)
)"),
R"(
GETVARARGS R0 3
FASTCALL3 19 R0 R1 R2 L0
MOVE R5 R0
MOVE R6 R1
MOVE R7 R2
GETIMPORT R4 2 [math.min]
CALL R4 3 1
L0: FASTCALL3 46 R0 R1 R2 L1
MOVE R6 R0
MOVE R7 R1
MOVE R8 R2
GETIMPORT R5 4 [math.clamp]
CALL R5 3 1
L1: ADD R3 R4 R5
RETURN R3 1
)");
}
TEST_CASE("FastcallSelect")
{
// select(_, ...) compiles to a builtin call
@ -4668,6 +4696,34 @@ L0: RETURN R0 -1
)");
}
TEST_CASE("VectorFastCall3")
{
ScopedFastFlag luauCompileFastcall3{FFlag::LuauCompileFastcall3, true};
const char* source = R"(
local a, b, c = ...
return Vector3.new(a, b, c)
)";
Luau::BytecodeBuilder bcb;
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code);
Luau::CompileOptions options;
options.vectorLib = "Vector3";
options.vectorCtor = "new";
Luau::compileOrThrow(bcb, source, options);
CHECK_EQ("\n" + bcb.dumpFunction(0), R"(
GETVARARGS R0 3
FASTCALL3 54 R0 R1 R2 L0
MOVE R4 R0
MOVE R5 R1
MOVE R6 R2
GETIMPORT R3 2 [Vector3.new]
CALL R3 3 -1
L0: RETURN R3 -1
)");
}
TEST_CASE("VectorLiterals")
{
CHECK_EQ("\n" + compileFunction("return Vector3.new(1, 2, 3)", 0, 2, /*enableVectors*/ true), R"(

View file

@ -48,7 +48,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "binary_op_type_family_errors")
LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ("Operator '+' could not be applied to operands of types number and string; there is no corresponding overload for __add", toString(result.errors[0]));
CHECK_EQ("Operator '+' could not be applied to operands of types number and string; there is no corresponding overload for __add",
toString(result.errors[0]));
else
CHECK_EQ("Type 'string' could not be converted into 'number'", toString(result.errors[0]));
}
@ -66,7 +67,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "unary_op_type_family_errors")
if (FFlag::DebugLuauDeferredConstraintResolution)
{
LUAU_REQUIRE_ERROR_COUNT(2, result);
CHECK_EQ("Operator '-' could not be applied to operand of type string; there is no corresponding overload for __unm", toString(result.errors[0]));
CHECK_EQ(
"Operator '-' could not be applied to operand of type string; there is no corresponding overload for __unm", toString(result.errors[0]));
CHECK_EQ("Type 'string' could not be converted into 'number'", toString(result.errors[1]));
}
else

View file

@ -241,3 +241,21 @@ using DifferFixtureWithBuiltins = DifferFixtureGeneric<BuiltinsFixture>;
} while (false)
#define LUAU_REQUIRE_NO_ERRORS(result) LUAU_REQUIRE_ERROR_COUNT(0, result)
#define LUAU_CHECK_ERRORS(result) \
do \
{ \
auto&& r = (result); \
validateErrors(r.errors); \
CHECK(!r.errors.empty()); \
} while (false)
#define LUAU_CHECK_ERROR_COUNT(count, result) \
do \
{ \
auto&& r = (result); \
validateErrors(r.errors); \
CHECK_MESSAGE(count == r.errors.size(), getErrors(r)); \
} while (false)
#define LUAU_CHECK_NO_ERRORS(result) LUAU_CHECK_ERROR_COUNT(0, result)

View file

@ -125,11 +125,7 @@ TEST_CASE_FIXTURE(GeneralizationFixture, "cache_fully_generalized_types")
CHECK(generalizedTypes->empty());
TypeId tinyTable = arena.addType(TableType{
TableType::Props{{"one", builtinTypes.numberType}, {"two", builtinTypes.stringType}},
std::nullopt,
TypeLevel{},
TableState::Sealed
});
TableType::Props{{"one", builtinTypes.numberType}, {"two", builtinTypes.stringType}}, std::nullopt, TypeLevel{}, TableState::Sealed});
generalize(tinyTable);
@ -142,17 +138,10 @@ TEST_CASE_FIXTURE(GeneralizationFixture, "dont_cache_types_that_arent_done_yet")
{
TypeId freeTy = arena.addType(FreeType{NotNull{globalScope.get()}, builtinTypes.neverType, builtinTypes.stringType});
TypeId fnTy = arena.addType(FunctionType{
builtinTypes.emptyTypePack,
arena.addTypePack(TypePack{{builtinTypes.numberType}})
});
TypeId fnTy = arena.addType(FunctionType{builtinTypes.emptyTypePack, arena.addTypePack(TypePack{{builtinTypes.numberType}})});
TypeId tableTy = arena.addType(TableType{
TableType::Props{{"one", builtinTypes.numberType}, {"two", freeTy}, {"three", fnTy}},
std::nullopt,
TypeLevel{},
TableState::Sealed
});
TableType::Props{{"one", builtinTypes.numberType}, {"two", freeTy}, {"three", fnTy}}, std::nullopt, TypeLevel{}, TableState::Sealed});
generalize(tableTy);
@ -174,11 +163,7 @@ TEST_CASE_FIXTURE(GeneralizationFixture, "functions_containing_cyclic_tables_can
});
asMutable(selfTy)->ty.emplace<TableType>(
TableType::Props{{"count", builtinTypes.numberType}, {"method", methodTy}},
std::nullopt,
TypeLevel{},
TableState::Sealed
);
TableType::Props{{"count", builtinTypes.numberType}, {"method", methodTy}}, std::nullopt, TypeLevel{}, TableState::Sealed);
generalize(methodTy);

View file

@ -12,9 +12,10 @@
#include <limits.h>
LUAU_FASTFLAG(LuauCodegenRemoveDeadStores5)
LUAU_FASTFLAG(DebugLuauAbortingChecks)
LUAU_FASTFLAG(LuauCodegenFixSplitStoreConstMismatch)
LUAU_FASTFLAG(LuauCodegenInstG)
LUAU_FASTFLAG(LuauCodegenFastcall3)
using namespace Luau::CodeGen;
@ -1117,6 +1118,8 @@ bb_0:
TEST_CASE_FIXTURE(IrBuilderFixture, "BuiltinFastcallsMayInvalidateMemory")
{
ScopedFastFlag luauCodegenInstG{FFlag::LuauCodegenInstG, true};
IrOp block = build.block(IrBlockKind::Internal);
IrOp fallback = build.block(IrBlockKind::Fallback);
@ -1129,8 +1132,8 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "BuiltinFastcallsMayInvalidateMemory")
build.inst(IrCmd::CHECK_NO_METATABLE, table, fallback);
build.inst(IrCmd::CHECK_READONLY, table, fallback);
build.inst(IrCmd::INVOKE_FASTCALL, build.constUint(LBF_SETMETATABLE), build.vmReg(1), build.vmReg(2), build.vmReg(3), build.constInt(3),
build.constInt(1));
build.inst(IrCmd::INVOKE_FASTCALL, build.constUint(LBF_SETMETATABLE), build.vmReg(1), build.vmReg(2), build.vmReg(3), build.undef(),
build.constInt(3), build.constInt(1));
build.inst(IrCmd::CHECK_NO_METATABLE, table, fallback);
build.inst(IrCmd::CHECK_READONLY, table, fallback);
@ -1151,7 +1154,7 @@ bb_0:
%1 = LOAD_POINTER R0
CHECK_NO_METATABLE %1, bb_fallback_1
CHECK_READONLY %1, bb_fallback_1
%4 = INVOKE_FASTCALL 61u, R1, R2, R3, 3i, 1i
%4 = INVOKE_FASTCALL 61u, R1, R2, R3, undef, 3i, 1i
CHECK_NO_METATABLE %1, bb_fallback_1
CHECK_READONLY %1, bb_fallback_1
STORE_DOUBLE R1, 0.5
@ -2546,8 +2549,6 @@ bb_0: ; useCount: 0
TEST_CASE_FIXTURE(IrBuilderFixture, "ForgprepInvalidation")
{
ScopedFastFlag luauCodegenRemoveDeadStores{FFlag::LuauCodegenRemoveDeadStores5, true};
IrOp block = build.block(IrBlockKind::Internal);
IrOp followup = build.block(IrBlockKind::Internal);
@ -2587,14 +2588,14 @@ bb_1:
TEST_CASE_FIXTURE(IrBuilderFixture, "FastCallEffects1")
{
ScopedFastFlag luauCodegenRemoveDeadStores{FFlag::LuauCodegenRemoveDeadStores5, true};
ScopedFastFlag luauCodegenFastcall3{FFlag::LuauCodegenFastcall3, true};
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::FASTCALL, build.constUint(LBF_MATH_FREXP), build.vmReg(1), build.vmReg(2), build.undef(), build.constInt(1), build.constInt(2));
build.inst(IrCmd::CHECK_TAG, build.vmReg(1), build.constTag(tnumber), build.vmExit(1));
build.inst(IrCmd::CHECK_TAG, build.vmReg(2), build.constTag(tnumber), build.vmExit(1));
build.inst(IrCmd::FASTCALL, build.constUint(LBF_MATH_FREXP), build.vmReg(1), build.vmReg(2), build.constInt(2));
build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(1)), build.constTag(tnumber), build.vmExit(1));
build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(2)), build.constTag(tnumber), build.vmExit(1));
build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(2));
updateUseCounts(build.function);
@ -2604,7 +2605,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "FastCallEffects1")
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
; in regs: R2
FASTCALL 14u, R1, R2, undef, 1i, 2i
FASTCALL 14u, R1, R2, 2i
RETURN R1, 2i
)");
@ -2612,14 +2613,14 @@ bb_0:
TEST_CASE_FIXTURE(IrBuilderFixture, "FastCallEffects2")
{
ScopedFastFlag luauCodegenRemoveDeadStores{FFlag::LuauCodegenRemoveDeadStores5, true};
ScopedFastFlag luauCodegenFastcall3{FFlag::LuauCodegenFastcall3, true};
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::FASTCALL, build.constUint(LBF_MATH_MODF), build.vmReg(1), build.vmReg(2), build.undef(), build.constInt(1), build.constInt(1));
build.inst(IrCmd::CHECK_TAG, build.vmReg(1), build.constTag(tnumber), build.vmExit(1));
build.inst(IrCmd::CHECK_TAG, build.vmReg(2), build.constTag(tnumber), build.vmExit(1));
build.inst(IrCmd::FASTCALL, build.constUint(LBF_MATH_MODF), build.vmReg(1), build.vmReg(2), build.constInt(1));
build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(1)), build.constTag(tnumber), build.vmExit(1));
build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(2)), build.constTag(tnumber), build.vmExit(1));
build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(2));
updateUseCounts(build.function);
@ -2629,8 +2630,9 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "FastCallEffects2")
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
; in regs: R2
FASTCALL 20u, R1, R2, undef, 1i, 1i
CHECK_TAG R2, tnumber, exit(1)
FASTCALL 20u, R1, R2, 1i
%3 = LOAD_TAG R2
CHECK_TAG %3, tnumber, exit(1)
RETURN R1, 2i
)");
@ -2642,7 +2644,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "InferNumberTagFromLimitedContext")
build.beginBlock(entry);
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(2.0));
build.inst(IrCmd::CHECK_TAG, build.vmReg(0), build.constTag(ttable), build.vmExit(1));
build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(0)), build.constTag(ttable), build.vmExit(1));
build.inst(IrCmd::STORE_TVALUE, build.vmReg(1), build.inst(IrCmd::LOAD_TVALUE, build.vmReg(0)));
build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(1));
@ -2666,7 +2668,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "DoNotProduceInvalidSplitStore1")
build.beginBlock(entry);
build.inst(IrCmd::STORE_INT, build.vmReg(0), build.constInt(1));
build.inst(IrCmd::CHECK_TAG, build.vmReg(0), build.constTag(ttable), build.vmExit(1));
build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(0)), build.constTag(ttable), build.vmExit(1));
build.inst(IrCmd::STORE_TVALUE, build.vmReg(1), build.inst(IrCmd::LOAD_TVALUE, build.vmReg(0)));
build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(1));
@ -2677,9 +2679,10 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "DoNotProduceInvalidSplitStore1")
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
STORE_INT R0, 1i
CHECK_TAG R0, ttable, exit(1)
%2 = LOAD_TVALUE R0
STORE_TVALUE R1, %2
%1 = LOAD_TAG R0
CHECK_TAG %1, ttable, exit(1)
%3 = LOAD_TVALUE R0
STORE_TVALUE R1, %3
RETURN R1, 1i
)");
@ -2693,7 +2696,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "DoNotProduceInvalidSplitStore2")
build.beginBlock(entry);
build.inst(IrCmd::STORE_INT, build.vmReg(0), build.constInt(1));
build.inst(IrCmd::CHECK_TAG, build.vmReg(0), build.constTag(tnumber), build.vmExit(1));
build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(0)), build.constTag(tnumber), build.vmExit(1));
build.inst(IrCmd::STORE_TVALUE, build.vmReg(1), build.inst(IrCmd::LOAD_TVALUE, build.vmReg(0)));
build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(1));
@ -2704,9 +2707,10 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "DoNotProduceInvalidSplitStore2")
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
STORE_INT R0, 1i
CHECK_TAG R0, tnumber, exit(1)
%2 = LOAD_TVALUE R0
STORE_TVALUE R1, %2
%1 = LOAD_TAG R0
CHECK_TAG %1, tnumber, exit(1)
%3 = LOAD_TVALUE R0
STORE_TVALUE R1, %3
RETURN R1, 1i
)");
@ -2809,13 +2813,16 @@ bb_1:
TEST_CASE_FIXTURE(IrBuilderFixture, "ExplicitUseOfRegisterInVarargSequence")
{
ScopedFastFlag luauCodegenInstG{FFlag::LuauCodegenInstG, true};
ScopedFastFlag luauCodegenFastcall3{FFlag::LuauCodegenFastcall3, true};
IrOp entry = build.block(IrBlockKind::Internal);
IrOp exit = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::FALLBACK_GETVARARGS, build.constUint(0), build.vmReg(1), build.constInt(-1));
IrOp results = build.inst(
IrCmd::INVOKE_FASTCALL, build.constUint(0), build.vmReg(0), build.vmReg(1), build.vmReg(2), build.constInt(-1), build.constInt(-1));
IrOp results = build.inst(IrCmd::INVOKE_FASTCALL, build.constUint(0), build.vmReg(0), build.vmReg(1), build.vmReg(2), build.undef(),
build.constInt(-1), build.constInt(-1));
build.inst(IrCmd::ADJUST_STACK_TO_REG, build.vmReg(0), results);
build.inst(IrCmd::JUMP, exit);
@ -2830,7 +2837,7 @@ bb_0:
; successors: bb_1
; out regs: R0...
FALLBACK_GETVARARGS 0u, R1, -1i
%1 = INVOKE_FASTCALL 0u, R0, R1, R2, -1i, -1i
%1 = INVOKE_FASTCALL 0u, R0, R1, R2, undef, -1i, -1i
ADJUST_STACK_TO_REG R0, %1
JUMP bb_1
@ -3023,8 +3030,6 @@ bb_1:
TEST_CASE_FIXTURE(IrBuilderFixture, "ForgprepImplicitUse")
{
ScopedFastFlag luauCodegenRemoveDeadStores{FFlag::LuauCodegenRemoveDeadStores5, true};
IrOp entry = build.block(IrBlockKind::Internal);
IrOp direct = build.block(IrBlockKind::Internal);
IrOp fallback = build.block(IrBlockKind::Internal);
@ -3585,8 +3590,6 @@ TEST_SUITE_BEGIN("DeadStoreRemoval");
TEST_CASE_FIXTURE(IrBuilderFixture, "SimpleDoubleStore")
{
ScopedFastFlag luauCodegenRemoveDeadStores{FFlag::LuauCodegenRemoveDeadStores5, true};
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
@ -3636,8 +3639,6 @@ bb_0:
TEST_CASE_FIXTURE(IrBuilderFixture, "UnusedAtReturn")
{
ScopedFastFlag luauCodegenRemoveDeadStores{FFlag::LuauCodegenRemoveDeadStores5, true};
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
@ -3669,8 +3670,6 @@ bb_0:
TEST_CASE_FIXTURE(IrBuilderFixture, "UnusedAtReturnPartial")
{
ScopedFastFlag luauCodegenRemoveDeadStores{FFlag::LuauCodegenRemoveDeadStores5, true};
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
@ -3699,8 +3698,6 @@ bb_0:
TEST_CASE_FIXTURE(IrBuilderFixture, "HiddenPointerUse1")
{
ScopedFastFlag luauCodegenRemoveDeadStores{FFlag::LuauCodegenRemoveDeadStores5, true};
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
@ -3729,8 +3726,6 @@ bb_0:
TEST_CASE_FIXTURE(IrBuilderFixture, "HiddenPointerUse2")
{
ScopedFastFlag luauCodegenRemoveDeadStores{FFlag::LuauCodegenRemoveDeadStores5, true};
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
@ -3763,8 +3758,6 @@ bb_0:
TEST_CASE_FIXTURE(IrBuilderFixture, "HiddenPointerUse3")
{
ScopedFastFlag luauCodegenRemoveDeadStores{FFlag::LuauCodegenRemoveDeadStores5, true};
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
@ -3793,8 +3786,6 @@ bb_0:
TEST_CASE_FIXTURE(IrBuilderFixture, "HiddenPointerUse4")
{
ScopedFastFlag luauCodegenRemoveDeadStores{FFlag::LuauCodegenRemoveDeadStores5, true};
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
@ -3827,8 +3818,6 @@ bb_0:
TEST_CASE_FIXTURE(IrBuilderFixture, "PartialVsFullStoresWithRecombination")
{
ScopedFastFlag luauCodegenRemoveDeadStores{FFlag::LuauCodegenRemoveDeadStores5, true};
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
@ -3852,8 +3841,6 @@ bb_0:
TEST_CASE_FIXTURE(IrBuilderFixture, "IgnoreFastcallAdjustment")
{
ScopedFastFlag luauCodegenRemoveDeadStores{FFlag::LuauCodegenRemoveDeadStores5, true};
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
@ -3880,8 +3867,6 @@ bb_0:
TEST_CASE_FIXTURE(IrBuilderFixture, "JumpImplicitLiveOut")
{
ScopedFastFlag luauCodegenRemoveDeadStores{FFlag::LuauCodegenRemoveDeadStores5, true};
IrOp entry = build.block(IrBlockKind::Internal);
IrOp next = build.block(IrBlockKind::Internal);
@ -3917,8 +3902,6 @@ bb_1:
TEST_CASE_FIXTURE(IrBuilderFixture, "KeepCapturedRegisterStores")
{
ScopedFastFlag luauCodegenRemoveDeadStores{FFlag::LuauCodegenRemoveDeadStores5, true};
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
@ -3956,7 +3939,6 @@ bb_0:
TEST_CASE_FIXTURE(IrBuilderFixture, "StoreCannotBeReplacedWithCheck")
{
ScopedFastFlag luauCodegenRemoveDeadStores{FFlag::LuauCodegenRemoveDeadStores5, true};
ScopedFastFlag debugLuauAbortingChecks{FFlag::DebugLuauAbortingChecks, true};
IrOp block = build.block(IrBlockKind::Internal);
@ -4025,8 +4007,6 @@ bb_2:
TEST_CASE_FIXTURE(IrBuilderFixture, "FullStoreHasToBeObservableFromFallbacks")
{
ScopedFastFlag luauCodegenRemoveDeadStores{FFlag::LuauCodegenRemoveDeadStores5, true};
IrOp entry = build.block(IrBlockKind::Internal);
IrOp fallback = build.block(IrBlockKind::Fallback);
IrOp last = build.block(IrBlockKind::Internal);
@ -4083,8 +4063,6 @@ bb_2:
TEST_CASE_FIXTURE(IrBuilderFixture, "FullStoreHasToBeObservableFromFallbacks2")
{
ScopedFastFlag luauCodegenRemoveDeadStores{FFlag::LuauCodegenRemoveDeadStores5, true};
IrOp entry = build.block(IrBlockKind::Internal);
IrOp fallback = build.block(IrBlockKind::Fallback);
IrOp last = build.block(IrBlockKind::Internal);
@ -4139,8 +4117,6 @@ bb_2:
TEST_CASE_FIXTURE(IrBuilderFixture, "FullStoreHasToBeObservableFromFallbacks3")
{
ScopedFastFlag luauCodegenRemoveDeadStores{FFlag::LuauCodegenRemoveDeadStores5, true};
IrOp entry = build.block(IrBlockKind::Internal);
IrOp fallback = build.block(IrBlockKind::Fallback);
IrOp last = build.block(IrBlockKind::Internal);
@ -4198,8 +4174,6 @@ bb_2:
TEST_CASE_FIXTURE(IrBuilderFixture, "SafePartialValueStoresWithPreservedTag")
{
ScopedFastFlag luauCodegenRemoveDeadStores{FFlag::LuauCodegenRemoveDeadStores5, true};
IrOp entry = build.block(IrBlockKind::Internal);
IrOp fallback = build.block(IrBlockKind::Fallback);
IrOp last = build.block(IrBlockKind::Internal);
@ -4253,8 +4227,6 @@ bb_2:
TEST_CASE_FIXTURE(IrBuilderFixture, "SafePartialValueStoresWithPreservedTag2")
{
ScopedFastFlag luauCodegenRemoveDeadStores{FFlag::LuauCodegenRemoveDeadStores5, true};
IrOp entry = build.block(IrBlockKind::Internal);
IrOp fallback = build.block(IrBlockKind::Fallback);
IrOp last = build.block(IrBlockKind::Internal);
@ -4307,8 +4279,6 @@ bb_2:
TEST_CASE_FIXTURE(IrBuilderFixture, "DoNotReturnWithPartialStores")
{
ScopedFastFlag luauCodegenRemoveDeadStores{FFlag::LuauCodegenRemoveDeadStores5, true};
IrOp entry = build.block(IrBlockKind::Internal);
IrOp success = build.block(IrBlockKind::Internal);
IrOp fail = build.block(IrBlockKind::Internal);
@ -4379,8 +4349,6 @@ bb_3:
TEST_CASE_FIXTURE(IrBuilderFixture, "PartialOverFullValue")
{
ScopedFastFlag luauCodegenRemoveDeadStores{FFlag::LuauCodegenRemoveDeadStores5, true};
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);

View file

@ -15,17 +15,15 @@
#include <memory>
#include <string_view>
LUAU_FASTFLAG(LuauCodegenRemoveDeadStores5)
LUAU_FASTFLAG(LuauCodegenDirectUserdataFlow)
LUAU_FASTFLAG(LuauCompileTypeInfo)
LUAU_FASTFLAG(LuauLoadTypeInfo)
LUAU_FASTFLAG(LuauCodegenTypeInfo)
LUAU_FASTFLAG(LuauCompileTempTypeInfo)
LUAU_FASTFLAG(LuauCodegenAnalyzeHostVectorOps)
LUAU_FASTFLAG(LuauCompileUserdataInfo)
LUAU_FASTFLAG(LuauLoadUserdataInfo)
LUAU_FASTFLAG(LuauCodegenUserdataOps)
LUAU_FASTFLAG(LuauCodegenUserdataAlloc)
LUAU_FASTFLAG(LuauCompileFastcall3)
LUAU_FASTFLAG(LuauCodegenFastcall3)
static std::string getCodegenAssembly(const char* source, bool includeIrTypes = false, int debugLevel = 1)
{
@ -159,8 +157,6 @@ bb_bytecode_1:
TEST_CASE("VectorComponentRead")
{
ScopedFastFlag luauCodegenRemoveDeadStores{FFlag::LuauCodegenRemoveDeadStores5, true};
CHECK_EQ("\n" + getCodegenAssembly(R"(
local function compsum(a: vector)
return a.X + a.Y + a.Z
@ -238,8 +234,6 @@ bb_bytecode_1:
TEST_CASE("VectorSubMulDiv")
{
ScopedFastFlag luauCodegenRemoveDeadStores{FFlag::LuauCodegenRemoveDeadStores5, true};
CHECK_EQ("\n" + getCodegenAssembly(R"(
local function vec3combo(a: vector, b: vector, c: vector, d: vector)
return a * b - c / d
@ -272,8 +266,6 @@ bb_bytecode_1:
TEST_CASE("VectorSubMulDiv2")
{
ScopedFastFlag luauCodegenRemoveDeadStores{FFlag::LuauCodegenRemoveDeadStores5, true};
CHECK_EQ("\n" + getCodegenAssembly(R"(
local function vec3combo(a: vector)
local tmp = a * a
@ -302,8 +294,6 @@ bb_bytecode_1:
TEST_CASE("VectorMulDivMixed")
{
ScopedFastFlag luauCodegenRemoveDeadStores{FFlag::LuauCodegenRemoveDeadStores5, true};
CHECK_EQ("\n" + getCodegenAssembly(R"(
local function vec3combo(a: vector, b: vector, c: vector, d: vector)
return a * 2 + b / 4 + 0.5 * c + 40 / d
@ -344,8 +334,6 @@ bb_bytecode_1:
TEST_CASE("ExtraMathMemoryOperands")
{
ScopedFastFlag luauCodegenRemoveDeadStores{FFlag::LuauCodegenRemoveDeadStores5, true};
CHECK_EQ("\n" + getCodegenAssembly(R"(
local function foo(a: number, b: number, c: number, d: number, e: number)
return math.floor(a) + math.ceil(b) + math.round(c) + math.sqrt(d) + math.abs(e)
@ -382,8 +370,6 @@ bb_bytecode_1:
TEST_CASE("DseInitialStackState")
{
ScopedFastFlag luauCodegenRemoveDeadStores{FFlag::LuauCodegenRemoveDeadStores5, true};
CHECK_EQ("\n" + getCodegenAssembly(R"(
local function foo()
while {} do
@ -422,7 +408,7 @@ bb_5:
TEST_CASE("DseInitialStackState2")
{
ScopedFastFlag luauCodegenRemoveDeadStores{FFlag::LuauCodegenRemoveDeadStores5, true};
ScopedFastFlag luauCodegenFastcall3{FFlag::LuauCodegenFastcall3, true};
CHECK_EQ("\n" + getCodegenAssembly(R"(
local function foo(a)
@ -435,7 +421,7 @@ end
bb_bytecode_0:
CHECK_SAFE_ENV exit(1)
CHECK_TAG R0, tnumber, exit(1)
FASTCALL 14u, R1, R0, undef, 1i, 2i
FASTCALL 14u, R1, R0, 2i
INTERRUPT 5u
RETURN R0, 1i
)");
@ -443,7 +429,7 @@ bb_bytecode_0:
TEST_CASE("DseInitialStackState3")
{
ScopedFastFlag luauCodegenRemoveDeadStores{FFlag::LuauCodegenRemoveDeadStores5, true};
ScopedFastFlag luauCodegenFastcall3{FFlag::LuauCodegenFastcall3, true};
CHECK_EQ("\n" + getCodegenAssembly(R"(
local function foo(a)
@ -456,7 +442,7 @@ end
bb_bytecode_0:
CHECK_SAFE_ENV exit(1)
CHECK_TAG R0, tnumber, exit(1)
FASTCALL 47u, R1, R0, undef, 1i, 1i
FASTCALL 47u, R1, R0, 1i
INTERRUPT 5u
RETURN R0, 1i
)");
@ -464,8 +450,6 @@ bb_bytecode_0:
TEST_CASE("VectorConstantTag")
{
ScopedFastFlag luauCodegenRemoveDeadStores{FFlag::LuauCodegenRemoveDeadStores5, true};
CHECK_EQ("\n" + getCodegenAssembly(R"(
local function vecrcp(a: vector)
return vector(1, 2, 3) + a
@ -491,8 +475,6 @@ bb_bytecode_1:
TEST_CASE("VectorNamecall")
{
ScopedFastFlag luauCodegenDirectUserdataFlow{FFlag::LuauCodegenDirectUserdataFlow, true};
CHECK_EQ("\n" + getCodegenAssembly(R"(
local function abs(a: vector)
return a:Abs()
@ -517,8 +499,6 @@ bb_bytecode_1:
TEST_CASE("VectorRandomProp")
{
ScopedFastFlag luauCodegenRemoveDeadStores{FFlag::LuauCodegenRemoveDeadStores5, true};
CHECK_EQ("\n" + getCodegenAssembly(R"(
local function foo(a: vector)
return a.XX + a.YY + a.ZZ
@ -559,7 +539,6 @@ bb_6:
TEST_CASE("VectorCustomAccess")
{
ScopedFastFlag luauCodegenRemoveDeadStores{FFlag::LuauCodegenRemoveDeadStores5, true};
ScopedFastFlag luauCodegenAnalyzeHostVectorOps{FFlag::LuauCodegenAnalyzeHostVectorOps, true};
CHECK_EQ("\n" + getCodegenAssembly(R"(
@ -594,8 +573,6 @@ bb_bytecode_1:
TEST_CASE("VectorCustomNamecall")
{
ScopedFastFlag luauCodegenRemoveDeadStores{FFlag::LuauCodegenRemoveDeadStores5, true};
ScopedFastFlag LuauCodegenDirectUserdataFlow{FFlag::LuauCodegenDirectUserdataFlow, true};
ScopedFastFlag luauCodegenAnalyzeHostVectorOps{FFlag::LuauCodegenAnalyzeHostVectorOps, true};
CHECK_EQ("\n" + getCodegenAssembly(R"(
@ -634,8 +611,6 @@ bb_bytecode_1:
TEST_CASE("VectorCustomAccessChain")
{
ScopedFastFlag luauCodegenRemoveDeadStores{FFlag::LuauCodegenRemoveDeadStores5, true};
ScopedFastFlag LuauCodegenDirectUserdataFlow{FFlag::LuauCodegenDirectUserdataFlow, true};
ScopedFastFlag luauCodegenAnalyzeHostVectorOps{FFlag::LuauCodegenAnalyzeHostVectorOps, true};
CHECK_EQ("\n" + getCodegenAssembly(R"(
@ -688,8 +663,6 @@ bb_bytecode_1:
TEST_CASE("VectorCustomNamecallChain")
{
ScopedFastFlag luauCodegenRemoveDeadStores{FFlag::LuauCodegenRemoveDeadStores5, true};
ScopedFastFlag LuauCodegenDirectUserdataFlow{FFlag::LuauCodegenDirectUserdataFlow, true};
ScopedFastFlag luauCodegenAnalyzeHostVectorOps{FFlag::LuauCodegenAnalyzeHostVectorOps, true};
CHECK_EQ("\n" + getCodegenAssembly(R"(
@ -749,9 +722,7 @@ bb_bytecode_1:
TEST_CASE("VectorCustomNamecallChain2")
{
ScopedFastFlag sffs[]{{FFlag::LuauLoadTypeInfo, true}, {FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCodegenTypeInfo, true},
{FFlag::LuauCodegenRemoveDeadStores5, true}, {FFlag::LuauCompileTempTypeInfo, true}, {FFlag::LuauCodegenDirectUserdataFlow, true},
{FFlag::LuauCodegenAnalyzeHostVectorOps, true}};
ScopedFastFlag sffs[]{{FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCompileTempTypeInfo, true}, {FFlag::LuauCodegenAnalyzeHostVectorOps, true}};
CHECK_EQ("\n" + getCodegenAssembly(R"(
type Vertex = {n: vector, b: vector}
@ -827,8 +798,6 @@ bb_6:
TEST_CASE("UserDataGetIndex")
{
ScopedFastFlag luauCodegenDirectUserdataFlow{FFlag::LuauCodegenDirectUserdataFlow, true};
CHECK_EQ("\n" + getCodegenAssembly(R"(
local function getxy(a: Point)
return a.x + a.y
@ -859,8 +828,6 @@ bb_4:
TEST_CASE("UserDataSetIndex")
{
ScopedFastFlag luauCodegenDirectUserdataFlow{FFlag::LuauCodegenDirectUserdataFlow, true};
CHECK_EQ("\n" + getCodegenAssembly(R"(
local function setxy(a: Point)
a.x = 3
@ -887,8 +854,6 @@ bb_bytecode_1:
TEST_CASE("UserDataNamecall")
{
ScopedFastFlag luauCodegenDirectUserdataFlow{FFlag::LuauCodegenDirectUserdataFlow, true};
CHECK_EQ("\n" + getCodegenAssembly(R"(
local function getxy(a: Point)
return a:GetX() + a:GetY()
@ -925,8 +890,7 @@ bb_4:
TEST_CASE("ExplicitUpvalueAndLocalTypes")
{
ScopedFastFlag sffs[]{{FFlag::LuauLoadTypeInfo, true}, {FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCodegenTypeInfo, true},
{FFlag::LuauCodegenRemoveDeadStores5, true}};
ScopedFastFlag sffs[]{{FFlag::LuauCompileTypeInfo, true}};
CHECK_EQ("\n" + getCodegenAssembly(R"(
local y: vector = ...
@ -969,8 +933,7 @@ bb_bytecode_0:
TEST_CASE("FastcallTypeInferThroughLocal")
{
ScopedFastFlag sffs[]{{FFlag::LuauLoadTypeInfo, true}, {FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCodegenTypeInfo, true},
{FFlag::LuauCodegenRemoveDeadStores5, true}};
ScopedFastFlag sffs[]{{FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCompileFastcall3, true}, {FFlag::LuauCodegenFastcall3, true}};
CHECK_EQ("\n" + getCodegenAssembly(R"(
local function getsum(x, c)
@ -985,43 +948,40 @@ end
/* includeIrTypes */ true),
R"(
; function getsum($arg0, $arg1) line 2
; R2: vector from 0 to 17
; R2: vector from 0 to 18
bb_bytecode_0:
%0 = LOAD_TVALUE R0
STORE_TVALUE R3, %0
STORE_DOUBLE R4, 2
STORE_TAG R4, tnumber
STORE_DOUBLE R5, 3
STORE_TAG R5, tnumber
CHECK_SAFE_ENV exit(4)
CHECK_TAG R3, tnumber, exit(4)
%13 = LOAD_DOUBLE R3
STORE_VECTOR R2, %13, 2, 3
CHECK_TAG R0, tnumber, exit(4)
%11 = LOAD_DOUBLE R0
STORE_VECTOR R2, %11, 2, 3
STORE_TAG R2, tvector
JUMP_IF_FALSY R1, bb_bytecode_1, bb_3
bb_3:
CHECK_TAG R2, tvector, exit(8)
%21 = LOAD_FLOAT R2, 0i
%26 = LOAD_FLOAT R2, 4i
%35 = ADD_NUM %21, %26
STORE_DOUBLE R3, %35
CHECK_TAG R2, tvector, exit(9)
%19 = LOAD_FLOAT R2, 0i
%24 = LOAD_FLOAT R2, 4i
%33 = ADD_NUM %19, %24
STORE_DOUBLE R3, %33
STORE_TAG R3, tnumber
INTERRUPT 13u
INTERRUPT 14u
RETURN R3, 1i
bb_bytecode_1:
CHECK_TAG R2, tvector, exit(14)
%42 = LOAD_FLOAT R2, 8i
STORE_DOUBLE R3, %42
CHECK_TAG R2, tvector, exit(15)
%40 = LOAD_FLOAT R2, 8i
STORE_DOUBLE R3, %40
STORE_TAG R3, tnumber
INTERRUPT 16u
INTERRUPT 17u
RETURN R3, 1i
)");
}
TEST_CASE("FastcallTypeInferThroughUpvalue")
{
ScopedFastFlag sffs[]{{FFlag::LuauLoadTypeInfo, true}, {FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCodegenTypeInfo, true},
{FFlag::LuauCodegenRemoveDeadStores5, true}};
ScopedFastFlag sffs[]{{FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCompileFastcall3, true}, {FFlag::LuauCodegenFastcall3, true}};
CHECK_EQ("\n" + getCodegenAssembly(R"(
local v = ...
@ -1040,48 +1000,45 @@ end
; function getsum($arg0, $arg1) line 4
; U0: vector
bb_bytecode_0:
%0 = LOAD_TVALUE R0
STORE_TVALUE R3, %0
STORE_DOUBLE R4, 2
STORE_TAG R4, tnumber
STORE_DOUBLE R5, 3
STORE_TAG R5, tnumber
CHECK_SAFE_ENV exit(4)
CHECK_TAG R3, tnumber, exit(4)
%13 = LOAD_DOUBLE R3
STORE_VECTOR R2, %13, 2, 3
CHECK_TAG R0, tnumber, exit(4)
%11 = LOAD_DOUBLE R0
STORE_VECTOR R2, %11, 2, 3
STORE_TAG R2, tvector
SET_UPVALUE U0, R2, tvector
JUMP_IF_FALSY R1, bb_bytecode_1, bb_3
bb_3:
GET_UPVALUE R4, U0
CHECK_TAG R4, tvector, exit(10)
%23 = LOAD_FLOAT R4, 0i
STORE_DOUBLE R3, %23
CHECK_TAG R4, tvector, exit(11)
%21 = LOAD_FLOAT R4, 0i
STORE_DOUBLE R3, %21
STORE_TAG R3, tnumber
GET_UPVALUE R5, U0
CHECK_TAG R5, tvector, exit(13)
%29 = LOAD_FLOAT R5, 4i
%38 = ADD_NUM %23, %29
STORE_DOUBLE R2, %38
CHECK_TAG R5, tvector, exit(14)
%27 = LOAD_FLOAT R5, 4i
%36 = ADD_NUM %21, %27
STORE_DOUBLE R2, %36
STORE_TAG R2, tnumber
INTERRUPT 16u
INTERRUPT 17u
RETURN R2, 1i
bb_bytecode_1:
GET_UPVALUE R3, U0
CHECK_TAG R3, tvector, exit(18)
%46 = LOAD_FLOAT R3, 8i
STORE_DOUBLE R2, %46
CHECK_TAG R3, tvector, exit(19)
%44 = LOAD_FLOAT R3, 8i
STORE_DOUBLE R2, %44
STORE_TAG R2, tnumber
INTERRUPT 20u
INTERRUPT 21u
RETURN R2, 1i
)");
}
TEST_CASE("LoadAndMoveTypePropagation")
{
ScopedFastFlag sffs[]{{FFlag::LuauLoadTypeInfo, true}, {FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCodegenTypeInfo, true},
{FFlag::LuauCodegenRemoveDeadStores5, true}};
ScopedFastFlag sffs[]{{FFlag::LuauCompileTypeInfo, true}};
CHECK_EQ("\n" + getCodegenAssembly(R"(
local function getsum(n)
@ -1148,8 +1105,7 @@ bb_bytecode_4:
TEST_CASE("ArgumentTypeRefinement")
{
ScopedFastFlag sffs[]{{FFlag::LuauLoadTypeInfo, true}, {FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCodegenTypeInfo, true},
{FFlag::LuauCodegenRemoveDeadStores5, true}};
ScopedFastFlag sffs[]{{FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCompileFastcall3, true}, {FFlag::LuauCodegenFastcall3, true}};
CHECK_EQ("\n" + getCodegenAssembly(R"(
local function getsum(x, y)
@ -1164,31 +1120,28 @@ end
bb_bytecode_0:
STORE_DOUBLE R3, 1
STORE_TAG R3, tnumber
%2 = LOAD_TVALUE R1
STORE_TVALUE R4, %2
STORE_DOUBLE R5, 3
STORE_TAG R5, tnumber
CHECK_SAFE_ENV exit(4)
CHECK_TAG R4, tnumber, exit(4)
%14 = LOAD_DOUBLE R4
STORE_VECTOR R2, 1, %14, 3
CHECK_TAG R1, tnumber, exit(4)
%12 = LOAD_DOUBLE R1
STORE_VECTOR R2, 1, %12, 3
STORE_TAG R2, tvector
%18 = LOAD_TVALUE R2
STORE_TVALUE R0, %18
%22 = LOAD_FLOAT R0, 4i
%27 = LOAD_FLOAT R0, 8i
%36 = ADD_NUM %22, %27
STORE_DOUBLE R2, %36
%16 = LOAD_TVALUE R2
STORE_TVALUE R0, %16
%20 = LOAD_FLOAT R0, 4i
%25 = LOAD_FLOAT R0, 8i
%34 = ADD_NUM %20, %25
STORE_DOUBLE R2, %34
STORE_TAG R2, tnumber
INTERRUPT 13u
INTERRUPT 14u
RETURN R2, 1i
)");
}
TEST_CASE("InlineFunctionType")
{
ScopedFastFlag sffs[]{{FFlag::LuauLoadTypeInfo, true}, {FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCodegenTypeInfo, true},
{FFlag::LuauCodegenRemoveDeadStores5, true}};
ScopedFastFlag sffs[]{{FFlag::LuauCompileTypeInfo, true}};
CHECK_EQ("\n" + getCodegenAssembly(R"(
local function inl(v: vector, s: number)
@ -1236,8 +1189,7 @@ bb_bytecode_0:
TEST_CASE("ResolveTablePathTypes")
{
ScopedFastFlag sffs[]{{FFlag::LuauLoadTypeInfo, true}, {FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCodegenTypeInfo, true},
{FFlag::LuauCodegenRemoveDeadStores5, true}, {FFlag::LuauCompileTempTypeInfo, true}};
ScopedFastFlag sffs[]{{FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCompileTempTypeInfo, true}};
CHECK_EQ("\n" + getCodegenAssembly(R"(
type Vertex = {pos: vector, normal: vector}
@ -1291,8 +1243,7 @@ bb_6:
TEST_CASE("ResolvableSimpleMath")
{
ScopedFastFlag sffs[]{{FFlag::LuauLoadTypeInfo, true}, {FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCodegenTypeInfo, true},
{FFlag::LuauCodegenRemoveDeadStores5, true}, {FFlag::LuauCompileTempTypeInfo, true}};
ScopedFastFlag sffs[]{{FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCompileTempTypeInfo, true}};
CHECK_EQ("\n" + getCodegenHeader(R"(
type Vertex = { p: vector, uv: vector, n: vector, t: vector, b: vector, h: number }
@ -1348,9 +1299,7 @@ end
TEST_CASE("ResolveVectorNamecalls")
{
ScopedFastFlag sffs[]{{FFlag::LuauLoadTypeInfo, true}, {FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCodegenTypeInfo, true},
{FFlag::LuauCodegenRemoveDeadStores5, true}, {FFlag::LuauCompileTempTypeInfo, true}, {FFlag::LuauCodegenDirectUserdataFlow, true},
{FFlag::LuauCodegenAnalyzeHostVectorOps, true}};
ScopedFastFlag sffs[]{{FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCompileTempTypeInfo, true}, {FFlag::LuauCodegenAnalyzeHostVectorOps, true}};
CHECK_EQ("\n" + getCodegenAssembly(R"(
type Vertex = {pos: vector, normal: vector}
@ -1414,8 +1363,7 @@ bb_6:
TEST_CASE("ImmediateTypeAnnotationHelp")
{
ScopedFastFlag sffs[]{{FFlag::LuauLoadTypeInfo, true}, {FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCodegenTypeInfo, true},
{FFlag::LuauCodegenRemoveDeadStores5, true}, {FFlag::LuauCompileTempTypeInfo, true}};
ScopedFastFlag sffs[]{{FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCompileTempTypeInfo, true}};
CHECK_EQ("\n" + getCodegenAssembly(R"(
local function foo(arr, i)
@ -1453,8 +1401,8 @@ bb_2:
TEST_CASE("UnaryTypeResolve")
{
ScopedFastFlag sffs[]{{FFlag::LuauLoadTypeInfo, true}, {FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCodegenTypeInfo, true},
{FFlag::LuauCodegenRemoveDeadStores5, true}, {FFlag::LuauCompileTempTypeInfo, true}};
ScopedFastFlag sffs[]{{FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCompileTempTypeInfo, true}, {FFlag::LuauCompileFastcall3, true},
{FFlag::LuauCodegenFastcall3, true}};
CHECK_EQ("\n" + getCodegenHeader(R"(
local function foo(a, b: vector, c)
@ -1467,17 +1415,16 @@ end
R"(
; function foo(a, b, c) line 2
; R1: vector [argument 'b']
; R3: boolean from 0 to 16 [local 'd']
; R4: vector from 1 to 16 [local 'e']
; R5: number from 2 to 16 [local 'f']
; R7: vector from 13 to 15
; R3: boolean from 0 to 17 [local 'd']
; R4: vector from 1 to 17 [local 'e']
; R5: number from 2 to 17 [local 'f']
; R7: vector from 14 to 16
)");
}
TEST_CASE("ForInManualAnnotation")
{
ScopedFastFlag sffs[]{{FFlag::LuauLoadTypeInfo, true}, {FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCodegenTypeInfo, true},
{FFlag::LuauCodegenRemoveDeadStores5, true}, {FFlag::LuauCompileTempTypeInfo, true}};
ScopedFastFlag sffs[]{{FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCompileTempTypeInfo, true}};
CHECK_EQ("\n" + getCodegenAssembly(R"(
type Vertex = {pos: vector, normal: vector}
@ -1572,8 +1519,7 @@ bb_12:
TEST_CASE("ForInAutoAnnotationIpairs")
{
ScopedFastFlag sffs[]{{FFlag::LuauLoadTypeInfo, true}, {FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCodegenTypeInfo, true},
{FFlag::LuauCodegenRemoveDeadStores5, true}, {FFlag::LuauCompileTempTypeInfo, true}};
ScopedFastFlag sffs[]{{FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCompileTempTypeInfo, true}};
CHECK_EQ("\n" + getCodegenHeader(R"(
type Vertex = {pos: vector, normal: vector}
@ -1600,8 +1546,7 @@ end
TEST_CASE("ForInAutoAnnotationPairs")
{
ScopedFastFlag sffs[]{{FFlag::LuauLoadTypeInfo, true}, {FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCodegenTypeInfo, true},
{FFlag::LuauCodegenRemoveDeadStores5, true}, {FFlag::LuauCompileTempTypeInfo, true}};
ScopedFastFlag sffs[]{{FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCompileTempTypeInfo, true}};
CHECK_EQ("\n" + getCodegenHeader(R"(
type Vertex = {pos: vector, normal: vector}
@ -1628,8 +1573,7 @@ end
TEST_CASE("ForInAutoAnnotationGeneric")
{
ScopedFastFlag sffs[]{{FFlag::LuauLoadTypeInfo, true}, {FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCodegenTypeInfo, true},
{FFlag::LuauCodegenRemoveDeadStores5, true}, {FFlag::LuauCompileTempTypeInfo, true}};
ScopedFastFlag sffs[]{{FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCompileTempTypeInfo, true}};
CHECK_EQ("\n" + getCodegenHeader(R"(
type Vertex = {pos: vector, normal: vector}
@ -1661,8 +1605,7 @@ TEST_CASE("CustomUserdataTypesTemp")
if (!Luau::CodeGen::isSupported())
return;
ScopedFastFlag sffs[]{{FFlag::LuauLoadTypeInfo, true}, {FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCodegenTypeInfo, true},
{FFlag::LuauCodegenRemoveDeadStores5, true}, {FFlag::LuauCompileTempTypeInfo, true}, {FFlag::LuauCompileUserdataInfo, false},
ScopedFastFlag sffs[]{{FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCompileTempTypeInfo, true}, {FFlag::LuauCompileUserdataInfo, false},
{FFlag::LuauLoadUserdataInfo, true}};
CHECK_EQ("\n" + getCodegenHeader(R"(
@ -1683,8 +1626,7 @@ TEST_CASE("CustomUserdataTypes")
if (!Luau::CodeGen::isSupported())
return;
ScopedFastFlag sffs[]{{FFlag::LuauLoadTypeInfo, true}, {FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCodegenTypeInfo, true},
{FFlag::LuauCodegenRemoveDeadStores5, true}, {FFlag::LuauCompileTempTypeInfo, true}, {FFlag::LuauCompileUserdataInfo, true},
ScopedFastFlag sffs[]{{FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCompileTempTypeInfo, true}, {FFlag::LuauCompileUserdataInfo, true},
{FFlag::LuauLoadUserdataInfo, true}};
CHECK_EQ("\n" + getCodegenHeader(R"(
@ -1705,9 +1647,8 @@ TEST_CASE("CustomUserdataPropertyAccess")
if (!Luau::CodeGen::isSupported())
return;
ScopedFastFlag sffs[]{{FFlag::LuauLoadTypeInfo, true}, {FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCodegenTypeInfo, true},
{FFlag::LuauCodegenRemoveDeadStores5, true}, {FFlag::LuauCompileTempTypeInfo, true}, {FFlag::LuauCompileUserdataInfo, true},
{FFlag::LuauLoadUserdataInfo, true}, {FFlag::LuauCodegenDirectUserdataFlow, true}, {FFlag::LuauCodegenUserdataOps, true}};
ScopedFastFlag sffs[]{{FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCompileTempTypeInfo, true}, {FFlag::LuauCompileUserdataInfo, true},
{FFlag::LuauLoadUserdataInfo, true}, {FFlag::LuauCodegenUserdataOps, true}};
CHECK_EQ("\n" + getCodegenAssembly(R"(
local function foo(v: vec2)
@ -1742,9 +1683,8 @@ TEST_CASE("CustomUserdataPropertyAccess2")
if (!Luau::CodeGen::isSupported())
return;
ScopedFastFlag sffs[]{{FFlag::LuauLoadTypeInfo, true}, {FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCodegenTypeInfo, true},
{FFlag::LuauCodegenRemoveDeadStores5, true}, {FFlag::LuauCompileTempTypeInfo, true}, {FFlag::LuauCompileUserdataInfo, true},
{FFlag::LuauLoadUserdataInfo, true}, {FFlag::LuauCodegenDirectUserdataFlow, true}, {FFlag::LuauCodegenUserdataOps, true}};
ScopedFastFlag sffs[]{{FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCompileTempTypeInfo, true}, {FFlag::LuauCompileUserdataInfo, true},
{FFlag::LuauLoadUserdataInfo, true}, {FFlag::LuauCodegenUserdataOps, true}};
CHECK_EQ("\n" + getCodegenAssembly(R"(
local function foo(a: mat3)
@ -1781,10 +1721,8 @@ TEST_CASE("CustomUserdataNamecall1")
if (!Luau::CodeGen::isSupported())
return;
ScopedFastFlag sffs[]{{FFlag::LuauLoadTypeInfo, true}, {FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCodegenTypeInfo, true},
{FFlag::LuauCodegenRemoveDeadStores5, true}, {FFlag::LuauCompileTempTypeInfo, true}, {FFlag::LuauCompileUserdataInfo, true},
{FFlag::LuauLoadUserdataInfo, true}, {FFlag::LuauCodegenDirectUserdataFlow, true}, {FFlag::LuauCodegenAnalyzeHostVectorOps, true},
{FFlag::LuauCodegenUserdataOps, true}};
ScopedFastFlag sffs[]{{FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCompileTempTypeInfo, true}, {FFlag::LuauCompileUserdataInfo, true},
{FFlag::LuauLoadUserdataInfo, true}, {FFlag::LuauCodegenAnalyzeHostVectorOps, true}, {FFlag::LuauCodegenUserdataOps, true}};
CHECK_EQ("\n" + getCodegenAssembly(R"(
local function foo(a: vec2, b: vec2)
@ -1830,10 +1768,9 @@ TEST_CASE("CustomUserdataNamecall2")
if (!Luau::CodeGen::isSupported())
return;
ScopedFastFlag sffs[]{{FFlag::LuauLoadTypeInfo, true}, {FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCodegenTypeInfo, true},
{FFlag::LuauCodegenRemoveDeadStores5, true}, {FFlag::LuauCompileTempTypeInfo, true}, {FFlag::LuauCompileUserdataInfo, true},
{FFlag::LuauLoadUserdataInfo, true}, {FFlag::LuauCodegenDirectUserdataFlow, true}, {FFlag::LuauCodegenAnalyzeHostVectorOps, true},
{FFlag::LuauCodegenUserdataOps, true}, {FFlag::LuauCodegenUserdataAlloc, true}};
ScopedFastFlag sffs[]{{FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCompileTempTypeInfo, true}, {FFlag::LuauCompileUserdataInfo, true},
{FFlag::LuauLoadUserdataInfo, true}, {FFlag::LuauCodegenAnalyzeHostVectorOps, true}, {FFlag::LuauCodegenUserdataOps, true},
{FFlag::LuauCodegenUserdataAlloc, true}};
CHECK_EQ("\n" + getCodegenAssembly(R"(
local function foo(a: vec2, b: vec2)
@ -1882,9 +1819,8 @@ TEST_CASE("CustomUserdataMetamethodDirectFlow")
if (!Luau::CodeGen::isSupported())
return;
ScopedFastFlag sffs[]{{FFlag::LuauLoadTypeInfo, true}, {FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCodegenTypeInfo, true},
{FFlag::LuauCodegenRemoveDeadStores5, true}, {FFlag::LuauCompileTempTypeInfo, true}, {FFlag::LuauCompileUserdataInfo, true},
{FFlag::LuauLoadUserdataInfo, true}, {FFlag::LuauCodegenDirectUserdataFlow, true}, {FFlag::LuauCodegenUserdataOps, true}};
ScopedFastFlag sffs[]{{FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCompileTempTypeInfo, true}, {FFlag::LuauCompileUserdataInfo, true},
{FFlag::LuauLoadUserdataInfo, true}, {FFlag::LuauCodegenUserdataOps, true}};
CHECK_EQ("\n" + getCodegenAssembly(R"(
local function foo(a: mat3, b: mat3)
@ -1916,9 +1852,8 @@ TEST_CASE("CustomUserdataMetamethodDirectFlow2")
if (!Luau::CodeGen::isSupported())
return;
ScopedFastFlag sffs[]{{FFlag::LuauLoadTypeInfo, true}, {FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCodegenTypeInfo, true},
{FFlag::LuauCodegenRemoveDeadStores5, true}, {FFlag::LuauCompileTempTypeInfo, true}, {FFlag::LuauCompileUserdataInfo, true},
{FFlag::LuauLoadUserdataInfo, true}, {FFlag::LuauCodegenDirectUserdataFlow, true}, {FFlag::LuauCodegenUserdataOps, true}};
ScopedFastFlag sffs[]{{FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCompileTempTypeInfo, true}, {FFlag::LuauCompileUserdataInfo, true},
{FFlag::LuauLoadUserdataInfo, true}, {FFlag::LuauCodegenUserdataOps, true}};
CHECK_EQ("\n" + getCodegenAssembly(R"(
local function foo(a: mat3)
@ -1948,9 +1883,8 @@ TEST_CASE("CustomUserdataMetamethodDirectFlow3")
if (!Luau::CodeGen::isSupported())
return;
ScopedFastFlag sffs[]{{FFlag::LuauLoadTypeInfo, true}, {FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCodegenTypeInfo, true},
{FFlag::LuauCodegenRemoveDeadStores5, true}, {FFlag::LuauCompileTempTypeInfo, true}, {FFlag::LuauCompileUserdataInfo, true},
{FFlag::LuauLoadUserdataInfo, true}, {FFlag::LuauCodegenDirectUserdataFlow, true}, {FFlag::LuauCodegenUserdataOps, true}};
ScopedFastFlag sffs[]{{FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCompileTempTypeInfo, true}, {FFlag::LuauCompileUserdataInfo, true},
{FFlag::LuauLoadUserdataInfo, true}, {FFlag::LuauCodegenUserdataOps, true}};
CHECK_EQ("\n" + getCodegenAssembly(R"(
local function foo(a: sequence)
@ -1980,10 +1914,8 @@ TEST_CASE("CustomUserdataMetamethod")
if (!Luau::CodeGen::isSupported())
return;
ScopedFastFlag sffs[]{{FFlag::LuauLoadTypeInfo, true}, {FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCodegenTypeInfo, true},
{FFlag::LuauCodegenRemoveDeadStores5, true}, {FFlag::LuauCompileTempTypeInfo, true}, {FFlag::LuauCompileUserdataInfo, true},
{FFlag::LuauLoadUserdataInfo, true}, {FFlag::LuauCodegenDirectUserdataFlow, true}, {FFlag::LuauCodegenUserdataOps, true},
{FFlag::LuauCodegenUserdataAlloc, true}};
ScopedFastFlag sffs[]{{FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCompileTempTypeInfo, true}, {FFlag::LuauCompileUserdataInfo, true},
{FFlag::LuauLoadUserdataInfo, true}, {FFlag::LuauCodegenUserdataOps, true}, {FFlag::LuauCodegenUserdataAlloc, true}};
CHECK_EQ("\n" + getCodegenAssembly(R"(
local function foo(a: vec2, b: vec2, c: vec2)

View file

@ -8,6 +8,9 @@
#include "doctest.h"
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
LUAU_FASTFLAG(LuauAttributeSyntax);
LUAU_FASTFLAG(LuauNativeAttribute);
LUAU_FASTFLAG(LintRedundantNativeAttribute);
using namespace Luau;
@ -1955,4 +1958,32 @@ local _ = a <= (b == 0)
CHECK_EQ(result.warnings[4].text, "X <= Y <= Z is equivalent to (X <= Y) <= Z; did you mean X <= Y and Y <= Z?");
}
TEST_CASE_FIXTURE(Fixture, "RedundantNativeAttribute")
{
ScopedFastFlag sff[] = {{FFlag::LuauAttributeSyntax, true}, {FFlag::LuauNativeAttribute, true}, {FFlag::LintRedundantNativeAttribute, true}};
LintResult result = lint(R"(
--!native
@native
local function f(a)
@native
local function g(b)
return (a + b)
end
return g
end
f(3)(4)
)");
REQUIRE(2 == result.warnings.size());
CHECK_EQ(result.warnings[0].text, "native attribute on a function is redundant in a native module; consider removing it");
CHECK_EQ(result.warnings[0].location, Location(Position(3, 0), Position(3, 7)));
CHECK_EQ(result.warnings[1].text, "native attribute on a function is redundant in a native module; consider removing it");
CHECK_EQ(result.warnings[1].location, Location(Position(5, 4), Position(5, 11)));
}
TEST_SUITE_END();

View file

@ -2298,10 +2298,14 @@ end
if (FFlag::DebugLuauDeferredConstraintResolution)
{
LUAU_REQUIRE_ERROR_COUNT(4, result);
CHECK(toString(result.errors[0]) == "Operator '-' could not be applied to operands of types unknown and number; there is no corresponding overload for __sub");
CHECK(toString(result.errors[1]) == "Operator '-' could not be applied to operands of types unknown and number; there is no corresponding overload for __sub");
CHECK(toString(result.errors[2]) == "Operator '-' could not be applied to operands of types unknown and number; there is no corresponding overload for __sub");
CHECK(toString(result.errors[3]) == "Operator '-' could not be applied to operands of types unknown and number; there is no corresponding overload for __sub");
CHECK(toString(result.errors[0]) ==
"Operator '-' could not be applied to operands of types unknown and number; there is no corresponding overload for __sub");
CHECK(toString(result.errors[1]) ==
"Operator '-' could not be applied to operands of types unknown and number; there is no corresponding overload for __sub");
CHECK(toString(result.errors[2]) ==
"Operator '-' could not be applied to operands of types unknown and number; there is no corresponding overload for __sub");
CHECK(toString(result.errors[3]) ==
"Operator '-' could not be applied to operands of types unknown and number; there is no corresponding overload for __sub");
}
else
{
@ -2719,7 +2723,6 @@ end
_ = _,{}
)");
}

View file

@ -1230,4 +1230,45 @@ TEST_CASE_FIXTURE(Fixture, "table_containing_non_final_type_is_erroneously_cache
CHECK(n1 == n2);
}
// This is doable with the new solver, but there are some problems we have to work out first.
// CLI-111113
TEST_CASE_FIXTURE(Fixture, "we_cannot_infer_functions_that_return_inconsistently")
{
CheckResult result = check(R"(
function find_first<T>(tbl: {T}, el)
for i, e in tbl do
if e == el then
return i
end
end
return nil
end
)");
#if 0
// This #if block describes what should happen.
LUAU_CHECK_NO_ERRORS(result);
// The second argument has type unknown because the == operator does not
// constrain the type of el.
CHECK("<T>({T}, unknown) -> number?" == toString(requireType("find_first")));
#else
// This is what actually happens right now.
if (FFlag::DebugLuauDeferredConstraintResolution)
{
LUAU_CHECK_ERROR_COUNT(2, result);
// The second argument should be unknown. CLI-111111
CHECK("<T>({T}, 'b) -> number" == toString(requireType("find_first")));
}
else
{
LUAU_CHECK_ERROR_COUNT(1, result);
CHECK("<T, b>({T}, b) -> number" == toString(requireType("find_first")));
}
#endif
}
TEST_SUITE_END();

View file

@ -21,12 +21,30 @@ LUAU_FASTFLAG(LuauInstantiateInSubtyping);
LUAU_FASTFLAG(LuauAlwaysCommitInferencesOfFunctionCalls);
LUAU_FASTFLAG(LuauFixIndexerSubtypingOrdering);
LUAU_FASTFLAG(DebugLuauSharedSelf);
LUAU_FASTFLAG(LuauMetatableInstantiationCloneCheck);
LUAU_DYNAMIC_FASTFLAG(LuauImproveNonFunctionCallError)
TEST_SUITE_BEGIN("TableTests");
TEST_CASE_FIXTURE(BuiltinsFixture, "generalization_shouldnt_seal_table_in_len_family_fn")
{
if (!FFlag::DebugLuauDeferredConstraintResolution)
return;
CheckResult result = check(R"(
local t = {}
for i = #t, 2, -1 do
t[i] = t[i + 1]
end
)");
LUAU_REQUIRE_NO_ERRORS(result);
const TableType* tType = get<TableType>(requireType("t"));
REQUIRE(tType != nullptr);
REQUIRE(tType->indexer);
CHECK_EQ(tType->indexer->indexType, builtinTypes->numberType);
CHECK_EQ(follow(tType->indexer->indexResultType), builtinTypes->unknownType);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "LUAU_ASSERT_arg_exprs_doesnt_trigger_assert")
{
CheckResult result = check(R"(
@ -4150,9 +4168,7 @@ TEST_CASE_FIXTURE(Fixture, "write_annotations_are_unsupported_even_with_the_new_
TEST_CASE_FIXTURE(Fixture, "read_and_write_only_table_properties_are_unsupported")
{
ScopedFastFlag sff[] = {
{FFlag::DebugLuauDeferredConstraintResolution, false}
};
ScopedFastFlag sff[] = {{FFlag::DebugLuauDeferredConstraintResolution, false}};
CheckResult result = check(R"(
type W = {read x: number}
@ -4176,9 +4192,7 @@ TEST_CASE_FIXTURE(Fixture, "read_and_write_only_table_properties_are_unsupported
TEST_CASE_FIXTURE(Fixture, "read_ond_write_only_indexers_are_unsupported")
{
ScopedFastFlag sff[] = {
{FFlag::DebugLuauDeferredConstraintResolution, false}
};
ScopedFastFlag sff[] = {{FFlag::DebugLuauDeferredConstraintResolution, false}};
CheckResult result = check(R"(
type T = {read [string]: number}
@ -4198,9 +4212,7 @@ TEST_CASE_FIXTURE(Fixture, "table_writes_introduce_write_properties")
if (!FFlag::DebugLuauDeferredConstraintResolution)
return;
ScopedFastFlag sff[] = {
{FFlag::DebugLuauDeferredConstraintResolution, true}
};
ScopedFastFlag sff[] = {{FFlag::DebugLuauDeferredConstraintResolution, true}};
CheckResult result = check(R"(
function oc(player, speaker)
@ -4354,8 +4366,6 @@ TEST_CASE_FIXTURE(Fixture, "mymovie_read_write_tables_bug_2")
TEST_CASE_FIXTURE(BuiltinsFixture, "instantiated_metatable_frozen_table_clone_mutation")
{
ScopedFastFlag luauMetatableInstantiationCloneCheck{FFlag::LuauMetatableInstantiationCloneCheck, true};
fileResolver.source["game/worker"] = R"(
type WorkerImpl<T..., R...> = {
destroy: (self: Worker<T..., R...>) -> boolean,
@ -4533,8 +4543,24 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_literal_inference_assert")
end;
}
)");
}
TEST_CASE_FIXTURE(BuiltinsFixture, "metatable_table_assertion_crash")
{
CheckResult result = check(R"(
local NexusInstance = {}
function NexusInstance:__InitMetaMethods(): ()
local Metatable = {}
local OriginalIndexTable = getmetatable(self).__index
setmetatable(self, Metatable)
Metatable.__newindex = function(_, Index: string, Value: any): ()
--Return if the new and old values are the same.
if self[Index] == Value then
end
end
end
)");
}
TEST_SUITE_END();

View file

@ -1536,7 +1536,7 @@ TEST_CASE_FIXTURE(Fixture, "typeof_cannot_refine_builtin_alias")
freeze(arena);
(void) check(R"(
(void)check(R"(
function foo(x)
if typeof(x) == 'GlobalTable' then
end

View file

@ -72,6 +72,7 @@ for _, b in pairs(c) do
assert(bit32.bxor(b, b) == 0)
assert(bit32.bxor(b, 0) == b)
assert(bit32.bxor(b, b, b) == b)
assert(bit32.bxor(b, b, b, b) == 0)
assert(bit32.bnot(b) ~= b)
assert(bit32.bnot(bit32.bnot(b)) == b)
assert(bit32.bnot(b) == 2^32 - 1 - b)

View file

@ -268,10 +268,33 @@ assert(math.min(1) == 1)
assert(math.min(1, 2) == 1)
assert(math.min(1, 2, -1) == -1)
assert(math.min(1, -1, 2) == -1)
assert(math.min(1, -1, 2, -2) == -2)
assert(math.max(1) == 1)
assert(math.max(1, 2) == 2)
assert(math.max(1, 2, -1) == 2)
assert(math.max(1, -1, 2) == 2)
assert(math.max(1, -1, 2, -2) == 2)
local ma, mb, mc, md
assert(pcall(function()
ma = 1
mb = -1
mc = 2
md = -2
end) == true)
-- min/max without contant-folding
assert(math.min(ma) == 1)
assert(math.min(ma, mc) == 1)
assert(math.min(ma, mc, mb) == -1)
assert(math.min(ma, mb, mc) == -1)
assert(math.min(ma, mb, mc, md) == -2)
assert(math.max(ma) == 1)
assert(math.max(ma, mc) == 2)
assert(math.max(ma, mc, mb) == 2)
assert(math.max(ma, mb, mc) == 2)
assert(math.max(ma, mb, mc, md) == 2)
-- noise
assert(math.noise(0.5) == 0)