diff --git a/Analysis/include/Luau/Scope.h b/Analysis/include/Luau/Scope.h index 5f1630d5b..0e6eff56d 100644 --- a/Analysis/include/Luau/Scope.h +++ b/Analysis/include/Luau/Scope.h @@ -102,4 +102,12 @@ bool subsumesStrict(Scope* left, Scope* right); // outermost-possible scope. bool subsumes(Scope* left, Scope* right); +inline Scope* max(Scope* left, Scope* right) +{ + if (subsumes(left, right)) + return right; + else + return left; +} + } // namespace Luau diff --git a/Analysis/src/BuiltinDefinitions.cpp b/Analysis/src/BuiltinDefinitions.cpp index f9ce87e0e..a9c519fef 100644 --- a/Analysis/src/BuiltinDefinitions.cpp +++ b/Analysis/src/BuiltinDefinitions.cpp @@ -24,7 +24,6 @@ */ LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution); -LUAU_FASTFLAGVARIABLE(LuauMakeStringMethodsChecked, false); namespace Luau { @@ -773,153 +772,87 @@ TypeId makeStringMetatable(NotNull builtinTypes) const TypePackId numberVariadicList = arena->addTypePack(TypePackVar{VariadicTypePack{numberType}}); - if (FFlag::LuauMakeStringMethodsChecked) - { - FunctionType formatFTV{arena->addTypePack(TypePack{{stringType}, variadicTailPack}), oneStringPack}; - formatFTV.magicFunction = &magicFunctionFormat; - formatFTV.isCheckedFunction = true; - const TypeId formatFn = arena->addType(formatFTV); - attachDcrMagicFunction(formatFn, dcrMagicFunctionFormat); - - - const TypeId stringToStringType = makeFunction(*arena, std::nullopt, {}, {}, {stringType}, {}, {stringType}, /* checked */ true); - - const TypeId replArgType = arena->addType( - UnionType{{stringType, arena->addType(TableType({}, TableIndexer(stringType, stringType), TypeLevel{}, TableState::Generic)), - makeFunction(*arena, std::nullopt, {}, {}, {stringType}, {}, {stringType}, /* checked */ false)}}); - const TypeId gsubFunc = - makeFunction(*arena, stringType, {}, {}, {stringType, replArgType, optionalNumber}, {}, {stringType, numberType}, /* checked */ false); - const TypeId gmatchFunc = makeFunction( - *arena, stringType, {}, {}, {stringType}, {}, {arena->addType(FunctionType{emptyPack, stringVariadicList})}, /* checked */ true); - attachMagicFunction(gmatchFunc, magicFunctionGmatch); - attachDcrMagicFunction(gmatchFunc, dcrMagicFunctionGmatch); - - FunctionType matchFuncTy{ - arena->addTypePack({stringType, stringType, optionalNumber}), arena->addTypePack(TypePackVar{VariadicTypePack{stringType}})}; - matchFuncTy.isCheckedFunction = true; - const TypeId matchFunc = arena->addType(matchFuncTy); - attachMagicFunction(matchFunc, magicFunctionMatch); - attachDcrMagicFunction(matchFunc, dcrMagicFunctionMatch); - - FunctionType findFuncTy{arena->addTypePack({stringType, stringType, optionalNumber, optionalBoolean}), - arena->addTypePack(TypePack{{optionalNumber, optionalNumber}, stringVariadicList})}; - findFuncTy.isCheckedFunction = true; - const TypeId findFunc = arena->addType(findFuncTy); - attachMagicFunction(findFunc, magicFunctionFind); - attachDcrMagicFunction(findFunc, dcrMagicFunctionFind); - - // string.byte : string -> number? -> number? -> ...number - FunctionType stringDotByte{arena->addTypePack({stringType, optionalNumber, optionalNumber}), numberVariadicList}; - stringDotByte.isCheckedFunction = true; - - // string.char : .... number -> string - FunctionType stringDotChar{numberVariadicList, arena->addTypePack({stringType})}; - stringDotChar.isCheckedFunction = true; - - // string.unpack : string -> string -> number? -> ...any - FunctionType stringDotUnpack{ - arena->addTypePack(TypePack{{stringType, stringType, optionalNumber}}), - variadicTailPack, - }; - stringDotUnpack.isCheckedFunction = true; - - TableType::Props stringLib = { - {"byte", {arena->addType(stringDotByte)}}, - {"char", {arena->addType(stringDotChar)}}, - {"find", {findFunc}}, - {"format", {formatFn}}, // FIXME - {"gmatch", {gmatchFunc}}, - {"gsub", {gsubFunc}}, - {"len", {makeFunction(*arena, stringType, {}, {}, {}, {}, {numberType}, /* checked */ true)}}, - {"lower", {stringToStringType}}, - {"match", {matchFunc}}, - {"rep", {makeFunction(*arena, stringType, {}, {}, {numberType}, {}, {stringType}, /* checked */ true)}}, - {"reverse", {stringToStringType}}, - {"sub", {makeFunction(*arena, stringType, {}, {}, {numberType, optionalNumber}, {}, {stringType}, /* checked */ true)}}, - {"upper", {stringToStringType}}, - {"split", {makeFunction(*arena, stringType, {}, {}, {optionalString}, {}, - {arena->addType(TableType{{}, TableIndexer{numberType, stringType}, TypeLevel{}, TableState::Sealed})}, - /* checked */ true)}}, - {"pack", {arena->addType(FunctionType{ - arena->addTypePack(TypePack{{stringType}, variadicTailPack}), - oneStringPack, - })}}, - {"packsize", {makeFunction(*arena, stringType, {}, {}, {}, {}, {numberType}, /* checked */ true)}}, - {"unpack", {arena->addType(stringDotUnpack)}}, - }; - assignPropDocumentationSymbols(stringLib, "@luau/global/string"); - - TypeId tableType = arena->addType(TableType{std::move(stringLib), std::nullopt, TypeLevel{}, TableState::Sealed}); - - if (TableType* ttv = getMutable(tableType)) - ttv->name = "typeof(string)"; - - return arena->addType(TableType{{{{"__index", {tableType}}}}, std::nullopt, TypeLevel{}, TableState::Sealed}); - } - else - { - FunctionType formatFTV{arena->addTypePack(TypePack{{stringType}, variadicTailPack}), oneStringPack}; - formatFTV.magicFunction = &magicFunctionFormat; - const TypeId formatFn = arena->addType(formatFTV); - attachDcrMagicFunction(formatFn, dcrMagicFunctionFormat); - - const TypeId stringToStringType = makeFunction(*arena, std::nullopt, {}, {}, {stringType}, {}, {stringType}); - - const TypeId replArgType = arena->addType( - UnionType{{stringType, arena->addType(TableType({}, TableIndexer(stringType, stringType), TypeLevel{}, TableState::Generic)), - makeFunction(*arena, std::nullopt, {}, {}, {stringType}, {}, {stringType})}}); - const TypeId gsubFunc = makeFunction(*arena, stringType, {}, {}, {stringType, replArgType, optionalNumber}, {}, {stringType, numberType}); - const TypeId gmatchFunc = - makeFunction(*arena, stringType, {}, {}, {stringType}, {}, {arena->addType(FunctionType{emptyPack, stringVariadicList})}); - attachMagicFunction(gmatchFunc, magicFunctionGmatch); - attachDcrMagicFunction(gmatchFunc, dcrMagicFunctionGmatch); - - const TypeId matchFunc = arena->addType(FunctionType{ - arena->addTypePack({stringType, stringType, optionalNumber}), arena->addTypePack(TypePackVar{VariadicTypePack{stringType}})}); - attachMagicFunction(matchFunc, magicFunctionMatch); - attachDcrMagicFunction(matchFunc, dcrMagicFunctionMatch); - - const TypeId findFunc = arena->addType(FunctionType{arena->addTypePack({stringType, stringType, optionalNumber, optionalBoolean}), - arena->addTypePack(TypePack{{optionalNumber, optionalNumber}, stringVariadicList})}); - attachMagicFunction(findFunc, magicFunctionFind); - attachDcrMagicFunction(findFunc, dcrMagicFunctionFind); - - TableType::Props stringLib = { - {"byte", {arena->addType(FunctionType{arena->addTypePack({stringType, optionalNumber, optionalNumber}), numberVariadicList})}}, - {"char", {arena->addType(FunctionType{numberVariadicList, arena->addTypePack({stringType})})}}, - {"find", {findFunc}}, - {"format", {formatFn}}, // FIXME - {"gmatch", {gmatchFunc}}, - {"gsub", {gsubFunc}}, - {"len", {makeFunction(*arena, stringType, {}, {}, {}, {}, {numberType})}}, - {"lower", {stringToStringType}}, - {"match", {matchFunc}}, - {"rep", {makeFunction(*arena, stringType, {}, {}, {numberType}, {}, {stringType})}}, - {"reverse", {stringToStringType}}, - {"sub", {makeFunction(*arena, stringType, {}, {}, {numberType, optionalNumber}, {}, {stringType})}}, - {"upper", {stringToStringType}}, - {"split", {makeFunction(*arena, stringType, {}, {}, {optionalString}, {}, - {arena->addType(TableType{{}, TableIndexer{numberType, stringType}, TypeLevel{}, TableState::Sealed})})}}, - {"pack", {arena->addType(FunctionType{ - arena->addTypePack(TypePack{{stringType}, variadicTailPack}), - oneStringPack, - })}}, - {"packsize", {makeFunction(*arena, stringType, {}, {}, {}, {}, {numberType})}}, - {"unpack", {arena->addType(FunctionType{ - arena->addTypePack(TypePack{{stringType, stringType, optionalNumber}}), - variadicTailPack, - })}}, - }; - - assignPropDocumentationSymbols(stringLib, "@luau/global/string"); - - TypeId tableType = arena->addType(TableType{std::move(stringLib), std::nullopt, TypeLevel{}, TableState::Sealed}); - - if (TableType* ttv = getMutable(tableType)) - ttv->name = "typeof(string)"; - - return arena->addType(TableType{{{{"__index", {tableType}}}}, std::nullopt, TypeLevel{}, TableState::Sealed}); - } + FunctionType formatFTV{arena->addTypePack(TypePack{{stringType}, variadicTailPack}), oneStringPack}; + formatFTV.magicFunction = &magicFunctionFormat; + formatFTV.isCheckedFunction = true; + const TypeId formatFn = arena->addType(formatFTV); + attachDcrMagicFunction(formatFn, dcrMagicFunctionFormat); + + + const TypeId stringToStringType = makeFunction(*arena, std::nullopt, {}, {}, {stringType}, {}, {stringType}, /* checked */ true); + + const TypeId replArgType = + arena->addType(UnionType{{stringType, arena->addType(TableType({}, TableIndexer(stringType, stringType), TypeLevel{}, TableState::Generic)), + makeFunction(*arena, std::nullopt, {}, {}, {stringType}, {}, {stringType}, /* checked */ false)}}); + const TypeId gsubFunc = + makeFunction(*arena, stringType, {}, {}, {stringType, replArgType, optionalNumber}, {}, {stringType, numberType}, /* checked */ false); + const TypeId gmatchFunc = + makeFunction(*arena, stringType, {}, {}, {stringType}, {}, {arena->addType(FunctionType{emptyPack, stringVariadicList})}, /* checked */ true); + attachMagicFunction(gmatchFunc, magicFunctionGmatch); + attachDcrMagicFunction(gmatchFunc, dcrMagicFunctionGmatch); + + FunctionType matchFuncTy{ + arena->addTypePack({stringType, stringType, optionalNumber}), arena->addTypePack(TypePackVar{VariadicTypePack{stringType}})}; + matchFuncTy.isCheckedFunction = true; + const TypeId matchFunc = arena->addType(matchFuncTy); + attachMagicFunction(matchFunc, magicFunctionMatch); + attachDcrMagicFunction(matchFunc, dcrMagicFunctionMatch); + + FunctionType findFuncTy{arena->addTypePack({stringType, stringType, optionalNumber, optionalBoolean}), + arena->addTypePack(TypePack{{optionalNumber, optionalNumber}, stringVariadicList})}; + findFuncTy.isCheckedFunction = true; + const TypeId findFunc = arena->addType(findFuncTy); + attachMagicFunction(findFunc, magicFunctionFind); + attachDcrMagicFunction(findFunc, dcrMagicFunctionFind); + + // string.byte : string -> number? -> number? -> ...number + FunctionType stringDotByte{arena->addTypePack({stringType, optionalNumber, optionalNumber}), numberVariadicList}; + stringDotByte.isCheckedFunction = true; + + // string.char : .... number -> string + FunctionType stringDotChar{numberVariadicList, arena->addTypePack({stringType})}; + stringDotChar.isCheckedFunction = true; + + // string.unpack : string -> string -> number? -> ...any + FunctionType stringDotUnpack{ + arena->addTypePack(TypePack{{stringType, stringType, optionalNumber}}), + variadicTailPack, + }; + stringDotUnpack.isCheckedFunction = true; + + TableType::Props stringLib = { + {"byte", {arena->addType(stringDotByte)}}, + {"char", {arena->addType(stringDotChar)}}, + {"find", {findFunc}}, + {"format", {formatFn}}, // FIXME + {"gmatch", {gmatchFunc}}, + {"gsub", {gsubFunc}}, + {"len", {makeFunction(*arena, stringType, {}, {}, {}, {}, {numberType}, /* checked */ true)}}, + {"lower", {stringToStringType}}, + {"match", {matchFunc}}, + {"rep", {makeFunction(*arena, stringType, {}, {}, {numberType}, {}, {stringType}, /* checked */ true)}}, + {"reverse", {stringToStringType}}, + {"sub", {makeFunction(*arena, stringType, {}, {}, {numberType, optionalNumber}, {}, {stringType}, /* checked */ true)}}, + {"upper", {stringToStringType}}, + {"split", {makeFunction(*arena, stringType, {}, {}, {optionalString}, {}, + {arena->addType(TableType{{}, TableIndexer{numberType, stringType}, TypeLevel{}, TableState::Sealed})}, + /* checked */ true)}}, + {"pack", {arena->addType(FunctionType{ + arena->addTypePack(TypePack{{stringType}, variadicTailPack}), + oneStringPack, + })}}, + {"packsize", {makeFunction(*arena, stringType, {}, {}, {}, {}, {numberType}, /* checked */ true)}}, + {"unpack", {arena->addType(stringDotUnpack)}}, + }; + + assignPropDocumentationSymbols(stringLib, "@luau/global/string"); + + TypeId tableType = arena->addType(TableType{std::move(stringLib), std::nullopt, TypeLevel{}, TableState::Sealed}); + + if (TableType* ttv = getMutable(tableType)) + ttv->name = "typeof(string)"; + + return arena->addType(TableType{{{{"__index", {tableType}}}}, std::nullopt, TypeLevel{}, TableState::Sealed}); } static std::optional> magicFunctionSelect( diff --git a/Analysis/src/Error.cpp b/Analysis/src/Error.cpp index 98b15b77b..2087e3d32 100644 --- a/Analysis/src/Error.cpp +++ b/Analysis/src/Error.cpp @@ -7,11 +7,13 @@ #include "Luau/NotNull.h" #include "Luau/StringUtils.h" #include "Luau/ToString.h" +#include "Luau/TypeFamily.h" #include #include #include #include +#include LUAU_FASTINTVARIABLE(LuauIndentTypeMismatchMaxTypeLength, 10) @@ -61,6 +63,23 @@ static std::string wrongNumberOfArgsString( namespace Luau { +// this list of binary operator type families is used for better stringification of type families errors +static const std::unordered_map kBinaryOps{ + {"add", "+"}, {"sub", "-"}, {"mul", "*"}, {"div", "/"}, {"idiv", "//"}, {"pow", "^"}, {"mod", "%"}, {"concat", ".."}, {"and", "and"}, + {"or", "or"}, {"lt", "< or >="}, {"le", "<= or >"}, {"eq", "== or ~="} +}; + +// this list of unary operator type families is used for better stringification of type families errors +static const std::unordered_map kUnaryOps{ + {"unm", "-"}, {"len", "#"}, {"not", "not"} +}; + +// this list of type families will receive a special error indicating that the user should file a bug on the GitHub repository +// putting a type family in this list indicates that it is expected to _always_ reduce +static const std::unordered_set kUnreachableTypeFamilies{ + "refine", "singleton", "union", "intersect" +}; + struct ErrorConverter { FileResolver* fileResolver = nullptr; @@ -565,6 +584,96 @@ struct ErrorConverter std::string operator()(const UninhabitedTypeFamily& e) const { + auto tfit = get(e.ty); + LUAU_ASSERT(tfit); // Luau analysis has actually done something wrong if this type is not a type family. + if (!tfit) + return "Unexpected type " + Luau::toString(e.ty) + " flagged as an uninhabited type family."; + + // unary operators + if (auto unaryString = kUnaryOps.find(tfit->family->name); unaryString != kUnaryOps.end()) + { + std::string result = "Operator '" + std::string(unaryString->second) + "' could not be applied to "; + + if (tfit->typeArguments.size() == 1 && tfit->packArguments.empty()) + { + result += "operand of type " + Luau::toString(tfit->typeArguments[0]); + + if (tfit->family->name != "not") + result += "; there is no corresponding overload for __" + tfit->family->name; + } + else + { + // if it's not the expected case, we ought to add a specialization later, but this is a sane default. + result += "operands of types "; + + bool isFirst = true; + for (auto arg : tfit->typeArguments) + { + if (!isFirst) + result += ", "; + + result += Luau::toString(arg); + isFirst = false; + } + + for (auto packArg : tfit->packArguments) + result += ", " + Luau::toString(packArg); + } + + return result; + } + + // binary operators + if (auto binaryString = kBinaryOps.find(tfit->family->name); binaryString != kBinaryOps.end()) + { + std::string result = "Operator '" + std::string(binaryString->second) + "' could not be applied to operands of types "; + + if (tfit->typeArguments.size() == 2 && tfit->packArguments.empty()) + { + // this is the expected case. + result += Luau::toString(tfit->typeArguments[0]) + " and " + Luau::toString(tfit->typeArguments[1]); + } + else + { + // if it's not the expected case, we ought to add a specialization later, but this is a sane default. + + bool isFirst = true; + for (auto arg : tfit->typeArguments) + { + if (!isFirst) + result += ", "; + + result += Luau::toString(arg); + isFirst = false; + } + + for (auto packArg : tfit->packArguments) + result += ", " + Luau::toString(packArg); + } + + result += "; there is no corresponding overload for __" + tfit->family->name; + + return result; + } + + // miscellaneous + + if ("keyof" == tfit->family->name || "rawkeyof" == tfit->family->name) + { + if (tfit->typeArguments.size() == 1 && tfit->packArguments.empty()) + return "Type '" + toString(tfit->typeArguments[0]) + "' does not have keys, so '" + Luau::toString(e.ty) + "' is invalid"; + else + return "Type family instance " + Luau::toString(e.ty) + " is ill-formed, and thus invalid"; + } + + if (kUnreachableTypeFamilies.count(tfit->family->name)) + { + return "Type family instance " + Luau::toString(e.ty) + " is uninhabited\n" + + "This is likely to be a bug, please report it at https://github.com/luau-lang/luau/issues"; + } + + // Everything should be specialized above to report a more descriptive error that hopefully does not mention "type families" explicitly. + // If we produce this message, it's an indication that we've missed a specialization and it should be fixed! return "Type family instance " + Luau::toString(e.ty) + " is uninhabited"; } diff --git a/Analysis/src/Normalize.cpp b/Analysis/src/Normalize.cpp index 3c63a7fdd..5b14fd5f2 100644 --- a/Analysis/src/Normalize.cpp +++ b/Analysis/src/Normalize.cpp @@ -2534,6 +2534,7 @@ std::optional Normalizer::intersectionOfTables(TypeId here, TypeId there state = tttv->state; TypeLevel level = max(httv->level, tttv->level); + Scope* scope = max(httv->scope, tttv->scope); std::unique_ptr result = nullptr; bool hereSubThere = true; @@ -2644,7 +2645,7 @@ std::optional Normalizer::intersectionOfTables(TypeId here, TypeId there if (prop.readTy || prop.writeTy) { if (!result.get()) - result = std::make_unique(TableType{state, level}); + result = std::make_unique(TableType{state, level, scope}); result->props[name] = prop; } } @@ -2654,7 +2655,7 @@ std::optional Normalizer::intersectionOfTables(TypeId here, TypeId there if (httv->props.count(name) == 0) { if (!result.get()) - result = std::make_unique(TableType{state, level}); + result = std::make_unique(TableType{state, level, scope}); result->props[name] = tprop; hereSubThere = false; @@ -2667,7 +2668,7 @@ std::optional Normalizer::intersectionOfTables(TypeId here, TypeId there TypeId index = unionType(httv->indexer->indexType, tttv->indexer->indexType); TypeId indexResult = intersectionType(httv->indexer->indexResultType, tttv->indexer->indexResultType); if (!result.get()) - result = std::make_unique(TableType{state, level}); + result = std::make_unique(TableType{state, level, scope}); result->indexer = {index, indexResult}; hereSubThere &= (httv->indexer->indexType == index) && (httv->indexer->indexResultType == indexResult); thereSubHere &= (tttv->indexer->indexType == index) && (tttv->indexer->indexResultType == indexResult); @@ -2675,14 +2676,14 @@ std::optional Normalizer::intersectionOfTables(TypeId here, TypeId there else if (httv->indexer) { if (!result.get()) - result = std::make_unique(TableType{state, level}); + result = std::make_unique(TableType{state, level, scope}); result->indexer = httv->indexer; thereSubHere = false; } else if (tttv->indexer) { if (!result.get()) - result = std::make_unique(TableType{state, level}); + result = std::make_unique(TableType{state, level, scope}); result->indexer = tttv->indexer; hereSubThere = false; } @@ -2697,7 +2698,7 @@ std::optional Normalizer::intersectionOfTables(TypeId here, TypeId there if (result.get()) table = arena->addType(std::move(*result)); else - table = arena->addType(TableType{state, level}); + table = arena->addType(TableType{state, level, scope}); } if (tmtable && hmtable) diff --git a/Analysis/src/Simplify.cpp b/Analysis/src/Simplify.cpp index d29546a22..ca78d54df 100644 --- a/Analysis/src/Simplify.cpp +++ b/Analysis/src/Simplify.cpp @@ -1255,6 +1255,10 @@ TypeId TypeSimplifier::union_(TypeId left, TypeId right) case Relation::Coincident: case Relation::Superset: return left; + case Relation::Subset: + newParts.insert(right); + changed = true; + break; default: newParts.insert(part); newParts.insert(right); diff --git a/Analysis/src/TypeChecker2.cpp b/Analysis/src/TypeChecker2.cpp index d0d371279..37e0f0390 100644 --- a/Analysis/src/TypeChecker2.cpp +++ b/Analysis/src/TypeChecker2.cpp @@ -1242,13 +1242,14 @@ struct TypeChecker2 void visit(AstExprConstantBool* expr) { -#if defined(LUAU_ENABLE_ASSERT) + // booleans use specialized inference logic for singleton types, which can lead to real type errors here. + const TypeId bestType = expr->value ? builtinTypes->trueType : builtinTypes->falseType; const TypeId inferredType = lookupType(expr); const SubtypingResult r = subtyping->isSubtype(bestType, inferredType); - LUAU_ASSERT(r.isSubtype || isErrorSuppressing(expr->location, inferredType)); -#endif + if (!r.isSubtype && !isErrorSuppressing(expr->location, inferredType)) + reportError(TypeMismatch{inferredType, bestType}, expr->location); } void visit(AstExprConstantNumber* expr) @@ -1264,13 +1265,14 @@ struct TypeChecker2 void visit(AstExprConstantString* expr) { -#if defined(LUAU_ENABLE_ASSERT) + // strings use specialized inference logic for singleton types, which can lead to real type errors here. + const TypeId bestType = module->internalTypes.addType(SingletonType{StringSingleton{std::string{expr->value.data, expr->value.size}}}); const TypeId inferredType = lookupType(expr); const SubtypingResult r = subtyping->isSubtype(bestType, inferredType); - LUAU_ASSERT(r.isSubtype || isErrorSuppressing(expr->location, inferredType)); -#endif + if (!r.isSubtype && !isErrorSuppressing(expr->location, inferredType)) + reportError(TypeMismatch{inferredType, bestType}, expr->location); } void visit(AstExprLocal* expr) diff --git a/Analysis/src/TypeFamily.cpp b/Analysis/src/TypeFamily.cpp index e336a5cd8..a8d7d2f7b 100644 --- a/Analysis/src/TypeFamily.cpp +++ b/Analysis/src/TypeFamily.cpp @@ -37,7 +37,7 @@ LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyApplicationCartesianProductLimit, 5'0 // when this value is set to a negative value, guessing will be totally disabled. LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyUseGuesserDepth, -1); -LUAU_FASTFLAG(DebugLuauLogSolver); +LUAU_FASTFLAGVARIABLE(DebugLuauLogTypeFamilies, false); namespace Luau { @@ -184,7 +184,7 @@ struct FamilyReducer if (subject->owningArena != ctx.arena.get()) ctx.ice->ice("Attempting to modify a type family instance from another arena", location); - if (FFlag::DebugLuauLogSolver) + if (FFlag::DebugLuauLogTypeFamilies) printf("%s -> %s\n", toString(subject, {true}).c_str(), toString(replacement, {true}).c_str()); asMutable(subject)->ty.template emplace>(replacement); @@ -206,7 +206,7 @@ struct FamilyReducer if (reduction.uninhabited || force) { - if (FFlag::DebugLuauLogSolver) + if (FFlag::DebugLuauLogTypeFamilies) printf("%s is uninhabited\n", toString(subject, {true}).c_str()); if constexpr (std::is_same_v) @@ -216,7 +216,7 @@ struct FamilyReducer } else if (!reduction.uninhabited && !force) { - if (FFlag::DebugLuauLogSolver) + if (FFlag::DebugLuauLogTypeFamilies) printf("%s is irreducible; blocked on %zu types, %zu packs\n", toString(subject, {true}).c_str(), reduction.blockedTypes.size(), reduction.blockedPacks.size()); @@ -243,7 +243,7 @@ struct FamilyReducer if (skip == SkipTestResult::Irreducible) { - if (FFlag::DebugLuauLogSolver) + if (FFlag::DebugLuauLogTypeFamilies) printf("%s is irreducible due to a dependency on %s\n", toString(subject, {true}).c_str(), toString(p, {true}).c_str()); irreducible.insert(subject); @@ -251,7 +251,7 @@ struct FamilyReducer } else if (skip == SkipTestResult::Defer) { - if (FFlag::DebugLuauLogSolver) + if (FFlag::DebugLuauLogTypeFamilies) printf("Deferring %s until %s is solved\n", toString(subject, {true}).c_str(), toString(p, {true}).c_str()); if constexpr (std::is_same_v) @@ -269,7 +269,7 @@ struct FamilyReducer if (skip == SkipTestResult::Irreducible) { - if (FFlag::DebugLuauLogSolver) + if (FFlag::DebugLuauLogTypeFamilies) printf("%s is irreducible due to a dependency on %s\n", toString(subject, {true}).c_str(), toString(p, {true}).c_str()); irreducible.insert(subject); @@ -277,7 +277,7 @@ struct FamilyReducer } else if (skip == SkipTestResult::Defer) { - if (FFlag::DebugLuauLogSolver) + if (FFlag::DebugLuauLogTypeFamilies) printf("Deferring %s until %s is solved\n", toString(subject, {true}).c_str(), toString(p, {true}).c_str()); if constexpr (std::is_same_v) @@ -297,7 +297,7 @@ struct FamilyReducer { if (shouldGuess.contains(subject)) { - if (FFlag::DebugLuauLogSolver) + if (FFlag::DebugLuauLogTypeFamilies) printf("Flagged %s for reduction with guesser.\n", toString(subject, {true}).c_str()); TypeFamilyReductionGuesser guesser{ctx.arena, ctx.builtins, ctx.normalizer}; @@ -305,14 +305,14 @@ struct FamilyReducer if (guessed) { - if (FFlag::DebugLuauLogSolver) + if (FFlag::DebugLuauLogTypeFamilies) printf("Selected %s as the guessed result type.\n", toString(*guessed, {true}).c_str()); replace(subject, *guessed); return true; } - if (FFlag::DebugLuauLogSolver) + if (FFlag::DebugLuauLogTypeFamilies) printf("Failed to produce a guess for the result of %s.\n", toString(subject, {true}).c_str()); } @@ -328,7 +328,7 @@ struct FamilyReducer if (irreducible.contains(subject)) return; - if (FFlag::DebugLuauLogSolver) + if (FFlag::DebugLuauLogTypeFamilies) printf("Trying to reduce %s\n", toString(subject, {true}).c_str()); if (const TypeFamilyInstanceType* tfit = get(subject)) @@ -337,7 +337,7 @@ struct FamilyReducer if (!testParameters(subject, tfit) && testCyclic != SkipTestResult::CyclicTypeFamily) { - if (FFlag::DebugLuauLogSolver) + if (FFlag::DebugLuauLogTypeFamilies) printf("Irreducible due to irreducible/pending and a non-cyclic family\n"); return; @@ -361,7 +361,7 @@ struct FamilyReducer if (irreducible.contains(subject)) return; - if (FFlag::DebugLuauLogSolver) + if (FFlag::DebugLuauLogTypeFamilies) printf("Trying to reduce %s\n", toString(subject, {true}).c_str()); if (const TypeFamilyInstanceTypePack* tfit = get(subject)) diff --git a/CodeGen/include/Luau/CodeGen.h b/CodeGen/include/Luau/CodeGen.h index 19a9b3c94..ac444b7bb 100644 --- a/CodeGen/include/Luau/CodeGen.h +++ b/CodeGen/include/Luau/CodeGen.h @@ -144,8 +144,17 @@ using UniqueSharedCodeGenContext = std::unique_ptr gpr, const std::vector& simd) = 0; - virtual size_t getSize() const = 0; - virtual size_t getFunctionCount() const = 0; + virtual size_t getUnwindInfoSize(size_t blockSize) const = 0; // This will place the unwinding data at the target address and might update values of some fields - virtual void finalize(char* target, size_t offset, void* funcAddress, size_t funcSize) const = 0; + virtual size_t finalize(char* target, size_t offset, void* funcAddress, size_t blockSize) const = 0; }; } // namespace CodeGen diff --git a/CodeGen/include/Luau/UnwindBuilderDwarf2.h b/CodeGen/include/Luau/UnwindBuilderDwarf2.h index 741aaed22..1b634dec3 100644 --- a/CodeGen/include/Luau/UnwindBuilderDwarf2.h +++ b/CodeGen/include/Luau/UnwindBuilderDwarf2.h @@ -33,10 +33,9 @@ class UnwindBuilderDwarf2 : public UnwindBuilder void prologueX64(uint32_t prologueSize, uint32_t stackSize, bool setupFrame, std::initializer_list gpr, const std::vector& simd) override; - size_t getSize() const override; - size_t getFunctionCount() const override; + size_t getUnwindInfoSize(size_t blockSize = 0) const override; - void finalize(char* target, size_t offset, void* funcAddress, size_t funcSize) const override; + size_t finalize(char* target, size_t offset, void* funcAddress, size_t blockSize) const override; private: size_t beginOffset = 0; diff --git a/CodeGen/include/Luau/UnwindBuilderWin.h b/CodeGen/include/Luau/UnwindBuilderWin.h index 3a7e1b5ab..bc43b94a5 100644 --- a/CodeGen/include/Luau/UnwindBuilderWin.h +++ b/CodeGen/include/Luau/UnwindBuilderWin.h @@ -53,10 +53,9 @@ class UnwindBuilderWin : public UnwindBuilder void prologueX64(uint32_t prologueSize, uint32_t stackSize, bool setupFrame, std::initializer_list gpr, const std::vector& simd) override; - size_t getSize() const override; - size_t getFunctionCount() const override; + size_t getUnwindInfoSize(size_t blockSize = 0) const override; - void finalize(char* target, size_t offset, void* funcAddress, size_t funcSize) const override; + size_t finalize(char* target, size_t offset, void* funcAddress, size_t blockSize) const override; private: size_t beginOffset = 0; diff --git a/CodeGen/src/CodeBlockUnwind.cpp b/CodeGen/src/CodeBlockUnwind.cpp index b88760545..cb2d693ac 100644 --- a/CodeGen/src/CodeBlockUnwind.cpp +++ b/CodeGen/src/CodeBlockUnwind.cpp @@ -102,17 +102,17 @@ void* createBlockUnwindInfo(void* context, uint8_t* block, size_t blockSize, siz UnwindBuilder* unwind = (UnwindBuilder*)context; // All unwinding related data is placed together at the start of the block - size_t unwindSize = unwind->getSize(); + size_t unwindSize = unwind->getUnwindInfoSize(blockSize); unwindSize = (unwindSize + (kCodeAlignment - 1)) & ~(kCodeAlignment - 1); // Match code allocator alignment CODEGEN_ASSERT(blockSize >= unwindSize); char* unwindData = (char*)block; - unwind->finalize(unwindData, unwindSize, block, blockSize); + [[maybe_unused]] size_t functionCount = unwind->finalize(unwindData, unwindSize, block, blockSize); #if defined(_WIN32) && defined(CODEGEN_TARGET_X64) #if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM) - if (!RtlAddFunctionTable((RUNTIME_FUNCTION*)block, uint32_t(unwind->getFunctionCount()), uintptr_t(block))) + if (!RtlAddFunctionTable((RUNTIME_FUNCTION*)block, uint32_t(functionCount), uintptr_t(block))) { CODEGEN_ASSERT(!"Failed to allocate function table"); return nullptr; diff --git a/CodeGen/src/CodeGen.cpp b/CodeGen/src/CodeGen.cpp index 5d6f1fb58..694a9f7e9 100644 --- a/CodeGen/src/CodeGen.cpp +++ b/CodeGen/src/CodeGen.cpp @@ -95,10 +95,6 @@ std::string toString(const CodeGenCompilationResult& result) return ""; } -void* gPerfLogContext = nullptr; -PerfLogFn gPerfLogFn = nullptr; - - void onDisable(lua_State* L, Proto* proto) { // do nothing if proto already uses bytecode @@ -196,59 +192,5 @@ bool isSupported() #endif } -void create(lua_State* L, AllocationCallback* allocationCallback, void* allocationCallbackContext) -{ - create_NEW(L, allocationCallback, allocationCallbackContext); -} - -void create(lua_State* L) -{ - create_NEW(L); -} - -void create(lua_State* L, SharedCodeGenContext* codeGenContext) -{ - create_NEW(L, codeGenContext); -} - -[[nodiscard]] bool isNativeExecutionEnabled(lua_State* L) -{ - return isNativeExecutionEnabled_NEW(L); -} - -void setNativeExecutionEnabled(lua_State* L, bool enabled) -{ - setNativeExecutionEnabled_NEW(L, enabled); -} - -CompilationResult compile(lua_State* L, int idx, unsigned int flags, CompilationStats* stats) -{ - Luau::CodeGen::CompilationOptions options{flags}; - - return compile_NEW(L, idx, options, stats); -} - -CompilationResult compile(const ModuleId& moduleId, lua_State* L, int idx, unsigned int flags, CompilationStats* stats) -{ - Luau::CodeGen::CompilationOptions options{flags}; - return compile_NEW(moduleId, L, idx, options, stats); -} - -CompilationResult compile(lua_State* L, int idx, const CompilationOptions& options, CompilationStats* stats) -{ - return compile_NEW(L, idx, options, stats); -} - -CompilationResult compile(const ModuleId& moduleId, lua_State* L, int idx, const CompilationOptions& options, CompilationStats* stats) -{ - return compile_NEW(moduleId, L, idx, options, stats); -} - -void setPerfLog(void* context, PerfLogFn logFn) -{ - gPerfLogContext = context; - gPerfLogFn = logFn; -} - } // namespace CodeGen } // namespace Luau diff --git a/CodeGen/src/CodeGenA64.cpp b/CodeGen/src/CodeGenA64.cpp index a18278c95..05ac90137 100644 --- a/CodeGen/src/CodeGenA64.cpp +++ b/CodeGen/src/CodeGenA64.cpp @@ -253,7 +253,7 @@ static EntryLocations buildEntryFunction(AssemblyBuilderA64& build, UnwindBuilde // Our entry function is special, it spans the whole remaining code area unwind.startFunction(); unwind.prologueA64(prologueSize, kStackSize, {x29, x30, x19, x20, x21, x22, x23, x24, x25}); - unwind.finishFunction(build.getLabelOffset(locations.start), kFullBlockFuncton); + unwind.finishFunction(build.getLabelOffset(locations.start), kFullBlockFunction); return locations; } diff --git a/CodeGen/src/CodeGenContext.cpp b/CodeGen/src/CodeGenContext.cpp index cdffb1236..a94388f6f 100644 --- a/CodeGen/src/CodeGenContext.cpp +++ b/CodeGen/src/CodeGenContext.cpp @@ -25,11 +25,17 @@ namespace CodeGen static const Instruction kCodeEntryInsn = LOP_NATIVECALL; // From CodeGen.cpp -extern void* gPerfLogContext; -extern PerfLogFn gPerfLogFn; +static void* gPerfLogContext = nullptr; +static PerfLogFn gPerfLogFn = nullptr; unsigned int getCpuFeaturesA64(); +void setPerfLog(void* context, PerfLogFn logFn) +{ + gPerfLogContext = context; + gPerfLogFn = logFn; +} + static void logPerfFunction(Proto* p, uintptr_t addr, unsigned size) { CODEGEN_ASSERT(p->source); @@ -365,17 +371,17 @@ static void initializeExecutionCallbacks(lua_State* L, BaseCodeGenContext* codeG ecb->getmemorysize = getMemorySize; } -void create_NEW(lua_State* L) +void create(lua_State* L) { - return create_NEW(L, size_t(FInt::LuauCodeGenBlockSize), size_t(FInt::LuauCodeGenMaxTotalSize), nullptr, nullptr); + return create(L, size_t(FInt::LuauCodeGenBlockSize), size_t(FInt::LuauCodeGenMaxTotalSize), nullptr, nullptr); } -void create_NEW(lua_State* L, AllocationCallback* allocationCallback, void* allocationCallbackContext) +void create(lua_State* L, AllocationCallback* allocationCallback, void* allocationCallbackContext) { - return create_NEW(L, size_t(FInt::LuauCodeGenBlockSize), size_t(FInt::LuauCodeGenMaxTotalSize), allocationCallback, allocationCallbackContext); + return create(L, size_t(FInt::LuauCodeGenBlockSize), size_t(FInt::LuauCodeGenMaxTotalSize), allocationCallback, allocationCallbackContext); } -void create_NEW(lua_State* L, size_t blockSize, size_t maxTotalSize, AllocationCallback* allocationCallback, void* allocationCallbackContext) +void create(lua_State* L, size_t blockSize, size_t maxTotalSize, AllocationCallback* allocationCallback, void* allocationCallbackContext) { std::unique_ptr codeGenContext = std::make_unique(blockSize, maxTotalSize, allocationCallback, allocationCallbackContext); @@ -386,7 +392,7 @@ void create_NEW(lua_State* L, size_t blockSize, size_t maxTotalSize, AllocationC initializeExecutionCallbacks(L, codeGenContext.release()); } -void create_NEW(lua_State* L, SharedCodeGenContext* codeGenContext) +void create(lua_State* L, SharedCodeGenContext* codeGenContext) { initializeExecutionCallbacks(L, codeGenContext); } @@ -575,22 +581,32 @@ template return compilationResult; } -CompilationResult compile_NEW(const ModuleId& moduleId, lua_State* L, int idx, const CompilationOptions& options, CompilationStats* stats) +CompilationResult compile(const ModuleId& moduleId, lua_State* L, int idx, const CompilationOptions& options, CompilationStats* stats) { return compileInternal(moduleId, L, idx, options, stats); } -CompilationResult compile_NEW(lua_State* L, int idx, const CompilationOptions& options, CompilationStats* stats) +CompilationResult compile(lua_State* L, int idx, const CompilationOptions& options, CompilationStats* stats) { return compileInternal({}, L, idx, options, stats); } -[[nodiscard]] bool isNativeExecutionEnabled_NEW(lua_State* L) +CompilationResult compile(lua_State* L, int idx, unsigned int flags, CompilationStats* stats) +{ + return compileInternal({}, L, idx, CompilationOptions{flags}, stats); +} + +CompilationResult compile(const ModuleId& moduleId, lua_State* L, int idx, unsigned int flags, CompilationStats* stats) +{ + return compileInternal(moduleId, L, idx, CompilationOptions{flags}, stats); +} + +[[nodiscard]] bool isNativeExecutionEnabled(lua_State* L) { return getCodeGenContext(L) != nullptr && L->global->ecb.enter == onEnter; } -void setNativeExecutionEnabled_NEW(lua_State* L, bool enabled) +void setNativeExecutionEnabled(lua_State* L, bool enabled) { if (getCodeGenContext(L) != nullptr) L->global->ecb.enter = enabled ? onEnter : onEnterDisabled; diff --git a/CodeGen/src/CodeGenContext.h b/CodeGen/src/CodeGenContext.h index c47121bce..516a7064d 100644 --- a/CodeGen/src/CodeGenContext.h +++ b/CodeGen/src/CodeGenContext.h @@ -88,33 +88,5 @@ class SharedCodeGenContext final : public BaseCodeGenContext SharedCodeAllocator sharedAllocator; }; - -// The following will become the public interface, and can be moved into -// CodeGen.h after the shared allocator work is complete. When the old -// implementation is removed, the _NEW suffix can be dropped from these -// functions. - -// Initializes native code-gen on the provided Luau VM, using a VM-specific -// code-gen context and either the default allocator parameters or custom -// allocator parameters. -void create_NEW(lua_State* L); -void create_NEW(lua_State* L, AllocationCallback* allocationCallback, void* allocationCallbackContext); -void create_NEW(lua_State* L, size_t blockSize, size_t maxTotalSize, AllocationCallback* allocationCallback, void* allocationCallbackContext); - -// Initializes native code-gen on the provided Luau VM, using the provided -// SharedCodeGenContext. Note that after this function is called, the -// SharedCodeGenContext must not be destroyed until after the Luau VM L is -// destroyed via lua_close. -void create_NEW(lua_State* L, SharedCodeGenContext* codeGenContext); - -CompilationResult compile_NEW(lua_State* L, int idx, const CompilationOptions& options, CompilationStats* stats); -CompilationResult compile_NEW(const ModuleId& moduleId, lua_State* L, int idx, const CompilationOptions& options, CompilationStats* stats); - -// Returns true if native execution is currently enabled for this VM -[[nodiscard]] bool isNativeExecutionEnabled_NEW(lua_State* L); - -// Enables or disables native excution for this VM -void setNativeExecutionEnabled_NEW(lua_State* L, bool enabled); - } // namespace CodeGen } // namespace Luau diff --git a/CodeGen/src/CodeGenX64.cpp b/CodeGen/src/CodeGenX64.cpp index 5e450c9a1..7f4a9e0c6 100644 --- a/CodeGen/src/CodeGenX64.cpp +++ b/CodeGen/src/CodeGenX64.cpp @@ -181,7 +181,7 @@ static EntryLocations buildEntryFunction(AssemblyBuilderX64& build, UnwindBuilde build.ret(); // Our entry function is special, it spans the whole remaining code area - unwind.finishFunction(build.getLabelOffset(locations.start), kFullBlockFuncton); + unwind.finishFunction(build.getLabelOffset(locations.start), kFullBlockFunction); return locations; } diff --git a/CodeGen/src/EmitCommonX64.cpp b/CodeGen/src/EmitCommonX64.cpp index c8d1e75a2..50f2208bb 100644 --- a/CodeGen/src/EmitCommonX64.cpp +++ b/CodeGen/src/EmitCommonX64.cpp @@ -14,6 +14,8 @@ #include +LUAU_FASTFLAGVARIABLE(LuauCodegenSplitDoarith, false) + namespace Luau { namespace CodeGen @@ -155,8 +157,45 @@ void callArithHelper(IrRegAllocX64& regs, AssemblyBuilderX64& build, int ra, Ope callWrap.addArgument(SizeX64::qword, luauRegAddress(ra)); callWrap.addArgument(SizeX64::qword, b); callWrap.addArgument(SizeX64::qword, c); - callWrap.addArgument(SizeX64::dword, tm); - callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaV_doarith)]); + + if (FFlag::LuauCodegenSplitDoarith) + { + switch (tm) + { + case TM_ADD: + callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaV_doarithadd)]); + break; + case TM_SUB: + callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaV_doarithsub)]); + break; + case TM_MUL: + callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaV_doarithmul)]); + break; + case TM_DIV: + callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaV_doarithdiv)]); + break; + case TM_IDIV: + callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaV_doarithidiv)]); + break; + case TM_MOD: + callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaV_doarithmod)]); + break; + case TM_POW: + callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaV_doarithpow)]); + break; + case TM_UNM: + callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaV_doarithunm)]); + break; + default: + CODEGEN_ASSERT(!"Invalid doarith helper operation tag"); + break; + } + } + else + { + callWrap.addArgument(SizeX64::dword, tm); + callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaV_doarith)]); + } emitUpdateBase(build); } diff --git a/CodeGen/src/IrLoweringA64.cpp b/CodeGen/src/IrLoweringA64.cpp index f35a15fab..c8cc07f4f 100644 --- a/CodeGen/src/IrLoweringA64.cpp +++ b/CodeGen/src/IrLoweringA64.cpp @@ -12,6 +12,7 @@ #include "lgc.h" LUAU_FASTFLAG(LuauCodegenRemoveDeadStores5) +LUAU_FASTFLAG(LuauCodegenSplitDoarith) namespace Luau { @@ -1242,9 +1243,47 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) else build.add(x3, rBase, uint16_t(vmRegOp(inst.c) * sizeof(TValue))); - build.mov(w4, TMS(intOp(inst.d))); - build.ldr(x5, mem(rNativeContext, offsetof(NativeContext, luaV_doarith))); - build.blr(x5); + if (FFlag::LuauCodegenSplitDoarith) + { + switch (TMS(intOp(inst.d))) + { + case TM_ADD: + build.ldr(x4, mem(rNativeContext, offsetof(NativeContext, luaV_doarithadd))); + break; + case TM_SUB: + build.ldr(x4, mem(rNativeContext, offsetof(NativeContext, luaV_doarithsub))); + break; + case TM_MUL: + build.ldr(x4, mem(rNativeContext, offsetof(NativeContext, luaV_doarithmul))); + break; + case TM_DIV: + build.ldr(x4, mem(rNativeContext, offsetof(NativeContext, luaV_doarithdiv))); + break; + case TM_IDIV: + build.ldr(x4, mem(rNativeContext, offsetof(NativeContext, luaV_doarithidiv))); + break; + case TM_MOD: + build.ldr(x4, mem(rNativeContext, offsetof(NativeContext, luaV_doarithmod))); + break; + case TM_POW: + build.ldr(x4, mem(rNativeContext, offsetof(NativeContext, luaV_doarithpow))); + break; + case TM_UNM: + build.ldr(x4, mem(rNativeContext, offsetof(NativeContext, luaV_doarithunm))); + break; + default: + CODEGEN_ASSERT(!"Invalid doarith helper operation tag"); + break; + } + + build.blr(x4); + } + else + { + build.mov(w4, TMS(intOp(inst.d))); + build.ldr(x5, mem(rNativeContext, offsetof(NativeContext, luaV_doarith))); + build.blr(x5); + } emitUpdateBase(build); break; diff --git a/CodeGen/src/NativeState.cpp b/CodeGen/src/NativeState.cpp index 5f6df4b6a..b3d07491a 100644 --- a/CodeGen/src/NativeState.cpp +++ b/CodeGen/src/NativeState.cpp @@ -43,6 +43,16 @@ void initFunctions(NativeState& data) data.context.luaV_lessequal = luaV_lessequal; data.context.luaV_equalval = luaV_equalval; data.context.luaV_doarith = luaV_doarith; + + data.context.luaV_doarithadd = luaV_doarithimpl; + data.context.luaV_doarithsub = luaV_doarithimpl; + data.context.luaV_doarithmul = luaV_doarithimpl; + data.context.luaV_doarithdiv = luaV_doarithimpl; + data.context.luaV_doarithidiv = luaV_doarithimpl; + data.context.luaV_doarithmod = luaV_doarithimpl; + data.context.luaV_doarithpow = luaV_doarithimpl; + data.context.luaV_doarithunm = luaV_doarithimpl; + data.context.luaV_dolen = luaV_dolen; data.context.luaV_gettable = luaV_gettable; data.context.luaV_settable = luaV_settable; @@ -121,6 +131,16 @@ void initFunctions(NativeContext& context) context.luaV_lessequal = luaV_lessequal; context.luaV_equalval = luaV_equalval; context.luaV_doarith = luaV_doarith; + + context.luaV_doarithadd = luaV_doarithimpl; + context.luaV_doarithsub = luaV_doarithimpl; + context.luaV_doarithmul = luaV_doarithimpl; + context.luaV_doarithdiv = luaV_doarithimpl; + context.luaV_doarithidiv = luaV_doarithimpl; + context.luaV_doarithmod = luaV_doarithimpl; + context.luaV_doarithpow = luaV_doarithimpl; + context.luaV_doarithunm = luaV_doarithimpl; + context.luaV_dolen = luaV_dolen; context.luaV_gettable = luaV_gettable; context.luaV_settable = luaV_settable; diff --git a/CodeGen/src/NativeState.h b/CodeGen/src/NativeState.h index 3e7c85e93..2edfc2701 100644 --- a/CodeGen/src/NativeState.h +++ b/CodeGen/src/NativeState.h @@ -34,6 +34,14 @@ struct NativeContext int (*luaV_lessequal)(lua_State* L, const TValue* l, const TValue* r) = nullptr; int (*luaV_equalval)(lua_State* L, const TValue* t1, const TValue* t2) = nullptr; void (*luaV_doarith)(lua_State* L, StkId ra, const TValue* rb, const TValue* rc, TMS op) = nullptr; + void (*luaV_doarithadd)(lua_State* L, StkId ra, const TValue* rb, const TValue* rc) = nullptr; + void (*luaV_doarithsub)(lua_State* L, StkId ra, const TValue* rb, const TValue* rc) = nullptr; + void (*luaV_doarithmul)(lua_State* L, StkId ra, const TValue* rb, const TValue* rc) = nullptr; + void (*luaV_doarithdiv)(lua_State* L, StkId ra, const TValue* rb, const TValue* rc) = nullptr; + void (*luaV_doarithidiv)(lua_State* L, StkId ra, const TValue* rb, const TValue* rc) = nullptr; + void (*luaV_doarithmod)(lua_State* L, StkId ra, const TValue* rb, const TValue* rc) = nullptr; + void (*luaV_doarithpow)(lua_State* L, StkId ra, const TValue* rb, const TValue* rc) = nullptr; + void (*luaV_doarithunm)(lua_State* L, StkId ra, const TValue* rb, const TValue* rc) = nullptr; void (*luaV_dolen)(lua_State* L, StkId ra, const TValue* rb) = nullptr; void (*luaV_gettable)(lua_State* L, const TValue* t, TValue* key, StkId val) = nullptr; void (*luaV_settable)(lua_State* L, const TValue* t, TValue* key, StkId val) = nullptr; diff --git a/CodeGen/src/UnwindBuilderDwarf2.cpp b/CodeGen/src/UnwindBuilderDwarf2.cpp index b1522e7bd..2f090b52a 100644 --- a/CodeGen/src/UnwindBuilderDwarf2.cpp +++ b/CodeGen/src/UnwindBuilderDwarf2.cpp @@ -202,7 +202,7 @@ void UnwindBuilderDwarf2::finishInfo() // Terminate section pos = writeu32(pos, 0); - CODEGEN_ASSERT(getSize() <= kRawDataLimit); + CODEGEN_ASSERT(getUnwindInfoSize() <= kRawDataLimit); } void UnwindBuilderDwarf2::prologueA64(uint32_t prologueSize, uint32_t stackSize, std::initializer_list regs) @@ -271,19 +271,14 @@ void UnwindBuilderDwarf2::prologueX64(uint32_t prologueSize, uint32_t stackSize, CODEGEN_ASSERT(prologueOffset == prologueSize); } -size_t UnwindBuilderDwarf2::getSize() const +size_t UnwindBuilderDwarf2::getUnwindInfoSize(size_t blockSize) const { return size_t(pos - rawData); } -size_t UnwindBuilderDwarf2::getFunctionCount() const +size_t UnwindBuilderDwarf2::finalize(char* target, size_t offset, void* funcAddress, size_t blockSize) const { - return unwindFunctions.size(); -} - -void UnwindBuilderDwarf2::finalize(char* target, size_t offset, void* funcAddress, size_t funcSize) const -{ - memcpy(target, rawData, getSize()); + memcpy(target, rawData, getUnwindInfoSize()); for (const UnwindFunctionDwarf2& func : unwindFunctions) { @@ -291,11 +286,13 @@ void UnwindBuilderDwarf2::finalize(char* target, size_t offset, void* funcAddres writeu64(fdeEntry + kFdeInitialLocationOffset, uintptr_t(funcAddress) + offset + func.beginOffset); - if (func.endOffset == kFullBlockFuncton) - writeu64(fdeEntry + kFdeAddressRangeOffset, funcSize - offset); + if (func.endOffset == kFullBlockFunction) + writeu64(fdeEntry + kFdeAddressRangeOffset, blockSize - offset); else writeu64(fdeEntry + kFdeAddressRangeOffset, func.endOffset - func.beginOffset); } + + return unwindFunctions.size(); } } // namespace CodeGen diff --git a/CodeGen/src/UnwindBuilderWin.cpp b/CodeGen/src/UnwindBuilderWin.cpp index 498470bda..2bcc03217 100644 --- a/CodeGen/src/UnwindBuilderWin.cpp +++ b/CodeGen/src/UnwindBuilderWin.cpp @@ -194,17 +194,12 @@ void UnwindBuilderWin::prologueX64(uint32_t prologueSize, uint32_t stackSize, bo this->prologSize = prologueSize; } -size_t UnwindBuilderWin::getSize() const +size_t UnwindBuilderWin::getUnwindInfoSize(size_t blockSize) const { return sizeof(UnwindFunctionWin) * unwindFunctions.size() + size_t(rawDataPos - rawData); } -size_t UnwindBuilderWin::getFunctionCount() const -{ - return unwindFunctions.size(); -} - -void UnwindBuilderWin::finalize(char* target, size_t offset, void* funcAddress, size_t funcSize) const +size_t UnwindBuilderWin::finalize(char* target, size_t offset, void* funcAddress, size_t blockSize) const { // Copy adjusted function information for (UnwindFunctionWin func : unwindFunctions) @@ -213,8 +208,8 @@ void UnwindBuilderWin::finalize(char* target, size_t offset, void* funcAddress, func.beginOffset += uint32_t(offset); // Whole block is a part of a 'single function' - if (func.endOffset == kFullBlockFuncton) - func.endOffset = uint32_t(funcSize); + if (func.endOffset == kFullBlockFunction) + func.endOffset = uint32_t(blockSize); else func.endOffset += uint32_t(offset); @@ -226,6 +221,8 @@ void UnwindBuilderWin::finalize(char* target, size_t offset, void* funcAddress, // Copy unwind codes memcpy(target, rawData, size_t(rawDataPos - rawData)); + + return unwindFunctions.size(); } } // namespace CodeGen diff --git a/VM/src/lvm.h b/VM/src/lvm.h index 5ec7bc165..96bc37f3f 100644 --- a/VM/src/lvm.h +++ b/VM/src/lvm.h @@ -16,6 +16,10 @@ LUAI_FUNC int luaV_lessthan(lua_State* L, const TValue* l, const TValue* r); LUAI_FUNC int luaV_lessequal(lua_State* L, const TValue* l, const TValue* r); LUAI_FUNC int luaV_equalval(lua_State* L, const TValue* t1, const TValue* t2); LUAI_FUNC void luaV_doarith(lua_State* L, StkId ra, const TValue* rb, const TValue* rc, TMS op); + +template +void luaV_doarithimpl(lua_State* L, StkId ra, const TValue* rb, const TValue* rc); + LUAI_FUNC void luaV_dolen(lua_State* L, StkId ra, const TValue* rb); LUAI_FUNC const TValue* luaV_tonumber(const TValue* obj, TValue* n); LUAI_FUNC const float* luaV_tovector(const TValue* obj); diff --git a/VM/src/lvmexecute.cpp b/VM/src/lvmexecute.cpp index 74e30c941..4ac21db35 100644 --- a/VM/src/lvmexecute.cpp +++ b/VM/src/lvmexecute.cpp @@ -16,6 +16,8 @@ #include +LUAU_FASTFLAGVARIABLE(LuauVmSplitDoarith, false) + // Disable c99-designator to avoid the warning in CGOTO dispatch table #ifdef __clang__ #if __has_warning("-Wc99-designator") @@ -1487,7 +1489,14 @@ static void luau_execute(lua_State* L) else { // slow-path, may invoke C/Lua via metamethods - VM_PROTECT(luaV_doarith(L, ra, rb, rc, TM_ADD)); + if (FFlag::LuauVmSplitDoarith) + { + VM_PROTECT(luaV_doarithimpl(L, ra, rb, rc)); + } + else + { + VM_PROTECT(luaV_doarith(L, ra, rb, rc, TM_ADD)); + } VM_NEXT(); } } @@ -1533,7 +1542,14 @@ static void luau_execute(lua_State* L) else { // slow-path, may invoke C/Lua via metamethods - VM_PROTECT(luaV_doarith(L, ra, rb, rc, TM_SUB)); + if (FFlag::LuauVmSplitDoarith) + { + VM_PROTECT(luaV_doarithimpl(L, ra, rb, rc)); + } + else + { + VM_PROTECT(luaV_doarith(L, ra, rb, rc, TM_SUB)); + } VM_NEXT(); } } @@ -1594,7 +1610,14 @@ static void luau_execute(lua_State* L) else { // slow-path, may invoke C/Lua via metamethods - VM_PROTECT(luaV_doarith(L, ra, rb, rc, TM_MUL)); + if (FFlag::LuauVmSplitDoarith) + { + VM_PROTECT(luaV_doarithimpl(L, ra, rb, rc)); + } + else + { + VM_PROTECT(luaV_doarith(L, ra, rb, rc, TM_MUL)); + } VM_NEXT(); } } @@ -1655,7 +1678,14 @@ static void luau_execute(lua_State* L) else { // slow-path, may invoke C/Lua via metamethods - VM_PROTECT(luaV_doarith(L, ra, rb, rc, TM_DIV)); + if (FFlag::LuauVmSplitDoarith) + { + VM_PROTECT(luaV_doarithimpl(L, ra, rb, rc)); + } + else + { + VM_PROTECT(luaV_doarith(L, ra, rb, rc, TM_DIV)); + } VM_NEXT(); } } @@ -1703,7 +1733,14 @@ static void luau_execute(lua_State* L) else { // slow-path, may invoke C/Lua via metamethods - VM_PROTECT(luaV_doarith(L, ra, rb, rc, TM_IDIV)); + if (FFlag::LuauVmSplitDoarith) + { + VM_PROTECT(luaV_doarithimpl(L, ra, rb, rc)); + } + else + { + VM_PROTECT(luaV_doarith(L, ra, rb, rc, TM_IDIV)); + } VM_NEXT(); } } @@ -1727,7 +1764,14 @@ static void luau_execute(lua_State* L) else { // slow-path, may invoke C/Lua via metamethods - VM_PROTECT(luaV_doarith(L, ra, rb, rc, TM_MOD)); + if (FFlag::LuauVmSplitDoarith) + { + VM_PROTECT(luaV_doarithimpl(L, ra, rb, rc)); + } + else + { + VM_PROTECT(luaV_doarith(L, ra, rb, rc, TM_MOD)); + } VM_NEXT(); } } @@ -1748,7 +1792,14 @@ static void luau_execute(lua_State* L) else { // slow-path, may invoke C/Lua via metamethods - VM_PROTECT(luaV_doarith(L, ra, rb, rc, TM_POW)); + if (FFlag::LuauVmSplitDoarith) + { + VM_PROTECT(luaV_doarithimpl(L, ra, rb, rc)); + } + else + { + VM_PROTECT(luaV_doarith(L, ra, rb, rc, TM_POW)); + } VM_NEXT(); } } @@ -1769,7 +1820,14 @@ static void luau_execute(lua_State* L) else { // slow-path, may invoke C/Lua via metamethods - VM_PROTECT(luaV_doarith(L, ra, rb, kv, TM_ADD)); + if (FFlag::LuauVmSplitDoarith) + { + VM_PROTECT(luaV_doarithimpl(L, ra, rb, kv)); + } + else + { + VM_PROTECT(luaV_doarith(L, ra, rb, kv, TM_ADD)); + } VM_NEXT(); } } @@ -1790,7 +1848,14 @@ static void luau_execute(lua_State* L) else { // slow-path, may invoke C/Lua via metamethods - VM_PROTECT(luaV_doarith(L, ra, rb, kv, TM_SUB)); + if (FFlag::LuauVmSplitDoarith) + { + VM_PROTECT(luaV_doarithimpl(L, ra, rb, kv)); + } + else + { + VM_PROTECT(luaV_doarith(L, ra, rb, kv, TM_SUB)); + } VM_NEXT(); } } @@ -1835,7 +1900,14 @@ static void luau_execute(lua_State* L) else { // slow-path, may invoke C/Lua via metamethods - VM_PROTECT(luaV_doarith(L, ra, rb, kv, TM_MUL)); + if (FFlag::LuauVmSplitDoarith) + { + VM_PROTECT(luaV_doarithimpl(L, ra, rb, kv)); + } + else + { + VM_PROTECT(luaV_doarith(L, ra, rb, kv, TM_MUL)); + } VM_NEXT(); } } @@ -1881,7 +1953,14 @@ static void luau_execute(lua_State* L) else { // slow-path, may invoke C/Lua via metamethods - VM_PROTECT(luaV_doarith(L, ra, rb, kv, TM_DIV)); + if (FFlag::LuauVmSplitDoarith) + { + VM_PROTECT(luaV_doarithimpl(L, ra, rb, kv)); + } + else + { + VM_PROTECT(luaV_doarith(L, ra, rb, kv, TM_DIV)); + } VM_NEXT(); } } @@ -1928,7 +2007,14 @@ static void luau_execute(lua_State* L) else { // slow-path, may invoke C/Lua via metamethods - VM_PROTECT(luaV_doarith(L, ra, rb, kv, TM_IDIV)); + if (FFlag::LuauVmSplitDoarith) + { + VM_PROTECT(luaV_doarithimpl(L, ra, rb, kv)); + } + else + { + VM_PROTECT(luaV_doarith(L, ra, rb, kv, TM_IDIV)); + } VM_NEXT(); } } @@ -1952,7 +2038,14 @@ static void luau_execute(lua_State* L) else { // slow-path, may invoke C/Lua via metamethods - VM_PROTECT(luaV_doarith(L, ra, rb, kv, TM_MOD)); + if (FFlag::LuauVmSplitDoarith) + { + VM_PROTECT(luaV_doarithimpl(L, ra, rb, kv)); + } + else + { + VM_PROTECT(luaV_doarith(L, ra, rb, kv, TM_MOD)); + } VM_NEXT(); } } @@ -1979,7 +2072,14 @@ static void luau_execute(lua_State* L) else { // slow-path, may invoke C/Lua via metamethods - VM_PROTECT(luaV_doarith(L, ra, rb, kv, TM_POW)); + if (FFlag::LuauVmSplitDoarith) + { + VM_PROTECT(luaV_doarithimpl(L, ra, rb, kv)); + } + else + { + VM_PROTECT(luaV_doarith(L, ra, rb, kv, TM_POW)); + } VM_NEXT(); } } @@ -2092,7 +2192,14 @@ static void luau_execute(lua_State* L) else { // slow-path, may invoke C/Lua via metamethods - VM_PROTECT(luaV_doarith(L, ra, rb, rb, TM_UNM)); + if (FFlag::LuauVmSplitDoarith) + { + VM_PROTECT(luaV_doarithimpl(L, ra, rb, rb)); + } + else + { + VM_PROTECT(luaV_doarith(L, ra, rb, rb, TM_UNM)); + } VM_NEXT(); } } @@ -2711,7 +2818,14 @@ static void luau_execute(lua_State* L) else { // slow-path, may invoke C/Lua via metamethods - VM_PROTECT(luaV_doarith(L, ra, kv, rc, TM_SUB)); + if (FFlag::LuauVmSplitDoarith) + { + VM_PROTECT(luaV_doarithimpl(L, ra, kv, rc)); + } + else + { + VM_PROTECT(luaV_doarith(L, ra, kv, rc, TM_SUB)); + } VM_NEXT(); } } @@ -2739,7 +2853,14 @@ static void luau_execute(lua_State* L) else { // slow-path, may invoke C/Lua via metamethods - VM_PROTECT(luaV_doarith(L, ra, kv, rc, TM_DIV)); + if (FFlag::LuauVmSplitDoarith) + { + VM_PROTECT(luaV_doarithimpl(L, ra, kv, rc)); + } + else + { + VM_PROTECT(luaV_doarith(L, ra, kv, rc, TM_DIV)); + } VM_NEXT(); } } diff --git a/VM/src/lvmutils.cpp b/VM/src/lvmutils.cpp index 4db8bba77..6ee542b08 100644 --- a/VM/src/lvmutils.cpp +++ b/VM/src/lvmutils.cpp @@ -373,6 +373,152 @@ void luaV_concat(lua_State* L, int total, int last) } while (total > 1); // repeat until only 1 result left } +template +void luaV_doarithimpl(lua_State* L, StkId ra, const TValue* rb, const TValue* rc) +{ + TValue tempb, tempc; + const TValue *b, *c; + + // vector operations that we support: + // v+v v-v -v (add/sub/neg) + // v*v s*v v*s (mul) + // v/v s/v v/s (div) + // v//v s//v v//s (floor div) + const float* vb = ttisvector(rb) ? vvalue(rb) : nullptr; + const float* vc = ttisvector(rc) ? vvalue(rc) : nullptr; + + if (vb && vc) + { + switch (op) + { + case TM_ADD: + setvvalue(ra, vb[0] + vc[0], vb[1] + vc[1], vb[2] + vc[2], vb[3] + vc[3]); + return; + case TM_SUB: + setvvalue(ra, vb[0] - vc[0], vb[1] - vc[1], vb[2] - vc[2], vb[3] - vc[3]); + return; + case TM_MUL: + setvvalue(ra, vb[0] * vc[0], vb[1] * vc[1], vb[2] * vc[2], vb[3] * vc[3]); + return; + case TM_DIV: + setvvalue(ra, vb[0] / vc[0], vb[1] / vc[1], vb[2] / vc[2], vb[3] / vc[3]); + return; + case TM_IDIV: + setvvalue(ra, float(luai_numidiv(vb[0], vc[0])), float(luai_numidiv(vb[1], vc[1])), float(luai_numidiv(vb[2], vc[2])), + float(luai_numidiv(vb[3], vc[3]))); + return; + case TM_UNM: + setvvalue(ra, -vb[0], -vb[1], -vb[2], -vb[3]); + return; + default: + break; + } + } + else if (vb) + { + c = ttisnumber(rc) ? rc : luaV_tonumber(rc, &tempc); + + if (c) + { + float nc = cast_to(float, nvalue(c)); + + switch (op) + { + case TM_MUL: + setvvalue(ra, vb[0] * nc, vb[1] * nc, vb[2] * nc, vb[3] * nc); + return; + case TM_DIV: + setvvalue(ra, vb[0] / nc, vb[1] / nc, vb[2] / nc, vb[3] / nc); + return; + case TM_IDIV: + setvvalue(ra, float(luai_numidiv(vb[0], nc)), float(luai_numidiv(vb[1], nc)), float(luai_numidiv(vb[2], nc)), + float(luai_numidiv(vb[3], nc))); + return; + default: + break; + } + } + } + else if (vc) + { + b = ttisnumber(rb) ? rb : luaV_tonumber(rb, &tempb); + + if (b) + { + float nb = cast_to(float, nvalue(b)); + + switch (op) + { + case TM_MUL: + setvvalue(ra, nb * vc[0], nb * vc[1], nb * vc[2], nb * vc[3]); + return; + case TM_DIV: + setvvalue(ra, nb / vc[0], nb / vc[1], nb / vc[2], nb / vc[3]); + return; + case TM_IDIV: + setvvalue(ra, float(luai_numidiv(nb, vc[0])), float(luai_numidiv(nb, vc[1])), float(luai_numidiv(nb, vc[2])), + float(luai_numidiv(nb, vc[3]))); + return; + default: + break; + } + } + } + + if ((b = luaV_tonumber(rb, &tempb)) != NULL && (c = luaV_tonumber(rc, &tempc)) != NULL) + { + double nb = nvalue(b), nc = nvalue(c); + + switch (op) + { + case TM_ADD: + setnvalue(ra, luai_numadd(nb, nc)); + break; + case TM_SUB: + setnvalue(ra, luai_numsub(nb, nc)); + break; + case TM_MUL: + setnvalue(ra, luai_nummul(nb, nc)); + break; + case TM_DIV: + setnvalue(ra, luai_numdiv(nb, nc)); + break; + case TM_IDIV: + setnvalue(ra, luai_numidiv(nb, nc)); + break; + case TM_MOD: + setnvalue(ra, luai_nummod(nb, nc)); + break; + case TM_POW: + setnvalue(ra, luai_numpow(nb, nc)); + break; + case TM_UNM: + setnvalue(ra, luai_numunm(nb)); + break; + default: + LUAU_ASSERT(0); + break; + } + } + else + { + if (!call_binTM(L, rb, rc, ra, op)) + { + luaG_aritherror(L, rb, rc, op); + } + } +} + +// instantiate private template implementation for external callers +template void luaV_doarithimpl(lua_State* L, StkId ra, const TValue* rb, const TValue* rc); +template void luaV_doarithimpl(lua_State* L, StkId ra, const TValue* rb, const TValue* rc); +template void luaV_doarithimpl(lua_State* L, StkId ra, const TValue* rb, const TValue* rc); +template void luaV_doarithimpl(lua_State* L, StkId ra, const TValue* rb, const TValue* rc); +template void luaV_doarithimpl(lua_State* L, StkId ra, const TValue* rb, const TValue* rc); +template void luaV_doarithimpl(lua_State* L, StkId ra, const TValue* rb, const TValue* rc); +template void luaV_doarithimpl(lua_State* L, StkId ra, const TValue* rb, const TValue* rc); +template void luaV_doarithimpl(lua_State* L, StkId ra, const TValue* rb, const TValue* rc); + void luaV_doarith(lua_State* L, StkId ra, const TValue* rb, const TValue* rc, TMS op) { TValue tempb, tempc; diff --git a/tests/CodeAllocator.test.cpp b/tests/CodeAllocator.test.cpp index 2ac0e5fdc..21228d6be 100644 --- a/tests/CodeAllocator.test.cpp +++ b/tests/CodeAllocator.test.cpp @@ -191,7 +191,7 @@ TEST_CASE("WindowsUnwindCodesX64") unwind.finishInfo(); std::vector data; - data.resize(unwind.getSize()); + data.resize(unwind.getUnwindInfoSize()); unwind.finalize(data.data(), 0, nullptr, 0); std::vector expected{0x44, 0x33, 0x22, 0x11, 0x22, 0x33, 0x44, 0x55, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x17, 0x0a, 0x05, 0x17, 0x82, 0x13, @@ -215,7 +215,7 @@ TEST_CASE("Dwarf2UnwindCodesX64") unwind.finishInfo(); std::vector data; - data.resize(unwind.getSize()); + data.resize(unwind.getUnwindInfoSize()); unwind.finalize(data.data(), 0, nullptr, 0); std::vector expected{0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x78, 0x10, 0x0c, 0x07, 0x08, 0x90, 0x01, 0x00, @@ -241,7 +241,7 @@ TEST_CASE("Dwarf2UnwindCodesA64") unwind.finishInfo(); std::vector data; - data.resize(unwind.getSize()); + data.resize(unwind.getUnwindInfoSize()); unwind.finalize(data.data(), 0, nullptr, 0); std::vector expected{0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x78, 0x1e, 0x0c, 0x1f, 0x00, 0x2c, 0x00, 0x00, diff --git a/tests/Error.test.cpp b/tests/Error.test.cpp index 677e32172..8dfcbde0a 100644 --- a/tests/Error.test.cpp +++ b/tests/Error.test.cpp @@ -6,6 +6,8 @@ using namespace Luau; +LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution) + TEST_SUITE_BEGIN("ErrorTests"); TEST_CASE("TypeError_code_should_return_nonzero_code") @@ -34,4 +36,44 @@ local x: Account = 5 CHECK_EQ("Type 'number' could not be converted into 'Account'", toString(result.errors[0])); } +TEST_CASE_FIXTURE(BuiltinsFixture, "binary_op_type_family_errors") +{ + frontend.options.retainFullTypeGraphs = false; + + CheckResult result = check(R"( + --!strict + local x = 1 + "foo" + )"); + + LUAU_REQUIRE_ERROR_COUNT(1, result); + + if (FFlag::DebugLuauDeferredConstraintResolution) + CHECK_EQ("Operator '+' could not be applied to operands of types number and string; there is no corresponding overload for __add", toString(result.errors[0])); + else + CHECK_EQ("Type 'string' could not be converted into 'number'", toString(result.errors[0])); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "unary_op_type_family_errors") +{ + frontend.options.retainFullTypeGraphs = false; + + CheckResult result = check(R"( + --!strict + local x = -"foo" + )"); + + + if (FFlag::DebugLuauDeferredConstraintResolution) + { + LUAU_REQUIRE_ERROR_COUNT(2, result); + CHECK_EQ("Operator '-' could not be applied to operand of type string; there is no corresponding overload for __unm", toString(result.errors[0])); + CHECK_EQ("Type 'string' could not be converted into 'number'", toString(result.errors[1])); + } + else + { + LUAU_REQUIRE_ERROR_COUNT(1, result); + CHECK_EQ("Type 'string' could not be converted into 'number'", toString(result.errors[0])); + } +} + TEST_SUITE_END(); diff --git a/tests/Simplify.test.cpp b/tests/Simplify.test.cpp index ddddbe677..b938b5f84 100644 --- a/tests/Simplify.test.cpp +++ b/tests/Simplify.test.cpp @@ -214,6 +214,14 @@ TEST_CASE_FIXTURE(SimplifyFixture, "any_and_indeterminate_types") CHECK(errorTy == anyLhsPending->options[1]); } +TEST_CASE_FIXTURE(SimplifyFixture, "union_where_lhs_elements_are_a_subset_of_the_rhs") +{ + TypeId lhs = union_(numberTy, stringTy); + TypeId rhs = union_(stringTy, numberTy); + + CHECK("number | string" == toString(union_(lhs, rhs))); +} + TEST_CASE_FIXTURE(SimplifyFixture, "unknown_and_indeterminate_types") { CHECK(freeTy == intersect(unknownTy, freeTy)); diff --git a/tests/TypeFamily.test.cpp b/tests/TypeFamily.test.cpp index 143850545..88dfbf473 100644 --- a/tests/TypeFamily.test.cpp +++ b/tests/TypeFamily.test.cpp @@ -391,8 +391,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_type_family_errors_if_it_has_nontable_ // FIXME(CLI-95289): we should actually only report the type family being uninhabited error at its first use, I think? LUAU_REQUIRE_ERROR_COUNT(2, result); - CHECK(toString(result.errors[0]) == "Type family instance keyof is uninhabited"); - CHECK(toString(result.errors[1]) == "Type family instance keyof is uninhabited"); + CHECK(toString(result.errors[0]) == "Type 'MyObject | boolean' does not have keys, so 'keyof' is invalid"); + CHECK(toString(result.errors[1]) == "Type 'MyObject | boolean' does not have keys, so 'keyof' is invalid"); } TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_type_family_string_indexer") @@ -517,8 +517,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "rawkeyof_type_family_errors_if_it_has_nontab // FIXME(CLI-95289): we should actually only report the type family being uninhabited error at its first use, I think? LUAU_REQUIRE_ERROR_COUNT(2, result); - CHECK(toString(result.errors[0]) == "Type family instance rawkeyof is uninhabited"); - CHECK(toString(result.errors[1]) == "Type family instance rawkeyof is uninhabited"); + CHECK(toString(result.errors[0]) == "Type 'MyObject | boolean' does not have keys, so 'rawkeyof' is invalid"); + CHECK(toString(result.errors[1]) == "Type 'MyObject | boolean' does not have keys, so 'rawkeyof' is invalid"); } TEST_CASE_FIXTURE(BuiltinsFixture, "rawkeyof_type_family_common_subset_if_union_of_differing_tables") @@ -590,8 +590,8 @@ TEST_CASE_FIXTURE(ClassFixture, "keyof_type_family_errors_if_it_has_nonclass_par // FIXME(CLI-95289): we should actually only report the type family being uninhabited error at its first use, I think? LUAU_REQUIRE_ERROR_COUNT(2, result); - CHECK(toString(result.errors[0]) == "Type family instance keyof is uninhabited"); - CHECK(toString(result.errors[1]) == "Type family instance keyof is uninhabited"); + CHECK(toString(result.errors[0]) == "Type 'BaseClass | boolean' does not have keys, so 'keyof' is invalid"); + CHECK(toString(result.errors[1]) == "Type 'BaseClass | boolean' does not have keys, so 'keyof' is invalid"); } TEST_CASE_FIXTURE(ClassFixture, "keyof_type_family_common_subset_if_union_of_differing_classes") diff --git a/tests/TypeInfer.functions.test.cpp b/tests/TypeInfer.functions.test.cpp index e424ddca6..34178fd9a 100644 --- a/tests/TypeInfer.functions.test.cpp +++ b/tests/TypeInfer.functions.test.cpp @@ -2298,10 +2298,10 @@ end if (FFlag::DebugLuauDeferredConstraintResolution) { LUAU_REQUIRE_ERROR_COUNT(4, result); - CHECK(toString(result.errors[0]) == "Type family instance sub is uninhabited"); - CHECK(toString(result.errors[1]) == "Type family instance sub is uninhabited"); - CHECK(toString(result.errors[2]) == "Type family instance sub is uninhabited"); - CHECK(toString(result.errors[3]) == "Type family instance sub is uninhabited"); + CHECK(toString(result.errors[0]) == "Operator '-' could not be applied to operands of types unknown and number; there is no corresponding overload for __sub"); + CHECK(toString(result.errors[1]) == "Operator '-' could not be applied to operands of types unknown and number; there is no corresponding overload for __sub"); + CHECK(toString(result.errors[2]) == "Operator '-' could not be applied to operands of types unknown and number; there is no corresponding overload for __sub"); + CHECK(toString(result.errors[3]) == "Operator '-' could not be applied to operands of types unknown and number; there is no corresponding overload for __sub"); } else { diff --git a/tests/TypeInfer.tables.test.cpp b/tests/TypeInfer.tables.test.cpp index d828ff659..bd0a41442 100644 --- a/tests/TypeInfer.tables.test.cpp +++ b/tests/TypeInfer.tables.test.cpp @@ -4479,4 +4479,31 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "index_results_compare_to_nil") LUAU_REQUIRE_NO_ERRORS(result); } +TEST_CASE_FIXTURE(BuiltinsFixture, "fuzzer_normalization_preserves_tbl_scopes") +{ + CheckResult result = check(R"( +Module 'l0': +do end + +Module 'l1': +local _ = {n0=nil,} +if if nil then _ then +if nil and (_)._ ~= (_)._ then +do end +while _ do +_ = _ +do end +end +end +do end +end +local l0 +while _ do +_ = nil +(_[_])._ %= `{# _}{bit32.extract(# _,1)}` +end + +)"); +} + TEST_SUITE_END(); diff --git a/tools/faillist.txt b/tools/faillist.txt index 2450eeb12..6939df548 100644 --- a/tools/faillist.txt +++ b/tools/faillist.txt @@ -219,7 +219,6 @@ TableTests.setmetatable_has_a_side_effect TableTests.shared_selfs TableTests.shared_selfs_from_free_param TableTests.shared_selfs_through_metatables -TableTests.should_not_unblock_table_type_twice TableTests.table_call_metamethod_basic TableTests.table_call_metamethod_must_be_callable TableTests.table_param_width_subtyping_2 @@ -288,7 +287,6 @@ TypeInfer.unify_nearly_identical_recursive_types TypeInferAnyError.can_subscript_any TypeInferAnyError.for_in_loop_iterator_is_error TypeInferAnyError.for_in_loop_iterator_is_error2 -TypeInferAnyError.metatable_of_any_can_be_a_table TypeInferAnyError.replace_every_free_type_when_unifying_a_complex_function_with_any TypeInferClasses.callable_classes TypeInferClasses.cannot_unify_class_instance_with_primitive