mirror of
https://github.com/luau-lang/luau.git
synced 2024-11-15 06:15:44 +08:00
Sync to upstream/release/624 (#1245)
# What's changed? * Optimize table.maxn. This function is now 5-14x faster * Reserve Luau stack space for error message. ## New Solver * Globals can be type-stated, but only if they are already in scope * Fix a stack overflow that could occur when normalizing certain kinds of recursive unions of intersections (of unions of intersections...) * Fix an assertion failure that would trigger when the __iter metamethod has a bad signature ## Native Codegen * Type propagation and temporary register type hints * Direct vector property access should only happen for names of right length * BytecodeAnalysis will only predict that some of the vector value fields are numbers --- ## Internal Contributors Co-authored-by: Alexander McCord <amccord@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Aviral Goel <agoel@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
This commit is contained in:
parent
7edd58afed
commit
8a64cb8b73
@ -395,6 +395,7 @@ public:
|
||||
TypeId negate(TypeId there);
|
||||
void subtractPrimitive(NormalizedType& here, TypeId ty);
|
||||
void subtractSingleton(NormalizedType& here, TypeId ty);
|
||||
NormalizationResult intersectNormalWithNegationTy(TypeId toNegate, NormalizedType& intersect, bool useDeprecated = false);
|
||||
|
||||
// ------- Normalizing intersections
|
||||
TypeId intersectionOfTops(TypeId here, TypeId there);
|
||||
|
@ -1900,10 +1900,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprGlobal* globa
|
||||
return Inference{*ty, refinementArena.proposition(key, builtinTypes->truthyType)};
|
||||
}
|
||||
else
|
||||
{
|
||||
reportError(global->location, UnknownSymbol{global->name.value, UnknownSymbol::Binding});
|
||||
return Inference{builtinTypes->errorRecoveryType()};
|
||||
}
|
||||
}
|
||||
|
||||
Inference ConstraintGenerator::checkIndexName(const ScopePtr& scope, const RefinementKey* key, AstExpr* indexee, const std::string& index, Location indexLocation)
|
||||
@ -2453,7 +2450,12 @@ ConstraintGenerator::LValueBounds ConstraintGenerator::checkLValue(const ScopePt
|
||||
{
|
||||
std::optional<TypeId> annotatedTy = scope->lookup(Symbol{global->name});
|
||||
if (annotatedTy)
|
||||
return {annotatedTy, arena->addType(BlockedType{})};
|
||||
{
|
||||
DefId def = dfg->getDef(global);
|
||||
TypeId assignedTy = arena->addType(BlockedType{});
|
||||
rootScope->lvalueTypes[def] = assignedTy;
|
||||
return {annotatedTy, assignedTy};
|
||||
}
|
||||
else
|
||||
return {annotatedTy, std::nullopt};
|
||||
}
|
||||
|
@ -1619,9 +1619,7 @@ std::pair<bool, std::optional<TypeId>> ConstraintSolver::tryDispatchSetIndexer(
|
||||
{
|
||||
if (tt->indexer)
|
||||
{
|
||||
if (isBlocked(tt->indexer->indexType))
|
||||
return {block(tt->indexer->indexType, constraint), std::nullopt};
|
||||
else if (isBlocked(tt->indexer->indexResultType))
|
||||
if (isBlocked(tt->indexer->indexResultType))
|
||||
return {block(tt->indexer->indexResultType, constraint), std::nullopt};
|
||||
|
||||
unify(constraint, indexType, tt->indexer->indexType);
|
||||
@ -2014,10 +2012,14 @@ bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const Iterabl
|
||||
if (std::optional<TypeId> instantiatedNextFn = instantiate(builtinTypes, arena, NotNull{&limits}, constraint->scope, nextFn))
|
||||
{
|
||||
const FunctionType* nextFn = get<FunctionType>(*instantiatedNextFn);
|
||||
LUAU_ASSERT(nextFn);
|
||||
const TypePackId nextRetPack = nextFn->retTypes;
|
||||
|
||||
pushConstraint(constraint->scope, constraint->location, UnpackConstraint{c.variables, nextRetPack, /* resultIsLValue=*/true});
|
||||
// If nextFn is nullptr, then the iterator function has an improper signature.
|
||||
if (nextFn)
|
||||
{
|
||||
const TypePackId nextRetPack = nextFn->retTypes;
|
||||
pushConstraint(constraint->scope, constraint->location, UnpackConstraint{c.variables, nextRetPack, /* resultIsLValue=*/true});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
|
@ -20,6 +20,7 @@ LUAU_FASTFLAGVARIABLE(LuauNormalizeAwayUninhabitableTables, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFixNormalizeCaching, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauNormalizeNotUnknownIntersection, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauFixCyclicUnionsOfIntersections, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauFixReduceStackPressure, false);
|
||||
|
||||
// This could theoretically be 2000 on amd64, but x86 requires this.
|
||||
LUAU_FASTINTVARIABLE(LuauNormalizeIterationLimit, 1200);
|
||||
@ -36,6 +37,11 @@ static bool fixCyclicUnionsOfIntersections()
|
||||
return FFlag::LuauFixCyclicUnionsOfIntersections || FFlag::DebugLuauDeferredConstraintResolution;
|
||||
}
|
||||
|
||||
static bool fixReduceStackPressure()
|
||||
{
|
||||
return FFlag::LuauFixReduceStackPressure || FFlag::DebugLuauDeferredConstraintResolution;
|
||||
}
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
@ -45,6 +51,14 @@ static bool normalizeAwayUninhabitableTables()
|
||||
return FFlag::LuauNormalizeAwayUninhabitableTables || FFlag::DebugLuauDeferredConstraintResolution;
|
||||
}
|
||||
|
||||
static bool shouldEarlyExit(NormalizationResult res)
|
||||
{
|
||||
// if res is hit limits, return control flow
|
||||
if (res == NormalizationResult::HitLimits || res == NormalizationResult::False)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
TypeIds::TypeIds(std::initializer_list<TypeId> tys)
|
||||
{
|
||||
for (TypeId ty : tys)
|
||||
@ -1729,6 +1743,27 @@ bool Normalizer::withinResourceLimits()
|
||||
return true;
|
||||
}
|
||||
|
||||
NormalizationResult Normalizer::intersectNormalWithNegationTy(TypeId toNegate, NormalizedType& intersect, bool useDeprecated)
|
||||
{
|
||||
|
||||
std::optional<NormalizedType> negated;
|
||||
if (useDeprecated)
|
||||
{
|
||||
const NormalizedType* normal = DEPRECATED_normalize(toNegate);
|
||||
negated = negateNormal(*normal);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::shared_ptr<const NormalizedType> normal = normalize(toNegate);
|
||||
negated = negateNormal(*normal);
|
||||
}
|
||||
|
||||
if (!negated)
|
||||
return NormalizationResult::False;
|
||||
intersectNormals(intersect, *negated);
|
||||
return NormalizationResult::True;
|
||||
}
|
||||
|
||||
// See above for an explaination of `ignoreSmallerTyvars`.
|
||||
NormalizationResult Normalizer::unionNormalWithTy(NormalizedType& here, TypeId there, Set<TypeId>& seenSetTypes, int ignoreSmallerTyvars)
|
||||
{
|
||||
@ -2541,8 +2576,8 @@ std::optional<TypeId> Normalizer::intersectionOfTables(TypeId here, TypeId there
|
||||
state = tttv->state;
|
||||
|
||||
TypeLevel level = max(httv->level, tttv->level);
|
||||
TableType result{state, level};
|
||||
|
||||
std::unique_ptr<TableType> result = nullptr;
|
||||
bool hereSubThere = true;
|
||||
bool thereSubHere = true;
|
||||
|
||||
@ -2563,8 +2598,18 @@ std::optional<TypeId> Normalizer::intersectionOfTables(TypeId here, TypeId there
|
||||
if (tprop.readTy.has_value())
|
||||
{
|
||||
// if the intersection of the read types of a property is uninhabited, the whole table is `never`.
|
||||
if (normalizeAwayUninhabitableTables() && NormalizationResult::False == isIntersectionInhabited(*hprop.readTy, *tprop.readTy))
|
||||
return {builtinTypes->neverType};
|
||||
if (fixReduceStackPressure())
|
||||
{
|
||||
if (normalizeAwayUninhabitableTables() &&
|
||||
NormalizationResult::True != isIntersectionInhabited(*hprop.readTy, *tprop.readTy))
|
||||
return {builtinTypes->neverType};
|
||||
}
|
||||
else
|
||||
{
|
||||
if (normalizeAwayUninhabitableTables() &&
|
||||
NormalizationResult::False == isIntersectionInhabited(*hprop.readTy, *tprop.readTy))
|
||||
return {builtinTypes->neverType};
|
||||
}
|
||||
|
||||
TypeId ty = simplifyIntersection(builtinTypes, NotNull{arena}, *hprop.readTy, *tprop.readTy).result;
|
||||
prop.readTy = ty;
|
||||
@ -2614,14 +2659,21 @@ std::optional<TypeId> Normalizer::intersectionOfTables(TypeId here, TypeId there
|
||||
// TODO: string indexers
|
||||
|
||||
if (prop.readTy || prop.writeTy)
|
||||
result.props[name] = prop;
|
||||
{
|
||||
if (!result.get())
|
||||
result = std::make_unique<TableType>(TableType{state, level});
|
||||
result->props[name] = prop;
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& [name, tprop] : tttv->props)
|
||||
{
|
||||
if (httv->props.count(name) == 0)
|
||||
{
|
||||
result.props[name] = tprop;
|
||||
if (!result.get())
|
||||
result = std::make_unique<TableType>(TableType{state, level});
|
||||
|
||||
result->props[name] = tprop;
|
||||
hereSubThere = false;
|
||||
}
|
||||
}
|
||||
@ -2631,18 +2683,24 @@ std::optional<TypeId> Normalizer::intersectionOfTables(TypeId here, TypeId there
|
||||
// TODO: What should intersection of indexes be?
|
||||
TypeId index = unionType(httv->indexer->indexType, tttv->indexer->indexType);
|
||||
TypeId indexResult = intersectionType(httv->indexer->indexResultType, tttv->indexer->indexResultType);
|
||||
result.indexer = {index, indexResult};
|
||||
if (!result.get())
|
||||
result = std::make_unique<TableType>(TableType{state, level});
|
||||
result->indexer = {index, indexResult};
|
||||
hereSubThere &= (httv->indexer->indexType == index) && (httv->indexer->indexResultType == indexResult);
|
||||
thereSubHere &= (tttv->indexer->indexType == index) && (tttv->indexer->indexResultType == indexResult);
|
||||
}
|
||||
else if (httv->indexer)
|
||||
{
|
||||
result.indexer = httv->indexer;
|
||||
if (!result.get())
|
||||
result = std::make_unique<TableType>(TableType{state, level});
|
||||
result->indexer = httv->indexer;
|
||||
thereSubHere = false;
|
||||
}
|
||||
else if (tttv->indexer)
|
||||
{
|
||||
result.indexer = tttv->indexer;
|
||||
if (!result.get())
|
||||
result = std::make_unique<TableType>(TableType{state, level});
|
||||
result->indexer = tttv->indexer;
|
||||
hereSubThere = false;
|
||||
}
|
||||
|
||||
@ -2652,7 +2710,12 @@ std::optional<TypeId> Normalizer::intersectionOfTables(TypeId here, TypeId there
|
||||
else if (thereSubHere)
|
||||
table = ttable;
|
||||
else
|
||||
table = arena->addType(std::move(result));
|
||||
{
|
||||
if (result.get())
|
||||
table = arena->addType(std::move(*result));
|
||||
else
|
||||
table = arena->addType(TableType{state, level});
|
||||
}
|
||||
|
||||
if (tmtable && hmtable)
|
||||
{
|
||||
@ -3150,19 +3213,15 @@ NormalizationResult Normalizer::intersectNormalWithTy(NormalizedType& here, Type
|
||||
{
|
||||
if (fixNormalizeCaching())
|
||||
{
|
||||
std::shared_ptr<const NormalizedType> normal = normalize(t);
|
||||
std::optional<NormalizedType> negated = negateNormal(*normal);
|
||||
if (!negated)
|
||||
return NormalizationResult::False;
|
||||
intersectNormals(here, *negated);
|
||||
NormalizationResult res = intersectNormalWithNegationTy(t, here);
|
||||
if (shouldEarlyExit(res))
|
||||
return res;
|
||||
}
|
||||
else
|
||||
{
|
||||
const NormalizedType* normal = DEPRECATED_normalize(t);
|
||||
std::optional<NormalizedType> negated = negateNormal(*normal);
|
||||
if (!negated)
|
||||
return NormalizationResult::False;
|
||||
intersectNormals(here, *negated);
|
||||
NormalizationResult res = intersectNormalWithNegationTy(t, here, /* useDeprecated */ true);
|
||||
if (shouldEarlyExit(res))
|
||||
return res;
|
||||
}
|
||||
}
|
||||
else if (const UnionType* itv = get<UnionType>(t))
|
||||
@ -3171,11 +3230,9 @@ NormalizationResult Normalizer::intersectNormalWithTy(NormalizedType& here, Type
|
||||
{
|
||||
for (TypeId part : itv->options)
|
||||
{
|
||||
std::shared_ptr<const NormalizedType> normalPart = normalize(part);
|
||||
std::optional<NormalizedType> negated = negateNormal(*normalPart);
|
||||
if (!negated)
|
||||
return NormalizationResult::False;
|
||||
intersectNormals(here, *negated);
|
||||
NormalizationResult res = intersectNormalWithNegationTy(part, here);
|
||||
if (shouldEarlyExit(res))
|
||||
return res;
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -3184,22 +3241,18 @@ NormalizationResult Normalizer::intersectNormalWithTy(NormalizedType& here, Type
|
||||
{
|
||||
for (TypeId part : itv->options)
|
||||
{
|
||||
std::shared_ptr<const NormalizedType> normalPart = normalize(part);
|
||||
std::optional<NormalizedType> negated = negateNormal(*normalPart);
|
||||
if (!negated)
|
||||
return NormalizationResult::False;
|
||||
intersectNormals(here, *negated);
|
||||
NormalizationResult res = intersectNormalWithNegationTy(part, here);
|
||||
if (shouldEarlyExit(res))
|
||||
return res;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (TypeId part : itv->options)
|
||||
{
|
||||
const NormalizedType* normalPart = DEPRECATED_normalize(part);
|
||||
std::optional<NormalizedType> negated = negateNormal(*normalPart);
|
||||
if (!negated)
|
||||
return NormalizationResult::False;
|
||||
intersectNormals(here, *negated);
|
||||
NormalizationResult res = intersectNormalWithNegationTy(part, here, /* useDeprecated */ true);
|
||||
if (shouldEarlyExit(res))
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1280,7 +1280,9 @@ struct TypeChecker2
|
||||
|
||||
void visit(AstExprGlobal* expr)
|
||||
{
|
||||
// TODO!
|
||||
NotNull<Scope> scope = stack.back();
|
||||
if (!scope->lookup(expr->name))
|
||||
reportError(UnknownSymbol{expr->name.value, UnknownSymbol::Binding}, expr->location);
|
||||
}
|
||||
|
||||
void visit(AstExprVarargs* expr)
|
||||
|
@ -317,6 +317,7 @@ static bool compileFile(const char* name, CompileFormat format, Luau::CodeGen::A
|
||||
{
|
||||
options.includeAssembly = format != CompileFormat::CodegenIr;
|
||||
options.includeIr = format != CompileFormat::CodegenAsm;
|
||||
options.includeIrTypes = format != CompileFormat::CodegenAsm;
|
||||
options.includeOutlinedCode = format == CompileFormat::CodegenVerbose;
|
||||
}
|
||||
|
||||
|
@ -229,6 +229,7 @@ if(LUAU_BUILD_TESTS)
|
||||
target_link_libraries(Luau.UnitTest PRIVATE Luau.Analysis Luau.Compiler Luau.CodeGen)
|
||||
|
||||
target_compile_options(Luau.Conformance PRIVATE ${LUAU_OPTIONS})
|
||||
target_compile_definitions(Luau.Conformance PRIVATE DOCTEST_CONFIG_DOUBLE_STRINGIFY)
|
||||
target_include_directories(Luau.Conformance PRIVATE extern)
|
||||
target_link_libraries(Luau.Conformance PRIVATE Luau.Analysis Luau.Compiler Luau.CodeGen Luau.VM)
|
||||
if(CMAKE_SYSTEM_NAME MATCHES "Android|iOS")
|
||||
|
@ -40,8 +40,12 @@ enum class CodeGenCompilationResult
|
||||
CodeGenAssemblerFinalizationFailure = 7, // Failure during assembler finalization
|
||||
CodeGenLoweringFailure = 8, // Lowering failed
|
||||
AllocationFailed = 9, // Native codegen failed due to an allocation error
|
||||
|
||||
Count = 10,
|
||||
};
|
||||
|
||||
std::string toString(const CodeGenCompilationResult& result);
|
||||
|
||||
struct ProtoCompilationFailure
|
||||
{
|
||||
CodeGenCompilationResult result = CodeGenCompilationResult::Success;
|
||||
|
@ -6,6 +6,9 @@
|
||||
#include "Luau/IrUtils.h"
|
||||
|
||||
#include "lobject.h"
|
||||
#include "lstate.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
@ -13,6 +16,7 @@ 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_FASTFLAG(LuauTypeInfoLookupImprovement)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCodegenVectorMispredictFix, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -771,10 +775,30 @@ void analyzeBytecodeTypes(IrFunction& function)
|
||||
|
||||
regTags[ra] = LBC_TYPE_ANY;
|
||||
|
||||
// Assuming that vector component is being indexed
|
||||
// TODO: check what key is used
|
||||
if (bcType.a == LBC_TYPE_VECTOR)
|
||||
regTags[ra] = LBC_TYPE_NUMBER;
|
||||
if (FFlag::LuauCodegenVectorMispredictFix)
|
||||
{
|
||||
if (bcType.a == LBC_TYPE_VECTOR)
|
||||
{
|
||||
TString* str = gco2ts(function.proto->k[kc].value.gc);
|
||||
const char* field = getstr(str);
|
||||
|
||||
if (str->len == 1)
|
||||
{
|
||||
// Same handling as LOP_GETTABLEKS block in lvmexecute.cpp - case-insensitive comparison with "X" / "Y" / "Z"
|
||||
char ch = field[0] | ' ';
|
||||
|
||||
if (ch == 'x' || ch == 'y' || ch == 'z')
|
||||
regTags[ra] = LBC_TYPE_NUMBER;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Assuming that vector component is being indexed
|
||||
// TODO: check what key is used
|
||||
if (bcType.a == LBC_TYPE_VECTOR)
|
||||
regTags[ra] = LBC_TYPE_NUMBER;
|
||||
}
|
||||
|
||||
bcType.result = regTags[ra];
|
||||
break;
|
||||
|
@ -65,6 +65,38 @@ namespace Luau
|
||||
namespace CodeGen
|
||||
{
|
||||
|
||||
std::string toString(const CodeGenCompilationResult& result)
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
case CodeGenCompilationResult::Success:
|
||||
return "Success";
|
||||
case CodeGenCompilationResult::NothingToCompile:
|
||||
return "NothingToCompile";
|
||||
case CodeGenCompilationResult::NotNativeModule:
|
||||
return "NotNativeModule";
|
||||
case CodeGenCompilationResult::CodeGenNotInitialized:
|
||||
return "CodeGenNotInitialized";
|
||||
case CodeGenCompilationResult::CodeGenOverflowInstructionLimit:
|
||||
return "CodeGenOverflowInstructionLimit";
|
||||
case CodeGenCompilationResult::CodeGenOverflowBlockLimit:
|
||||
return "CodeGenOverflowBlockLimit";
|
||||
case CodeGenCompilationResult::CodeGenOverflowBlockInstructionLimit:
|
||||
return "CodeGenOverflowBlockInstructionLimit";
|
||||
case CodeGenCompilationResult::CodeGenAssemblerFinalizationFailure:
|
||||
return "CodeGenAssemblerFinalizationFailure";
|
||||
case CodeGenCompilationResult::CodeGenLoweringFailure:
|
||||
return "CodeGenLoweringFailure";
|
||||
case CodeGenCompilationResult::AllocationFailed:
|
||||
return "AllocationFailed";
|
||||
case CodeGenCompilationResult::Count:
|
||||
return "Count";
|
||||
}
|
||||
|
||||
CODEGEN_ASSERT(false);
|
||||
return "";
|
||||
}
|
||||
|
||||
static const Instruction kCodeEntryInsn = LOP_NATIVECALL;
|
||||
|
||||
void* gPerfLogContext = nullptr;
|
||||
|
@ -13,12 +13,55 @@
|
||||
#include "lapi.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauCodegenTypeInfo)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCodegenIrTypeNames, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
namespace CodeGen
|
||||
{
|
||||
|
||||
static const LocVar* tryFindLocal(const Proto* proto, int reg, int pcpos)
|
||||
{
|
||||
CODEGEN_ASSERT(FFlag::LuauCodegenIrTypeNames);
|
||||
|
||||
for (int i = 0; i < proto->sizelocvars; i++)
|
||||
{
|
||||
const LocVar& local = proto->locvars[i];
|
||||
|
||||
if (reg == local.reg && pcpos >= local.startpc && pcpos < local.endpc)
|
||||
return &local;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const char* tryFindLocalName(const Proto* proto, int reg, int pcpos)
|
||||
{
|
||||
CODEGEN_ASSERT(FFlag::LuauCodegenIrTypeNames);
|
||||
|
||||
const LocVar* var = tryFindLocal(proto, reg, pcpos);
|
||||
|
||||
if (var && var->varname)
|
||||
return getstr(var->varname);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const char* tryFindUpvalueName(const Proto* proto, int upval)
|
||||
{
|
||||
CODEGEN_ASSERT(FFlag::LuauCodegenIrTypeNames);
|
||||
|
||||
if (proto->upvalues)
|
||||
{
|
||||
CODEGEN_ASSERT(upval < proto->sizeupvalues);
|
||||
|
||||
if (proto->upvalues[upval])
|
||||
return getstr(proto->upvalues[upval]);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template<typename AssemblyBuilder>
|
||||
static void logFunctionHeader(AssemblyBuilder& build, Proto* proto)
|
||||
{
|
||||
@ -29,12 +72,22 @@ static void logFunctionHeader(AssemblyBuilder& build, Proto* proto)
|
||||
|
||||
for (int i = 0; i < proto->numparams; i++)
|
||||
{
|
||||
LocVar* var = proto->locvars ? &proto->locvars[proto->sizelocvars - proto->numparams + i] : nullptr;
|
||||
|
||||
if (var && var->varname)
|
||||
build.logAppend("%s%s", i == 0 ? "" : ", ", getstr(var->varname));
|
||||
if (FFlag::LuauCodegenIrTypeNames)
|
||||
{
|
||||
if (const char* name = tryFindLocalName(proto, i, 0))
|
||||
build.logAppend("%s%s", i == 0 ? "" : ", ", name);
|
||||
else
|
||||
build.logAppend("%s$arg%d", i == 0 ? "" : ", ", i);
|
||||
}
|
||||
else
|
||||
build.logAppend("%s$arg%d", i == 0 ? "" : ", ", i);
|
||||
{
|
||||
LocVar* var = proto->locvars ? &proto->locvars[proto->sizelocvars - proto->numparams + i] : nullptr;
|
||||
|
||||
if (var && var->varname)
|
||||
build.logAppend("%s%s", i == 0 ? "" : ", ", getstr(var->varname));
|
||||
else
|
||||
build.logAppend("%s$arg%d", i == 0 ? "" : ", ", i);
|
||||
}
|
||||
}
|
||||
|
||||
if (proto->numparams != 0 && proto->is_vararg)
|
||||
@ -59,21 +112,58 @@ static void logFunctionTypes(AssemblyBuilder& build, const IrFunction& function)
|
||||
{
|
||||
uint8_t ty = typeInfo.argumentTypes[i];
|
||||
|
||||
if (ty != LBC_TYPE_ANY)
|
||||
build.logAppend("; R%d: %s [argument]\n", int(i), getBytecodeTypeName(ty));
|
||||
if (FFlag::LuauCodegenIrTypeNames)
|
||||
{
|
||||
if (ty != LBC_TYPE_ANY)
|
||||
{
|
||||
if (const char* name = tryFindLocalName(function.proto, int(i), 0))
|
||||
build.logAppend("; R%d: %s [argument '%s']\n", int(i), getBytecodeTypeName(ty), name);
|
||||
else
|
||||
build.logAppend("; R%d: %s [argument]\n", int(i), getBytecodeTypeName(ty));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ty != LBC_TYPE_ANY)
|
||||
build.logAppend("; R%d: %s [argument]\n", int(i), getBytecodeTypeName(ty));
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < typeInfo.upvalueTypes.size(); i++)
|
||||
{
|
||||
uint8_t ty = typeInfo.upvalueTypes[i];
|
||||
|
||||
if (ty != LBC_TYPE_ANY)
|
||||
build.logAppend("; U%d: %s\n", int(i), getBytecodeTypeName(ty));
|
||||
if (FFlag::LuauCodegenIrTypeNames)
|
||||
{
|
||||
if (ty != LBC_TYPE_ANY)
|
||||
{
|
||||
if (const char* name = tryFindUpvalueName(function.proto, int(i)))
|
||||
build.logAppend("; U%d: %s ['%s']\n", int(i), getBytecodeTypeName(ty), name);
|
||||
else
|
||||
build.logAppend("; U%d: %s\n", int(i), getBytecodeTypeName(ty));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ty != LBC_TYPE_ANY)
|
||||
build.logAppend("; U%d: %s\n", int(i), getBytecodeTypeName(ty));
|
||||
}
|
||||
}
|
||||
|
||||
for (const BytecodeRegTypeInfo& el : typeInfo.regTypes)
|
||||
{
|
||||
build.logAppend("; R%d: %s from %d to %d\n", el.reg, getBytecodeTypeName(el.type), el.startpc, el.endpc);
|
||||
if (FFlag::LuauCodegenIrTypeNames)
|
||||
{
|
||||
// Using last active position as the PC because 'startpc' for type info is before local is initialized
|
||||
if (const char* name = tryFindLocalName(function.proto, el.reg, el.endpc - 1))
|
||||
build.logAppend("; R%d: %s from %d to %d [local '%s']\n", el.reg, getBytecodeTypeName(el.type), el.startpc, el.endpc, name);
|
||||
else
|
||||
build.logAppend("; R%d: %s from %d to %d\n", el.reg, getBytecodeTypeName(el.type), el.startpc, el.endpc);
|
||||
}
|
||||
else
|
||||
{
|
||||
build.logAppend("; R%d: %s from %d to %d\n", el.reg, getBytecodeTypeName(el.type), el.startpc, el.endpc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include "ltm.h"
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauCodegenDirectUserdataFlow, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCodegenFixVectorFields, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -1197,19 +1198,19 @@ void translateInstGetTableKS(IrBuilder& build, const Instruction* pc, int pcpos)
|
||||
TString* str = gco2ts(build.function.proto->k[aux].value.gc);
|
||||
const char* field = getstr(str);
|
||||
|
||||
if (*field == 'X' || *field == 'x')
|
||||
if ((!FFlag::LuauCodegenFixVectorFields || str->len == 1) && (*field == 'X' || *field == 'x'))
|
||||
{
|
||||
IrOp value = build.inst(IrCmd::LOAD_FLOAT, build.vmReg(rb), build.constInt(0));
|
||||
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(ra), value);
|
||||
build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TNUMBER));
|
||||
}
|
||||
else if (*field == 'Y' || *field == 'y')
|
||||
else if ((!FFlag::LuauCodegenFixVectorFields || str->len == 1) && (*field == 'Y' || *field == 'y'))
|
||||
{
|
||||
IrOp value = build.inst(IrCmd::LOAD_FLOAT, build.vmReg(rb), build.constInt(4));
|
||||
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(ra), value);
|
||||
build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TNUMBER));
|
||||
}
|
||||
else if (*field == 'Z' || *field == 'z')
|
||||
else if ((!FFlag::LuauCodegenFixVectorFields || str->len == 1) && (*field == 'Z' || *field == 'z'))
|
||||
{
|
||||
IrOp value = build.inst(IrCmd::LOAD_FLOAT, build.vmReg(rb), build.constInt(8));
|
||||
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(ra), value);
|
||||
|
@ -454,7 +454,7 @@ BuiltinInfo getBuiltinInfo(int bfid)
|
||||
case LBF_BUFFER_WRITEF32:
|
||||
case LBF_BUFFER_WRITEF64:
|
||||
return {3, 0, BuiltinInfo::Flag_NoneSafe};
|
||||
};
|
||||
}
|
||||
|
||||
LUAU_UNREACHABLE();
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCompileRepeatUntilSkippedLocals, false)
|
||||
LUAU_FASTFLAG(LuauCompileTypeInfo)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTypeInfoLookupImprovement, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCompileTempTypeInfo, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -108,6 +109,8 @@ struct Compiler
|
||||
, builtins(nullptr)
|
||||
, functionTypes(nullptr)
|
||||
, localTypes(nullptr)
|
||||
, exprTypes(nullptr)
|
||||
, builtinTypes(options.vectorType)
|
||||
{
|
||||
// preallocate some buffers that are very likely to grow anyway; this works around std::vector's inefficient growth policy for small arrays
|
||||
localStack.reserve(16);
|
||||
@ -916,6 +919,9 @@ struct Compiler
|
||||
|
||||
bytecode.emitABC(LOP_NAMECALL, regs, selfreg, uint8_t(BytecodeBuilder::getStringHash(iname)));
|
||||
bytecode.emitAux(cid);
|
||||
|
||||
if (FFlag::LuauCompileTempTypeInfo)
|
||||
hintTemporaryExprRegType(fi->expr, selfreg, LBC_TYPE_TABLE, /* instLength */ 2);
|
||||
}
|
||||
else if (bfid >= 0)
|
||||
{
|
||||
@ -1570,6 +1576,9 @@ struct Compiler
|
||||
uint8_t rl = compileExprAuto(expr->left, rs);
|
||||
|
||||
bytecode.emitABC(getBinaryOpArith(expr->op, /* k= */ true), target, rl, uint8_t(rc));
|
||||
|
||||
if (FFlag::LuauCompileTempTypeInfo)
|
||||
hintTemporaryExprRegType(expr->left, rl, LBC_TYPE_NUMBER, /* instLength */ 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -1583,6 +1592,9 @@ struct Compiler
|
||||
LuauOpcode op = (expr->op == AstExprBinary::Sub) ? LOP_SUBRK : LOP_DIVRK;
|
||||
|
||||
bytecode.emitABC(op, target, uint8_t(lc), uint8_t(rr));
|
||||
|
||||
if (FFlag::LuauCompileTempTypeInfo)
|
||||
hintTemporaryExprRegType(expr->right, rr, LBC_TYPE_NUMBER, /* instLength */ 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -1591,6 +1603,12 @@ struct Compiler
|
||||
uint8_t rr = compileExprAuto(expr->right, rs);
|
||||
|
||||
bytecode.emitABC(getBinaryOpArith(expr->op), target, rl, rr);
|
||||
|
||||
if (FFlag::LuauCompileTempTypeInfo)
|
||||
{
|
||||
hintTemporaryExprRegType(expr->left, rl, LBC_TYPE_NUMBER, /* instLength */ 1);
|
||||
hintTemporaryExprRegType(expr->right, rr, LBC_TYPE_NUMBER, /* instLength */ 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -2030,6 +2048,9 @@ struct Compiler
|
||||
|
||||
bytecode.emitABC(LOP_GETTABLEKS, target, reg, uint8_t(BytecodeBuilder::getStringHash(iname)));
|
||||
bytecode.emitAux(cid);
|
||||
|
||||
if (FFlag::LuauCompileTempTypeInfo)
|
||||
hintTemporaryExprRegType(expr->expr, reg, LBC_TYPE_TABLE, /* instLength */ 2);
|
||||
}
|
||||
|
||||
void compileExprIndexExpr(AstExprIndexExpr* expr, uint8_t target)
|
||||
@ -3410,6 +3431,14 @@ struct Compiler
|
||||
uint8_t rr = compileExprAuto(stat->value, rs);
|
||||
|
||||
bytecode.emitABC(getBinaryOpArith(stat->op), target, target, rr);
|
||||
|
||||
if (FFlag::LuauCompileTempTypeInfo)
|
||||
{
|
||||
if (var.kind != LValue::Kind_Local)
|
||||
hintTemporaryRegType(stat->var, target, LBC_TYPE_NUMBER, /* instLength */ 1);
|
||||
|
||||
hintTemporaryExprRegType(stat->value, rr, LBC_TYPE_NUMBER, /* instLength */ 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -3794,6 +3823,27 @@ struct Compiler
|
||||
return !node->is<AstStatBlock>() && !node->is<AstStatTypeAlias>();
|
||||
}
|
||||
|
||||
void hintTemporaryRegType(AstExpr* expr, int reg, LuauBytecodeType expectedType, int instLength)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauCompileTempTypeInfo);
|
||||
|
||||
// If we know the type of a temporary and it's not the type that would be expected by codegen, provide a hint
|
||||
if (LuauBytecodeType* ty = exprTypes.find(expr))
|
||||
{
|
||||
if (*ty != expectedType)
|
||||
bytecode.pushLocalTypeInfo(*ty, reg, bytecode.getDebugPC() - instLength, bytecode.getDebugPC());
|
||||
}
|
||||
}
|
||||
|
||||
void hintTemporaryExprRegType(AstExpr* expr, int reg, LuauBytecodeType expectedType, int instLength)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauCompileTempTypeInfo);
|
||||
|
||||
// If we allocated a temporary register for the operation argument, try hinting its type
|
||||
if (!getExprLocal(expr))
|
||||
hintTemporaryRegType(expr, reg, expectedType, instLength);
|
||||
}
|
||||
|
||||
struct FenvVisitor : AstVisitor
|
||||
{
|
||||
bool& getfenvUsed;
|
||||
@ -4046,6 +4096,9 @@ struct Compiler
|
||||
DenseHashMap<AstExprCall*, int> builtins;
|
||||
DenseHashMap<AstExprFunction*, std::string> functionTypes;
|
||||
DenseHashMap<AstLocal*, LuauBytecodeType> localTypes;
|
||||
DenseHashMap<AstExpr*, LuauBytecodeType> exprTypes;
|
||||
|
||||
BuiltinTypes builtinTypes;
|
||||
|
||||
const DenseHashMap<AstExprCall*, int>* builtinsFold = nullptr;
|
||||
bool builtinsFoldMathK = false;
|
||||
@ -4141,12 +4194,14 @@ void compileOrThrow(BytecodeBuilder& bytecode, const ParseResult& parseResult, c
|
||||
if (FFlag::LuauCompileTypeInfo)
|
||||
{
|
||||
if (options.typeInfoLevel >= 1)
|
||||
buildTypeMap(compiler.functionTypes, compiler.localTypes, root, options.vectorType);
|
||||
buildTypeMap(compiler.functionTypes, compiler.localTypes, compiler.exprTypes, root, options.vectorType, compiler.builtinTypes,
|
||||
compiler.builtins, compiler.globals);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (functionVisitor.hasTypes)
|
||||
buildTypeMap(compiler.functionTypes, compiler.localTypes, root, options.vectorType);
|
||||
buildTypeMap(compiler.functionTypes, compiler.localTypes, compiler.exprTypes, root, options.vectorType, compiler.builtinTypes,
|
||||
compiler.builtins, compiler.globals);
|
||||
}
|
||||
|
||||
for (AstExprFunction* expr : functions)
|
||||
|
@ -4,6 +4,7 @@
|
||||
#include "Luau/BytecodeBuilder.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauCompileTypeInfo)
|
||||
LUAU_FASTFLAG(LuauCompileTempTypeInfo)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -37,10 +38,10 @@ static LuauBytecodeType getPrimitiveType(AstName name)
|
||||
return LBC_TYPE_INVALID;
|
||||
}
|
||||
|
||||
static LuauBytecodeType getType(AstType* ty, const AstArray<AstGenericType>& generics, const DenseHashMap<AstName, AstStatTypeAlias*>& typeAliases,
|
||||
bool resolveAliases, const char* vectorType)
|
||||
static LuauBytecodeType getType(const AstType* ty, const AstArray<AstGenericType>& generics,
|
||||
const DenseHashMap<AstName, AstStatTypeAlias*>& typeAliases, bool resolveAliases, const char* vectorType)
|
||||
{
|
||||
if (AstTypeReference* ref = ty->as<AstTypeReference>())
|
||||
if (const AstTypeReference* ref = ty->as<AstTypeReference>())
|
||||
{
|
||||
if (ref->prefix)
|
||||
return LBC_TYPE_ANY;
|
||||
@ -66,15 +67,15 @@ static LuauBytecodeType getType(AstType* ty, const AstArray<AstGenericType>& gen
|
||||
// not primitive or alias or generic => host-provided, we assume userdata for now
|
||||
return LBC_TYPE_USERDATA;
|
||||
}
|
||||
else if (AstTypeTable* table = ty->as<AstTypeTable>())
|
||||
else if (const AstTypeTable* table = ty->as<AstTypeTable>())
|
||||
{
|
||||
return LBC_TYPE_TABLE;
|
||||
}
|
||||
else if (AstTypeFunction* func = ty->as<AstTypeFunction>())
|
||||
else if (const AstTypeFunction* func = ty->as<AstTypeFunction>())
|
||||
{
|
||||
return LBC_TYPE_FUNCTION;
|
||||
}
|
||||
else if (AstTypeUnion* un = ty->as<AstTypeUnion>())
|
||||
else if (const AstTypeUnion* un = ty->as<AstTypeUnion>())
|
||||
{
|
||||
bool optional = false;
|
||||
LuauBytecodeType type = LBC_TYPE_INVALID;
|
||||
@ -104,7 +105,7 @@ static LuauBytecodeType getType(AstType* ty, const AstArray<AstGenericType>& gen
|
||||
|
||||
return LuauBytecodeType(type | (optional && (type != LBC_TYPE_ANY) ? LBC_TYPE_OPTIONAL_BIT : 0));
|
||||
}
|
||||
else if (AstTypeIntersection* inter = ty->as<AstTypeIntersection>())
|
||||
else if (const AstTypeIntersection* inter = ty->as<AstTypeIntersection>())
|
||||
{
|
||||
return LBC_TYPE_ANY;
|
||||
}
|
||||
@ -144,21 +145,44 @@ static std::string getFunctionType(const AstExprFunction* func, const DenseHashM
|
||||
return typeInfo;
|
||||
}
|
||||
|
||||
static bool isMatchingGlobal(const DenseHashMap<AstName, Compile::Global>& globals, AstExpr* node, const char* name)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauCompileTempTypeInfo);
|
||||
|
||||
if (AstExprGlobal* expr = node->as<AstExprGlobal>())
|
||||
return Compile::getGlobalState(globals, expr->name) == Compile::Global::Default && expr->name == name;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
struct TypeMapVisitor : AstVisitor
|
||||
{
|
||||
DenseHashMap<AstExprFunction*, std::string>& functionTypes;
|
||||
DenseHashMap<AstLocal*, LuauBytecodeType>& localTypes;
|
||||
DenseHashMap<AstExpr*, LuauBytecodeType>& exprTypes;
|
||||
const char* vectorType;
|
||||
const BuiltinTypes& builtinTypes;
|
||||
const DenseHashMap<AstExprCall*, int>& builtinCalls;
|
||||
const DenseHashMap<AstName, Compile::Global>& globals;
|
||||
|
||||
DenseHashMap<AstName, AstStatTypeAlias*> typeAliases;
|
||||
std::vector<std::pair<AstName, AstStatTypeAlias*>> typeAliasStack;
|
||||
DenseHashMap<AstLocal*, const AstType*> resolvedLocals;
|
||||
DenseHashMap<AstExpr*, const AstType*> resolvedExprs;
|
||||
|
||||
TypeMapVisitor(
|
||||
DenseHashMap<AstExprFunction*, std::string>& functionTypes, DenseHashMap<AstLocal*, LuauBytecodeType>& localTypes, const char* vectorType)
|
||||
TypeMapVisitor(DenseHashMap<AstExprFunction*, std::string>& functionTypes, DenseHashMap<AstLocal*, LuauBytecodeType>& localTypes,
|
||||
DenseHashMap<AstExpr*, LuauBytecodeType>& exprTypes, const char* vectorType, const BuiltinTypes& builtinTypes,
|
||||
const DenseHashMap<AstExprCall*, int>& builtinCalls, const DenseHashMap<AstName, Compile::Global>& globals)
|
||||
: functionTypes(functionTypes)
|
||||
, localTypes(localTypes)
|
||||
, exprTypes(exprTypes)
|
||||
, vectorType(vectorType)
|
||||
, builtinTypes(builtinTypes)
|
||||
, builtinCalls(builtinCalls)
|
||||
, globals(globals)
|
||||
, typeAliases(AstName())
|
||||
, resolvedLocals(nullptr)
|
||||
, resolvedExprs(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
@ -189,6 +213,64 @@ struct TypeMapVisitor : AstVisitor
|
||||
}
|
||||
}
|
||||
|
||||
const AstType* resolveAliases(const AstType* ty)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauCompileTempTypeInfo);
|
||||
|
||||
if (const AstTypeReference* ref = ty->as<AstTypeReference>())
|
||||
{
|
||||
if (ref->prefix)
|
||||
return ty;
|
||||
|
||||
if (AstStatTypeAlias* const* alias = typeAliases.find(ref->name); alias && *alias)
|
||||
return (*alias)->type;
|
||||
}
|
||||
|
||||
return ty;
|
||||
}
|
||||
|
||||
const AstTableIndexer* tryGetTableIndexer(AstExpr* expr)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauCompileTempTypeInfo);
|
||||
|
||||
if (const AstType** typePtr = resolvedExprs.find(expr))
|
||||
{
|
||||
if (const AstTypeTable* tableTy = (*typePtr)->as<AstTypeTable>())
|
||||
return tableTy->indexer;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
LuauBytecodeType recordResolvedType(AstExpr* expr, const AstType* ty)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauCompileTempTypeInfo);
|
||||
|
||||
ty = resolveAliases(ty);
|
||||
|
||||
resolvedExprs[expr] = ty;
|
||||
|
||||
LuauBytecodeType bty = getType(ty, {}, typeAliases, /* resolveAliases= */ true, vectorType);
|
||||
exprTypes[expr] = bty;
|
||||
return bty;
|
||||
}
|
||||
|
||||
LuauBytecodeType recordResolvedType(AstLocal* local, const AstType* ty)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauCompileTempTypeInfo);
|
||||
|
||||
ty = resolveAliases(ty);
|
||||
|
||||
resolvedLocals[local] = ty;
|
||||
|
||||
LuauBytecodeType bty = getType(ty, {}, typeAliases, /* resolveAliases= */ true, vectorType);
|
||||
|
||||
if (bty != LBC_TYPE_ANY)
|
||||
localTypes[local] = bty;
|
||||
|
||||
return bty;
|
||||
}
|
||||
|
||||
bool visit(AstStatBlock* node) override
|
||||
{
|
||||
size_t aliasStackTop = pushTypeAliases(node);
|
||||
@ -216,6 +298,60 @@ struct TypeMapVisitor : AstVisitor
|
||||
return false;
|
||||
}
|
||||
|
||||
// for...in statement can contain type annotations on locals (we might even infer some for ipairs/pairs/generalized iteration)
|
||||
bool visit(AstStatForIn* node) override
|
||||
{
|
||||
if (!FFlag::LuauCompileTempTypeInfo)
|
||||
return true;
|
||||
|
||||
for (AstExpr* expr : node->values)
|
||||
expr->visit(this);
|
||||
|
||||
// This is similar to how Compiler matches builtin iteration, but we also handle generalized iteration case
|
||||
if (node->vars.size == 2 && node->values.size == 1)
|
||||
{
|
||||
if (AstExprCall* call = node->values.data[0]->as<AstExprCall>(); call && call->args.size == 1)
|
||||
{
|
||||
AstExpr* func = call->func;
|
||||
AstExpr* arg = call->args.data[0];
|
||||
|
||||
if (isMatchingGlobal(globals, func, "ipairs"))
|
||||
{
|
||||
if (const AstTableIndexer* indexer = tryGetTableIndexer(arg))
|
||||
{
|
||||
recordResolvedType(node->vars.data[0], &builtinTypes.numberType);
|
||||
recordResolvedType(node->vars.data[1], indexer->resultType);
|
||||
}
|
||||
}
|
||||
else if (isMatchingGlobal(globals, func, "pairs"))
|
||||
{
|
||||
if (const AstTableIndexer* indexer = tryGetTableIndexer(arg))
|
||||
{
|
||||
recordResolvedType(node->vars.data[0], indexer->indexType);
|
||||
recordResolvedType(node->vars.data[1], indexer->resultType);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (const AstTableIndexer* indexer = tryGetTableIndexer(node->values.data[0]))
|
||||
{
|
||||
recordResolvedType(node->vars.data[0], indexer->indexType);
|
||||
recordResolvedType(node->vars.data[1], indexer->resultType);
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < node->vars.size; i++)
|
||||
{
|
||||
AstLocal* var = node->vars.data[i];
|
||||
|
||||
if (AstType* annotation = var->annotation)
|
||||
recordResolvedType(var, annotation);
|
||||
}
|
||||
|
||||
node->body->visit(this);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(AstExprFunction* node) override
|
||||
{
|
||||
std::string type = getFunctionType(node, typeAliases, vectorType);
|
||||
@ -223,32 +359,405 @@ struct TypeMapVisitor : AstVisitor
|
||||
if (!type.empty())
|
||||
functionTypes[node] = std::move(type);
|
||||
|
||||
return true;
|
||||
return true; // Let generic visitor step into all expressions
|
||||
}
|
||||
|
||||
bool visit(AstExprLocal* node) override
|
||||
{
|
||||
if (FFlag::LuauCompileTypeInfo)
|
||||
if (FFlag::LuauCompileTempTypeInfo)
|
||||
{
|
||||
AstLocal* local = node->local;
|
||||
|
||||
if (AstType* annotation = local->annotation)
|
||||
if (FFlag::LuauCompileTypeInfo)
|
||||
{
|
||||
LuauBytecodeType ty = getType(annotation, {}, typeAliases, /* resolveAliases= */ true, vectorType);
|
||||
AstLocal* local = node->local;
|
||||
|
||||
if (ty != LBC_TYPE_ANY)
|
||||
localTypes[local] = ty;
|
||||
if (AstType* annotation = local->annotation)
|
||||
{
|
||||
LuauBytecodeType ty = recordResolvedType(node, annotation);
|
||||
|
||||
if (ty != LBC_TYPE_ANY)
|
||||
localTypes[local] = ty;
|
||||
}
|
||||
else if (const AstType** typePtr = resolvedLocals.find(local))
|
||||
{
|
||||
localTypes[local] = recordResolvedType(node, *typePtr);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (FFlag::LuauCompileTypeInfo)
|
||||
{
|
||||
AstLocal* local = node->local;
|
||||
|
||||
if (AstType* annotation = local->annotation)
|
||||
{
|
||||
LuauBytecodeType ty = getType(annotation, {}, typeAliases, /* resolveAliases= */ true, vectorType);
|
||||
|
||||
if (ty != LBC_TYPE_ANY)
|
||||
localTypes[local] = ty;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool visit(AstStatLocal* node) override
|
||||
{
|
||||
if (!FFlag::LuauCompileTempTypeInfo)
|
||||
return true;
|
||||
|
||||
for (AstExpr* expr : node->values)
|
||||
expr->visit(this);
|
||||
|
||||
for (size_t i = 0; i < node->vars.size; i++)
|
||||
{
|
||||
AstLocal* var = node->vars.data[i];
|
||||
|
||||
// Propagate from the value that's being assigned
|
||||
// This simple propagation doesn't handle type packs in tail position
|
||||
if (var->annotation == nullptr)
|
||||
{
|
||||
if (i < node->values.size)
|
||||
{
|
||||
if (const AstType** typePtr = resolvedExprs.find(node->values.data[i]))
|
||||
resolvedLocals[var] = *typePtr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(AstExprIndexExpr* node) override
|
||||
{
|
||||
if (!FFlag::LuauCompileTempTypeInfo)
|
||||
return true;
|
||||
|
||||
node->expr->visit(this);
|
||||
node->index->visit(this);
|
||||
|
||||
if (const AstTableIndexer* indexer = tryGetTableIndexer(node->expr))
|
||||
recordResolvedType(node, indexer->resultType);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(AstExprIndexName* node) override
|
||||
{
|
||||
if (!FFlag::LuauCompileTempTypeInfo)
|
||||
return true;
|
||||
|
||||
node->expr->visit(this);
|
||||
|
||||
if (const AstType** typePtr = resolvedExprs.find(node->expr))
|
||||
{
|
||||
if (const AstTypeTable* tableTy = (*typePtr)->as<AstTypeTable>())
|
||||
{
|
||||
for (const AstTableProp& prop : tableTy->props)
|
||||
{
|
||||
if (prop.name == node->index)
|
||||
{
|
||||
recordResolvedType(node, prop.type);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (LuauBytecodeType* typeBcPtr = exprTypes.find(node->expr))
|
||||
{
|
||||
if (*typeBcPtr == LBC_TYPE_VECTOR)
|
||||
{
|
||||
if (node->index == "X" || node->index == "Y" || node->index == "Z")
|
||||
recordResolvedType(node, &builtinTypes.numberType);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(AstExprUnary* node) override
|
||||
{
|
||||
if (!FFlag::LuauCompileTempTypeInfo)
|
||||
return true;
|
||||
|
||||
node->expr->visit(this);
|
||||
|
||||
switch (node->op)
|
||||
{
|
||||
case AstExprUnary::Not:
|
||||
recordResolvedType(node, &builtinTypes.booleanType);
|
||||
break;
|
||||
case AstExprUnary::Minus:
|
||||
{
|
||||
const AstType** typePtr = resolvedExprs.find(node->expr);
|
||||
LuauBytecodeType* bcTypePtr = exprTypes.find(node->expr);
|
||||
|
||||
if (!typePtr || !bcTypePtr)
|
||||
return false;
|
||||
|
||||
if (*bcTypePtr == LBC_TYPE_VECTOR)
|
||||
recordResolvedType(node, *typePtr);
|
||||
else if (*bcTypePtr == LBC_TYPE_NUMBER)
|
||||
recordResolvedType(node, *typePtr);
|
||||
|
||||
break;
|
||||
}
|
||||
case AstExprUnary::Len:
|
||||
recordResolvedType(node, &builtinTypes.numberType);
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(AstExprBinary* node) override
|
||||
{
|
||||
if (!FFlag::LuauCompileTempTypeInfo)
|
||||
return true;
|
||||
|
||||
node->left->visit(this);
|
||||
node->right->visit(this);
|
||||
|
||||
// Comparisons result in a boolean
|
||||
if (node->op == AstExprBinary::CompareNe || node->op == AstExprBinary::CompareEq || node->op == AstExprBinary::CompareLt ||
|
||||
node->op == AstExprBinary::CompareLe || node->op == AstExprBinary::CompareGt || node->op == AstExprBinary::CompareGe)
|
||||
{
|
||||
recordResolvedType(node, &builtinTypes.booleanType);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (node->op == AstExprBinary::Concat || node->op == AstExprBinary::And || node->op == AstExprBinary::Or)
|
||||
return false;
|
||||
|
||||
const AstType** leftTypePtr = resolvedExprs.find(node->left);
|
||||
LuauBytecodeType* leftBcTypePtr = exprTypes.find(node->left);
|
||||
|
||||
if (!leftTypePtr || !leftBcTypePtr)
|
||||
return false;
|
||||
|
||||
const AstType** rightTypePtr = resolvedExprs.find(node->right);
|
||||
LuauBytecodeType* rightBcTypePtr = exprTypes.find(node->right);
|
||||
|
||||
if (!rightTypePtr || !rightBcTypePtr)
|
||||
return false;
|
||||
|
||||
if (*leftBcTypePtr == LBC_TYPE_VECTOR)
|
||||
recordResolvedType(node, *leftTypePtr);
|
||||
else if (*rightBcTypePtr == LBC_TYPE_VECTOR)
|
||||
recordResolvedType(node, *rightTypePtr);
|
||||
else if (*leftBcTypePtr == LBC_TYPE_NUMBER && *rightBcTypePtr == LBC_TYPE_NUMBER)
|
||||
recordResolvedType(node, *leftTypePtr);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(AstExprGroup* node) override
|
||||
{
|
||||
if (!FFlag::LuauCompileTempTypeInfo)
|
||||
return true;
|
||||
|
||||
node->expr->visit(this);
|
||||
|
||||
if (const AstType** typePtr = resolvedExprs.find(node->expr))
|
||||
recordResolvedType(node, *typePtr);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(AstExprTypeAssertion* node) override
|
||||
{
|
||||
if (!FFlag::LuauCompileTempTypeInfo)
|
||||
return true;
|
||||
|
||||
node->expr->visit(this);
|
||||
|
||||
recordResolvedType(node, node->annotation);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(AstExprConstantBool* node) override
|
||||
{
|
||||
if (!FFlag::LuauCompileTempTypeInfo)
|
||||
return true;
|
||||
|
||||
recordResolvedType(node, &builtinTypes.booleanType);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(AstExprConstantNumber* node) override
|
||||
{
|
||||
if (!FFlag::LuauCompileTempTypeInfo)
|
||||
return true;
|
||||
|
||||
recordResolvedType(node, &builtinTypes.numberType);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(AstExprConstantString* node) override
|
||||
{
|
||||
if (!FFlag::LuauCompileTempTypeInfo)
|
||||
return true;
|
||||
|
||||
recordResolvedType(node, &builtinTypes.stringType);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(AstExprInterpString* node) override
|
||||
{
|
||||
if (!FFlag::LuauCompileTempTypeInfo)
|
||||
return true;
|
||||
|
||||
recordResolvedType(node, &builtinTypes.stringType);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(AstExprIfElse* node) override
|
||||
{
|
||||
if (!FFlag::LuauCompileTempTypeInfo)
|
||||
return true;
|
||||
|
||||
node->condition->visit(this);
|
||||
node->trueExpr->visit(this);
|
||||
node->falseExpr->visit(this);
|
||||
|
||||
const AstType** trueTypePtr = resolvedExprs.find(node->trueExpr);
|
||||
LuauBytecodeType* trueBcTypePtr = exprTypes.find(node->trueExpr);
|
||||
LuauBytecodeType* falseBcTypePtr = exprTypes.find(node->falseExpr);
|
||||
|
||||
// Optimistic check that both expressions are of the same kind, as AstType* cannot be compared
|
||||
if (trueTypePtr && trueBcTypePtr && falseBcTypePtr && *trueBcTypePtr == *falseBcTypePtr)
|
||||
recordResolvedType(node, *trueTypePtr);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(AstExprCall* node) override
|
||||
{
|
||||
if (!FFlag::LuauCompileTempTypeInfo)
|
||||
return true;
|
||||
|
||||
if (const int* bfid = builtinCalls.find(node))
|
||||
{
|
||||
switch (LuauBuiltinFunction(*bfid))
|
||||
{
|
||||
case LBF_NONE:
|
||||
case LBF_ASSERT:
|
||||
case LBF_RAWSET:
|
||||
case LBF_RAWGET:
|
||||
case LBF_TABLE_INSERT:
|
||||
case LBF_TABLE_UNPACK:
|
||||
case LBF_SELECT_VARARG:
|
||||
case LBF_GETMETATABLE:
|
||||
case LBF_SETMETATABLE:
|
||||
case LBF_BUFFER_WRITEU8:
|
||||
case LBF_BUFFER_WRITEU16:
|
||||
case LBF_BUFFER_WRITEU32:
|
||||
case LBF_BUFFER_WRITEF32:
|
||||
case LBF_BUFFER_WRITEF64:
|
||||
break;
|
||||
case LBF_MATH_ABS:
|
||||
case LBF_MATH_ACOS:
|
||||
case LBF_MATH_ASIN:
|
||||
case LBF_MATH_ATAN2:
|
||||
case LBF_MATH_ATAN:
|
||||
case LBF_MATH_CEIL:
|
||||
case LBF_MATH_COSH:
|
||||
case LBF_MATH_COS:
|
||||
case LBF_MATH_DEG:
|
||||
case LBF_MATH_EXP:
|
||||
case LBF_MATH_FLOOR:
|
||||
case LBF_MATH_FMOD:
|
||||
case LBF_MATH_FREXP:
|
||||
case LBF_MATH_LDEXP:
|
||||
case LBF_MATH_LOG10:
|
||||
case LBF_MATH_LOG:
|
||||
case LBF_MATH_MAX:
|
||||
case LBF_MATH_MIN:
|
||||
case LBF_MATH_MODF:
|
||||
case LBF_MATH_POW:
|
||||
case LBF_MATH_RAD:
|
||||
case LBF_MATH_SINH:
|
||||
case LBF_MATH_SIN:
|
||||
case LBF_MATH_SQRT:
|
||||
case LBF_MATH_TANH:
|
||||
case LBF_MATH_TAN:
|
||||
case LBF_BIT32_ARSHIFT:
|
||||
case LBF_BIT32_BAND:
|
||||
case LBF_BIT32_BNOT:
|
||||
case LBF_BIT32_BOR:
|
||||
case LBF_BIT32_BXOR:
|
||||
case LBF_BIT32_BTEST:
|
||||
case LBF_BIT32_EXTRACT:
|
||||
case LBF_BIT32_LROTATE:
|
||||
case LBF_BIT32_LSHIFT:
|
||||
case LBF_BIT32_REPLACE:
|
||||
case LBF_BIT32_RROTATE:
|
||||
case LBF_BIT32_RSHIFT:
|
||||
case LBF_STRING_BYTE:
|
||||
case LBF_STRING_LEN:
|
||||
case LBF_MATH_CLAMP:
|
||||
case LBF_MATH_SIGN:
|
||||
case LBF_MATH_ROUND:
|
||||
case LBF_BIT32_COUNTLZ:
|
||||
case LBF_BIT32_COUNTRZ:
|
||||
case LBF_RAWLEN:
|
||||
case LBF_BIT32_EXTRACTK:
|
||||
case LBF_TONUMBER:
|
||||
case LBF_BIT32_BYTESWAP:
|
||||
case LBF_BUFFER_READI8:
|
||||
case LBF_BUFFER_READU8:
|
||||
case LBF_BUFFER_READI16:
|
||||
case LBF_BUFFER_READU16:
|
||||
case LBF_BUFFER_READI32:
|
||||
case LBF_BUFFER_READU32:
|
||||
case LBF_BUFFER_READF32:
|
||||
case LBF_BUFFER_READF64:
|
||||
recordResolvedType(node, &builtinTypes.numberType);
|
||||
break;
|
||||
|
||||
case LBF_TYPE:
|
||||
case LBF_STRING_CHAR:
|
||||
case LBF_TYPEOF:
|
||||
case LBF_STRING_SUB:
|
||||
case LBF_TOSTRING:
|
||||
recordResolvedType(node, &builtinTypes.stringType);
|
||||
break;
|
||||
|
||||
case LBF_RAWEQUAL:
|
||||
recordResolvedType(node, &builtinTypes.booleanType);
|
||||
break;
|
||||
|
||||
case LBF_VECTOR:
|
||||
recordResolvedType(node, &builtinTypes.vectorType);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true; // Let generic visitor step into all expressions
|
||||
}
|
||||
|
||||
// AstExpr classes that are not covered:
|
||||
// * AstExprConstantNil is not resolved to 'nil' because that doesn't help codegen operations and often used as an initializer before real value
|
||||
// * AstExprGlobal is not supported as we don't have info on globals
|
||||
// * AstExprVarargs cannot be resolved to a testable type
|
||||
// * AstExprTable cannot be reconstructed into a specific AstTypeTable and table annotations don't really help codegen
|
||||
// * AstExprCall is very complex (especially if builtins and registered globals are included), will be extended in the future
|
||||
};
|
||||
|
||||
void buildTypeMap(DenseHashMap<AstExprFunction*, std::string>& functionTypes, DenseHashMap<AstLocal*, LuauBytecodeType>& localTypes, AstNode* root,
|
||||
const char* vectorType)
|
||||
void buildTypeMap(DenseHashMap<AstExprFunction*, std::string>& functionTypes, DenseHashMap<AstLocal*, LuauBytecodeType>& localTypes,
|
||||
DenseHashMap<AstExpr*, LuauBytecodeType>& exprTypes, AstNode* root, const char* vectorType, const BuiltinTypes& builtinTypes,
|
||||
const DenseHashMap<AstExprCall*, int>& builtinCalls, const DenseHashMap<AstName, Compile::Global>& globals)
|
||||
{
|
||||
TypeMapVisitor visitor(functionTypes, localTypes, vectorType);
|
||||
TypeMapVisitor visitor(functionTypes, localTypes, exprTypes, vectorType, builtinTypes, builtinCalls, globals);
|
||||
root->visit(&visitor);
|
||||
}
|
||||
|
||||
|
@ -4,13 +4,29 @@
|
||||
#include "Luau/Ast.h"
|
||||
#include "Luau/Bytecode.h"
|
||||
#include "Luau/DenseHash.h"
|
||||
#include "ValueTracking.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
void buildTypeMap(DenseHashMap<AstExprFunction*, std::string>& functionTypes, DenseHashMap<AstLocal*, LuauBytecodeType>& localTypes, AstNode* root,
|
||||
const char* vectorType);
|
||||
struct BuiltinTypes
|
||||
{
|
||||
BuiltinTypes(const char* vectorType)
|
||||
: vectorType{{}, std::nullopt, AstName{vectorType}, std::nullopt, {}}
|
||||
{
|
||||
}
|
||||
|
||||
// AstName use here will not match the AstNameTable, but the was we use them here always force a full string compare
|
||||
AstTypeReference booleanType{{}, std::nullopt, AstName{"boolean"}, std::nullopt, {}};
|
||||
AstTypeReference numberType{{}, std::nullopt, AstName{"number"}, std::nullopt, {}};
|
||||
AstTypeReference stringType{{}, std::nullopt, AstName{"string"}, std::nullopt, {}};
|
||||
AstTypeReference vectorType;
|
||||
};
|
||||
|
||||
void buildTypeMap(DenseHashMap<AstExprFunction*, std::string>& functionTypes, DenseHashMap<AstLocal*, LuauBytecodeType>& localTypes,
|
||||
DenseHashMap<AstExpr*, LuauBytecodeType>& exprTypes, AstNode* root, const char* vectorType, const BuiltinTypes& builtinTypes,
|
||||
const DenseHashMap<AstExprCall*, int>& builtinCalls, const DenseHashMap<AstName, Compile::Global>& globals);
|
||||
|
||||
} // namespace Luau
|
||||
|
@ -12,6 +12,8 @@
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauPushErrorStackCheck, false)
|
||||
|
||||
static const char* getfuncname(Closure* f);
|
||||
|
||||
static int currentpc(lua_State* L, CallInfo* ci)
|
||||
@ -330,12 +332,18 @@ 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);
|
||||
|
||||
pusherror(L, result);
|
||||
luaD_throw(L, LUA_ERRRUN);
|
||||
}
|
||||
|
||||
void luaG_pusherror(lua_State* L, const char* error)
|
||||
{
|
||||
if (FFlag::LuauPushErrorStackCheck)
|
||||
lua_rawcheckstack(L, 1);
|
||||
|
||||
pusherror(L, error);
|
||||
}
|
||||
|
||||
|
@ -53,6 +53,10 @@
|
||||
* for each block size there's a page free list that contains pages that have at least one free block
|
||||
* (global_State::freegcopages). This free list is used to make sure object allocation is O(1).
|
||||
*
|
||||
* When LUAU_ASSERTENABLED is enabled, all non-GCO pages are also linked in a list (global_State::allpages).
|
||||
* Because this list is not strictly required for runtime operations, it is only tracked for the purposes of
|
||||
* debugging. While overhead of linking those pages together is very small, unnecessary operations are avoided.
|
||||
*
|
||||
* Compared to GCOs, regular allocations have two important differences: they can be freed in isolation,
|
||||
* and they don't start with a GC header. Because of this, each allocation is prefixed with block metadata,
|
||||
* which contains the pointer to the page for allocated blocks, and the pointer to the next free block
|
||||
@ -190,6 +194,12 @@ const SizeClassConfig kSizeClassConfig;
|
||||
#define metadata(block) (*(void**)(block))
|
||||
#define freegcolink(block) (*(void**)((char*)block + kGCOLinkOffset))
|
||||
|
||||
#if defined(LUAU_ASSERTENABLED)
|
||||
#define debugpageset(x) (x)
|
||||
#else
|
||||
#define debugpageset(x) NULL
|
||||
#endif
|
||||
|
||||
struct lua_Page
|
||||
{
|
||||
// list of pages with free blocks
|
||||
@ -336,7 +346,7 @@ static void* newblock(lua_State* L, int sizeClass)
|
||||
|
||||
// slow path: no page in the freelist, allocate a new one
|
||||
if (!page)
|
||||
page = newclasspage(L, g->freepages, NULL, sizeClass, true);
|
||||
page = newclasspage(L, g->freepages, debugpageset(&g->allpages), sizeClass, true);
|
||||
|
||||
LUAU_ASSERT(!page->prev);
|
||||
LUAU_ASSERT(page->freeList || page->freeNext >= 0);
|
||||
@ -457,7 +467,7 @@ static void freeblock(lua_State* L, int sizeClass, void* block)
|
||||
|
||||
// if it's the last block in the page, we don't need the page
|
||||
if (page->busyBlocks == 0)
|
||||
freeclasspage(L, g->freepages, NULL, page, sizeClass);
|
||||
freeclasspage(L, g->freepages, debugpageset(&g->allpages), page, sizeClass);
|
||||
}
|
||||
|
||||
static void freegcoblock(lua_State* L, int sizeClass, void* block, lua_Page* page)
|
||||
|
@ -204,6 +204,7 @@ lua_State* lua_newstate(lua_Alloc f, void* ud)
|
||||
g->freepages[i] = NULL;
|
||||
g->freegcopages[i] = NULL;
|
||||
}
|
||||
g->allpages = NULL;
|
||||
g->allgcopages = NULL;
|
||||
g->sweepgcopage = NULL;
|
||||
for (i = 0; i < LUA_T_COUNT; i++)
|
||||
|
@ -188,7 +188,8 @@ typedef struct global_State
|
||||
|
||||
struct lua_Page* freepages[LUA_SIZECLASSES]; // free page linked list for each size class for non-collectable objects
|
||||
struct lua_Page* freegcopages[LUA_SIZECLASSES]; // free page linked list for each size class for collectable objects
|
||||
struct lua_Page* allgcopages; // page linked list with all pages for all classes
|
||||
struct lua_Page* allpages; // page linked list with all pages for all non-collectable object classes (available with LUAU_ASSERTENABLED)
|
||||
struct lua_Page* allgcopages; // page linked list with all pages for all collectable object classes
|
||||
struct lua_Page* sweepgcopage; // position of the sweep in `allgcopages'
|
||||
|
||||
size_t memcatbytes[LUA_MEMORY_CATEGORIES]; // total amount of memory used by each memory category
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include "lvm.h"
|
||||
|
||||
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauFastCrossTableMove, false)
|
||||
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauFastTableMaxn, false)
|
||||
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauFasterConcat, false)
|
||||
|
||||
static int foreachi(lua_State* L)
|
||||
@ -55,17 +56,45 @@ static int maxn(lua_State* L)
|
||||
{
|
||||
double max = 0;
|
||||
luaL_checktype(L, 1, LUA_TTABLE);
|
||||
lua_pushnil(L); // first key
|
||||
while (lua_next(L, 1))
|
||||
|
||||
if (DFFlag::LuauFastTableMaxn)
|
||||
{
|
||||
lua_pop(L, 1); // remove value
|
||||
if (lua_type(L, -1) == LUA_TNUMBER)
|
||||
Table* t = hvalue(L->base);
|
||||
|
||||
for (int i = 0; i < t->sizearray; i++)
|
||||
{
|
||||
double v = lua_tonumber(L, -1);
|
||||
if (v > max)
|
||||
max = v;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
lua_pushnil(L); // first key
|
||||
while (lua_next(L, 1))
|
||||
{
|
||||
lua_pop(L, 1); // remove value
|
||||
if (lua_type(L, -1) == LUA_TNUMBER)
|
||||
{
|
||||
double v = lua_tonumber(L, -1);
|
||||
if (v > max)
|
||||
max = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lua_pushnumber(L, max);
|
||||
return 1;
|
||||
}
|
||||
|
@ -18,8 +18,12 @@ LUAU_FASTFLAG(LuauCompileTypeInfo)
|
||||
LUAU_FASTFLAG(LuauLoadTypeInfo)
|
||||
LUAU_FASTFLAG(LuauCodegenTypeInfo)
|
||||
LUAU_FASTFLAG(LuauTypeInfoLookupImprovement)
|
||||
LUAU_FASTFLAG(LuauCodegenIrTypeNames)
|
||||
LUAU_FASTFLAG(LuauCompileTempTypeInfo)
|
||||
LUAU_FASTFLAG(LuauCodegenFixVectorFields)
|
||||
LUAU_FASTFLAG(LuauCodegenVectorMispredictFix)
|
||||
|
||||
static std::string getCodegenAssembly(const char* source, bool includeIrTypes = false)
|
||||
static std::string getCodegenAssembly(const char* source, bool includeIrTypes = false, int debugLevel = 1)
|
||||
{
|
||||
Luau::CodeGen::AssemblyOptions options;
|
||||
|
||||
@ -47,7 +51,7 @@ static std::string getCodegenAssembly(const char* source, bool includeIrTypes =
|
||||
Luau::CompileOptions copts = {};
|
||||
|
||||
copts.optimizationLevel = 2;
|
||||
copts.debugLevel = 1;
|
||||
copts.debugLevel = debugLevel;
|
||||
copts.typeInfoLevel = 1;
|
||||
copts.vectorCtor = "vector";
|
||||
copts.vectorType = "vector";
|
||||
@ -66,6 +70,20 @@ static std::string getCodegenAssembly(const char* source, bool includeIrTypes =
|
||||
return "";
|
||||
}
|
||||
|
||||
static std::string getCodegenHeader(const char* source)
|
||||
{
|
||||
std::string assembly = getCodegenAssembly(source, /* includeIrTypes */ true, /* debugLevel */ 2);
|
||||
|
||||
auto bytecodeStart = assembly.find("bb_bytecode_0:");
|
||||
|
||||
if (bytecodeStart == std::string::npos)
|
||||
bytecodeStart = assembly.find("bb_0:");
|
||||
|
||||
REQUIRE(bytecodeStart != std::string::npos);
|
||||
|
||||
return assembly.substr(0, bytecodeStart);
|
||||
}
|
||||
|
||||
TEST_SUITE_BEGIN("IrLowering");
|
||||
|
||||
TEST_CASE("VectorReciprocal")
|
||||
@ -451,6 +469,50 @@ bb_bytecode_1:
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE("VectorRandomProp")
|
||||
{
|
||||
ScopedFastFlag luauCodegenRemoveDeadStores{FFlag::LuauCodegenRemoveDeadStores5, true};
|
||||
ScopedFastFlag luauCodegenFixVectorFields{FFlag::LuauCodegenFixVectorFields, true};
|
||||
ScopedFastFlag luauCodegenVectorMispredictFix{FFlag::LuauCodegenVectorMispredictFix, true};
|
||||
|
||||
CHECK_EQ("\n" + getCodegenAssembly(R"(
|
||||
local function foo(a: vector)
|
||||
return a.XX + a.YY + a.ZZ
|
||||
end
|
||||
)"),
|
||||
R"(
|
||||
; function foo($arg0) line 2
|
||||
bb_0:
|
||||
CHECK_TAG R0, tvector, exit(entry)
|
||||
JUMP bb_2
|
||||
bb_2:
|
||||
JUMP bb_bytecode_1
|
||||
bb_bytecode_1:
|
||||
FALLBACK_GETTABLEKS 0u, R3, R0, K0
|
||||
FALLBACK_GETTABLEKS 2u, R4, R0, K1
|
||||
CHECK_TAG R3, tnumber, bb_fallback_3
|
||||
CHECK_TAG R4, tnumber, bb_fallback_3
|
||||
%14 = LOAD_DOUBLE R3
|
||||
%16 = ADD_NUM %14, R4
|
||||
STORE_DOUBLE R2, %16
|
||||
STORE_TAG R2, tnumber
|
||||
JUMP bb_4
|
||||
bb_4:
|
||||
CHECK_TAG R0, tvector, exit(5)
|
||||
FALLBACK_GETTABLEKS 5u, R3, R0, K2
|
||||
CHECK_TAG R2, tnumber, bb_fallback_5
|
||||
CHECK_TAG R3, tnumber, bb_fallback_5
|
||||
%30 = LOAD_DOUBLE R2
|
||||
%32 = ADD_NUM %30, R3
|
||||
STORE_DOUBLE R1, %32
|
||||
STORE_TAG R1, tnumber
|
||||
JUMP bb_6
|
||||
bb_6:
|
||||
INTERRUPT 8u
|
||||
RETURN R1, 1i
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE("UserDataGetIndex")
|
||||
{
|
||||
ScopedFastFlag luauCodegenDirectUserdataFlow{FFlag::LuauCodegenDirectUserdataFlow, true};
|
||||
@ -860,4 +922,422 @@ bb_bytecode_0:
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE("ResolveTablePathTypes")
|
||||
{
|
||||
ScopedFastFlag sffs[]{{FFlag::LuauLoadTypeInfo, true}, {FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCodegenTypeInfo, true},
|
||||
{FFlag::LuauCodegenRemoveDeadStores5, true}, {FFlag::LuauTypeInfoLookupImprovement, true}, {FFlag::LuauCodegenIrTypeNames, true},
|
||||
{FFlag::LuauCompileTempTypeInfo, true}};
|
||||
|
||||
CHECK_EQ("\n" + getCodegenAssembly(R"(
|
||||
type Vertex = {pos: vector, normal: vector}
|
||||
|
||||
local function foo(arr: {Vertex}, i)
|
||||
local v = arr[i]
|
||||
|
||||
return v.pos.Y
|
||||
end
|
||||
)",
|
||||
/* includeIrTypes */ true, /* debugLevel */ 2),
|
||||
R"(
|
||||
; function foo(arr, i) line 4
|
||||
; R0: table [argument 'arr']
|
||||
; R2: table from 0 to 6 [local 'v']
|
||||
; R4: vector from 3 to 5
|
||||
bb_0:
|
||||
CHECK_TAG R0, ttable, exit(entry)
|
||||
JUMP bb_2
|
||||
bb_2:
|
||||
JUMP bb_bytecode_1
|
||||
bb_bytecode_1:
|
||||
CHECK_TAG R1, tnumber, bb_fallback_3
|
||||
%8 = LOAD_POINTER R0
|
||||
%9 = LOAD_DOUBLE R1
|
||||
%10 = TRY_NUM_TO_INDEX %9, bb_fallback_3
|
||||
%11 = SUB_INT %10, 1i
|
||||
CHECK_ARRAY_SIZE %8, %11, bb_fallback_3
|
||||
CHECK_NO_METATABLE %8, bb_fallback_3
|
||||
%14 = GET_ARR_ADDR %8, %11
|
||||
%15 = LOAD_TVALUE %14
|
||||
STORE_TVALUE R2, %15
|
||||
JUMP bb_4
|
||||
bb_4:
|
||||
CHECK_TAG R2, ttable, exit(1)
|
||||
%23 = LOAD_POINTER R2
|
||||
%24 = GET_SLOT_NODE_ADDR %23, 1u, K0
|
||||
CHECK_SLOT_MATCH %24, K0, bb_fallback_5
|
||||
%26 = LOAD_TVALUE %24, 0i
|
||||
STORE_TVALUE R4, %26
|
||||
JUMP bb_6
|
||||
bb_6:
|
||||
CHECK_TAG R4, tvector, exit(3)
|
||||
%33 = LOAD_FLOAT R4, 4i
|
||||
STORE_DOUBLE R3, %33
|
||||
STORE_TAG R3, tnumber
|
||||
INTERRUPT 5u
|
||||
RETURN R3, 1i
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE("ResolvableSimpleMath")
|
||||
{
|
||||
ScopedFastFlag sffs[]{{FFlag::LuauLoadTypeInfo, true}, {FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCodegenTypeInfo, true},
|
||||
{FFlag::LuauCodegenRemoveDeadStores5, true}, {FFlag::LuauTypeInfoLookupImprovement, true}, {FFlag::LuauCodegenIrTypeNames, true},
|
||||
{FFlag::LuauCompileTempTypeInfo, true}};
|
||||
|
||||
CHECK_EQ("\n" + getCodegenHeader(R"(
|
||||
type Vertex = { p: vector, uv: vector, n: vector, t: vector, b: vector, h: number }
|
||||
local mesh: { vertices: {Vertex}, indices: {number} } = ...
|
||||
|
||||
local function compute()
|
||||
for i = 1,#mesh.indices,3 do
|
||||
local a = mesh.vertices[mesh.indices[i]]
|
||||
local b = mesh.vertices[mesh.indices[i + 1]]
|
||||
local c = mesh.vertices[mesh.indices[i + 2]]
|
||||
|
||||
local vba = b.p - a.p
|
||||
local vca = c.p - a.p
|
||||
|
||||
local uvba = b.uv - a.uv
|
||||
local uvca = c.uv - a.uv
|
||||
|
||||
local r = 1.0 / (uvba.X * uvca.Y - uvca.X * uvba.Y);
|
||||
|
||||
local sdir = (uvca.Y * vba - uvba.Y * vca) * r
|
||||
|
||||
a.t += sdir
|
||||
end
|
||||
end
|
||||
)"),
|
||||
R"(
|
||||
; function compute() line 5
|
||||
; U0: table ['mesh']
|
||||
; R2: number from 0 to 78 [local 'i']
|
||||
; R3: table from 7 to 78 [local 'a']
|
||||
; R4: table from 15 to 78 [local 'b']
|
||||
; R5: table from 24 to 78 [local 'c']
|
||||
; R6: vector from 33 to 78 [local 'vba']
|
||||
; R7: vector from 37 to 38
|
||||
; R7: vector from 38 to 78 [local 'vca']
|
||||
; R8: vector from 37 to 38
|
||||
; R8: vector from 42 to 43
|
||||
; R8: vector from 43 to 78 [local 'uvba']
|
||||
; R9: vector from 42 to 43
|
||||
; R9: vector from 47 to 48
|
||||
; R9: vector from 48 to 78 [local 'uvca']
|
||||
; R10: vector from 47 to 48
|
||||
; R10: vector from 52 to 53
|
||||
; R10: number from 53 to 78 [local 'r']
|
||||
; R11: vector from 52 to 53
|
||||
; R11: vector from 65 to 78 [local 'sdir']
|
||||
; R12: vector from 72 to 73
|
||||
; R12: vector from 75 to 76
|
||||
; R13: vector from 71 to 72
|
||||
; R14: vector from 71 to 72
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE("ResolveVectorNamecalls")
|
||||
{
|
||||
ScopedFastFlag sffs[]{{FFlag::LuauLoadTypeInfo, true}, {FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCodegenTypeInfo, true},
|
||||
{FFlag::LuauCodegenRemoveDeadStores5, true}, {FFlag::LuauTypeInfoLookupImprovement, true}, {FFlag::LuauCodegenIrTypeNames, true},
|
||||
{FFlag::LuauCompileTempTypeInfo, true}, {FFlag::LuauCodegenDirectUserdataFlow, true}};
|
||||
|
||||
CHECK_EQ("\n" + getCodegenAssembly(R"(
|
||||
type Vertex = {pos: vector, normal: vector}
|
||||
|
||||
local function foo(arr: {Vertex}, i)
|
||||
return arr[i].normal:Dot(vector(0.707, 0, 0.707))
|
||||
end
|
||||
)",
|
||||
/* includeIrTypes */ true),
|
||||
R"(
|
||||
; function foo($arg0, $arg1) line 4
|
||||
; R0: table [argument]
|
||||
; R2: vector from 4 to 6
|
||||
bb_0:
|
||||
CHECK_TAG R0, ttable, exit(entry)
|
||||
JUMP bb_2
|
||||
bb_2:
|
||||
JUMP bb_bytecode_1
|
||||
bb_bytecode_1:
|
||||
CHECK_TAG R1, tnumber, bb_fallback_3
|
||||
%8 = LOAD_POINTER R0
|
||||
%9 = LOAD_DOUBLE R1
|
||||
%10 = TRY_NUM_TO_INDEX %9, bb_fallback_3
|
||||
%11 = SUB_INT %10, 1i
|
||||
CHECK_ARRAY_SIZE %8, %11, bb_fallback_3
|
||||
CHECK_NO_METATABLE %8, bb_fallback_3
|
||||
%14 = GET_ARR_ADDR %8, %11
|
||||
%15 = LOAD_TVALUE %14
|
||||
STORE_TVALUE R3, %15
|
||||
JUMP bb_4
|
||||
bb_4:
|
||||
CHECK_TAG R3, ttable, bb_fallback_5
|
||||
%23 = LOAD_POINTER R3
|
||||
%24 = GET_SLOT_NODE_ADDR %23, 1u, K0
|
||||
CHECK_SLOT_MATCH %24, K0, bb_fallback_5
|
||||
%26 = LOAD_TVALUE %24, 0i
|
||||
STORE_TVALUE R2, %26
|
||||
JUMP bb_6
|
||||
bb_6:
|
||||
%31 = LOAD_TVALUE K1, 0i, tvector
|
||||
STORE_TVALUE R4, %31
|
||||
CHECK_TAG R2, tvector, exit(4)
|
||||
FALLBACK_NAMECALL 4u, R2, R2, K2
|
||||
INTERRUPT 6u
|
||||
SET_SAVEDPC 7u
|
||||
CALL R2, 2i, -1i
|
||||
INTERRUPT 7u
|
||||
RETURN R2, -1i
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE("ImmediateTypeAnnotationHelp")
|
||||
{
|
||||
ScopedFastFlag sffs[]{{FFlag::LuauLoadTypeInfo, true}, {FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCodegenTypeInfo, true},
|
||||
{FFlag::LuauCodegenRemoveDeadStores5, true}, {FFlag::LuauTypeInfoLookupImprovement, true}, {FFlag::LuauCodegenIrTypeNames, true},
|
||||
{FFlag::LuauCompileTempTypeInfo, true}};
|
||||
|
||||
CHECK_EQ("\n" + getCodegenAssembly(R"(
|
||||
local function foo(arr, i)
|
||||
return (arr[i] :: vector) / 5
|
||||
end
|
||||
)",
|
||||
/* includeIrTypes */ true),
|
||||
R"(
|
||||
; function foo($arg0, $arg1) line 2
|
||||
; R3: vector from 1 to 2
|
||||
bb_bytecode_0:
|
||||
CHECK_TAG R0, ttable, bb_fallback_1
|
||||
CHECK_TAG R1, tnumber, bb_fallback_1
|
||||
%4 = LOAD_POINTER R0
|
||||
%5 = LOAD_DOUBLE R1
|
||||
%6 = TRY_NUM_TO_INDEX %5, bb_fallback_1
|
||||
%7 = SUB_INT %6, 1i
|
||||
CHECK_ARRAY_SIZE %4, %7, bb_fallback_1
|
||||
CHECK_NO_METATABLE %4, bb_fallback_1
|
||||
%10 = GET_ARR_ADDR %4, %7
|
||||
%11 = LOAD_TVALUE %10
|
||||
STORE_TVALUE R3, %11
|
||||
JUMP bb_2
|
||||
bb_2:
|
||||
CHECK_TAG R3, tvector, exit(1)
|
||||
%19 = LOAD_TVALUE R3
|
||||
%20 = NUM_TO_VEC 5
|
||||
%21 = DIV_VEC %19, %20
|
||||
%22 = TAG_VECTOR %21
|
||||
STORE_TVALUE R2, %22
|
||||
INTERRUPT 2u
|
||||
RETURN R2, 1i
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE("UnaryTypeResolve")
|
||||
{
|
||||
ScopedFastFlag sffs[]{{FFlag::LuauLoadTypeInfo, true}, {FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCodegenTypeInfo, true},
|
||||
{FFlag::LuauCodegenRemoveDeadStores5, true}, {FFlag::LuauTypeInfoLookupImprovement, true}, {FFlag::LuauCodegenIrTypeNames, true},
|
||||
{FFlag::LuauCompileTempTypeInfo, true}};
|
||||
|
||||
CHECK_EQ("\n" + getCodegenHeader(R"(
|
||||
local function foo(a, b: vector, c)
|
||||
local d = not a
|
||||
local e = -b
|
||||
local f = #c
|
||||
return (if d then e else vector(f, 2, 3)).X
|
||||
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
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE("ForInManualAnnotation")
|
||||
{
|
||||
ScopedFastFlag sffs[]{{FFlag::LuauLoadTypeInfo, true}, {FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCodegenTypeInfo, true},
|
||||
{FFlag::LuauCodegenRemoveDeadStores5, true}, {FFlag::LuauTypeInfoLookupImprovement, true}, {FFlag::LuauCodegenIrTypeNames, true},
|
||||
{FFlag::LuauCompileTempTypeInfo, true}};
|
||||
|
||||
CHECK_EQ("\n" + getCodegenAssembly(R"(
|
||||
type Vertex = {pos: vector, normal: vector}
|
||||
|
||||
local function foo(a: {Vertex})
|
||||
local sum = 0
|
||||
for k, v: Vertex in ipairs(a) do
|
||||
sum += v.pos.X
|
||||
end
|
||||
return sum
|
||||
end
|
||||
)",
|
||||
/* includeIrTypes */ true, /* debugLevel */ 2),
|
||||
R"(
|
||||
; function foo(a) line 4
|
||||
; R0: table [argument 'a']
|
||||
; R1: number from 0 to 14 [local 'sum']
|
||||
; R5: number from 5 to 11 [local 'k']
|
||||
; R6: table from 5 to 11 [local 'v']
|
||||
; R8: vector from 8 to 10
|
||||
bb_0:
|
||||
CHECK_TAG R0, ttable, exit(entry)
|
||||
JUMP bb_4
|
||||
bb_4:
|
||||
JUMP bb_bytecode_1
|
||||
bb_bytecode_1:
|
||||
STORE_DOUBLE R1, 0
|
||||
STORE_TAG R1, tnumber
|
||||
CHECK_SAFE_ENV exit(1)
|
||||
JUMP_EQ_TAG K1, tnil, bb_fallback_6, bb_5
|
||||
bb_5:
|
||||
%9 = LOAD_TVALUE K1
|
||||
STORE_TVALUE R2, %9
|
||||
JUMP bb_7
|
||||
bb_7:
|
||||
%15 = LOAD_TVALUE R0
|
||||
STORE_TVALUE R3, %15
|
||||
INTERRUPT 4u
|
||||
SET_SAVEDPC 5u
|
||||
CALL R2, 1i, 3i
|
||||
CHECK_SAFE_ENV exit(5)
|
||||
CHECK_TAG R3, ttable, bb_fallback_8
|
||||
CHECK_TAG R4, tnumber, bb_fallback_8
|
||||
JUMP_CMP_NUM R4, 0, not_eq, bb_fallback_8, bb_9
|
||||
bb_9:
|
||||
STORE_TAG R2, tnil
|
||||
STORE_POINTER R4, 0i
|
||||
STORE_EXTRA R4, 128i
|
||||
STORE_TAG R4, tlightuserdata
|
||||
JUMP bb_bytecode_3
|
||||
bb_bytecode_2:
|
||||
CHECK_TAG R6, ttable, exit(6)
|
||||
%35 = LOAD_POINTER R6
|
||||
%36 = GET_SLOT_NODE_ADDR %35, 6u, K2
|
||||
CHECK_SLOT_MATCH %36, K2, bb_fallback_10
|
||||
%38 = LOAD_TVALUE %36, 0i
|
||||
STORE_TVALUE R8, %38
|
||||
JUMP bb_11
|
||||
bb_11:
|
||||
CHECK_TAG R8, tvector, exit(8)
|
||||
%45 = LOAD_FLOAT R8, 0i
|
||||
STORE_DOUBLE R7, %45
|
||||
STORE_TAG R7, tnumber
|
||||
CHECK_TAG R1, tnumber, exit(10)
|
||||
%52 = LOAD_DOUBLE R1
|
||||
%54 = ADD_NUM %52, %45
|
||||
STORE_DOUBLE R1, %54
|
||||
JUMP bb_bytecode_3
|
||||
bb_bytecode_3:
|
||||
INTERRUPT 11u
|
||||
CHECK_TAG R2, tnil, bb_fallback_13
|
||||
%60 = LOAD_POINTER R3
|
||||
%61 = LOAD_INT R4
|
||||
%62 = GET_ARR_ADDR %60, %61
|
||||
CHECK_ARRAY_SIZE %60, %61, bb_12
|
||||
%64 = LOAD_TAG %62
|
||||
JUMP_EQ_TAG %64, tnil, bb_12, bb_14
|
||||
bb_14:
|
||||
%66 = ADD_INT %61, 1i
|
||||
STORE_INT R4, %66
|
||||
%68 = INT_TO_NUM %66
|
||||
STORE_DOUBLE R5, %68
|
||||
STORE_TAG R5, tnumber
|
||||
%71 = LOAD_TVALUE %62
|
||||
STORE_TVALUE R6, %71
|
||||
JUMP bb_bytecode_2
|
||||
bb_12:
|
||||
INTERRUPT 13u
|
||||
RETURN R1, 1i
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE("ForInAutoAnnotationIpairs")
|
||||
{
|
||||
ScopedFastFlag sffs[]{{FFlag::LuauLoadTypeInfo, true}, {FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCodegenTypeInfo, true},
|
||||
{FFlag::LuauCodegenRemoveDeadStores5, true}, {FFlag::LuauTypeInfoLookupImprovement, true}, {FFlag::LuauCodegenIrTypeNames, true},
|
||||
{FFlag::LuauCompileTempTypeInfo, true}};
|
||||
|
||||
CHECK_EQ("\n" + getCodegenHeader(R"(
|
||||
type Vertex = {pos: vector, normal: vector}
|
||||
|
||||
local function foo(a: {Vertex})
|
||||
local sum = 0
|
||||
for k, v in ipairs(a) do
|
||||
local n = v.pos.X
|
||||
sum += n
|
||||
end
|
||||
return sum
|
||||
end
|
||||
)"),
|
||||
R"(
|
||||
; function foo(a) line 4
|
||||
; R0: table [argument 'a']
|
||||
; R1: number from 0 to 14 [local 'sum']
|
||||
; R5: number from 5 to 11 [local 'k']
|
||||
; R6: table from 5 to 11 [local 'v']
|
||||
; R7: number from 6 to 11 [local 'n']
|
||||
; R8: vector from 8 to 10
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE("ForInAutoAnnotationPairs")
|
||||
{
|
||||
ScopedFastFlag sffs[]{{FFlag::LuauLoadTypeInfo, true}, {FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCodegenTypeInfo, true},
|
||||
{FFlag::LuauCodegenRemoveDeadStores5, true}, {FFlag::LuauTypeInfoLookupImprovement, true}, {FFlag::LuauCodegenIrTypeNames, true},
|
||||
{FFlag::LuauCompileTempTypeInfo, true}};
|
||||
|
||||
CHECK_EQ("\n" + getCodegenHeader(R"(
|
||||
type Vertex = {pos: vector, normal: vector}
|
||||
|
||||
local function foo(a: {[string]: Vertex})
|
||||
local sum = 0
|
||||
for k, v in pairs(a) do
|
||||
local n = v.pos.X
|
||||
sum += n
|
||||
end
|
||||
return sum
|
||||
end
|
||||
)"),
|
||||
R"(
|
||||
; function foo(a) line 4
|
||||
; R0: table [argument 'a']
|
||||
; R1: number from 0 to 14 [local 'sum']
|
||||
; R5: string from 5 to 11 [local 'k']
|
||||
; R6: table from 5 to 11 [local 'v']
|
||||
; R7: number from 6 to 11 [local 'n']
|
||||
; R8: vector from 8 to 10
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE("ForInAutoAnnotationGeneric")
|
||||
{
|
||||
ScopedFastFlag sffs[]{{FFlag::LuauLoadTypeInfo, true}, {FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCodegenTypeInfo, true},
|
||||
{FFlag::LuauCodegenRemoveDeadStores5, true}, {FFlag::LuauTypeInfoLookupImprovement, true}, {FFlag::LuauCodegenIrTypeNames, true},
|
||||
{FFlag::LuauCompileTempTypeInfo, true}};
|
||||
|
||||
CHECK_EQ("\n" + getCodegenHeader(R"(
|
||||
type Vertex = {pos: vector, normal: vector}
|
||||
|
||||
local function foo(a: {Vertex})
|
||||
local sum = 0
|
||||
for k, v in a do
|
||||
local n = v.pos.X
|
||||
sum += n
|
||||
end
|
||||
return sum
|
||||
end
|
||||
)"),
|
||||
R"(
|
||||
; function foo(a) line 4
|
||||
; R0: table [argument 'a']
|
||||
; R1: number from 0 to 13 [local 'sum']
|
||||
; R5: number from 4 to 10 [local 'k']
|
||||
; R6: table from 4 to 10 [local 'v']
|
||||
; R7: number from 5 to 10 [local 'n']
|
||||
; R8: vector from 7 to 9
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -14,7 +14,7 @@ LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
LUAU_FASTFLAG(LuauFixNormalizeCaching)
|
||||
LUAU_FASTFLAG(LuauNormalizeNotUnknownIntersection)
|
||||
LUAU_FASTFLAG(LuauFixCyclicUnionsOfIntersections);
|
||||
|
||||
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||
using namespace Luau;
|
||||
|
||||
namespace
|
||||
@ -962,4 +962,32 @@ TEST_CASE_FIXTURE(NormalizeFixture, "intersect_with_not_unknown")
|
||||
CHECK("never" == toString(normalizer.typeFromNormal(*normalized.get())));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "cyclic_stack_overflow_1")
|
||||
{
|
||||
ScopedFastInt sfi{FInt::LuauTypeInferRecursionLimit, 165};
|
||||
this->unifierState.counters.recursionLimit = FInt::LuauTypeInferRecursionLimit;
|
||||
TypeId t1 = arena.addType(TableType{});
|
||||
TypeId t2 = arena.addType(TableType{});
|
||||
TypeId t3 = arena.addType(IntersectionType{{t1, t2}});
|
||||
asMutable(t1)->ty.get_if<TableType>()->props = {{"foo", Property::readonly(t2)}};
|
||||
asMutable(t2)->ty.get_if<TableType>()->props = {{"foo", Property::readonly(t1)}};
|
||||
|
||||
std::shared_ptr<const NormalizedType> normalized = normalizer.normalize(t3);
|
||||
CHECK(normalized);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "cyclic_stack_overflow_2")
|
||||
{
|
||||
ScopedFastInt sfi{FInt::LuauTypeInferRecursionLimit, 165};
|
||||
this->unifierState.counters.recursionLimit = FInt::LuauTypeInferRecursionLimit;
|
||||
TypeId t1 = arena.addType(TableType{});
|
||||
TypeId t2 = arena.addType(TableType{});
|
||||
TypeId t3 = arena.addType(IntersectionType{{t1, t2}});
|
||||
asMutable(t1)->ty.get_if<TableType>()->props = {{"foo", Property::readonly(t3)}};
|
||||
asMutable(t2)->ty.get_if<TableType>()->props = {{"foo", Property::readonly(t1)}};
|
||||
|
||||
std::shared_ptr<const NormalizedType> normalized = normalizer.normalize(t3);
|
||||
CHECK(normalized);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -17,6 +17,39 @@ LUAU_FASTFLAG(LuauAlwaysCommitInferencesOfFunctionCalls);
|
||||
|
||||
TEST_SUITE_BEGIN("TypeInferClasses");
|
||||
|
||||
TEST_CASE_FIXTURE(ClassFixture, "Luau.Analyze.CLI_crashes_on_this_test")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local CircularQueue = {}
|
||||
CircularQueue.__index = CircularQueue
|
||||
|
||||
function CircularQueue:new()
|
||||
local newCircularQueue = {
|
||||
head = nil,
|
||||
}
|
||||
setmetatable(newCircularQueue, CircularQueue)
|
||||
|
||||
return newCircularQueue
|
||||
end
|
||||
|
||||
function CircularQueue:push()
|
||||
local newListNode
|
||||
|
||||
if self.head then
|
||||
newListNode = {
|
||||
prevNode = self.head.prevNode,
|
||||
nextNode = self.head,
|
||||
}
|
||||
newListNode.prevNode.nextNode = newListNode
|
||||
newListNode.nextNode.prevNode = newListNode
|
||||
end
|
||||
end
|
||||
|
||||
return CircularQueue
|
||||
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ClassFixture, "call_method_of_a_class")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
|
@ -4423,4 +4423,20 @@ TEST_CASE_FIXTURE(Fixture, "setindexer_multiple_tables_intersection")
|
||||
CHECK("({ [string]: number } & { [thread]: boolean }, boolean | number) -> ()" == toString(requireType("f")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "insert_a_and_f_of_a_into_table_res_in_a_loop")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local function f(t)
|
||||
local res = {}
|
||||
|
||||
for k, a in t do
|
||||
res[k] = f(a)
|
||||
res[k] = a
|
||||
end
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -782,7 +782,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "no_heap_use_after_free_error")
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
else
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "infer_type_assertion_value_type")
|
||||
@ -1540,19 +1543,33 @@ TEST_CASE_FIXTURE(Fixture, "typeof_cannot_refine_builtin_alias")
|
||||
)");
|
||||
}
|
||||
|
||||
/*
|
||||
* We had an issue where we tripped the canMutate() check when binding one
|
||||
* blocked type to another.
|
||||
*/
|
||||
TEST_CASE_FIXTURE(Fixture, "delay_setIndexer_constraint_if_the_indexers_type_is_blocked")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "bad_iter_metamethod")
|
||||
{
|
||||
(void) check(R"(
|
||||
local SG = GetService(true)
|
||||
local lines: { [string]: typeof(SG.ScreenGui) } = {}
|
||||
lines[deadline] = nil -- This line
|
||||
CheckResult result = check(R"(
|
||||
function iter(): unknown
|
||||
return nil
|
||||
end
|
||||
|
||||
local a = {__iter = iter}
|
||||
setmetatable(a, a)
|
||||
|
||||
for i in a do
|
||||
end
|
||||
)");
|
||||
|
||||
// As long as type inference doesn't trip an assert or crash, we're good!
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
CannotCallNonFunction* ccnf = get<CannotCallNonFunction>(result.errors[0]);
|
||||
REQUIRE(ccnf);
|
||||
|
||||
CHECK("unknown" == toString(ccnf->ty));
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -490,5 +490,34 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "typestates_do_not_apply_to_the_initial_local
|
||||
CHECK("number" == toString(requireTypeAtPosition({5, 14}), {true}));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "typestate_globals")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
loadDefinition(R"(
|
||||
declare foo: string | number
|
||||
declare function f(x: string): ()
|
||||
)");
|
||||
|
||||
CheckResult result = check(R"(
|
||||
foo = "a"
|
||||
f(foo)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "typestate_unknown_global")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
x = 5
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
CHECK(get<UnknownSymbol>(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -306,10 +306,14 @@ end
|
||||
|
||||
|
||||
assert(table.maxn{} == 0)
|
||||
assert(table.maxn{[-100] = 1} == 0)
|
||||
assert(table.maxn{["1000"] = true} == 0)
|
||||
assert(table.maxn{["1000"] = true, [24.5] = 3} == 24.5)
|
||||
assert(table.maxn{[1000] = true} == 1000)
|
||||
assert(table.maxn{[10] = true, [100*math.pi] = print} == 100*math.pi)
|
||||
a = {[10] = 1, [20] = 2}
|
||||
a[20] = nil
|
||||
assert(table.maxn(a) == 10)
|
||||
|
||||
|
||||
-- int overflow
|
||||
|
@ -37,7 +37,6 @@ Differ.metatable_metamissing_left
|
||||
Differ.metatable_metamissing_right
|
||||
Differ.metatable_metanormal
|
||||
Differ.negation
|
||||
FrontendTest.accumulate_cached_errors_in_consistent_order
|
||||
FrontendTest.environments
|
||||
FrontendTest.imported_table_modification_2
|
||||
FrontendTest.it_should_be_safe_to_stringify_errors_when_full_type_graph_is_discarded
|
||||
@ -182,6 +181,7 @@ TableTests.infer_array
|
||||
TableTests.infer_indexer_from_array_like_table
|
||||
TableTests.infer_indexer_from_its_variable_type_and_unifiable
|
||||
TableTests.inferred_return_type_of_free_table
|
||||
TableTests.insert_a_and_f_of_a_into_table_res_in_a_loop
|
||||
TableTests.invariant_table_properties_means_instantiating_tables_in_assignment_is_unsound
|
||||
TableTests.invariant_table_properties_means_instantiating_tables_in_call_is_unsound
|
||||
TableTests.length_operator_union
|
||||
@ -269,7 +269,6 @@ TypeInfer.dont_report_type_errors_within_an_AstExprError
|
||||
TypeInfer.dont_report_type_errors_within_an_AstStatError
|
||||
TypeInfer.globals
|
||||
TypeInfer.globals2
|
||||
TypeInfer.globals_are_banned_in_strict_mode
|
||||
TypeInfer.infer_through_group_expr
|
||||
TypeInfer.no_stack_overflow_from_isoptional
|
||||
TypeInfer.recursive_function_that_invokes_itself_with_a_refinement_of_its_parameter
|
||||
@ -366,7 +365,6 @@ TypeInferLoops.properly_infer_iteratee_is_a_free_table
|
||||
TypeInferLoops.repeat_loop
|
||||
TypeInferLoops.varlist_declared_by_for_in_loop_should_be_free
|
||||
TypeInferLoops.while_loop
|
||||
TypeInferModules.custom_require_global
|
||||
TypeInferModules.do_not_modify_imported_types_5
|
||||
TypeInferModules.require
|
||||
TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_it_wont_help_2
|
||||
|
94
tools/stackdbg.py
Normal file
94
tools/stackdbg.py
Normal file
@ -0,0 +1,94 @@
|
||||
#!usr/bin/python3
|
||||
"""
|
||||
To use this command, simply run the command:
|
||||
`command script import /path/to/your/game-engine/Client/Luau/tools/stackdbg.py`
|
||||
in the `lldb` interpreter. You can also add it to your .lldbinit file to have it be
|
||||
automatically imported.
|
||||
|
||||
If using vscode, you can add the above command to your launch.json under `preRunCommands` for the appropriate target. For example:
|
||||
{
|
||||
"name": "Luau.UnitTest",
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/build/ninja/common-tests/noopt/Luau/Luau.UnitTest",
|
||||
"preRunCommands": [
|
||||
"command script import ${workspaceFolder}/Client/Luau/tools/stackdbg.py"
|
||||
],
|
||||
}
|
||||
|
||||
Once this is loaded,
|
||||
`(lldb) help stack`
|
||||
or
|
||||
`(lldb) stack -h
|
||||
or
|
||||
`(lldb) stack --help
|
||||
|
||||
can get you started
|
||||
"""
|
||||
|
||||
import lldb
|
||||
import functools
|
||||
import argparse
|
||||
import shlex
|
||||
|
||||
# Dumps the collected frame data
|
||||
def dump(collected):
|
||||
for (frame_name, size_in_kb, live_size_kb, variables) in collected:
|
||||
print(f'{frame_name}, locals: {size_in_kb}kb, fp-sp: {live_size_kb}kb')
|
||||
for (var_name, var_size, variable_obj) in variables:
|
||||
print(f' {var_name}, {var_size} bytes')
|
||||
|
||||
def dbg_stack_pressure(frame, frames_to_show = 5, sort_frames = False, vars_to_show = 5, sort_vars = True):
|
||||
totalKb = 0
|
||||
collect = []
|
||||
for f in frame.thread:
|
||||
frame_name = f.GetFunctionName()
|
||||
variables = [ (v.GetName(), v.GetByteSize(), v) for v in f.get_locals() ]
|
||||
if sort_vars:
|
||||
variables.sort(key = lambda x: x[1], reverse = True)
|
||||
size_in_kb = functools.reduce(lambda x,y : x + y[1], variables, 0) / 1024
|
||||
|
||||
fp = f.GetFP()
|
||||
sp = f.GetSP()
|
||||
live_size_kb = round((fp - sp) / 1024, 2)
|
||||
|
||||
size_in_kb = round(size_in_kb, 2)
|
||||
totalKb += size_in_kb
|
||||
collect.append((frame_name, size_in_kb, live_size_kb, variables[:vars_to_show]))
|
||||
if sort_frames:
|
||||
collect.sort(key = lambda x: x[1], reverse = True)
|
||||
|
||||
print("******************** Report Stack Usage ********************")
|
||||
totalMb = round(totalKb / 1024, 2)
|
||||
print(f'{len(frame.thread)} stack frames used {totalMb}MB')
|
||||
dump(collect[:frames_to_show])
|
||||
|
||||
def stack(debugger, command, result, internal_dict):
|
||||
"""
|
||||
usage: [-h] [-f FRAMES] [-fd] [-v VARS] [-vd]
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
-f FRAMES, --frames FRAMES
|
||||
How many stack frames to display
|
||||
-fd, --sort_frames Sort frames
|
||||
-v VARS, --vars VARS How many variables per frame to display
|
||||
-vd, --sort_vars Sort frames
|
||||
"""
|
||||
|
||||
frame = debugger.GetSelectedTarget().GetProcess().GetSelectedThread().GetSelectedFrame()
|
||||
args = shlex.split(command)
|
||||
argparser = argparse.ArgumentParser(allow_abbrev = True)
|
||||
argparser.add_argument("-f", "--frames", required=False, help="How many stack frames to display", default=5, type=int)
|
||||
argparser.add_argument("-fd", "--sort_frames", required=False, help="Sort frames in descending order of stack usage", action="store_true", default=False)
|
||||
argparser.add_argument("-v", "--vars", required=False, help="How many variables per frame to display", default=5, type=int)
|
||||
argparser.add_argument("-vd", "--sort_vars", required=False, help="Sort locals in descending order of stack usage ", action="store_true", default=False)
|
||||
|
||||
args = argparser.parse_args(args)
|
||||
dbg_stack_pressure(frame, frames_to_show=args.frames, sort_frames=args.sort_frames, vars_to_show=args.vars, sort_vars=args.sort_vars)
|
||||
|
||||
# Initialization code to add commands
|
||||
def __lldb_init_module(debugger, internal_dict):
|
||||
debugger.HandleCommand('command script add -f stackdbg.stack stack')
|
||||
print("The 'stack' python command has been installed and is ready for use.")
|
||||
|
Loading…
Reference in New Issue
Block a user