From 8a64cb8b73996bd69c2734c607acd4b7d092358a Mon Sep 17 00:00:00 2001 From: Andy Friesen Date: Fri, 3 May 2024 13:17:51 -0700 Subject: [PATCH] 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 Co-authored-by: Andy Friesen Co-authored-by: Aviral Goel Co-authored-by: Vyacheslav Egorov --- Analysis/include/Luau/Normalize.h | 1 + Analysis/src/ConstraintGenerator.cpp | 10 +- Analysis/src/ConstraintSolver.cpp | 14 +- Analysis/src/Normalize.cpp | 121 ++++-- Analysis/src/TypeChecker2.cpp | 4 +- CLI/Compile.cpp | 1 + CMakeLists.txt | 1 + CodeGen/include/Luau/CodeGen.h | 4 + CodeGen/src/BytecodeAnalysis.cpp | 32 +- CodeGen/src/CodeGen.cpp | 32 ++ CodeGen/src/CodeGenAssembly.cpp | 110 +++++- CodeGen/src/IrTranslation.cpp | 7 +- Compiler/src/Builtins.cpp | 2 +- Compiler/src/Compiler.cpp | 59 ++- Compiler/src/Types.cpp | 551 ++++++++++++++++++++++++++- Compiler/src/Types.h | 20 +- VM/src/ldebug.cpp | 8 + VM/src/lmem.cpp | 14 +- VM/src/lstate.cpp | 1 + VM/src/lstate.h | 3 +- VM/src/ltablib.cpp | 43 ++- tests/IrLowering.test.cpp | 484 ++++++++++++++++++++++- tests/Normalize.test.cpp | 30 +- tests/TypeInfer.classes.test.cpp | 33 ++ tests/TypeInfer.tables.test.cpp | 16 + tests/TypeInfer.test.cpp | 39 +- tests/TypeInfer.typestates.test.cpp | 29 ++ tests/conformance/tables.lua | 4 + tools/faillist.txt | 4 +- tools/stackdbg.py | 94 +++++ 30 files changed, 1656 insertions(+), 115 deletions(-) create mode 100644 tools/stackdbg.py diff --git a/Analysis/include/Luau/Normalize.h b/Analysis/include/Luau/Normalize.h index 35e0c7a1..6d75568e 100644 --- a/Analysis/include/Luau/Normalize.h +++ b/Analysis/include/Luau/Normalize.h @@ -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); diff --git a/Analysis/src/ConstraintGenerator.cpp b/Analysis/src/ConstraintGenerator.cpp index 16e2014a..c559a256 100644 --- a/Analysis/src/ConstraintGenerator.cpp +++ b/Analysis/src/ConstraintGenerator.cpp @@ -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 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}; } diff --git a/Analysis/src/ConstraintSolver.cpp b/Analysis/src/ConstraintSolver.cpp index 6a9dd031..ff56a37d 100644 --- a/Analysis/src/ConstraintSolver.cpp +++ b/Analysis/src/ConstraintSolver.cpp @@ -1619,9 +1619,7 @@ std::pair> 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 instantiatedNextFn = instantiate(builtinTypes, arena, NotNull{&limits}, constraint->scope, nextFn)) { const FunctionType* nextFn = get(*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 diff --git a/Analysis/src/Normalize.cpp b/Analysis/src/Normalize.cpp index 848c8684..a124be66 100644 --- a/Analysis/src/Normalize.cpp +++ b/Analysis/src/Normalize.cpp @@ -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 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 negated; + if (useDeprecated) + { + const NormalizedType* normal = DEPRECATED_normalize(toNegate); + negated = negateNormal(*normal); + } + else + { + std::shared_ptr 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& seenSetTypes, int ignoreSmallerTyvars) { @@ -2541,8 +2576,8 @@ std::optional Normalizer::intersectionOfTables(TypeId here, TypeId there state = tttv->state; TypeLevel level = max(httv->level, tttv->level); - TableType result{state, level}; + std::unique_ptr result = nullptr; bool hereSubThere = true; bool thereSubHere = true; @@ -2563,8 +2598,18 @@ std::optional 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 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{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{state, level}); + + result->props[name] = tprop; hereSubThere = false; } } @@ -2631,18 +2683,24 @@ std::optional 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{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{state, level}); + result->indexer = httv->indexer; thereSubHere = false; } else if (tttv->indexer) { - result.indexer = tttv->indexer; + if (!result.get()) + result = std::make_unique(TableType{state, level}); + result->indexer = tttv->indexer; hereSubThere = false; } @@ -2652,7 +2710,12 @@ std::optional 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 normal = normalize(t); - std::optional 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 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(t)) @@ -3171,11 +3230,9 @@ NormalizationResult Normalizer::intersectNormalWithTy(NormalizedType& here, Type { for (TypeId part : itv->options) { - std::shared_ptr normalPart = normalize(part); - std::optional 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 normalPart = normalize(part); - std::optional 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 negated = negateNormal(*normalPart); - if (!negated) - return NormalizationResult::False; - intersectNormals(here, *negated); + NormalizationResult res = intersectNormalWithNegationTy(part, here, /* useDeprecated */ true); + if (shouldEarlyExit(res)) + return res; } } } diff --git a/Analysis/src/TypeChecker2.cpp b/Analysis/src/TypeChecker2.cpp index a888564e..faa5ffdb 100644 --- a/Analysis/src/TypeChecker2.cpp +++ b/Analysis/src/TypeChecker2.cpp @@ -1280,7 +1280,9 @@ struct TypeChecker2 void visit(AstExprGlobal* expr) { - // TODO! + NotNull scope = stack.back(); + if (!scope->lookup(expr->name)) + reportError(UnknownSymbol{expr->name.value, UnknownSymbol::Binding}, expr->location); } void visit(AstExprVarargs* expr) diff --git a/CLI/Compile.cpp b/CLI/Compile.cpp index 44a6ef77..dd6b14ab 100644 --- a/CLI/Compile.cpp +++ b/CLI/Compile.cpp @@ -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; } diff --git a/CMakeLists.txt b/CMakeLists.txt index 985cda1c..5b7e551e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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") diff --git a/CodeGen/include/Luau/CodeGen.h b/CodeGen/include/Luau/CodeGen.h index 9765035b..9b56034f 100644 --- a/CodeGen/include/Luau/CodeGen.h +++ b/CodeGen/include/Luau/CodeGen.h @@ -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; diff --git a/CodeGen/src/BytecodeAnalysis.cpp b/CodeGen/src/BytecodeAnalysis.cpp index 7c39f5fc..e3ce9166 100644 --- a/CodeGen/src/BytecodeAnalysis.cpp +++ b/CodeGen/src/BytecodeAnalysis.cpp @@ -6,6 +6,9 @@ #include "Luau/IrUtils.h" #include "lobject.h" +#include "lstate.h" + +#include #include @@ -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; diff --git a/CodeGen/src/CodeGen.cpp b/CodeGen/src/CodeGen.cpp index 9ef9980a..a5f6721e 100644 --- a/CodeGen/src/CodeGen.cpp +++ b/CodeGen/src/CodeGen.cpp @@ -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; diff --git a/CodeGen/src/CodeGenAssembly.cpp b/CodeGen/src/CodeGenAssembly.cpp index 96c73ce2..8324b7cc 100644 --- a/CodeGen/src/CodeGenAssembly.cpp +++ b/CodeGen/src/CodeGenAssembly.cpp @@ -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 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); + } } } diff --git a/CodeGen/src/IrTranslation.cpp b/CodeGen/src/IrTranslation.cpp index 20150f9a..84e3b639 100644 --- a/CodeGen/src/IrTranslation.cpp +++ b/CodeGen/src/IrTranslation.cpp @@ -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); diff --git a/Compiler/src/Builtins.cpp b/Compiler/src/Builtins.cpp index 2b09b7e0..c576e3a4 100644 --- a/Compiler/src/Builtins.cpp +++ b/Compiler/src/Builtins.cpp @@ -454,7 +454,7 @@ BuiltinInfo getBuiltinInfo(int bfid) case LBF_BUFFER_WRITEF32: case LBF_BUFFER_WRITEF64: return {3, 0, BuiltinInfo::Flag_NoneSafe}; - }; + } LUAU_UNREACHABLE(); } diff --git a/Compiler/src/Compiler.cpp b/Compiler/src/Compiler.cpp index df096d3a..d5cd78a5 100644 --- a/Compiler/src/Compiler.cpp +++ b/Compiler/src/Compiler.cpp @@ -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() && !node->is(); } + 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 builtins; DenseHashMap functionTypes; DenseHashMap localTypes; + DenseHashMap exprTypes; + + BuiltinTypes builtinTypes; const DenseHashMap* 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) diff --git a/Compiler/src/Types.cpp b/Compiler/src/Types.cpp index d05a7ba2..eaa2d8be 100644 --- a/Compiler/src/Types.cpp +++ b/Compiler/src/Types.cpp @@ -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& generics, const DenseHashMap& typeAliases, - bool resolveAliases, const char* vectorType) +static LuauBytecodeType getType(const AstType* ty, const AstArray& generics, + const DenseHashMap& typeAliases, bool resolveAliases, const char* vectorType) { - if (AstTypeReference* ref = ty->as()) + if (const AstTypeReference* ref = ty->as()) { if (ref->prefix) return LBC_TYPE_ANY; @@ -66,15 +67,15 @@ static LuauBytecodeType getType(AstType* ty, const AstArray& gen // not primitive or alias or generic => host-provided, we assume userdata for now return LBC_TYPE_USERDATA; } - else if (AstTypeTable* table = ty->as()) + else if (const AstTypeTable* table = ty->as()) { return LBC_TYPE_TABLE; } - else if (AstTypeFunction* func = ty->as()) + else if (const AstTypeFunction* func = ty->as()) { return LBC_TYPE_FUNCTION; } - else if (AstTypeUnion* un = ty->as()) + else if (const AstTypeUnion* un = ty->as()) { bool optional = false; LuauBytecodeType type = LBC_TYPE_INVALID; @@ -104,7 +105,7 @@ static LuauBytecodeType getType(AstType* ty, const AstArray& gen return LuauBytecodeType(type | (optional && (type != LBC_TYPE_ANY) ? LBC_TYPE_OPTIONAL_BIT : 0)); } - else if (AstTypeIntersection* inter = ty->as()) + else if (const AstTypeIntersection* inter = ty->as()) { return LBC_TYPE_ANY; } @@ -144,21 +145,44 @@ static std::string getFunctionType(const AstExprFunction* func, const DenseHashM return typeInfo; } +static bool isMatchingGlobal(const DenseHashMap& globals, AstExpr* node, const char* name) +{ + LUAU_ASSERT(FFlag::LuauCompileTempTypeInfo); + + if (AstExprGlobal* expr = node->as()) + return Compile::getGlobalState(globals, expr->name) == Compile::Global::Default && expr->name == name; + + return false; +} + struct TypeMapVisitor : AstVisitor { DenseHashMap& functionTypes; DenseHashMap& localTypes; + DenseHashMap& exprTypes; const char* vectorType; + const BuiltinTypes& builtinTypes; + const DenseHashMap& builtinCalls; + const DenseHashMap& globals; DenseHashMap typeAliases; std::vector> typeAliasStack; + DenseHashMap resolvedLocals; + DenseHashMap resolvedExprs; - TypeMapVisitor( - DenseHashMap& functionTypes, DenseHashMap& localTypes, const char* vectorType) + TypeMapVisitor(DenseHashMap& functionTypes, DenseHashMap& localTypes, + DenseHashMap& exprTypes, const char* vectorType, const BuiltinTypes& builtinTypes, + const DenseHashMap& builtinCalls, const DenseHashMap& 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()) + { + 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()) + 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(); 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()) + { + 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& functionTypes, DenseHashMap& localTypes, AstNode* root, - const char* vectorType) +void buildTypeMap(DenseHashMap& functionTypes, DenseHashMap& localTypes, + DenseHashMap& exprTypes, AstNode* root, const char* vectorType, const BuiltinTypes& builtinTypes, + const DenseHashMap& builtinCalls, const DenseHashMap& globals) { - TypeMapVisitor visitor(functionTypes, localTypes, vectorType); + TypeMapVisitor visitor(functionTypes, localTypes, exprTypes, vectorType, builtinTypes, builtinCalls, globals); root->visit(&visitor); } diff --git a/Compiler/src/Types.h b/Compiler/src/Types.h index de11fde9..b1aff8a2 100644 --- a/Compiler/src/Types.h +++ b/Compiler/src/Types.h @@ -4,13 +4,29 @@ #include "Luau/Ast.h" #include "Luau/Bytecode.h" #include "Luau/DenseHash.h" +#include "ValueTracking.h" #include namespace Luau { -void buildTypeMap(DenseHashMap& functionTypes, DenseHashMap& 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& functionTypes, DenseHashMap& localTypes, + DenseHashMap& exprTypes, AstNode* root, const char* vectorType, const BuiltinTypes& builtinTypes, + const DenseHashMap& builtinCalls, const DenseHashMap& globals); } // namespace Luau diff --git a/VM/src/ldebug.cpp b/VM/src/ldebug.cpp index 7122b035..0e792366 100644 --- a/VM/src/ldebug.cpp +++ b/VM/src/ldebug.cpp @@ -12,6 +12,8 @@ #include #include +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); } diff --git a/VM/src/lmem.cpp b/VM/src/lmem.cpp index 3de18cf9..f6cc07c9 100644 --- a/VM/src/lmem.cpp +++ b/VM/src/lmem.cpp @@ -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) diff --git a/VM/src/lstate.cpp b/VM/src/lstate.cpp index 858f61a3..dbc1dd10 100644 --- a/VM/src/lstate.cpp +++ b/VM/src/lstate.cpp @@ -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++) diff --git a/VM/src/lstate.h b/VM/src/lstate.h index 97546511..21d7071c 100644 --- a/VM/src/lstate.h +++ b/VM/src/lstate.h @@ -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 diff --git a/VM/src/ltablib.cpp b/VM/src/ltablib.cpp index a57d6cf7..545c1d2d 100644 --- a/VM/src/ltablib.cpp +++ b/VM/src/ltablib.cpp @@ -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; } diff --git a/tests/IrLowering.test.cpp b/tests/IrLowering.test.cpp index 479329b4..0c0d6378 100644 --- a/tests/IrLowering.test.cpp +++ b/tests/IrLowering.test.cpp @@ -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(); diff --git a/tests/Normalize.test.cpp b/tests/Normalize.test.cpp index 1a9ffd65..36398289 100644 --- a/tests/Normalize.test.cpp +++ b/tests/Normalize.test.cpp @@ -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()->props = {{"foo", Property::readonly(t2)}}; + asMutable(t2)->ty.get_if()->props = {{"foo", Property::readonly(t1)}}; + + std::shared_ptr 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()->props = {{"foo", Property::readonly(t3)}}; + asMutable(t2)->ty.get_if()->props = {{"foo", Property::readonly(t1)}}; + + std::shared_ptr normalized = normalizer.normalize(t3); + CHECK(normalized); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.classes.test.cpp b/tests/TypeInfer.classes.test.cpp index 6112bd02..8f8aef84 100644 --- a/tests/TypeInfer.classes.test.cpp +++ b/tests/TypeInfer.classes.test.cpp @@ -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"( diff --git a/tests/TypeInfer.tables.test.cpp b/tests/TypeInfer.tables.test.cpp index 4cc07fba..4446bbc9 100644 --- a/tests/TypeInfer.tables.test.cpp +++ b/tests/TypeInfer.tables.test.cpp @@ -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(); diff --git a/tests/TypeInfer.test.cpp b/tests/TypeInfer.test.cpp index 140f462a..9ea9539f 100644 --- a/tests/TypeInfer.test.cpp +++ b/tests/TypeInfer.test.cpp @@ -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(result.errors[0]); + REQUIRE(ccnf); + + CHECK("unknown" == toString(ccnf->ty)); + } + else + { + LUAU_REQUIRE_NO_ERRORS(result); + } } TEST_SUITE_END(); diff --git a/tests/TypeInfer.typestates.test.cpp b/tests/TypeInfer.typestates.test.cpp index dbb9815d..3116022b 100644 --- a/tests/TypeInfer.typestates.test.cpp +++ b/tests/TypeInfer.typestates.test.cpp @@ -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(result.errors[0])); +} TEST_SUITE_END(); diff --git a/tests/conformance/tables.lua b/tests/conformance/tables.lua index 75163fd1..3f1efd8e 100644 --- a/tests/conformance/tables.lua +++ b/tests/conformance/tables.lua @@ -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 diff --git a/tools/faillist.txt b/tools/faillist.txt index 469e3a84..db3eeba5 100644 --- a/tools/faillist.txt +++ b/tools/faillist.txt @@ -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 diff --git a/tools/stackdbg.py b/tools/stackdbg.py new file mode 100644 index 00000000..de656c60 --- /dev/null +++ b/tools/stackdbg.py @@ -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.") +