From 551a43c4247e928ecfaa432b419495ac755c7d84 Mon Sep 17 00:00:00 2001 From: Lily Brown Date: Fri, 1 Sep 2023 10:58:27 -0700 Subject: [PATCH 1/5] Sync to upstream/release/593 (#1024) - Updated Roblox copyright to 2023 - Floor division operator `//` (implements #832) - Autocomplete now shows `end` within `do` blocks - Restore BraceType when using `Lexer::lookahead` (fixes #1019) # New typechecker - Subtyping tests between metatables and tables - Subtyping tests between string singletons and tables - Subtyping tests for class types # Native codegen - Fixed macOS test failure (wrong spill restore offset) - Fixed clobbering of non-volatile xmm registers on Windows - Fixed wrong storage location of SSA reg spills - Implemented A64 support for add/sub extended - Eliminated zextReg from A64 lowering - Remove identical table slot lookups - Propagate values from predecessor into the linear block - Disabled reuse slot optimization - Keep `LuaNode::val` check for nil when optimizing `CHECK_SLOT_MATCH` - Implemented IR translation of `table.insert` builtin - Fixed mmap error handling on macOS/Linux # Tooling - Used `|` as a column separator instead of `+` in `bench.py` - Added a `table.sort` micro-benchmark - Switched `libprotobuf-mutator` to a less problematic version --- Analysis/include/Luau/Metamethods.h | 1 + Analysis/include/Luau/Subtyping.h | 18 +- Analysis/src/AstJsonEncoder.cpp | 7 + Analysis/src/Autocomplete.cpp | 14 +- Analysis/src/ConstraintGraphBuilder.cpp | 4 +- Analysis/src/ConstraintSolver.cpp | 6 + Analysis/src/Error.cpp | 22 +- Analysis/src/Subtyping.cpp | 181 ++++++++++++++- Analysis/src/Transpiler.cpp | 13 ++ Analysis/src/TypeChecker2.cpp | 6 + Analysis/src/TypeInfer.cpp | 10 +- Ast/include/Luau/Ast.h | 8 +- Ast/include/Luau/Lexer.h | 2 + Ast/src/Ast.cpp | 8 +- Ast/src/Lexer.cpp | 58 ++++- Ast/src/Parser.cpp | 28 ++- CodeGen/include/Luau/AssemblyBuilderA64.h | 1 + CodeGen/include/Luau/IrData.h | 30 ++- CodeGen/include/Luau/IrUtils.h | 3 + CodeGen/include/Luau/RegisterA64.h | 12 - CodeGen/src/AssemblyBuilderA64.cpp | 30 ++- CodeGen/src/CodeAllocator.cpp | 6 +- CodeGen/src/CodeGenLower.h | 14 +- CodeGen/src/EmitCommonX64.cpp | 11 +- CodeGen/src/EmitCommonX64.h | 4 +- CodeGen/src/IrAnalysis.cpp | 2 +- CodeGen/src/IrBuilder.cpp | 6 + CodeGen/src/IrDump.cpp | 6 + CodeGen/src/IrLoweringA64.cpp | 147 ++++++++---- CodeGen/src/IrLoweringX64.cpp | 43 +++- CodeGen/src/IrRegAllocA64.cpp | 8 +- CodeGen/src/IrRegAllocX64.cpp | 11 +- CodeGen/src/IrTranslateBuiltins.cpp | 24 ++ CodeGen/src/IrTranslation.cpp | 18 +- CodeGen/src/IrUtils.cpp | 10 +- CodeGen/src/IrValueLocationTracking.cpp | 3 +- CodeGen/src/NativeState.cpp | 1 + CodeGen/src/NativeState.h | 1 + CodeGen/src/OptimizeConstProp.cpp | 139 +++++++++++- CodeGen/src/OptimizeFinalX64.cpp | 1 + Common/include/Luau/Bytecode.h | 14 +- Compiler/include/Luau/BytecodeBuilder.h | 1 - Compiler/src/BytecodeBuilder.cpp | 50 ++--- Compiler/src/Compiler.cpp | 13 ++ Compiler/src/ConstantFolding.cpp | 8 + Config/src/Config.cpp | 20 +- LICENSE.txt | 4 +- Makefile | 1 + VM/include/lua.h | 2 +- VM/src/lapi.cpp | 2 +- VM/src/lnumutils.h | 7 + VM/src/ltm.cpp | 1 + VM/src/ltm.h | 1 + VM/src/lvmexecute.cpp | 98 +++++++- VM/src/lvmutils.cpp | 22 +- bench/micro_tests/test_TableSort.lua | 22 ++ bench/tabulate.py | 2 +- fuzz/luau.proto | 23 +- fuzz/protoprint.cpp | 2 + tests/AssemblyBuilderA64.test.cpp | 10 + tests/Autocomplete.test.cpp | 27 +++ tests/Conformance.test.cpp | 21 ++ tests/IrBuilder.test.cpp | 130 +++++++++++ tests/Parser.test.cpp | 36 +++ tests/Subtyping.test.cpp | 246 ++++++++++++++++++++- tests/ToString.test.cpp | 8 - tests/Transpiler.test.cpp | 5 +- tests/TypeInfer.aliases.test.cpp | 5 - tests/TypeInfer.builtins.test.cpp | 2 - tests/TypeInfer.classes.test.cpp | 4 - tests/TypeInfer.functions.test.cpp | 28 --- tests/TypeInfer.generics.test.cpp | 3 - tests/TypeInfer.intersectionTypes.test.cpp | 53 ----- tests/TypeInfer.modules.test.cpp | 6 - tests/TypeInfer.operators.test.cpp | 20 ++ tests/TypeInfer.provisional.test.cpp | 41 +++- tests/TypeInfer.singletons.test.cpp | 8 - tests/TypeInfer.tables.test.cpp | 19 -- tests/TypeInfer.test.cpp | 3 - tests/TypeInfer.tryUnify.test.cpp | 2 - tests/TypeInfer.typePacks.cpp | 3 - tests/TypeInfer.unionTypes.test.cpp | 18 -- tests/conformance/basic.lua | 13 ++ tests/conformance/events.lua | 3 + tests/conformance/math.lua | 1 + tests/conformance/native.lua | 41 ++++ tests/conformance/userdata.lua | 6 + tests/conformance/vector.lua | 12 + 88 files changed, 1606 insertions(+), 378 deletions(-) create mode 100644 bench/micro_tests/test_TableSort.lua diff --git a/Analysis/include/Luau/Metamethods.h b/Analysis/include/Luau/Metamethods.h index 84b0092f..747b7201 100644 --- a/Analysis/include/Luau/Metamethods.h +++ b/Analysis/include/Luau/Metamethods.h @@ -19,6 +19,7 @@ static const std::unordered_map kBinaryOpMetamet {AstExprBinary::Op::Sub, "__sub"}, {AstExprBinary::Op::Mul, "__mul"}, {AstExprBinary::Op::Div, "__div"}, + {AstExprBinary::Op::FloorDiv, "__idiv"}, {AstExprBinary::Op::Pow, "__pow"}, {AstExprBinary::Op::Mod, "__mod"}, {AstExprBinary::Op::Concat, "__concat"}, diff --git a/Analysis/include/Luau/Subtyping.h b/Analysis/include/Luau/Subtyping.h index 8864ef81..a69952f6 100644 --- a/Analysis/include/Luau/Subtyping.h +++ b/Analysis/include/Luau/Subtyping.h @@ -2,6 +2,7 @@ #pragma once #include "Luau/Type.h" +#include "Luau/TypePack.h" #include "Luau/UnifierSharedState.h" #include @@ -14,8 +15,10 @@ template struct TryPair; struct InternalErrorReporter; +class TypeIds; class Normalizer; struct NormalizedType; +struct NormalizedClassType; struct SubtypingResult { @@ -30,6 +33,8 @@ struct SubtypingResult void andAlso(const SubtypingResult& other); void orElse(const SubtypingResult& other); + // Only negates the `isSubtype`. + static SubtypingResult negate(const SubtypingResult& result); static SubtypingResult all(const std::vector& results); static SubtypingResult any(const std::vector& results); }; @@ -63,7 +68,7 @@ struct Subtyping DenseHashMap mappedGenericPacks{nullptr}; using SeenSet = std::unordered_set, TypeIdPairHash>; - + SeenSet seenTypes; // TODO cache @@ -88,8 +93,19 @@ private: SubtypingResult isSubtype_(const SingletonType* subSingleton, const PrimitiveType* superPrim); SubtypingResult isSubtype_(const SingletonType* subSingleton, const SingletonType* superSingleton); SubtypingResult isSubtype_(const TableType* subTable, const TableType* superTable); + SubtypingResult isSubtype_(const MetatableType* subMt, const MetatableType* superMt); + SubtypingResult isSubtype_(const MetatableType* subMt, const TableType* superTable); + SubtypingResult isSubtype_(const ClassType* subClass, const ClassType* superClass); + SubtypingResult isSubtype_(const ClassType* subClass, const TableType* superTable); // Actually a class <: shape. SubtypingResult isSubtype_(const FunctionType* subFunction, const FunctionType* superFunction); + SubtypingResult isSubtype_(const PrimitiveType* subPrim, const TableType* superTable); + SubtypingResult isSubtype_(const SingletonType* subSingleton, const TableType* superTable); + SubtypingResult isSubtype_(const NormalizedType* subNorm, const NormalizedType* superNorm); + SubtypingResult isSubtype_(const NormalizedClassType& subClass, const NormalizedClassType& superClass, const TypeIds& superTables); + SubtypingResult isSubtype_(const TypeIds& subTypes, const TypeIds& superTypes); + + SubtypingResult isSubtype_(const VariadicTypePack* subVariadic, const VariadicTypePack* superVariadic); bool bindGeneric(TypeId subTp, TypeId superTp); bool bindGeneric(TypePackId subTp, TypePackId superTp); diff --git a/Analysis/src/AstJsonEncoder.cpp b/Analysis/src/AstJsonEncoder.cpp index f2943c4d..5c4f9504 100644 --- a/Analysis/src/AstJsonEncoder.cpp +++ b/Analysis/src/AstJsonEncoder.cpp @@ -8,6 +8,8 @@ #include +LUAU_FASTFLAG(LuauFloorDivision) + namespace Luau { @@ -514,6 +516,9 @@ struct AstJsonEncoder : public AstVisitor return writeString("Mul"); case AstExprBinary::Div: return writeString("Div"); + case AstExprBinary::FloorDiv: + LUAU_ASSERT(FFlag::LuauFloorDivision); + return writeString("FloorDiv"); case AstExprBinary::Mod: return writeString("Mod"); case AstExprBinary::Pow: @@ -536,6 +541,8 @@ struct AstJsonEncoder : public AstVisitor return writeString("And"); case AstExprBinary::Or: return writeString("Or"); + default: + LUAU_ASSERT(!"Unknown Op"); } } diff --git a/Analysis/src/Autocomplete.cpp b/Analysis/src/Autocomplete.cpp index 471fd006..baeac469 100644 --- a/Analysis/src/Autocomplete.cpp +++ b/Analysis/src/Autocomplete.cpp @@ -15,6 +15,7 @@ LUAU_FASTFLAG(DebugLuauReadWriteProperties) LUAU_FASTFLAGVARIABLE(LuauAnonymousAutofilled1, false); LUAU_FASTFLAGVARIABLE(LuauAutocompleteLastTypecheck, false) +LUAU_FASTFLAGVARIABLE(LuauAutocompleteDoEnd, false) LUAU_FASTFLAGVARIABLE(LuauAutocompleteStringLiteralBounds, false); static const std::unordered_set kStatementStartingKeywords = { @@ -1089,14 +1090,19 @@ static AutocompleteEntryMap autocompleteStatement( { if (AstStatForIn* statForIn = (*it)->as(); statForIn && !statForIn->hasEnd) result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword}); - if (AstStatFor* statFor = (*it)->as(); statFor && !statFor->hasEnd) + else if (AstStatFor* statFor = (*it)->as(); statFor && !statFor->hasEnd) result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword}); - if (AstStatIf* statIf = (*it)->as(); statIf && !statIf->hasEnd) + else if (AstStatIf* statIf = (*it)->as(); statIf && !statIf->hasEnd) result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword}); - if (AstStatWhile* statWhile = (*it)->as(); statWhile && !statWhile->hasEnd) + else if (AstStatWhile* statWhile = (*it)->as(); statWhile && !statWhile->hasEnd) result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword}); - if (AstExprFunction* exprFunction = (*it)->as(); exprFunction && !exprFunction->hasEnd) + else if (AstExprFunction* exprFunction = (*it)->as(); exprFunction && !exprFunction->hasEnd) result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword}); + if (FFlag::LuauAutocompleteDoEnd) + { + if (AstStatBlock* exprBlock = (*it)->as(); exprBlock && !exprBlock->hasEnd) + result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword}); + } } if (ancestry.size() >= 2) diff --git a/Analysis/src/ConstraintGraphBuilder.cpp b/Analysis/src/ConstraintGraphBuilder.cpp index 7d35ebc6..d2413adb 100644 --- a/Analysis/src/ConstraintGraphBuilder.cpp +++ b/Analysis/src/ConstraintGraphBuilder.cpp @@ -24,6 +24,7 @@ LUAU_FASTINT(LuauCheckRecursionLimit); LUAU_FASTFLAG(DebugLuauLogSolverToJson); LUAU_FASTFLAG(DebugLuauMagicTypes); LUAU_FASTFLAG(LuauParseDeclareClassIndexer); +LUAU_FASTFLAG(LuauFloorDivision); namespace Luau { @@ -1170,7 +1171,8 @@ static bool isMetamethod(const Name& name) { return name == "__index" || name == "__newindex" || name == "__call" || name == "__concat" || name == "__unm" || name == "__add" || name == "__sub" || name == "__mul" || name == "__div" || name == "__mod" || name == "__pow" || name == "__tostring" || - name == "__metatable" || name == "__eq" || name == "__lt" || name == "__le" || name == "__mode" || name == "__iter" || name == "__len"; + name == "__metatable" || name == "__eq" || name == "__lt" || name == "__le" || name == "__mode" || name == "__iter" || name == "__len" || + (FFlag::LuauFloorDivision && name == "__idiv"); } ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatDeclareClass* declaredClass) diff --git a/Analysis/src/ConstraintSolver.cpp b/Analysis/src/ConstraintSolver.cpp index 78dc0d54..fd0c1c00 100644 --- a/Analysis/src/ConstraintSolver.cpp +++ b/Analysis/src/ConstraintSolver.cpp @@ -22,6 +22,7 @@ #include "Luau/VisitType.h" LUAU_FASTFLAGVARIABLE(DebugLuauLogSolver, false); +LUAU_FASTFLAG(LuauFloorDivision); namespace Luau { @@ -719,6 +720,8 @@ bool ConstraintSolver::tryDispatch(const BinaryConstraint& c, NotNullnormalize(leftType); if (hasTypeInIntersection(leftType) && force) asMutable(leftType)->ty.emplace(anyPresent ? builtinTypes->anyType : builtinTypes->numberType); diff --git a/Analysis/src/Error.cpp b/Analysis/src/Error.cpp index 615ce41e..4505f627 100644 --- a/Analysis/src/Error.cpp +++ b/Analysis/src/Error.cpp @@ -12,7 +12,6 @@ #include #include -LUAU_FASTFLAGVARIABLE(LuauIndentTypeMismatch, false) LUAU_FASTINTVARIABLE(LuauIndentTypeMismatchMaxTypeLength, 10) static std::string wrongNumberOfArgsString( @@ -94,31 +93,18 @@ struct ErrorConverter { std::string givenModuleName = fileResolver->getHumanReadableModuleName(*givenDefinitionModule); std::string wantedModuleName = fileResolver->getHumanReadableModuleName(*wantedDefinitionModule); - if (FFlag::LuauIndentTypeMismatch) - result = constructErrorMessage(givenTypeName, wantedTypeName, givenModuleName, wantedModuleName); - else - result = "Type '" + givenTypeName + "' from '" + givenModuleName + "' could not be converted into '" + wantedTypeName + - "' from '" + wantedModuleName + "'"; + result = constructErrorMessage(givenTypeName, wantedTypeName, givenModuleName, wantedModuleName); } else { - if (FFlag::LuauIndentTypeMismatch) - result = constructErrorMessage(givenTypeName, wantedTypeName, *givenDefinitionModule, *wantedDefinitionModule); - else - result = "Type '" + givenTypeName + "' from '" + *givenDefinitionModule + "' could not be converted into '" + - wantedTypeName + "' from '" + *wantedDefinitionModule + "'"; + result = constructErrorMessage(givenTypeName, wantedTypeName, *givenDefinitionModule, *wantedDefinitionModule); } } } } if (result.empty()) - { - if (FFlag::LuauIndentTypeMismatch) - result = constructErrorMessage(givenTypeName, wantedTypeName, std::nullopt, std::nullopt); - else - result = "Type '" + givenTypeName + "' could not be converted into '" + wantedTypeName + "'"; - } + result = constructErrorMessage(givenTypeName, wantedTypeName, std::nullopt, std::nullopt); if (tm.error) @@ -126,7 +112,7 @@ struct ErrorConverter result += "\ncaused by:\n "; if (!tm.reason.empty()) - result += tm.reason + (FFlag::LuauIndentTypeMismatch ? " \n" : " "); + result += tm.reason + " \n"; result += Luau::toString(*tm.error, TypeErrorToStringOptions{fileResolver}); } diff --git a/Analysis/src/Subtyping.cpp b/Analysis/src/Subtyping.cpp index b55686b9..3f3c9319 100644 --- a/Analysis/src/Subtyping.cpp +++ b/Analysis/src/Subtyping.cpp @@ -58,6 +58,15 @@ void SubtypingResult::orElse(const SubtypingResult& other) normalizationTooComplex |= other.normalizationTooComplex; } +SubtypingResult SubtypingResult::negate(const SubtypingResult& result) +{ + return SubtypingResult{ + !result.isSubtype, + result.isErrorSuppressing, + result.normalizationTooComplex, + }; +} + SubtypingResult SubtypingResult::all(const std::vector& results) { SubtypingResult acc{true, false}; @@ -137,10 +146,10 @@ SubtypingResult Subtyping::isSubtype_(TypeId subTy, TypeId superTy) SeenSetPopper ssp{&seenTypes, typePair}; - if (auto superUnion = get(superTy)) - return isSubtype_(subTy, superUnion); - else if (auto subUnion = get(subTy)) + if (auto subUnion = get(subTy)) return isSubtype_(subUnion, superTy); + else if (auto superUnion = get(superTy)) + return isSubtype_(subTy, superUnion); else if (auto superIntersection = get(superTy)) return isSubtype_(subTy, superIntersection); else if (auto subIntersection = get(subTy)) @@ -196,6 +205,18 @@ SubtypingResult Subtyping::isSubtype_(TypeId subTy, TypeId superTy) return isSubtype_(p); else if (auto p = get2(subTy, superTy)) return isSubtype_(p); + else if (auto p = get2(subTy, superTy)) + return isSubtype_(p); + else if (auto p = get2(subTy, superTy)) + return isSubtype_(p); + else if (auto p = get2(subTy, superTy)) + return isSubtype_(p); + else if (auto p = get2(subTy, superTy)) + return isSubtype_(p); + else if (auto p = get2(subTy, superTy)) + return isSubtype_(p); + else if (auto p = get2(subTy, superTy)) + return isSubtype_(p); return {false}; } @@ -323,7 +344,7 @@ SubtypingResult Subtyping::isSubtype_(TypePackId subTp, TypePackId superTp) { if (auto p = get2(*subTail, *superTail)) { - results.push_back(isSubtype_(p.first->ty, p.second->ty)); + results.push_back(isSubtype_(p)); } else if (auto p = get2(*subTail, *superTail)) { @@ -472,7 +493,6 @@ SubtypingResult Subtyping::isSubtype_(TypeId subTy, const IntersectionType* supe SubtypingResult Subtyping::isSubtype_(const IntersectionType* subIntersection, TypeId superTy) { - // TODO: Semantic subtyping here. // As per TAPL: A & B <: T iff A <: T || B <: T std::vector subtypings; for (TypeId ty : subIntersection) @@ -520,6 +540,59 @@ SubtypingResult Subtyping::isSubtype_(const TableType* subTable, const TableType return result; } +SubtypingResult Subtyping::isSubtype_(const MetatableType* subMt, const MetatableType* superMt) +{ + return SubtypingResult::all({ + isSubtype_(subMt->table, superMt->table), + isSubtype_(subMt->metatable, superMt->metatable), + }); +} + +SubtypingResult Subtyping::isSubtype_(const MetatableType* subMt, const TableType* superTable) +{ + if (auto subTable = get(subMt->table)) { + // Metatables cannot erase properties from the table they're attached to, so + // the subtyping rule for this is just if the table component is a subtype + // of the supertype table. + // + // There's a flaw here in that if the __index metamethod contributes a new + // field that would satisfy the subtyping relationship, we'll erronously say + // that the metatable isn't a subtype of the table, even though they have + // compatible properties/shapes. We'll revisit this later when we have a + // better understanding of how important this is. + return isSubtype_(subTable, superTable); + } + else + { + // TODO: This may be a case we actually hit? + return {false}; + } +} + +SubtypingResult Subtyping::isSubtype_(const ClassType* subClass, const ClassType* superClass) +{ + return {isSubclass(subClass, superClass)}; +} + +SubtypingResult Subtyping::isSubtype_(const ClassType* subClass, const TableType* superTable) +{ + SubtypingResult result{true}; + + for (const auto& [name, prop]: superTable->props) + { + if (auto classProp = lookupClassProp(subClass, name)) + { + // Table properties are invariant + result.andAlso(isSubtype_(classProp->type(), prop.type())); + result.andAlso(isSubtype_(prop.type(), classProp->type())); + } + else + return SubtypingResult{false}; + } + + return result; +} + SubtypingResult Subtyping::isSubtype_(const FunctionType* subFunction, const FunctionType* superFunction) { SubtypingResult result; @@ -533,6 +606,47 @@ SubtypingResult Subtyping::isSubtype_(const FunctionType* subFunction, const Fun return result; } +SubtypingResult Subtyping::isSubtype_(const PrimitiveType* subPrim, const TableType* superTable) +{ + SubtypingResult result{false}; + if (subPrim->type == PrimitiveType::String) + { + if (auto metatable = getMetatable(builtinTypes->stringType, builtinTypes)) + { + if (auto mttv = get(follow(metatable))) + { + if (auto it = mttv->props.find("__index"); it != mttv->props.end()) + { + if (auto stringTable = get(it->second.type())) + result.orElse(isSubtype_(stringTable, superTable)); + } + } + } + } + + return result; +} + +SubtypingResult Subtyping::isSubtype_(const SingletonType* subSingleton, const TableType* superTable) +{ + SubtypingResult result{false}; + if (auto stringleton = get(subSingleton)) + { + if (auto metatable = getMetatable(builtinTypes->stringType, builtinTypes)) + { + if (auto mttv = get(follow(metatable))) + { + if (auto it = mttv->props.find("__index"); it != mttv->props.end()) + { + if (auto stringTable = get(it->second.type())) + result.orElse(isSubtype_(stringTable, superTable)); + } + } + } + } + return result; +} + SubtypingResult Subtyping::isSubtype_(const NormalizedType* subNorm, const NormalizedType* superNorm) { if (!subNorm || !superNorm) @@ -540,15 +654,14 @@ SubtypingResult Subtyping::isSubtype_(const NormalizedType* subNorm, const Norma SubtypingResult result = isSubtype_(subNorm->tops, superNorm->tops); result.andAlso(isSubtype_(subNorm->booleans, superNorm->booleans)); - // isSubtype_(subNorm->classes, superNorm->classes); - // isSubtype_(subNorm->classes, superNorm->tables); + result.andAlso(isSubtype_(subNorm->classes, superNorm->classes, superNorm->tables)); result.andAlso(isSubtype_(subNorm->errors, superNorm->errors)); result.andAlso(isSubtype_(subNorm->nils, superNorm->nils)); result.andAlso(isSubtype_(subNorm->numbers, superNorm->numbers)); result.isSubtype &= Luau::isSubtype(subNorm->strings, superNorm->strings); // isSubtype_(subNorm->strings, superNorm->tables); result.andAlso(isSubtype_(subNorm->threads, superNorm->threads)); - // isSubtype_(subNorm->tables, superNorm->tables); + result.andAlso(isSubtype_(subNorm->tables, superNorm->tables)); // isSubtype_(subNorm->tables, superNorm->strings); // isSubtype_(subNorm->tables, superNorm->classes); // isSubtype_(subNorm->functions, superNorm->functions); @@ -557,6 +670,58 @@ SubtypingResult Subtyping::isSubtype_(const NormalizedType* subNorm, const Norma return result; } +SubtypingResult Subtyping::isSubtype_(const NormalizedClassType& subClass, const NormalizedClassType& superClass, const TypeIds& superTables) +{ + for (const auto& [subClassTy, _] : subClass.classes) + { + SubtypingResult result; + + for (const auto& [superClassTy, superNegations] : superClass.classes) + { + result.orElse(isSubtype_(subClassTy, superClassTy)); + if (!result.isSubtype) + continue; + + for (TypeId negation : superNegations) + { + result.andAlso(SubtypingResult::negate(isSubtype_(subClassTy, negation))); + if (result.isSubtype) + break; + } + } + + if (result.isSubtype) + continue; + + for (TypeId superTableTy : superTables) + result.orElse(isSubtype_(subClassTy, superTableTy)); + + if (!result.isSubtype) + return result; + } + + return {true}; +} + +SubtypingResult Subtyping::isSubtype_(const TypeIds& subTypes, const TypeIds& superTypes) +{ + std::vector results; + + for (TypeId subTy : subTypes) + { + results.emplace_back(); + for (TypeId superTy : superTypes) + results.back().orElse(isSubtype_(subTy, superTy)); + } + + return SubtypingResult::all(results); +} + +SubtypingResult Subtyping::isSubtype_(const VariadicTypePack* subVariadic, const VariadicTypePack* superVariadic) +{ + return isSubtype_(subVariadic->ty, superVariadic->ty); +} + bool Subtyping::bindGeneric(TypeId subTy, TypeId superTy) { if (variance == Variance::Covariant) diff --git a/Analysis/src/Transpiler.cpp b/Analysis/src/Transpiler.cpp index cdfe6549..8fd40772 100644 --- a/Analysis/src/Transpiler.cpp +++ b/Analysis/src/Transpiler.cpp @@ -10,6 +10,8 @@ #include #include +LUAU_FASTFLAG(LuauFloorDivision) + namespace { bool isIdentifierStartChar(char c) @@ -467,10 +469,13 @@ struct Printer case AstExprBinary::Sub: case AstExprBinary::Mul: case AstExprBinary::Div: + case AstExprBinary::FloorDiv: case AstExprBinary::Mod: case AstExprBinary::Pow: case AstExprBinary::CompareLt: case AstExprBinary::CompareGt: + LUAU_ASSERT(FFlag::LuauFloorDivision || a->op != AstExprBinary::FloorDiv); + writer.maybeSpace(a->right->location.begin, 2); writer.symbol(toString(a->op)); break; @@ -487,6 +492,8 @@ struct Printer writer.maybeSpace(a->right->location.begin, 4); writer.keyword(toString(a->op)); break; + default: + LUAU_ASSERT(!"Unknown Op"); } visualize(*a->right); @@ -753,6 +760,12 @@ struct Printer writer.maybeSpace(a->value->location.begin, 2); writer.symbol("/="); break; + case AstExprBinary::FloorDiv: + LUAU_ASSERT(FFlag::LuauFloorDivision); + + writer.maybeSpace(a->value->location.begin, 2); + writer.symbol("//="); + break; case AstExprBinary::Mod: writer.maybeSpace(a->value->location.begin, 2); writer.symbol("%="); diff --git a/Analysis/src/TypeChecker2.cpp b/Analysis/src/TypeChecker2.cpp index a7653b7c..65da9dfa 100644 --- a/Analysis/src/TypeChecker2.cpp +++ b/Analysis/src/TypeChecker2.cpp @@ -23,6 +23,7 @@ #include LUAU_FASTFLAG(DebugLuauMagicTypes) +LUAU_FASTFLAG(LuauFloorDivision); namespace Luau { @@ -1817,6 +1818,8 @@ struct TypeChecker2 bool typesHaveIntersection = normalizer.isIntersectionInhabited(leftType, rightType); if (auto it = kBinaryOpMetamethods.find(expr->op); it != kBinaryOpMetamethods.end()) { + LUAU_ASSERT(FFlag::LuauFloorDivision || expr->op != AstExprBinary::Op::FloorDiv); + std::optional leftMt = getMetatable(leftType, builtinTypes); std::optional rightMt = getMetatable(rightType, builtinTypes); bool matches = leftMt == rightMt; @@ -2002,8 +2005,11 @@ struct TypeChecker2 case AstExprBinary::Op::Sub: case AstExprBinary::Op::Mul: case AstExprBinary::Op::Div: + case AstExprBinary::Op::FloorDiv: case AstExprBinary::Op::Pow: case AstExprBinary::Op::Mod: + LUAU_ASSERT(FFlag::LuauFloorDivision || expr->op != AstExprBinary::Op::FloorDiv); + reportErrors(tryUnify(scope, expr->left->location, leftType, builtinTypes->numberType)); reportErrors(tryUnify(scope, expr->right->location, rightType, builtinTypes->numberType)); diff --git a/Analysis/src/TypeInfer.cpp b/Analysis/src/TypeInfer.cpp index 569f9720..5349f16a 100644 --- a/Analysis/src/TypeInfer.cpp +++ b/Analysis/src/TypeInfer.cpp @@ -40,6 +40,7 @@ LUAU_FASTFLAG(LuauOccursIsntAlwaysFailure) LUAU_FASTFLAGVARIABLE(LuauTinyControlFlowAnalysis, false) LUAU_FASTFLAGVARIABLE(LuauAlwaysCommitInferencesOfFunctionCalls, false) LUAU_FASTFLAG(LuauParseDeclareClassIndexer) +LUAU_FASTFLAG(LuauFloorDivision); namespace Luau { @@ -200,7 +201,8 @@ static bool isMetamethod(const Name& name) { return name == "__index" || name == "__newindex" || name == "__call" || name == "__concat" || name == "__unm" || name == "__add" || name == "__sub" || name == "__mul" || name == "__div" || name == "__mod" || name == "__pow" || name == "__tostring" || - name == "__metatable" || name == "__eq" || name == "__lt" || name == "__le" || name == "__mode" || name == "__iter" || name == "__len"; + name == "__metatable" || name == "__eq" || name == "__lt" || name == "__le" || name == "__mode" || name == "__iter" || name == "__len" || + (FFlag::LuauFloorDivision && name == "__idiv"); } size_t HashBoolNamePair::operator()(const std::pair& pair) const @@ -2571,6 +2573,9 @@ std::string opToMetaTableEntry(const AstExprBinary::Op& op) return "__mul"; case AstExprBinary::Div: return "__div"; + case AstExprBinary::FloorDiv: + LUAU_ASSERT(FFlag::LuauFloorDivision); + return "__idiv"; case AstExprBinary::Mod: return "__mod"; case AstExprBinary::Pow: @@ -3064,8 +3069,11 @@ TypeId TypeChecker::checkBinaryOperation( case AstExprBinary::Sub: case AstExprBinary::Mul: case AstExprBinary::Div: + case AstExprBinary::FloorDiv: case AstExprBinary::Mod: case AstExprBinary::Pow: + LUAU_ASSERT(FFlag::LuauFloorDivision || expr.op != AstExprBinary::FloorDiv); + reportErrors(tryUnify(lhsType, numberType, scope, expr.left->location)); reportErrors(tryUnify(rhsType, numberType, scope, expr.right->location)); return numberType; diff --git a/Ast/include/Luau/Ast.h b/Ast/include/Luau/Ast.h index 7478e15d..b60fec28 100644 --- a/Ast/include/Luau/Ast.h +++ b/Ast/include/Luau/Ast.h @@ -457,6 +457,7 @@ public: Sub, Mul, Div, + FloorDiv, Mod, Pow, Concat, @@ -467,7 +468,9 @@ public: CompareGt, CompareGe, And, - Or + Or, + + Op__Count }; AstExprBinary(const Location& location, Op op, AstExpr* left, AstExpr* right); @@ -531,11 +534,12 @@ class AstStatBlock : public AstStat public: LUAU_RTTI(AstStatBlock) - AstStatBlock(const Location& location, const AstArray& body); + AstStatBlock(const Location& location, const AstArray& body, bool hasEnd=true); void visit(AstVisitor* visitor) override; AstArray body; + bool hasEnd = false; }; class AstStatIf : public AstStat diff --git a/Ast/include/Luau/Lexer.h b/Ast/include/Luau/Lexer.h index e1415183..7d15212a 100644 --- a/Ast/include/Luau/Lexer.h +++ b/Ast/include/Luau/Lexer.h @@ -62,6 +62,7 @@ struct Lexeme Dot3, SkinnyArrow, DoubleColon, + FloorDiv, InterpStringBegin, InterpStringMid, @@ -73,6 +74,7 @@ struct Lexeme SubAssign, MulAssign, DivAssign, + FloorDivAssign, ModAssign, PowAssign, ConcatAssign, diff --git a/Ast/src/Ast.cpp b/Ast/src/Ast.cpp index 9b3acb7f..52b77de3 100644 --- a/Ast/src/Ast.cpp +++ b/Ast/src/Ast.cpp @@ -3,6 +3,8 @@ #include "Luau/Common.h" +LUAU_FASTFLAG(LuauFloorDivision) + namespace Luau { @@ -279,6 +281,9 @@ std::string toString(AstExprBinary::Op op) return "*"; case AstExprBinary::Div: return "/"; + case AstExprBinary::FloorDiv: + LUAU_ASSERT(FFlag::LuauFloorDivision); + return "//"; case AstExprBinary::Mod: return "%"; case AstExprBinary::Pow: @@ -375,9 +380,10 @@ void AstExprError::visit(AstVisitor* visitor) } } -AstStatBlock::AstStatBlock(const Location& location, const AstArray& body) +AstStatBlock::AstStatBlock(const Location& location, const AstArray& body, bool hasEnd) : AstStat(ClassIndex(), location) , body(body) + , hasEnd(hasEnd) { } diff --git a/Ast/src/Lexer.cpp b/Ast/src/Lexer.cpp index fe32e2a1..894d2dd7 100644 --- a/Ast/src/Lexer.cpp +++ b/Ast/src/Lexer.cpp @@ -6,7 +6,9 @@ #include +LUAU_FASTFLAGVARIABLE(LuauFloorDivision, false) LUAU_FASTFLAGVARIABLE(LuauLexerConsumeFast, false) +LUAU_FASTFLAGVARIABLE(LuauLexerLookaheadRemembersBraceType, false) namespace Luau { @@ -138,6 +140,9 @@ std::string Lexeme::toString() const case DoubleColon: return "'::'"; + case FloorDiv: + return FFlag::LuauFloorDivision ? "'//'" : ""; + case AddAssign: return "'+='"; @@ -150,6 +155,9 @@ std::string Lexeme::toString() const case DivAssign: return "'/='"; + case FloorDivAssign: + return FFlag::LuauFloorDivision ? "'//='" : ""; + case ModAssign: return "'%='"; @@ -402,6 +410,8 @@ Lexeme Lexer::lookahead() unsigned int currentLineOffset = lineOffset; Lexeme currentLexeme = lexeme; Location currentPrevLocation = prevLocation; + size_t currentBraceStackSize = braceStack.size(); + BraceType currentBraceType = braceStack.empty() ? BraceType::Normal : braceStack.back(); Lexeme result = next(); @@ -410,6 +420,13 @@ Lexeme Lexer::lookahead() lineOffset = currentLineOffset; lexeme = currentLexeme; prevLocation = currentPrevLocation; + if (FFlag::LuauLexerLookaheadRemembersBraceType) + { + if (braceStack.size() < currentBraceStackSize) + braceStack.push_back(currentBraceType); + else if (braceStack.size() > currentBraceStackSize) + braceStack.pop_back(); + } return result; } @@ -901,15 +918,46 @@ Lexeme Lexer::readNext() return Lexeme(Location(start, 1), '+'); case '/': - consume(); - - if (peekch() == '=') + { + if (FFlag::LuauFloorDivision) { consume(); - return Lexeme(Location(start, 2), Lexeme::DivAssign); + + char ch = peekch(); + + if (ch == '=') + { + consume(); + return Lexeme(Location(start, 2), Lexeme::DivAssign); + } + else if (ch == '/') + { + consume(); + + if (peekch() == '=') + { + consume(); + return Lexeme(Location(start, 3), Lexeme::FloorDivAssign); + } + else + return Lexeme(Location(start, 2), Lexeme::FloorDiv); + } + else + return Lexeme(Location(start, 1), '/'); } else - return Lexeme(Location(start, 1), '/'); + { + consume(); + + if (peekch() == '=') + { + consume(); + return Lexeme(Location(start, 2), Lexeme::DivAssign); + } + else + return Lexeme(Location(start, 1), '/'); + } + } case '*': consume(); diff --git a/Ast/src/Parser.cpp b/Ast/src/Parser.cpp index 20186dfc..d59b6b40 100644 --- a/Ast/src/Parser.cpp +++ b/Ast/src/Parser.cpp @@ -14,6 +14,7 @@ LUAU_FASTINTVARIABLE(LuauRecursionLimit, 1000) LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100) LUAU_FASTFLAGVARIABLE(LuauParseDeclareClassIndexer, false) +LUAU_FASTFLAG(LuauFloorDivision) namespace Luau { @@ -460,11 +461,11 @@ AstStat* Parser::parseDo() Lexeme matchDo = lexer.current(); nextLexeme(); // do - AstStat* body = parseBlock(); + AstStatBlock* body = parseBlock(); body->location.begin = start.begin; - expectMatchEndAndConsume(Lexeme::ReservedEnd, matchDo); + body->hasEnd = expectMatchEndAndConsume(Lexeme::ReservedEnd, matchDo); return body; } @@ -1766,6 +1767,12 @@ std::optional Parser::parseBinaryOp(const Lexeme& l) return AstExprBinary::Mul; else if (l.type == '/') return AstExprBinary::Div; + else if (l.type == Lexeme::FloorDiv) + { + LUAU_ASSERT(FFlag::LuauFloorDivision); + + return AstExprBinary::FloorDiv; + } else if (l.type == '%') return AstExprBinary::Mod; else if (l.type == '^') @@ -1802,6 +1809,12 @@ std::optional Parser::parseCompoundOp(const Lexeme& l) return AstExprBinary::Mul; else if (l.type == Lexeme::DivAssign) return AstExprBinary::Div; + else if (l.type == Lexeme::FloorDivAssign) + { + LUAU_ASSERT(FFlag::LuauFloorDivision); + + return AstExprBinary::FloorDiv; + } else if (l.type == Lexeme::ModAssign) return AstExprBinary::Mod; else if (l.type == Lexeme::PowAssign) @@ -1872,12 +1885,13 @@ std::optional Parser::checkBinaryConfusables(const BinaryOpPr AstExpr* Parser::parseExpr(unsigned int limit) { static const BinaryOpPriority binaryPriority[] = { - {6, 6}, {6, 6}, {7, 7}, {7, 7}, {7, 7}, // `+' `-' `*' `/' `%' - {10, 9}, {5, 4}, // power and concat (right associative) - {3, 3}, {3, 3}, // equality and inequality - {3, 3}, {3, 3}, {3, 3}, {3, 3}, // order - {2, 2}, {1, 1} // logical (and/or) + {6, 6}, {6, 6}, {7, 7}, {7, 7}, {7, 7}, {7, 7}, // `+' `-' `*' `/' `//' `%' + {10, 9}, {5, 4}, // power and concat (right associative) + {3, 3}, {3, 3}, // equality and inequality + {3, 3}, {3, 3}, {3, 3}, {3, 3}, // order + {2, 2}, {1, 1} // logical (and/or) }; + static_assert(sizeof(binaryPriority) / sizeof(binaryPriority[0]) == size_t(AstExprBinary::Op__Count), "binaryPriority needs an entry per op"); unsigned int recursionCounterOld = recursionCounter; diff --git a/CodeGen/include/Luau/AssemblyBuilderA64.h b/CodeGen/include/Luau/AssemblyBuilderA64.h index 3fc37d1d..931003b3 100644 --- a/CodeGen/include/Luau/AssemblyBuilderA64.h +++ b/CodeGen/include/Luau/AssemblyBuilderA64.h @@ -221,6 +221,7 @@ private: void placeFMOV(const char* name, RegisterA64 dst, double src, uint32_t op); void placeBM(const char* name, RegisterA64 dst, RegisterA64 src1, uint32_t src2, uint8_t op); void placeBFM(const char* name, RegisterA64 dst, RegisterA64 src1, int src2, uint8_t op, int immr, int imms); + void placeER(const char* name, RegisterA64 dst, RegisterA64 src1, RegisterA64 src2, uint8_t op, int shift); void place(uint32_t word); diff --git a/CodeGen/include/Luau/IrData.h b/CodeGen/include/Luau/IrData.h index e8e56d19..12465906 100644 --- a/CodeGen/include/Luau/IrData.h +++ b/CodeGen/include/Luau/IrData.h @@ -67,6 +67,7 @@ enum class IrCmd : uint8_t // Get pointer (LuaNode) to table node element at the active cached slot index // A: pointer (Table) // B: unsigned int (pcpos) + // C: Kn GET_SLOT_NODE_ADDR, // Get pointer (LuaNode) to table node element at the main position of the specified key hash @@ -132,6 +133,7 @@ enum class IrCmd : uint8_t SUB_NUM, MUL_NUM, DIV_NUM, + IDIV_NUM, MOD_NUM, // Get the minimum/maximum of two numbers @@ -253,6 +255,11 @@ enum class IrCmd : uint8_t // A: pointer (Table) DUP_TABLE, + // Insert an integer key into a table + // A: pointer (Table) + // B: int (key) + TABLE_SETNUM, + // Try to convert a double number into a table index (int) or jump if it's not an integer // A: double // B: block @@ -411,6 +418,12 @@ enum class IrCmd : uint8_t // When undef is specified instead of a block, execution is aborted on check failure CHECK_NODE_NO_NEXT, + // Guard against table node with 'nil' value + // A: pointer (LuaNode) + // B: block/vmexit/undef + // When undef is specified instead of a block, execution is aborted on check failure + CHECK_NODE_VALUE, + // Special operations // Check interrupt handler @@ -832,6 +845,8 @@ struct IrBlock uint32_t finish = ~0u; uint32_t sortkey = ~0u; + uint32_t chainkey = 0; + uint32_t expectedNextBlock = ~0u; Label label; }; @@ -993,23 +1008,26 @@ struct IrFunction valueRestoreOps[instIdx] = location; } - IrOp findRestoreOp(uint32_t instIdx) const + IrOp findRestoreOp(uint32_t instIdx, bool limitToCurrentBlock) const { if (instIdx >= valueRestoreOps.size()) return {}; const IrBlock& block = blocks[validRestoreOpBlockIdx]; - // Values can only reference restore operands in the current block - if (instIdx < block.start || instIdx > block.finish) - return {}; + // When spilled, values can only reference restore operands in the current block + if (limitToCurrentBlock) + { + if (instIdx < block.start || instIdx > block.finish) + return {}; + } return valueRestoreOps[instIdx]; } - IrOp findRestoreOp(const IrInst& inst) const + IrOp findRestoreOp(const IrInst& inst, bool limitToCurrentBlock) const { - return findRestoreOp(getInstIndex(inst)); + return findRestoreOp(getInstIndex(inst), limitToCurrentBlock); } }; diff --git a/CodeGen/include/Luau/IrUtils.h b/CodeGen/include/Luau/IrUtils.h index 9c077914..3def51a8 100644 --- a/CodeGen/include/Luau/IrUtils.h +++ b/CodeGen/include/Luau/IrUtils.h @@ -128,6 +128,7 @@ inline bool isNonTerminatingJump(IrCmd cmd) case IrCmd::CHECK_ARRAY_SIZE: case IrCmd::CHECK_SLOT_MATCH: case IrCmd::CHECK_NODE_NO_NEXT: + case IrCmd::CHECK_NODE_VALUE: return true; default: break; @@ -156,6 +157,7 @@ inline bool hasResult(IrCmd cmd) case IrCmd::SUB_NUM: case IrCmd::MUL_NUM: case IrCmd::DIV_NUM: + case IrCmd::IDIV_NUM: case IrCmd::MOD_NUM: case IrCmd::MIN_NUM: case IrCmd::MAX_NUM: @@ -168,6 +170,7 @@ inline bool hasResult(IrCmd cmd) case IrCmd::NOT_ANY: case IrCmd::CMP_ANY: case IrCmd::TABLE_LEN: + case IrCmd::TABLE_SETNUM: case IrCmd::STRING_LEN: case IrCmd::NEW_TABLE: case IrCmd::DUP_TABLE: diff --git a/CodeGen/include/Luau/RegisterA64.h b/CodeGen/include/Luau/RegisterA64.h index d50369e3..beb34ca7 100644 --- a/CodeGen/include/Luau/RegisterA64.h +++ b/CodeGen/include/Luau/RegisterA64.h @@ -47,18 +47,6 @@ constexpr RegisterA64 castReg(KindA64 kind, RegisterA64 reg) return RegisterA64{kind, reg.index}; } -// This is equivalent to castReg(KindA64::x), but is separate because it implies different semantics -// Specifically, there are cases when it's useful to treat a wN register as an xN register *after* it has been assigned a value -// Since all A64 instructions that write to wN implicitly zero the top half, this works when we need zero extension semantics -// Crucially, this is *not* safe on an ABI boundary - an int parameter in wN register may have anything in its top half in certain cases -// However, as long as our codegen doesn't use 32-bit truncation by using castReg x=>w, we can safely rely on this. -constexpr RegisterA64 zextReg(RegisterA64 reg) -{ - LUAU_ASSERT(reg.kind == KindA64::w); - - return RegisterA64{KindA64::x, reg.index}; -} - constexpr RegisterA64 noreg{KindA64::none, 0}; constexpr RegisterA64 w0{KindA64::w, 0}; diff --git a/CodeGen/src/AssemblyBuilderA64.cpp b/CodeGen/src/AssemblyBuilderA64.cpp index e2e713ee..f385cd0a 100644 --- a/CodeGen/src/AssemblyBuilderA64.cpp +++ b/CodeGen/src/AssemblyBuilderA64.cpp @@ -105,7 +105,10 @@ void AssemblyBuilderA64::movk(RegisterA64 dst, uint16_t src, int shift) void AssemblyBuilderA64::add(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2, int shift) { - placeSR3("add", dst, src1, src2, 0b00'01011, shift); + if (src1.kind == KindA64::x && src2.kind == KindA64::w) + placeER("add", dst, src1, src2, 0b00'01011, shift); + else + placeSR3("add", dst, src1, src2, 0b00'01011, shift); } void AssemblyBuilderA64::add(RegisterA64 dst, RegisterA64 src1, uint16_t src2) @@ -115,7 +118,10 @@ void AssemblyBuilderA64::add(RegisterA64 dst, RegisterA64 src1, uint16_t src2) void AssemblyBuilderA64::sub(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2, int shift) { - placeSR3("sub", dst, src1, src2, 0b10'01011, shift); + if (src1.kind == KindA64::x && src2.kind == KindA64::w) + placeER("sub", dst, src1, src2, 0b10'01011, shift); + else + placeSR3("sub", dst, src1, src2, 0b10'01011, shift); } void AssemblyBuilderA64::sub(RegisterA64 dst, RegisterA64 src1, uint16_t src2) @@ -1075,6 +1081,22 @@ void AssemblyBuilderA64::placeBFM(const char* name, RegisterA64 dst, RegisterA64 commit(); } +void AssemblyBuilderA64::placeER(const char* name, RegisterA64 dst, RegisterA64 src1, RegisterA64 src2, uint8_t op, int shift) +{ + if (logText) + log(name, dst, src1, src2, shift); + + LUAU_ASSERT(dst.kind == KindA64::x && src1.kind == KindA64::x); + LUAU_ASSERT(src2.kind == KindA64::w); + LUAU_ASSERT(shift >= 0 && shift <= 4); + + uint32_t sf = (dst.kind == KindA64::x) ? 0x80000000 : 0; // could be useful in the future for byte->word extends + int option = 0b010; // UXTW + + place(dst.index | (src1.index << 5) | (shift << 10) | (option << 13) | (src2.index << 16) | (1 << 21) | (op << 24) | sf); + commit(); +} + void AssemblyBuilderA64::place(uint32_t word) { LUAU_ASSERT(codePos < codeEnd); @@ -1167,7 +1189,9 @@ void AssemblyBuilderA64::log(const char* opcode, RegisterA64 dst, RegisterA64 sr log(src1); text.append(","); log(src2); - if (shift > 0) + if (src1.kind == KindA64::x && src2.kind == KindA64::w) + logAppend(" UXTW #%d", shift); + else if (shift > 0) logAppend(" LSL #%d", shift); else if (shift < 0) logAppend(" LSR #%d", -shift); diff --git a/CodeGen/src/CodeAllocator.cpp b/CodeGen/src/CodeAllocator.cpp index fe45b9a4..84e48a1b 100644 --- a/CodeGen/src/CodeAllocator.cpp +++ b/CodeGen/src/CodeAllocator.cpp @@ -71,10 +71,12 @@ static uint8_t* allocatePagesImpl(size_t size) LUAU_ASSERT(size == alignToPageSize(size)); #ifdef __APPLE__ - return (uint8_t*)mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON | MAP_JIT, -1, 0); + void* result = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON | MAP_JIT, -1, 0); #else - return (uint8_t*)mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); + void* result = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); #endif + + return (result == MAP_FAILED) ? nullptr : static_cast(result); } static void freePagesImpl(uint8_t* mem, size_t size) diff --git a/CodeGen/src/CodeGenLower.h b/CodeGen/src/CodeGenLower.h index 171a8c0e..24e1c38c 100644 --- a/CodeGen/src/CodeGenLower.h +++ b/CodeGen/src/CodeGenLower.h @@ -74,7 +74,11 @@ inline bool lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction& return (a.kind == IrBlockKind::Fallback) < (b.kind == IrBlockKind::Fallback); // Try to order by instruction order - return a.sortkey < b.sortkey; + if (a.sortkey != b.sortkey) + return a.sortkey < b.sortkey; + + // Chains of blocks are merged together by having the same sort key and consecutive chain key + return a.chainkey < b.chainkey; }); // For each IR instruction that begins a bytecode instruction, which bytecode instruction is it? @@ -100,6 +104,9 @@ inline bool lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction& IrBlock dummy; dummy.start = ~0u; + // Make sure entry block is first + LUAU_ASSERT(sortedBlocks[0] == 0); + for (size_t i = 0; i < sortedBlocks.size(); ++i) { uint32_t blockIndex = sortedBlocks[i]; @@ -137,6 +144,11 @@ inline bool lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction& IrBlock& nextBlock = getNextBlock(function, sortedBlocks, dummy, i); + // Optimizations often propagate information between blocks + // To make sure the register and spill state is correct when blocks are lowered, we check that sorted block order matches the expected one + if (block.expectedNextBlock != ~0u) + LUAU_ASSERT(function.getBlockIndex(nextBlock) == block.expectedNextBlock); + for (uint32_t index = block.start; index <= block.finish; index++) { LUAU_ASSERT(index < function.instructions.size()); diff --git a/CodeGen/src/EmitCommonX64.cpp b/CodeGen/src/EmitCommonX64.cpp index c4da5467..c831c0bc 100644 --- a/CodeGen/src/EmitCommonX64.cpp +++ b/CodeGen/src/EmitCommonX64.cpp @@ -147,13 +147,14 @@ void callSetTable(IrRegAllocX64& regs, AssemblyBuilderX64& build, int rb, Operan emitUpdateBase(build); } -void checkObjectBarrierConditions(AssemblyBuilderX64& build, RegisterX64 tmp, RegisterX64 object, int ra, int ratag, Label& skip) +void checkObjectBarrierConditions(AssemblyBuilderX64& build, RegisterX64 tmp, RegisterX64 object, IrOp ra, int ratag, Label& skip) { // Barrier should've been optimized away if we know that it's not collectable, checking for correctness if (ratag == -1 || !isGCO(ratag)) { // iscollectable(ra) - build.cmp(luauRegTag(ra), LUA_TSTRING); + OperandX64 tag = (ra.kind == IrOpKind::VmReg) ? luauRegTag(vmRegOp(ra)) : luauConstantTag(vmConstOp(ra)); + build.cmp(tag, LUA_TSTRING); build.jcc(ConditionX64::Less, skip); } @@ -162,12 +163,14 @@ void checkObjectBarrierConditions(AssemblyBuilderX64& build, RegisterX64 tmp, Re build.jcc(ConditionX64::Zero, skip); // iswhite(gcvalue(ra)) - build.mov(tmp, luauRegValue(ra)); + OperandX64 value = (ra.kind == IrOpKind::VmReg) ? luauRegValue(vmRegOp(ra)) : luauConstantValue(vmConstOp(ra)); + build.mov(tmp, value); build.test(byte[tmp + offsetof(GCheader, marked)], bit2mask(WHITE0BIT, WHITE1BIT)); build.jcc(ConditionX64::Zero, skip); } -void callBarrierObject(IrRegAllocX64& regs, AssemblyBuilderX64& build, RegisterX64 object, IrOp objectOp, int ra, int ratag) + +void callBarrierObject(IrRegAllocX64& regs, AssemblyBuilderX64& build, RegisterX64 object, IrOp objectOp, IrOp ra, int ratag) { Label skip; diff --git a/CodeGen/src/EmitCommonX64.h b/CodeGen/src/EmitCommonX64.h index dd9b082b..3288a164 100644 --- a/CodeGen/src/EmitCommonX64.h +++ b/CodeGen/src/EmitCommonX64.h @@ -202,8 +202,8 @@ void callArithHelper(IrRegAllocX64& regs, AssemblyBuilderX64& build, int ra, int void callLengthHelper(IrRegAllocX64& regs, AssemblyBuilderX64& build, int ra, int rb); void callGetTable(IrRegAllocX64& regs, AssemblyBuilderX64& build, int rb, OperandX64 c, int ra); void callSetTable(IrRegAllocX64& regs, AssemblyBuilderX64& build, int rb, OperandX64 c, int ra); -void checkObjectBarrierConditions(AssemblyBuilderX64& build, RegisterX64 tmp, RegisterX64 object, int ra, int ratag, Label& skip); -void callBarrierObject(IrRegAllocX64& regs, AssemblyBuilderX64& build, RegisterX64 object, IrOp objectOp, int ra, int ratag); +void checkObjectBarrierConditions(AssemblyBuilderX64& build, RegisterX64 tmp, RegisterX64 object, IrOp ra, int ratag, Label& skip); +void callBarrierObject(IrRegAllocX64& regs, AssemblyBuilderX64& build, RegisterX64 object, IrOp objectOp, IrOp ra, int ratag); void callBarrierTableFast(IrRegAllocX64& regs, AssemblyBuilderX64& build, RegisterX64 table, IrOp tableOp); void callStepGc(IrRegAllocX64& regs, AssemblyBuilderX64& build); diff --git a/CodeGen/src/IrAnalysis.cpp b/CodeGen/src/IrAnalysis.cpp index eb4630dd..b14f1470 100644 --- a/CodeGen/src/IrAnalysis.cpp +++ b/CodeGen/src/IrAnalysis.cpp @@ -257,7 +257,7 @@ static void visitVmRegDefsUses(T& visitor, IrFunction& function, const IrBlock& break; case IrCmd::BARRIER_OBJ: case IrCmd::BARRIER_TABLE_FORWARD: - visitor.use(inst.b); + visitor.maybeUse(inst.b); break; case IrCmd::CLOSE_UPVALS: // Closing an upvalue should be counted as a register use (it copies the fresh register value) diff --git a/CodeGen/src/IrBuilder.cpp b/CodeGen/src/IrBuilder.cpp index aebc0ba7..3ee82c76 100644 --- a/CodeGen/src/IrBuilder.cpp +++ b/CodeGen/src/IrBuilder.cpp @@ -333,6 +333,9 @@ void IrBuilder::translateInst(LuauOpcode op, const Instruction* pc, int i) case LOP_DIV: translateInstBinary(*this, pc, i, TM_DIV); break; + case LOP_IDIV: + translateInstBinary(*this, pc, i, TM_IDIV); + break; case LOP_MOD: translateInstBinary(*this, pc, i, TM_MOD); break; @@ -351,6 +354,9 @@ void IrBuilder::translateInst(LuauOpcode op, const Instruction* pc, int i) case LOP_DIVK: translateInstBinaryK(*this, pc, i, TM_DIV); break; + case LOP_IDIVK: + translateInstBinaryK(*this, pc, i, TM_IDIV); + break; case LOP_MODK: translateInstBinaryK(*this, pc, i, TM_MOD); break; diff --git a/CodeGen/src/IrDump.cpp b/CodeGen/src/IrDump.cpp index 67f77b64..7ed1a295 100644 --- a/CodeGen/src/IrDump.cpp +++ b/CodeGen/src/IrDump.cpp @@ -125,6 +125,8 @@ const char* getCmdName(IrCmd cmd) return "MUL_NUM"; case IrCmd::DIV_NUM: return "DIV_NUM"; + case IrCmd::IDIV_NUM: + return "IDIV_NUM"; case IrCmd::MOD_NUM: return "MOD_NUM"; case IrCmd::MIN_NUM: @@ -169,6 +171,8 @@ const char* getCmdName(IrCmd cmd) return "JUMP_SLOT_MATCH"; case IrCmd::TABLE_LEN: return "TABLE_LEN"; + case IrCmd::TABLE_SETNUM: + return "TABLE_SETNUM"; case IrCmd::STRING_LEN: return "STRING_LEN"; case IrCmd::NEW_TABLE: @@ -229,6 +233,8 @@ const char* getCmdName(IrCmd cmd) return "CHECK_SLOT_MATCH"; case IrCmd::CHECK_NODE_NO_NEXT: return "CHECK_NODE_NO_NEXT"; + case IrCmd::CHECK_NODE_VALUE: + return "CHECK_NODE_VALUE"; case IrCmd::INTERRUPT: return "INTERRUPT"; case IrCmd::CHECK_GC: diff --git a/CodeGen/src/IrLoweringA64.cpp b/CodeGen/src/IrLoweringA64.cpp index d944a766..4369d120 100644 --- a/CodeGen/src/IrLoweringA64.cpp +++ b/CodeGen/src/IrLoweringA64.cpp @@ -58,30 +58,6 @@ inline ConditionA64 getConditionFP(IrCondition cond) } } -static void checkObjectBarrierConditions(AssemblyBuilderA64& build, RegisterA64 object, RegisterA64 temp, int ra, int ratag, Label& skip) -{ - RegisterA64 tempw = castReg(KindA64::w, temp); - - // Barrier should've been optimized away if we know that it's not collectable, checking for correctness - if (ratag == -1 || !isGCO(ratag)) - { - // iscollectable(ra) - build.ldr(tempw, mem(rBase, ra * sizeof(TValue) + offsetof(TValue, tt))); - build.cmp(tempw, LUA_TSTRING); - build.b(ConditionA64::Less, skip); - } - - // isblack(obj2gco(o)) - build.ldrb(tempw, mem(object, offsetof(GCheader, marked))); - build.tbz(tempw, BLACKBIT, skip); - - // iswhite(gcvalue(ra)) - build.ldr(temp, mem(rBase, ra * sizeof(TValue) + offsetof(TValue, value))); - build.ldrb(tempw, mem(temp, offsetof(GCheader, marked))); - build.tst(tempw, bit2mask(WHITE0BIT, WHITE1BIT)); - build.b(ConditionA64::Equal, skip); // Equal = Zero after tst -} - static void emitAddOffset(AssemblyBuilderA64& build, RegisterA64 dst, RegisterA64 src, size_t offset) { LUAU_ASSERT(dst != src); @@ -98,6 +74,47 @@ static void emitAddOffset(AssemblyBuilderA64& build, RegisterA64 dst, RegisterA6 } } +static void checkObjectBarrierConditions(AssemblyBuilderA64& build, RegisterA64 object, RegisterA64 temp, IrOp ra, int ratag, Label& skip) +{ + RegisterA64 tempw = castReg(KindA64::w, temp); + AddressA64 addr = temp; + + // iscollectable(ra) + if (ratag == -1 || !isGCO(ratag)) + { + if (ra.kind == IrOpKind::VmReg) + { + addr = mem(rBase, vmRegOp(ra) * sizeof(TValue) + offsetof(TValue, tt)); + } + else if (ra.kind == IrOpKind::VmConst) + { + emitAddOffset(build, temp, rConstants, vmConstOp(ra) * sizeof(TValue) + offsetof(TValue, tt)); + } + + build.ldr(tempw, addr); + build.cmp(tempw, LUA_TSTRING); + build.b(ConditionA64::Less, skip); + } + + // isblack(obj2gco(o)) + build.ldrb(tempw, mem(object, offsetof(GCheader, marked))); + build.tbz(tempw, BLACKBIT, skip); + + // iswhite(gcvalue(ra)) + if (ra.kind == IrOpKind::VmReg) + { + addr = mem(rBase, vmRegOp(ra) * sizeof(TValue) + offsetof(TValue, value)); + } + else if (ra.kind == IrOpKind::VmConst) + { + emitAddOffset(build, temp, rConstants, vmConstOp(ra) * sizeof(TValue) + offsetof(TValue, value)); + } + build.ldr(temp, addr); + build.ldrb(tempw, mem(temp, offsetof(GCheader, marked))); + build.tst(tempw, bit2mask(WHITE0BIT, WHITE1BIT)); + build.b(ConditionA64::Equal, skip); // Equal = Zero after tst +} + static void emitAbort(AssemblyBuilderA64& build, Label& abort) { Label skip; @@ -242,7 +259,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) if (inst.b.kind == IrOpKind::Inst) { - build.add(inst.regA64, inst.regA64, zextReg(regOp(inst.b)), kTValueSizeLog2); + build.add(inst.regA64, inst.regA64, regOp(inst.b), kTValueSizeLog2); } else if (inst.b.kind == IrOpKind::Constant) { @@ -271,6 +288,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) RegisterA64 temp1 = regs.allocTemp(KindA64::x); RegisterA64 temp1w = castReg(KindA64::w, temp1); RegisterA64 temp2 = regs.allocTemp(KindA64::w); + RegisterA64 temp2x = castReg(KindA64::x, temp2); // note: since the stride of the load is the same as the destination register size, we can range check the array index, not the byte offset if (uintOp(inst.b) <= AddressA64::kMaxOffset) @@ -288,7 +306,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) // note: this may clobber inst.a, so it's important that we don't use it after this build.ldr(inst.regA64, mem(regOp(inst.a), offsetof(Table, node))); - build.add(inst.regA64, inst.regA64, zextReg(temp2), kLuaNodeSizeLog2); + build.add(inst.regA64, inst.regA64, temp2x, kLuaNodeSizeLog2); // "zero extend" temp2 to get a larger shift (top 32 bits are zero) break; } case IrCmd::GET_HASH_NODE_ADDR: @@ -296,6 +314,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) inst.regA64 = regs.allocReuse(KindA64::x, index, {inst.a}); RegisterA64 temp1 = regs.allocTemp(KindA64::w); RegisterA64 temp2 = regs.allocTemp(KindA64::w); + RegisterA64 temp2x = castReg(KindA64::x, temp2); // hash & ((1 << lsizenode) - 1) == hash & ~(-1 << lsizenode) build.mov(temp1, -1); @@ -306,7 +325,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) // note: this may clobber inst.a, so it's important that we don't use it after this build.ldr(inst.regA64, mem(regOp(inst.a), offsetof(Table, node))); - build.add(inst.regA64, inst.regA64, zextReg(temp2), kLuaNodeSizeLog2); + build.add(inst.regA64, inst.regA64, temp2x, kLuaNodeSizeLog2); // "zero extend" temp2 to get a larger shift (top 32 bits are zero) break; } case IrCmd::GET_CLOSURE_UPVAL_ADDR: @@ -477,6 +496,15 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) build.fdiv(inst.regA64, temp1, temp2); break; } + case IrCmd::IDIV_NUM: + { + inst.regA64 = regs.allocReuse(KindA64::d, index, {inst.a, inst.b}); + RegisterA64 temp1 = tempDouble(inst.a); + RegisterA64 temp2 = tempDouble(inst.b); + build.fdiv(inst.regA64, temp1, temp2); + build.frintm(inst.regA64, inst.regA64); + break; + } case IrCmd::MOD_NUM: { inst.regA64 = regs.allocReg(KindA64::d, index); // can't allocReuse because both A and B are used twice @@ -604,9 +632,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) emitUpdateBase(build); - // since w0 came from a call, we need to move it so that we don't violate zextReg safety contract - inst.regA64 = regs.allocReg(KindA64::w, index); - build.mov(inst.regA64, w0); + inst.regA64 = regs.takeReg(w0, index); break; } case IrCmd::JUMP: @@ -750,8 +776,8 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) build.mov(x0, reg); build.ldr(x1, mem(rNativeContext, offsetof(NativeContext, luaH_getn))); build.blr(x1); - inst.regA64 = regs.allocReg(KindA64::d, index); - build.scvtf(inst.regA64, w0); + + inst.regA64 = regs.takeReg(w0, index); break; } case IrCmd::STRING_LEN: @@ -761,6 +787,33 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) build.ldr(inst.regA64, mem(regOp(inst.a), offsetof(TString, len))); break; } + case IrCmd::TABLE_SETNUM: + { + // note: we need to call regOp before spill so that we don't do redundant reloads + RegisterA64 table = regOp(inst.a); + RegisterA64 key = regOp(inst.b); + RegisterA64 temp = regs.allocTemp(KindA64::w); + + regs.spill(build, index, {table, key}); + + if (w1 != key) + { + build.mov(x1, table); + build.mov(w2, key); + } + else + { + build.mov(temp, w1); + build.mov(x1, table); + build.mov(w2, temp); + } + + build.mov(x0, rState); + build.ldr(x3, mem(rNativeContext, offsetof(NativeContext, luaH_setnum))); + build.blr(x3); + inst.regA64 = regs.takeReg(x0, index); + break; + } case IrCmd::NEW_TABLE: { regs.spill(build, index); @@ -854,8 +907,6 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) inst.regA64 = regs.allocReg(KindA64::w, index); RegisterA64 temp = tempDouble(inst.a); build.fcvtzs(castReg(KindA64::x, inst.regA64), temp); - // truncation needs to clear high bits to preserve zextReg safety contract - build.mov(inst.regA64, inst.regA64); break; } case IrCmd::ADJUST_STACK_TO_REG: @@ -870,7 +921,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) else if (inst.b.kind == IrOpKind::Inst) { build.add(temp, rBase, uint16_t(vmRegOp(inst.a) * sizeof(TValue))); - build.add(temp, temp, zextReg(regOp(inst.b)), kTValueSizeLog2); + build.add(temp, temp, regOp(inst.b), kTValueSizeLog2); build.str(temp, mem(rState, offsetof(lua_State, top))); } else @@ -919,9 +970,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) build.ldr(x6, mem(rNativeContext, offsetof(NativeContext, luauF_table) + uintOp(inst.a) * sizeof(luau_FastFunction))); build.blr(x6); - // since w0 came from a call, we need to move it so that we don't violate zextReg safety contract - inst.regA64 = regs.allocReg(KindA64::w, index); - build.mov(inst.regA64, w0); + inst.regA64 = regs.takeReg(w0, index); break; } case IrCmd::CHECK_FASTCALL_RES: @@ -1063,7 +1112,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) if (inst.c.kind == IrOpKind::Undef || isGCO(tagOp(inst.c))) { Label skip; - checkObjectBarrierConditions(build, temp1, temp2, vmRegOp(inst.b), inst.c.kind == IrOpKind::Undef ? -1 : tagOp(inst.c), skip); + checkObjectBarrierConditions(build, temp1, temp2, inst.b, inst.c.kind == IrOpKind::Undef ? -1 : tagOp(inst.c), skip); size_t spills = regs.spill(build, index, {temp1}); @@ -1244,6 +1293,17 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) finalizeTargetLabel(inst.b, fresh); break; } + case IrCmd::CHECK_NODE_VALUE: + { + Label fresh; // used when guard aborts execution or jumps to a VM exit + RegisterA64 temp = regs.allocTemp(KindA64::w); + + build.ldr(temp, mem(regOp(inst.a), offsetof(LuaNode, val.tt))); + LUAU_ASSERT(LUA_TNIL == 0); + build.cbz(temp, getTargetLabel(inst.b, fresh)); + finalizeTargetLabel(inst.b, fresh); + break; + } case IrCmd::INTERRUPT: { regs.spill(build, index); @@ -1288,7 +1348,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) RegisterA64 temp = regs.allocTemp(KindA64::x); Label skip; - checkObjectBarrierConditions(build, regOp(inst.a), temp, vmRegOp(inst.b), inst.c.kind == IrOpKind::Undef ? -1 : tagOp(inst.c), skip); + checkObjectBarrierConditions(build, regOp(inst.a), temp, inst.b, inst.c.kind == IrOpKind::Undef ? -1 : tagOp(inst.c), skip); RegisterA64 reg = regOp(inst.a); // note: we need to call regOp before spill so that we don't do redundant reloads size_t spills = regs.spill(build, index, {reg}); @@ -1332,13 +1392,14 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) RegisterA64 temp = regs.allocTemp(KindA64::x); Label skip; - checkObjectBarrierConditions(build, regOp(inst.a), temp, vmRegOp(inst.b), inst.c.kind == IrOpKind::Undef ? -1 : tagOp(inst.c), skip); + checkObjectBarrierConditions(build, regOp(inst.a), temp, inst.b, inst.c.kind == IrOpKind::Undef ? -1 : tagOp(inst.c), skip); RegisterA64 reg = regOp(inst.a); // note: we need to call regOp before spill so that we don't do redundant reloads + AddressA64 addr = tempAddr(inst.b, offsetof(TValue, value)); size_t spills = regs.spill(build, index, {reg}); build.mov(x1, reg); build.mov(x0, rState); - build.ldr(x2, mem(rBase, vmRegOp(inst.b) * sizeof(TValue) + offsetof(TValue, value))); + build.ldr(x2, addr); build.ldr(x3, mem(rNativeContext, offsetof(NativeContext, luaC_barriertable))); build.blr(x3); @@ -1829,7 +1890,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) LUAU_ASSERT(sizeof(TString*) == 8); if (inst.a.kind == IrOpKind::Inst) - build.add(inst.regA64, rGlobalState, zextReg(regOp(inst.a)), 3); + build.add(inst.regA64, rGlobalState, regOp(inst.a), 3); else if (inst.a.kind == IrOpKind::Constant) build.add(inst.regA64, rGlobalState, uint16_t(tagOp(inst.a)) * 8); else diff --git a/CodeGen/src/IrLoweringX64.cpp b/CodeGen/src/IrLoweringX64.cpp index 2a436d54..261f5717 100644 --- a/CodeGen/src/IrLoweringX64.cpp +++ b/CodeGen/src/IrLoweringX64.cpp @@ -407,6 +407,22 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) build.vdivsd(inst.regX64, regOp(inst.a), memRegDoubleOp(inst.b)); } break; + case IrCmd::IDIV_NUM: + inst.regX64 = regs.allocRegOrReuse(SizeX64::xmmword, index, {inst.a, inst.b}); + + if (inst.a.kind == IrOpKind::Constant) + { + ScopedRegX64 tmp{regs, SizeX64::xmmword}; + + build.vmovsd(tmp.reg, memRegDoubleOp(inst.a)); + build.vdivsd(inst.regX64, tmp.reg, memRegDoubleOp(inst.b)); + } + else + { + build.vdivsd(inst.regX64, regOp(inst.a), memRegDoubleOp(inst.b)); + } + build.vroundsd(inst.regX64, inst.regX64, inst.regX64, RoundingModeX64::RoundToNegativeInfinity); + break; case IrCmd::MOD_NUM: { inst.regX64 = regs.allocRegOrReuse(SizeX64::xmmword, index, {inst.a, inst.b}); @@ -697,9 +713,17 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) IrCallWrapperX64 callWrap(regs, build, index); callWrap.addArgument(SizeX64::qword, regOp(inst.a), inst.a); callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaH_getn)]); - - inst.regX64 = regs.allocReg(SizeX64::xmmword, index); - build.vcvtsi2sd(inst.regX64, inst.regX64, eax); + inst.regX64 = regs.takeReg(eax, index); + break; + } + case IrCmd::TABLE_SETNUM: + { + IrCallWrapperX64 callWrap(regs, build, index); + callWrap.addArgument(SizeX64::qword, rState); + callWrap.addArgument(SizeX64::qword, regOp(inst.a), inst.a); + callWrap.addArgument(SizeX64::dword, regOp(inst.b), inst.b); + callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaH_setnum)]); + inst.regX64 = regs.takeReg(rax, index); break; } case IrCmd::STRING_LEN: @@ -997,7 +1021,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) tmp1.free(); if (inst.c.kind == IrOpKind::Undef || isGCO(tagOp(inst.c))) - callBarrierObject(regs, build, tmp2.release(), {}, vmRegOp(inst.b), inst.c.kind == IrOpKind::Undef ? -1 : tagOp(inst.c)); + callBarrierObject(regs, build, tmp2.release(), {}, inst.b, inst.c.kind == IrOpKind::Undef ? -1 : tagOp(inst.c)); break; } case IrCmd::CHECK_TAG: @@ -1106,6 +1130,12 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) jumpOrAbortOnUndef(ConditionX64::NotZero, inst.b, next); break; } + case IrCmd::CHECK_NODE_VALUE: + { + build.cmp(dword[regOp(inst.a) + offsetof(LuaNode, val) + offsetof(TValue, tt)], LUA_TNIL); + jumpOrAbortOnUndef(ConditionX64::Equal, inst.b, next); + break; + } case IrCmd::INTERRUPT: { unsigned pcpos = uintOp(inst.a); @@ -1132,7 +1162,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) callStepGc(regs, build); break; case IrCmd::BARRIER_OBJ: - callBarrierObject(regs, build, regOp(inst.a), inst.a, vmRegOp(inst.b), inst.c.kind == IrOpKind::Undef ? -1 : tagOp(inst.c)); + callBarrierObject(regs, build, regOp(inst.a), inst.a, inst.b, inst.c.kind == IrOpKind::Undef ? -1 : tagOp(inst.c)); break; case IrCmd::BARRIER_TABLE_BACK: callBarrierTableFast(regs, build, regOp(inst.a), inst.a); @@ -1142,7 +1172,8 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) Label skip; ScopedRegX64 tmp{regs, SizeX64::qword}; - checkObjectBarrierConditions(build, tmp.reg, regOp(inst.a), vmRegOp(inst.b), inst.c.kind == IrOpKind::Undef ? -1 : tagOp(inst.c), skip); + + checkObjectBarrierConditions(build, tmp.reg, regOp(inst.a), inst.b, inst.c.kind == IrOpKind::Undef ? -1 : tagOp(inst.c), skip); { ScopedSpills spillGuard(regs); diff --git a/CodeGen/src/IrRegAllocA64.cpp b/CodeGen/src/IrRegAllocA64.cpp index 5afcc8dd..f552c17f 100644 --- a/CodeGen/src/IrRegAllocA64.cpp +++ b/CodeGen/src/IrRegAllocA64.cpp @@ -70,9 +70,9 @@ static int getReloadOffset(IrCmd cmd) LUAU_UNREACHABLE(); } -static AddressA64 getReloadAddress(const IrFunction& function, const IrInst& inst) +static AddressA64 getReloadAddress(const IrFunction& function, const IrInst& inst, bool limitToCurrentBlock) { - IrOp location = function.findRestoreOp(inst); + IrOp location = function.findRestoreOp(inst, limitToCurrentBlock); if (location.kind == IrOpKind::VmReg) return mem(rBase, vmRegOp(location) * sizeof(TValue) + getReloadOffset(inst.cmd)); @@ -99,7 +99,7 @@ static void restoreInst(AssemblyBuilderA64& build, uint32_t& freeSpillSlots, IrF else { LUAU_ASSERT(!inst.spilled && inst.needsReload); - AddressA64 addr = getReloadAddress(function, function.instructions[s.inst]); + AddressA64 addr = getReloadAddress(function, function.instructions[s.inst], /*limitToCurrentBlock*/ false); LUAU_ASSERT(addr.base != xzr); build.ldr(reg, addr); } @@ -321,7 +321,7 @@ size_t IrRegAllocA64::spill(AssemblyBuilderA64& build, uint32_t index, std::init { // instead of spilling the register to never reload it, we assume the register is not needed anymore } - else if (getReloadAddress(function, def).base != xzr) + else if (getReloadAddress(function, def, /*limitToCurrentBlock*/ true).base != xzr) { // instead of spilling the register to stack, we can reload it from VM stack/constants // we still need to record the spill for restore(start) to work diff --git a/CodeGen/src/IrRegAllocX64.cpp b/CodeGen/src/IrRegAllocX64.cpp index 091def39..7690f69a 100644 --- a/CodeGen/src/IrRegAllocX64.cpp +++ b/CodeGen/src/IrRegAllocX64.cpp @@ -338,7 +338,9 @@ unsigned IrRegAllocX64::findSpillStackSlot(IrValueKind valueKind) IrOp IrRegAllocX64::getRestoreOp(const IrInst& inst) const { - if (IrOp location = function.findRestoreOp(inst); location.kind == IrOpKind::VmReg || location.kind == IrOpKind::VmConst) + // When restoring the value, we allow cross-block restore because we have commited to the target location at spill time + if (IrOp location = function.findRestoreOp(inst, /*limitToCurrentBlock*/ false); + location.kind == IrOpKind::VmReg || location.kind == IrOpKind::VmConst) return location; return IrOp(); @@ -346,11 +348,16 @@ IrOp IrRegAllocX64::getRestoreOp(const IrInst& inst) const bool IrRegAllocX64::hasRestoreOp(const IrInst& inst) const { - return getRestoreOp(inst).kind != IrOpKind::None; + // When checking if value has a restore operation to spill it, we only allow it in the same block + IrOp location = function.findRestoreOp(inst, /*limitToCurrentBlock*/ true); + + return location.kind == IrOpKind::VmReg || location.kind == IrOpKind::VmConst; } OperandX64 IrRegAllocX64::getRestoreAddress(const IrInst& inst, IrOp restoreOp) { + LUAU_ASSERT(restoreOp.kind != IrOpKind::None); + switch (getCmdValueKind(inst.cmd)) { case IrValueKind::Unknown: diff --git a/CodeGen/src/IrTranslateBuiltins.cpp b/CodeGen/src/IrTranslateBuiltins.cpp index 8392ad84..8513f786 100644 --- a/CodeGen/src/IrTranslateBuiltins.cpp +++ b/CodeGen/src/IrTranslateBuiltins.cpp @@ -748,6 +748,28 @@ static BuiltinImplResult translateBuiltinVector(IrBuilder& build, int nparams, i return {BuiltinImplType::Full, 1}; } +static BuiltinImplResult translateBuiltinTableInsert(IrBuilder& build, int nparams, int ra, int arg, IrOp args, int nresults, int pcpos) +{ + if (nparams != 2 || nresults > 0) + return {BuiltinImplType::None, -1}; + + build.loadAndCheckTag(build.vmReg(arg), LUA_TTABLE, build.vmExit(pcpos)); + + IrOp table = build.inst(IrCmd::LOAD_POINTER, build.vmReg(arg)); + build.inst(IrCmd::CHECK_READONLY, table, build.vmExit(pcpos)); + + IrOp pos = build.inst(IrCmd::ADD_INT, build.inst(IrCmd::TABLE_LEN, table), build.constInt(1)); + + IrOp setnum = build.inst(IrCmd::TABLE_SETNUM, table, pos); + + IrOp va = build.inst(IrCmd::LOAD_TVALUE, args); + build.inst(IrCmd::STORE_TVALUE, setnum, va); + + build.inst(IrCmd::BARRIER_TABLE_FORWARD, table, args, build.undef()); + + return {BuiltinImplType::Full, 0}; +} + static BuiltinImplResult translateBuiltinStringLen(IrBuilder& build, int nparams, int ra, int arg, IrOp args, int nresults, int pcpos) { if (nparams < 1 || nresults > 1) @@ -849,6 +871,8 @@ BuiltinImplResult translateBuiltin(IrBuilder& build, int bfid, int ra, int arg, return translateBuiltinTypeof(build, nparams, ra, arg, args, nresults); case LBF_VECTOR: return translateBuiltinVector(build, nparams, ra, arg, args, nresults, pcpos); + case LBF_TABLE_INSERT: + return translateBuiltinTableInsert(build, nparams, ra, arg, args, nresults, pcpos); case LBF_STRING_LEN: return translateBuiltinStringLen(build, nparams, ra, arg, args, nresults, pcpos); default: diff --git a/CodeGen/src/IrTranslation.cpp b/CodeGen/src/IrTranslation.cpp index 5a92132f..26ad727a 100644 --- a/CodeGen/src/IrTranslation.cpp +++ b/CodeGen/src/IrTranslation.cpp @@ -382,6 +382,9 @@ static void translateInstBinaryNumeric(IrBuilder& build, int ra, int rb, int rc, case TM_DIV: result = build.inst(IrCmd::DIV_NUM, vb, vc); break; + case TM_IDIV: + result = build.inst(IrCmd::IDIV_NUM, vb, vc); + break; case TM_MOD: result = build.inst(IrCmd::MOD_NUM, vb, vc); break; @@ -472,8 +475,9 @@ void translateInstLength(IrBuilder& build, const Instruction* pc, int pcpos) build.inst(IrCmd::CHECK_NO_METATABLE, vb, fallback); IrOp va = build.inst(IrCmd::TABLE_LEN, vb); + IrOp vai = build.inst(IrCmd::INT_TO_NUM, va); - build.inst(IrCmd::STORE_DOUBLE, build.vmReg(ra), va); + build.inst(IrCmd::STORE_DOUBLE, build.vmReg(ra), vai); build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TNUMBER)); IrOp next = build.blockAtInst(pcpos + 1); @@ -554,7 +558,7 @@ IrOp translateFastCallN(IrBuilder& build, const Instruction* pc, int pcpos, bool IrOp builtinArgs = args; - if (customArgs.kind == IrOpKind::VmConst) + if (customArgs.kind == IrOpKind::VmConst && bfid != LBF_TABLE_INSERT) { TValue protok = build.function.proto->k[customArgs.index]; @@ -976,7 +980,7 @@ void translateInstGetTableKS(IrBuilder& build, const Instruction* pc, int pcpos) IrOp vb = build.inst(IrCmd::LOAD_POINTER, build.vmReg(rb)); - IrOp addrSlotEl = build.inst(IrCmd::GET_SLOT_NODE_ADDR, vb, build.constUint(pcpos)); + IrOp addrSlotEl = build.inst(IrCmd::GET_SLOT_NODE_ADDR, vb, build.constUint(pcpos), build.vmConst(aux)); build.inst(IrCmd::CHECK_SLOT_MATCH, addrSlotEl, build.vmConst(aux), fallback); @@ -1003,7 +1007,7 @@ void translateInstSetTableKS(IrBuilder& build, const Instruction* pc, int pcpos) IrOp vb = build.inst(IrCmd::LOAD_POINTER, build.vmReg(rb)); - IrOp addrSlotEl = build.inst(IrCmd::GET_SLOT_NODE_ADDR, vb, build.constUint(pcpos)); + IrOp addrSlotEl = build.inst(IrCmd::GET_SLOT_NODE_ADDR, vb, build.constUint(pcpos), build.vmConst(aux)); build.inst(IrCmd::CHECK_SLOT_MATCH, addrSlotEl, build.vmConst(aux), fallback); build.inst(IrCmd::CHECK_READONLY, vb, fallback); @@ -1028,7 +1032,7 @@ void translateInstGetGlobal(IrBuilder& build, const Instruction* pc, int pcpos) IrOp fallback = build.block(IrBlockKind::Fallback); IrOp env = build.inst(IrCmd::LOAD_ENV); - IrOp addrSlotEl = build.inst(IrCmd::GET_SLOT_NODE_ADDR, env, build.constUint(pcpos)); + IrOp addrSlotEl = build.inst(IrCmd::GET_SLOT_NODE_ADDR, env, build.constUint(pcpos), build.vmConst(aux)); build.inst(IrCmd::CHECK_SLOT_MATCH, addrSlotEl, build.vmConst(aux), fallback); @@ -1050,7 +1054,7 @@ void translateInstSetGlobal(IrBuilder& build, const Instruction* pc, int pcpos) IrOp fallback = build.block(IrBlockKind::Fallback); IrOp env = build.inst(IrCmd::LOAD_ENV); - IrOp addrSlotEl = build.inst(IrCmd::GET_SLOT_NODE_ADDR, env, build.constUint(pcpos)); + IrOp addrSlotEl = build.inst(IrCmd::GET_SLOT_NODE_ADDR, env, build.constUint(pcpos), build.vmConst(aux)); build.inst(IrCmd::CHECK_SLOT_MATCH, addrSlotEl, build.vmConst(aux), fallback); build.inst(IrCmd::CHECK_READONLY, env, fallback); @@ -1141,7 +1145,7 @@ void translateInstNamecall(IrBuilder& build, const Instruction* pc, int pcpos) build.loadAndCheckTag(indexPtr, LUA_TTABLE, fallback); IrOp index = build.inst(IrCmd::LOAD_POINTER, indexPtr); - IrOp addrIndexNodeEl = build.inst(IrCmd::GET_SLOT_NODE_ADDR, index, build.constUint(pcpos)); + IrOp addrIndexNodeEl = build.inst(IrCmd::GET_SLOT_NODE_ADDR, index, build.constUint(pcpos), build.vmConst(aux)); build.inst(IrCmd::CHECK_SLOT_MATCH, addrIndexNodeEl, build.vmConst(aux), fallback); // TODO: original 'table' was clobbered by a call inside 'FASTGETTM' diff --git a/CodeGen/src/IrUtils.cpp b/CodeGen/src/IrUtils.cpp index e51dca99..07704388 100644 --- a/CodeGen/src/IrUtils.cpp +++ b/CodeGen/src/IrUtils.cpp @@ -54,6 +54,7 @@ IrValueKind getCmdValueKind(IrCmd cmd) case IrCmd::SUB_NUM: case IrCmd::MUL_NUM: case IrCmd::DIV_NUM: + case IrCmd::IDIV_NUM: case IrCmd::MOD_NUM: case IrCmd::MIN_NUM: case IrCmd::MAX_NUM: @@ -79,7 +80,9 @@ IrValueKind getCmdValueKind(IrCmd cmd) case IrCmd::JUMP_SLOT_MATCH: return IrValueKind::None; case IrCmd::TABLE_LEN: - return IrValueKind::Double; + return IrValueKind::Int; + case IrCmd::TABLE_SETNUM: + return IrValueKind::Pointer; case IrCmd::STRING_LEN: return IrValueKind::Int; case IrCmd::NEW_TABLE: @@ -119,6 +122,7 @@ IrValueKind getCmdValueKind(IrCmd cmd) case IrCmd::CHECK_ARRAY_SIZE: case IrCmd::CHECK_SLOT_MATCH: case IrCmd::CHECK_NODE_NO_NEXT: + case IrCmd::CHECK_NODE_VALUE: case IrCmd::INTERRUPT: case IrCmd::CHECK_GC: case IrCmd::BARRIER_OBJ: @@ -464,6 +468,10 @@ void foldConstants(IrBuilder& build, IrFunction& function, IrBlock& block, uint3 if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant) substitute(function, inst, build.constDouble(function.doubleOp(inst.a) / function.doubleOp(inst.b))); break; + case IrCmd::IDIV_NUM: + if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant) + substitute(function, inst, build.constDouble(luai_numidiv(function.doubleOp(inst.a), function.doubleOp(inst.b)))); + break; case IrCmd::MOD_NUM: if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant) substitute(function, inst, build.constDouble(luai_nummod(function.doubleOp(inst.a), function.doubleOp(inst.b)))); diff --git a/CodeGen/src/IrValueLocationTracking.cpp b/CodeGen/src/IrValueLocationTracking.cpp index c32d718c..04c210d9 100644 --- a/CodeGen/src/IrValueLocationTracking.cpp +++ b/CodeGen/src/IrValueLocationTracking.cpp @@ -108,13 +108,14 @@ void IrValueLocationTracking::beforeInstLowering(IrInst& inst) case IrCmd::FINDUPVAL: break; - // These instrucitons read VmReg only after optimizeMemoryOperandsX64 + // These instructions read VmReg only after optimizeMemoryOperandsX64 case IrCmd::CHECK_TAG: case IrCmd::CHECK_TRUTHY: case IrCmd::ADD_NUM: case IrCmd::SUB_NUM: case IrCmd::MUL_NUM: case IrCmd::DIV_NUM: + case IrCmd::IDIV_NUM: case IrCmd::MOD_NUM: case IrCmd::MIN_NUM: case IrCmd::MAX_NUM: diff --git a/CodeGen/src/NativeState.cpp b/CodeGen/src/NativeState.cpp index 13ef33d3..6f567a2b 100644 --- a/CodeGen/src/NativeState.cpp +++ b/CodeGen/src/NativeState.cpp @@ -53,6 +53,7 @@ void initFunctions(NativeState& data) data.context.luaH_new = luaH_new; data.context.luaH_clone = luaH_clone; data.context.luaH_resizearray = luaH_resizearray; + data.context.luaH_setnum = luaH_setnum; data.context.luaC_barriertable = luaC_barriertable; data.context.luaC_barrierf = luaC_barrierf; diff --git a/CodeGen/src/NativeState.h b/CodeGen/src/NativeState.h index 85b7a3a3..f0b8561c 100644 --- a/CodeGen/src/NativeState.h +++ b/CodeGen/src/NativeState.h @@ -44,6 +44,7 @@ struct NativeContext Table* (*luaH_new)(lua_State* L, int narray, int lnhash) = nullptr; Table* (*luaH_clone)(lua_State* L, Table* tt) = nullptr; void (*luaH_resizearray)(lua_State* L, Table* t, int nasize) = nullptr; + TValue* (*luaH_setnum)(lua_State* L, Table* t, int key); void (*luaC_barriertable)(lua_State* L, Table* t, GCObject* v) = nullptr; void (*luaC_barrierf)(lua_State* L, GCObject* o, GCObject* v) = nullptr; diff --git a/CodeGen/src/OptimizeConstProp.cpp b/CodeGen/src/OptimizeConstProp.cpp index 4b09d423..03c26cdd 100644 --- a/CodeGen/src/OptimizeConstProp.cpp +++ b/CodeGen/src/OptimizeConstProp.cpp @@ -13,7 +13,10 @@ #include LUAU_FASTINTVARIABLE(LuauCodeGenMinLinearBlockPath, 3) +LUAU_FASTINTVARIABLE(LuauCodeGenReuseSlotLimit, 64) LUAU_FASTFLAGVARIABLE(DebugLuauAbortingChecks, false) +LUAU_FASTFLAGVARIABLE(LuauReuseHashSlots2, false) +LUAU_FASTFLAGVARIABLE(LuauKeepVmapLinear, false) namespace Luau { @@ -174,6 +177,10 @@ struct ConstPropState { for (int i = 0; i <= maxReg; ++i) invalidateHeap(regs[i]); + + // If table memory has changed, we can't reuse previously computed and validated table slot lookups + getSlotNodeCache.clear(); + checkSlotMatchCache.clear(); } void invalidateHeap(RegisterInfo& reg) @@ -190,6 +197,21 @@ struct ConstPropState inSafeEnv = false; } + void invalidateTableArraySize() + { + for (int i = 0; i <= maxReg; ++i) + invalidateTableArraySize(regs[i]); + + // If table memory has changed, we can't reuse previously computed and validated table slot lookups + getSlotNodeCache.clear(); + checkSlotMatchCache.clear(); + } + + void invalidateTableArraySize(RegisterInfo& reg) + { + reg.knownTableArraySize = -1; + } + void createRegLink(uint32_t instIdx, IrOp regOp) { LUAU_ASSERT(!instLink.contains(instIdx)); @@ -367,6 +389,8 @@ struct ConstPropState instLink.clear(); valueMap.clear(); + getSlotNodeCache.clear(); + checkSlotMatchCache.clear(); } IrFunction& function; @@ -384,6 +408,9 @@ struct ConstPropState DenseHashMap instLink{~0u}; DenseHashMap valueMap; + + std::vector getSlotNodeCache; + std::vector checkSlotMatchCache; }; static void handleBuiltinEffects(ConstPropState& state, LuauBuiltinFunction bfid, uint32_t firstReturnReg, int nresults) @@ -863,7 +890,25 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& case IrCmd::NOP: case IrCmd::LOAD_ENV: case IrCmd::GET_ARR_ADDR: + break; case IrCmd::GET_SLOT_NODE_ADDR: + if (!FFlag::LuauReuseHashSlots2) + break; + + for (uint32_t prevIdx : state.getSlotNodeCache) + { + const IrInst& prev = function.instructions[prevIdx]; + + if (prev.a == inst.a && prev.c == inst.c) + { + substitute(function, inst, IrOp{IrOpKind::Inst, prevIdx}); + return; // Break out from both the loop and the switch + } + } + + if (int(state.getSlotNodeCache.size()) < FInt::LuauCodeGenReuseSlotLimit) + state.getSlotNodeCache.push_back(index); + break; case IrCmd::GET_HASH_NODE_ADDR: case IrCmd::GET_CLOSURE_UPVAL_ADDR: break; @@ -873,6 +918,7 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& case IrCmd::SUB_NUM: case IrCmd::MUL_NUM: case IrCmd::DIV_NUM: + case IrCmd::IDIV_NUM: case IrCmd::MOD_NUM: case IrCmd::MIN_NUM: case IrCmd::MAX_NUM: @@ -892,6 +938,10 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& case IrCmd::JUMP_EQ_POINTER: case IrCmd::JUMP_SLOT_MATCH: case IrCmd::TABLE_LEN: + break; + case IrCmd::TABLE_SETNUM: + state.invalidateTableArraySize(); + break; case IrCmd::STRING_LEN: case IrCmd::NEW_TABLE: case IrCmd::DUP_TABLE: @@ -938,7 +988,26 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& break; } case IrCmd::CHECK_SLOT_MATCH: + if (!FFlag::LuauReuseHashSlots2) + break; + + for (uint32_t prevIdx : state.checkSlotMatchCache) + { + const IrInst& prev = function.instructions[prevIdx]; + + if (prev.a == inst.a && prev.b == inst.b) + { + // Only a check for 'nil' value is left + replace(function, block, index, {IrCmd::CHECK_NODE_VALUE, inst.a, inst.c}); + return; // Break out from both the loop and the switch + } + } + + if (int(state.checkSlotMatchCache.size()) < FInt::LuauCodeGenReuseSlotLimit) + state.checkSlotMatchCache.push_back(index); + break; case IrCmd::CHECK_NODE_NO_NEXT: + case IrCmd::CHECK_NODE_VALUE: case IrCmd::BARRIER_TABLE_BACK: case IrCmd::RETURN: case IrCmd::COVERAGE: @@ -999,7 +1068,10 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& if (RegisterInfo* info = state.tryGetRegisterInfo(inst.b); info && info->knownTableArraySize >= 0) replace(function, inst.f, build.constUint(info->knownTableArraySize)); - state.valueMap.clear(); // TODO: this can be relaxed when x64 emitInstSetList becomes aware of register allocator + // TODO: this can be relaxed when x64 emitInstSetList becomes aware of register allocator + state.valueMap.clear(); + state.getSlotNodeCache.clear(); + state.checkSlotMatchCache.clear(); break; case IrCmd::CALL: state.invalidateRegistersFrom(vmRegOp(inst.a)); @@ -1012,7 +1084,11 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& break; case IrCmd::FORGLOOP: state.invalidateRegistersFrom(vmRegOp(inst.a) + 2); // Rn and Rn+1 are not modified - state.valueMap.clear(); // TODO: this can be relaxed when x64 emitInstForGLoop becomes aware of register allocator + + // TODO: this can be relaxed when x64 emitInstForGLoop becomes aware of register allocator + state.valueMap.clear(); + state.getSlotNodeCache.clear(); + state.checkSlotMatchCache.clear(); break; case IrCmd::FORGLOOP_FALLBACK: state.invalidateRegistersFrom(vmRegOp(inst.a) + 2); // Rn and Rn+1 are not modified @@ -1076,8 +1152,15 @@ static void constPropInBlock(IrBuilder& build, IrBlock& block, ConstPropState& s constPropInInst(state, build, function, block, inst, index); } - // Value numbering and load/store propagation is not performed between blocks - state.valueMap.clear(); + if (!FFlag::LuauKeepVmapLinear) + { + // Value numbering and load/store propagation is not performed between blocks + state.valueMap.clear(); + + // Same for table slot data propagation + state.getSlotNodeCache.clear(); + state.checkSlotMatchCache.clear(); + } } static void constPropInBlockChain(IrBuilder& build, std::vector& visited, IrBlock* block, ConstPropState& state) @@ -1086,6 +1169,9 @@ static void constPropInBlockChain(IrBuilder& build, std::vector& visite state.clear(); + const uint32_t startSortkey = block->sortkey; + uint32_t chainPos = 0; + while (block) { uint32_t blockIdx = function.getBlockIndex(*block); @@ -1094,19 +1180,40 @@ static void constPropInBlockChain(IrBuilder& build, std::vector& visite constPropInBlock(build, *block, state); + if (FFlag::LuauKeepVmapLinear) + { + // Value numbering and load/store propagation is not performed between blocks right now + // This is because cross-block value uses limit creation of linear block (restriction in collectDirectBlockJumpPath) + state.valueMap.clear(); + + // Same for table slot data propagation + state.getSlotNodeCache.clear(); + state.checkSlotMatchCache.clear(); + } + + // Blocks in a chain are guaranteed to follow each other + // We force that by giving all blocks the same sorting key, but consecutive chain keys + block->sortkey = startSortkey; + block->chainkey = chainPos++; + IrInst& termInst = function.instructions[block->finish]; IrBlock* nextBlock = nullptr; // Unconditional jump into a block with a single user (current block) allows us to continue optimization // with the information we have gathered so far (unless we have already visited that block earlier) - if (termInst.cmd == IrCmd::JUMP && termInst.a.kind != IrOpKind::VmExit) + if (termInst.cmd == IrCmd::JUMP && termInst.a.kind == IrOpKind::Block) { IrBlock& target = function.blockOp(termInst.a); uint32_t targetIdx = function.getBlockIndex(target); if (target.useCount == 1 && !visited[targetIdx] && target.kind != IrBlockKind::Fallback) + { + // Make sure block ordering guarantee is checked at lowering time + block->expectedNextBlock = function.getBlockIndex(target); + nextBlock = ⌖ + } } block = nextBlock; @@ -1134,7 +1241,7 @@ static std::vector collectDirectBlockJumpPath(IrFunction& function, st IrBlock* nextBlock = nullptr; // A chain is made from internal blocks that were not a part of bytecode CFG - if (termInst.cmd == IrCmd::JUMP && termInst.a.kind != IrOpKind::VmExit) + if (termInst.cmd == IrCmd::JUMP && termInst.a.kind == IrOpKind::Block) { IrBlock& target = function.blockOp(termInst.a); uint32_t targetIdx = function.getBlockIndex(target); @@ -1175,8 +1282,8 @@ static void tryCreateLinearBlock(IrBuilder& build, std::vector& visited if (termInst.cmd != IrCmd::JUMP) return; - // And it can't be jump to a VM exit - if (termInst.a.kind == IrOpKind::VmExit) + // And it can't be jump to a VM exit or undef + if (termInst.a.kind != IrOpKind::Block) return; // And it has to jump to a block with more than one user @@ -1196,14 +1303,14 @@ static void tryCreateLinearBlock(IrBuilder& build, std::vector& visited // Initialize state with the knowledge of our current block state.clear(); - // TODO: using values from the first block can cause 'live out' of the linear block predecessor to not have all required registers constPropInBlock(build, startingBlock, state); // Verify that target hasn't changed LUAU_ASSERT(function.instructions[startingBlock.finish].a.index == targetBlockIdx); // Note: using startingBlock after this line is unsafe as the reference may be reallocated by build.block() below - uint32_t startingInsn = startingBlock.start; + const uint32_t startingSortKey = startingBlock.sortkey; + const uint32_t startingChainKey = startingBlock.chainkey; // Create new linearized block into which we are going to redirect starting block jump IrOp newBlock = build.block(IrBlockKind::Linearized); @@ -1213,7 +1320,11 @@ static void tryCreateLinearBlock(IrBuilder& build, std::vector& visited // By default, blocks are ordered according to start instruction; we alter sort order to make sure linearized block is placed right after the // starting block - function.blocks[newBlock.index].sortkey = startingInsn + 1; + function.blocks[newBlock.index].sortkey = startingSortKey; + function.blocks[newBlock.index].chainkey = startingChainKey + 1; + + // Make sure block ordering guarantee is checked at lowering time + function.blocks[blockIdx].expectedNextBlock = newBlock.index; replace(function, termInst.a, newBlock); @@ -1252,6 +1363,12 @@ static void tryCreateLinearBlock(IrBuilder& build, std::vector& visited def.varargStart = pathDef.varargStart; } } + + // Update predecessors + function.cfg.predecessorsOffsets.push_back(uint32_t(function.cfg.predecessors.size())); + function.cfg.predecessors.push_back(blockIdx); + + // Updating successors will require visiting the instructions again and we don't have a current use for linearized block successor list } // Optimize our linear block diff --git a/CodeGen/src/OptimizeFinalX64.cpp b/CodeGen/src/OptimizeFinalX64.cpp index 63642c46..3f0469fa 100644 --- a/CodeGen/src/OptimizeFinalX64.cpp +++ b/CodeGen/src/OptimizeFinalX64.cpp @@ -58,6 +58,7 @@ static void optimizeMemoryOperandsX64(IrFunction& function, IrBlock& block) case IrCmd::SUB_NUM: case IrCmd::MUL_NUM: case IrCmd::DIV_NUM: + case IrCmd::IDIV_NUM: case IrCmd::MOD_NUM: case IrCmd::MIN_NUM: case IrCmd::MAX_NUM: diff --git a/Common/include/Luau/Bytecode.h b/Common/include/Luau/Bytecode.h index 976dd04f..36b570ce 100644 --- a/Common/include/Luau/Bytecode.h +++ b/Common/include/Luau/Bytecode.h @@ -44,7 +44,7 @@ // Version 1: Baseline version for the open-source release. Supported until 0.521. // Version 2: Adds Proto::linedefined. Supported until 0.544. // Version 3: Adds FORGPREP/JUMPXEQK* and enhances AUX encoding for FORGLOOP. Removes FORGLOOP_NEXT/INEXT and JUMPIFEQK/JUMPIFNOTEQK. Currently supported. -// Version 4: Adds Proto::flags and typeinfo. Currently supported. +// Version 4: Adds Proto::flags, typeinfo, and floor division opcodes IDIV/IDIVK. Currently supported. // Bytecode opcode, part of the instruction header enum LuauOpcode @@ -390,6 +390,18 @@ enum LuauOpcode LOP_JUMPXEQKN, LOP_JUMPXEQKS, + // IDIV: compute floor division between two source registers and put the result into target register + // A: target register + // B: source register 1 + // C: source register 2 + LOP_IDIV, + + // IDIVK compute floor division between the source register and a constant and put the result into target register + // A: target register + // B: source register + // C: constant table index (0..255) + LOP_IDIVK, + // Enum entry for number of opcodes, not a valid opcode by itself! LOP__COUNT }; diff --git a/Compiler/include/Luau/BytecodeBuilder.h b/Compiler/include/Luau/BytecodeBuilder.h index 48b89404..f5098d17 100644 --- a/Compiler/include/Luau/BytecodeBuilder.h +++ b/Compiler/include/Luau/BytecodeBuilder.h @@ -15,7 +15,6 @@ class BytecodeEncoder public: virtual ~BytecodeEncoder() {} - virtual uint8_t encodeOp(uint8_t op) = 0; virtual void encode(uint32_t* data, size_t count) = 0; }; diff --git a/Compiler/src/BytecodeBuilder.cpp b/Compiler/src/BytecodeBuilder.cpp index 96754569..4ebed69a 100644 --- a/Compiler/src/BytecodeBuilder.cpp +++ b/Compiler/src/BytecodeBuilder.cpp @@ -8,7 +8,8 @@ #include LUAU_FASTFLAGVARIABLE(BytecodeVersion4, false) -LUAU_FASTFLAGVARIABLE(BytecodeEnc, false) + +LUAU_FASTFLAG(LuauFloorDivision) namespace Luau { @@ -238,7 +239,7 @@ void BytecodeBuilder::endFunction(uint8_t maxstacksize, uint8_t numupvalues, uin // very approximate: 4 bytes per instruction for code, 1 byte for debug line, and 1-2 bytes for aux data like constants plus overhead func.data.reserve(32 + insns.size() * 7); - if (FFlag::BytecodeEnc && encoder) + if (encoder) encoder->encode(insns.data(), insns.size()); writeFunction(func.data, currentFunction, flags); @@ -625,29 +626,8 @@ void BytecodeBuilder::writeFunction(std::string& ss, uint32_t id, uint8_t flags) // instructions writeVarInt(ss, uint32_t(insns.size())); - if (encoder && !FFlag::BytecodeEnc) - { - for (size_t i = 0; i < insns.size();) - { - uint8_t op = LUAU_INSN_OP(insns[i]); - LUAU_ASSERT(op < LOP__COUNT); - - int oplen = getOpLength(LuauOpcode(op)); - uint8_t openc = encoder->encodeOp(op); - - writeInt(ss, openc | (insns[i] & ~0xff)); - - for (int j = 1; j < oplen; ++j) - writeInt(ss, insns[i + j]); - - i += oplen; - } - } - else - { - for (uint32_t insn : insns) - writeInt(ss, insn); - } + for (uint32_t insn : insns) + writeInt(ss, insn); // constants writeVarInt(ss, uint32_t(constants.size())); @@ -1306,8 +1286,11 @@ void BytecodeBuilder::validateInstructions() const case LOP_SUB: case LOP_MUL: case LOP_DIV: + case LOP_IDIV: case LOP_MOD: case LOP_POW: + LUAU_ASSERT(FFlag::LuauFloorDivision || op != LOP_IDIV); + VREG(LUAU_INSN_A(insn)); VREG(LUAU_INSN_B(insn)); VREG(LUAU_INSN_C(insn)); @@ -1317,8 +1300,11 @@ void BytecodeBuilder::validateInstructions() const case LOP_SUBK: case LOP_MULK: case LOP_DIVK: + case LOP_IDIVK: case LOP_MODK: case LOP_POWK: + LUAU_ASSERT(FFlag::LuauFloorDivision || op != LOP_IDIVK); + VREG(LUAU_INSN_A(insn)); VREG(LUAU_INSN_B(insn)); VCONST(LUAU_INSN_C(insn), Number); @@ -1885,6 +1871,12 @@ void BytecodeBuilder::dumpInstruction(const uint32_t* code, std::string& result, formatAppend(result, "DIV R%d R%d R%d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn)); break; + case LOP_IDIV: + LUAU_ASSERT(FFlag::LuauFloorDivision); + + formatAppend(result, "IDIV R%d R%d R%d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn)); + break; + case LOP_MOD: formatAppend(result, "MOD R%d R%d R%d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn)); break; @@ -1917,6 +1909,14 @@ void BytecodeBuilder::dumpInstruction(const uint32_t* code, std::string& result, result.append("]\n"); break; + case LOP_IDIVK: + LUAU_ASSERT(FFlag::LuauFloorDivision); + + formatAppend(result, "IDIVK R%d R%d K%d [", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn)); + dumpConstant(result, LUAU_INSN_C(insn)); + result.append("]\n"); + break; + case LOP_MODK: formatAppend(result, "MODK R%d R%d K%d [", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn)); dumpConstant(result, LUAU_INSN_C(insn)); diff --git a/Compiler/src/Compiler.cpp b/Compiler/src/Compiler.cpp index f9a00f64..4fdf659b 100644 --- a/Compiler/src/Compiler.cpp +++ b/Compiler/src/Compiler.cpp @@ -26,6 +26,8 @@ LUAU_FASTINTVARIABLE(LuauCompileInlineThreshold, 25) LUAU_FASTINTVARIABLE(LuauCompileInlineThresholdMaxBoost, 300) LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5) +LUAU_FASTFLAG(LuauFloorDivision) + namespace Luau { @@ -1019,6 +1021,11 @@ struct Compiler case AstExprBinary::Div: return k ? LOP_DIVK : LOP_DIV; + case AstExprBinary::FloorDiv: + LUAU_ASSERT(FFlag::LuauFloorDivision); + + return k ? LOP_IDIVK : LOP_IDIV; + case AstExprBinary::Mod: return k ? LOP_MODK : LOP_MOD; @@ -1469,9 +1476,12 @@ struct Compiler case AstExprBinary::Sub: case AstExprBinary::Mul: case AstExprBinary::Div: + case AstExprBinary::FloorDiv: case AstExprBinary::Mod: case AstExprBinary::Pow: { + LUAU_ASSERT(FFlag::LuauFloorDivision || expr->op != AstExprBinary::FloorDiv); + int32_t rc = getConstantNumber(expr->right); if (rc >= 0 && rc <= 255) @@ -3192,9 +3202,12 @@ struct Compiler case AstExprBinary::Sub: case AstExprBinary::Mul: case AstExprBinary::Div: + case AstExprBinary::FloorDiv: case AstExprBinary::Mod: case AstExprBinary::Pow: { + LUAU_ASSERT(FFlag::LuauFloorDivision || stat->op != AstExprBinary::FloorDiv); + if (var.kind != LValue::Kind_Local) compileLValueUse(var, target, /* set= */ false); diff --git a/Compiler/src/ConstantFolding.cpp b/Compiler/src/ConstantFolding.cpp index a49a7748..5b098a11 100644 --- a/Compiler/src/ConstantFolding.cpp +++ b/Compiler/src/ConstantFolding.cpp @@ -104,6 +104,14 @@ static void foldBinary(Constant& result, AstExprBinary::Op op, const Constant& l } break; + case AstExprBinary::FloorDiv: + if (la.type == Constant::Type_Number && ra.type == Constant::Type_Number) + { + result.type = Constant::Type_Number; + result.valueNumber = floor(la.valueNumber / ra.valueNumber); + } + break; + case AstExprBinary::Mod: if (la.type == Constant::Type_Number && ra.type == Constant::Type_Number) { diff --git a/Config/src/Config.cpp b/Config/src/Config.cpp index 9369743e..8e9802cf 100644 --- a/Config/src/Config.cpp +++ b/Config/src/Config.cpp @@ -4,6 +4,7 @@ #include "Luau/Lexer.h" #include "Luau/StringUtils.h" +LUAU_FASTFLAG(LuauFloorDivision) namespace Luau { @@ -112,14 +113,23 @@ static void next(Lexer& lexer) lexer.next(); // skip C-style comments as Lexer only understands Lua-style comments atm - while (lexer.current().type == '/') + + if (FFlag::LuauFloorDivision) { - Lexeme peek = lexer.lookahead(); + while (lexer.current().type == Luau::Lexeme::FloorDiv) + lexer.nextline(); + } + else + { + while (lexer.current().type == '/') + { + Lexeme peek = lexer.lookahead(); - if (peek.type != '/' || peek.location.begin != lexer.current().location.end) - break; + if (peek.type != '/' || peek.location.begin != lexer.current().location.end) + break; - lexer.nextline(); + lexer.nextline(); + } } } diff --git a/LICENSE.txt b/LICENSE.txt index 40fc04c1..34496ced 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019-2022 Roblox Corporation +Copyright (c) 2019-2023 Roblox Corporation Copyright (c) 1994–2019 Lua.org, PUC-Rio. Permission is hereby granted, free of charge, to any person obtaining a copy of @@ -19,4 +19,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +SOFTWARE. diff --git a/Makefile b/Makefile index f0f008be..2e5e9791 100644 --- a/Makefile +++ b/Makefile @@ -255,6 +255,7 @@ $(BUILD)/fuzz/protoprint.cpp.o: fuzz/luau.pb.cpp build/libprotobuf-mutator: git clone https://github.com/google/libprotobuf-mutator build/libprotobuf-mutator + git -C build/libprotobuf-mutator checkout 212a7be1eb08e7f9c79732d2aab9b2097085d936 CXX= cmake -S build/libprotobuf-mutator -B build/libprotobuf-mutator $(DPROTOBUF) make -C build/libprotobuf-mutator -j8 diff --git a/VM/include/lua.h b/VM/include/lua.h index e49e6ad9..43c60d77 100644 --- a/VM/include/lua.h +++ b/VM/include/lua.h @@ -443,7 +443,7 @@ typedef struct lua_Callbacks lua_Callbacks; LUA_API lua_Callbacks* lua_callbacks(lua_State* L); /****************************************************************************** - * Copyright (c) 2019-2022 Roblox Corporation + * Copyright (c) 2019-2023 Roblox Corporation * Copyright (C) 1994-2008 Lua.org, PUC-Rio. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining diff --git a/VM/src/lapi.cpp b/VM/src/lapi.cpp index 054faa7c..2b98e47d 100644 --- a/VM/src/lapi.cpp +++ b/VM/src/lapi.cpp @@ -38,7 +38,7 @@ const char* lua_ident = "$Lua: Lua 5.1.4 Copyright (C) 1994-2008 Lua.org, PUC-Ri "$Authors: R. Ierusalimschy, L. H. de Figueiredo & W. Celes $\n" "$URL: www.lua.org $\n"; -const char* luau_ident = "$Luau: Copyright (C) 2019-2022 Roblox Corporation $\n" +const char* luau_ident = "$Luau: Copyright (C) 2019-2023 Roblox Corporation $\n" "$URL: luau-lang.org $\n"; #define api_checknelems(L, n) api_check(L, (n) <= (L->top - L->base)) diff --git a/VM/src/lnumutils.h b/VM/src/lnumutils.h index 5b27e2b8..38bfb322 100644 --- a/VM/src/lnumutils.h +++ b/VM/src/lnumutils.h @@ -40,6 +40,13 @@ inline double luai_nummod(double a, double b) } LUAU_FASTMATH_END +LUAU_FASTMATH_BEGIN +inline double luai_numidiv(double a, double b) +{ + return floor(a / b); +} +LUAU_FASTMATH_END + #define luai_num2int(i, d) ((i) = (int)(d)) // On MSVC in 32-bit, double to unsigned cast compiles into a call to __dtoui3, so we invoke x87->int64 conversion path manually diff --git a/VM/src/ltm.cpp b/VM/src/ltm.cpp index d753e8a4..d8c69a70 100644 --- a/VM/src/ltm.cpp +++ b/VM/src/ltm.cpp @@ -48,6 +48,7 @@ const char* const luaT_eventname[] = { "__sub", "__mul", "__div", + "__idiv", "__mod", "__pow", "__unm", diff --git a/VM/src/ltm.h b/VM/src/ltm.h index 4b1c2818..7dafd4ed 100644 --- a/VM/src/ltm.h +++ b/VM/src/ltm.h @@ -27,6 +27,7 @@ typedef enum TM_SUB, TM_MUL, TM_DIV, + TM_IDIV, TM_MOD, TM_POW, TM_UNM, diff --git a/VM/src/lvmexecute.cpp b/VM/src/lvmexecute.cpp index 0d5a53df..44bba9c0 100644 --- a/VM/src/lvmexecute.cpp +++ b/VM/src/lvmexecute.cpp @@ -103,7 +103,8 @@ VM_DISPATCH_OP(LOP_LOADKX), VM_DISPATCH_OP(LOP_JUMPX), VM_DISPATCH_OP(LOP_FASTCALL), VM_DISPATCH_OP(LOP_COVERAGE), \ VM_DISPATCH_OP(LOP_CAPTURE), VM_DISPATCH_OP(LOP_DEP_JUMPIFEQK), VM_DISPATCH_OP(LOP_DEP_JUMPIFNOTEQK), VM_DISPATCH_OP(LOP_FASTCALL1), \ VM_DISPATCH_OP(LOP_FASTCALL2), VM_DISPATCH_OP(LOP_FASTCALL2K), VM_DISPATCH_OP(LOP_FORGPREP), VM_DISPATCH_OP(LOP_JUMPXEQKNIL), \ - VM_DISPATCH_OP(LOP_JUMPXEQKB), VM_DISPATCH_OP(LOP_JUMPXEQKN), VM_DISPATCH_OP(LOP_JUMPXEQKS), + VM_DISPATCH_OP(LOP_JUMPXEQKB), VM_DISPATCH_OP(LOP_JUMPXEQKN), VM_DISPATCH_OP(LOP_JUMPXEQKS), VM_DISPATCH_OP(LOP_IDIV), \ + VM_DISPATCH_OP(LOP_IDIVK), #if defined(__GNUC__) || defined(__clang__) #define VM_USE_CGOTO 1 @@ -1660,6 +1661,54 @@ reentry: } } + VM_CASE(LOP_IDIV) + { + Instruction insn = *pc++; + StkId ra = VM_REG(LUAU_INSN_A(insn)); + StkId rb = VM_REG(LUAU_INSN_B(insn)); + StkId rc = VM_REG(LUAU_INSN_C(insn)); + + // fast-path + if (LUAU_LIKELY(ttisnumber(rb) && ttisnumber(rc))) + { + setnvalue(ra, luai_numidiv(nvalue(rb), nvalue(rc))); + VM_NEXT(); + } + else if (ttisvector(rb) && ttisnumber(rc)) + { + const float* vb = vvalue(rb); + float vc = cast_to(float, nvalue(rc)); + setvvalue(ra, float(luai_numidiv(vb[0], vc)), float(luai_numidiv(vb[1], vc)), float(luai_numidiv(vb[2], vc)), + float(luai_numidiv(vb[3], vc))); + VM_NEXT(); + } + else + { + // fast-path for userdata with C functions + StkId rbc = ttisnumber(rb) ? rc : rb; + const TValue* fn = 0; + if (ttisuserdata(rbc) && (fn = luaT_gettmbyobj(L, rbc, TM_IDIV)) && ttisfunction(fn) && clvalue(fn)->isC) + { + // note: it's safe to push arguments past top for complicated reasons (see top of the file) + LUAU_ASSERT(L->top + 3 < L->stack + L->stacksize); + StkId top = L->top; + setobj2s(L, top + 0, fn); + setobj2s(L, top + 1, rb); + setobj2s(L, top + 2, rc); + L->top = top + 3; + + VM_PROTECT(luaV_callTM(L, 2, LUAU_INSN_A(insn))); + VM_NEXT(); + } + else + { + // slow-path, may invoke C/Lua via metamethods + VM_PROTECT(luaV_doarith(L, ra, rb, rc, TM_IDIV)); + VM_NEXT(); + } + } + } + VM_CASE(LOP_MOD) { Instruction insn = *pc++; @@ -1838,6 +1887,53 @@ reentry: } } + VM_CASE(LOP_IDIVK) + { + Instruction insn = *pc++; + StkId ra = VM_REG(LUAU_INSN_A(insn)); + StkId rb = VM_REG(LUAU_INSN_B(insn)); + TValue* kv = VM_KV(LUAU_INSN_C(insn)); + + // fast-path + if (LUAU_LIKELY(ttisnumber(rb))) + { + setnvalue(ra, luai_numidiv(nvalue(rb), nvalue(kv))); + VM_NEXT(); + } + else if (ttisvector(rb)) + { + const float* vb = vvalue(rb); + float vc = cast_to(float, nvalue(kv)); + setvvalue(ra, float(luai_numidiv(vb[0], vc)), float(luai_numidiv(vb[1], vc)), float(luai_numidiv(vb[2], vc)), + float(luai_numidiv(vb[3], vc))); + VM_NEXT(); + } + else + { + // fast-path for userdata with C functions + const TValue* fn = 0; + if (ttisuserdata(rb) && (fn = luaT_gettmbyobj(L, rb, TM_IDIV)) && ttisfunction(fn) && clvalue(fn)->isC) + { + // note: it's safe to push arguments past top for complicated reasons (see top of the file) + LUAU_ASSERT(L->top + 3 < L->stack + L->stacksize); + StkId top = L->top; + setobj2s(L, top + 0, fn); + setobj2s(L, top + 1, rb); + setobj2s(L, top + 2, kv); + L->top = top + 3; + + VM_PROTECT(luaV_callTM(L, 2, LUAU_INSN_A(insn))); + VM_NEXT(); + } + else + { + // slow-path, may invoke C/Lua via metamethods + VM_PROTECT(luaV_doarith(L, ra, rb, kv, TM_IDIV)); + VM_NEXT(); + } + } + } + VM_CASE(LOP_MODK) { Instruction insn = *pc++; diff --git a/VM/src/lvmutils.cpp b/VM/src/lvmutils.cpp index b77207da..df30db2f 100644 --- a/VM/src/lvmutils.cpp +++ b/VM/src/lvmutils.cpp @@ -394,6 +394,9 @@ void luaV_doarith(lua_State* L, StkId ra, const TValue* rb, const TValue* rc, TM 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; @@ -410,7 +413,12 @@ void luaV_doarith(lua_State* L, StkId ra, const TValue* rb, const TValue* rc, TM } else { - // vector operations that we support: v + v, v - v, v * v, s * v, v * s, v / v, s / v, v / s, -v + // 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 = luaV_tovector(rb); const float* vc = luaV_tovector(rc); @@ -430,6 +438,10 @@ void luaV_doarith(lua_State* L, StkId ra, const TValue* rb, const TValue* rc, TM 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; @@ -453,6 +465,10 @@ void luaV_doarith(lua_State* L, StkId ra, const TValue* rb, const TValue* rc, TM 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; } @@ -474,6 +490,10 @@ void luaV_doarith(lua_State* L, StkId ra, const TValue* rb, const TValue* rc, TM 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; } diff --git a/bench/micro_tests/test_TableSort.lua b/bench/micro_tests/test_TableSort.lua new file mode 100644 index 00000000..80031d1c --- /dev/null +++ b/bench/micro_tests/test_TableSort.lua @@ -0,0 +1,22 @@ +local bench = script and require(script.Parent.bench_support) or require("bench_support") + +local arr_months = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"} + +local arr_num = {} +for i=1,100 do table.insert(arr_num, math.sin(i)) end + +local arr_numk = {} +for i=1,10000 do table.insert(arr_numk, math.sin(i)) end + +function test(arr) + local t = table.create(#arr) + + for i=1,1e6/#arr do + table.move(arr, 1, #arr, 1, t) + table.sort(t) + end +end + +bench.runCode(function() test(arr_months) end, "table.sort: 12 strings") +bench.runCode(function() test(arr_num) end, "table.sort: 100 numbers") +bench.runCode(function() test(arr_numk) end, "table.sort: 10k numbers") diff --git a/bench/tabulate.py b/bench/tabulate.py index fc034171..0b77209f 100644 --- a/bench/tabulate.py +++ b/bench/tabulate.py @@ -45,7 +45,7 @@ class TablePrinter(object): def _print_horizontal_separator(self): for i, align_width in enumerate(self._widths): if i > 0: - print('-+-', end='') + print('-|-', end='') print('-' * (align_width+1), end='') print() pass diff --git a/fuzz/luau.proto b/fuzz/luau.proto index e51d687b..5fed9ddc 100644 --- a/fuzz/luau.proto +++ b/fuzz/luau.proto @@ -135,17 +135,18 @@ message ExprBinary { Sub = 1; Mul = 2; Div = 3; - Mod = 4; - Pow = 5; - Concat = 6; - CompareNe = 7; - CompareEq = 8; - CompareLt = 9; - CompareLe = 10; - CompareGt = 11; - CompareGe = 12; - And = 13; - Or = 14; + FloorDiv = 4; + Mod = 5; + Pow = 6; + Concat = 7; + CompareNe = 8; + CompareEq = 9; + CompareLt = 10; + CompareLe = 11; + CompareGt = 12; + CompareGe = 13; + And = 14; + Or = 15; } required Op op = 1; diff --git a/fuzz/protoprint.cpp b/fuzz/protoprint.cpp index 5c7c5bf6..4eab2893 100644 --- a/fuzz/protoprint.cpp +++ b/fuzz/protoprint.cpp @@ -495,6 +495,8 @@ struct ProtoToLuau source += " * "; else if (expr.op() == luau::ExprBinary::Div) source += " / "; + else if (expr.op() == luau::ExprBinary::FloorDiv) + source += " // "; else if (expr.op() == luau::ExprBinary::Mod) source += " % "; else if (expr.op() == luau::ExprBinary::Pow) diff --git a/tests/AssemblyBuilderA64.test.cpp b/tests/AssemblyBuilderA64.test.cpp index ba8d40c2..56722c00 100644 --- a/tests/AssemblyBuilderA64.test.cpp +++ b/tests/AssemblyBuilderA64.test.cpp @@ -107,6 +107,13 @@ TEST_CASE_FIXTURE(AssemblyBuilderA64Fixture, "Binary") SINGLE_COMPARE(cmp(w0, 42), 0x7100A81F); } +TEST_CASE_FIXTURE(AssemblyBuilderA64Fixture, "BinaryExtended") +{ + // reg, reg + SINGLE_COMPARE(add(x0, x1, w2, 3), 0x8B224C20); + SINGLE_COMPARE(sub(x0, x1, w2, 3), 0xCB224C20); +} + TEST_CASE_FIXTURE(AssemblyBuilderA64Fixture, "BinaryImm") { // instructions @@ -524,6 +531,8 @@ TEST_CASE("LogTest") build.ldr(x0, mem(x1, 1, AddressKindA64::pre)); build.ldr(x0, mem(x1, 1, AddressKindA64::post)); + build.add(x1, x2, w3, 3); + build.setLabel(l); build.ret(); @@ -560,6 +569,7 @@ TEST_CASE("LogTest") ldr x0,[x1,#1] ldr x0,[x1,#1]! ldr x0,[x1]!,#1 + add x1,x2,w3 UXTW #3 .L1: ret )"; diff --git a/tests/Autocomplete.test.cpp b/tests/Autocomplete.test.cpp index fac23e88..6bfdc0f4 100644 --- a/tests/Autocomplete.test.cpp +++ b/tests/Autocomplete.test.cpp @@ -986,6 +986,33 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_end_with_lambda") CHECK_EQ(ac.context, AutocompleteContext::Statement); } +TEST_CASE_FIXTURE(ACFixture, "autocomplete_end_of_do_block") +{ + ScopedFastFlag sff{"LuauAutocompleteDoEnd", true}; + + check("do @1"); + + auto ac = autocomplete('1'); + + CHECK(ac.entryMap.count("end")); + + check(R"( + function f() + do + @1 + end + @2 + )"); + + ac = autocomplete('1'); + + CHECK(ac.entryMap.count("end")); + + ac = autocomplete('2'); + + CHECK(ac.entryMap.count("end")); +} + TEST_CASE_FIXTURE(ACFixture, "stop_at_first_stat_when_recommending_keywords") { check(R"( diff --git a/tests/Conformance.test.cpp b/tests/Conformance.test.cpp index a06138fc..75c38762 100644 --- a/tests/Conformance.test.cpp +++ b/tests/Conformance.test.cpp @@ -24,6 +24,7 @@ extern bool codegen; extern int optimizationLevel; LUAU_FASTFLAG(LuauPCallDebuggerFix); +LUAU_FASTFLAG(LuauFloorDivision); static lua_CompileOptions defaultOptions() { @@ -280,6 +281,7 @@ TEST_CASE("Assert") TEST_CASE("Basic") { + ScopedFastFlag sffs{"LuauFloorDivision", true}; runConformance("basic.lua"); } @@ -363,6 +365,7 @@ TEST_CASE("Errors") TEST_CASE("Events") { + ScopedFastFlag sffs{"LuauFloorDivision", true}; runConformance("events.lua"); } @@ -444,6 +447,8 @@ TEST_CASE("Pack") TEST_CASE("Vector") { + ScopedFastFlag sffs{"LuauFloorDivision", true}; + lua_CompileOptions copts = defaultOptions(); copts.vectorCtor = "vector"; @@ -1616,6 +1621,9 @@ static void pushInt64(lua_State* L, int64_t value) TEST_CASE("Userdata") { + + ScopedFastFlag sffs{"LuauFloorDivision", true}; + runConformance("userdata.lua", [](lua_State* L) { // create metatable with all the metamethods lua_newtable(L); @@ -1735,6 +1743,19 @@ TEST_CASE("Userdata") nullptr); lua_setfield(L, -2, "__div"); + // __idiv + lua_pushcfunction( + L, + [](lua_State* L) { + // for testing we use different semantics here compared to __div: __idiv rounds to negative inf, __div truncates (rounds to zero) + // additionally, division loses precision here outside of 2^53 range + // we do not necessarily recommend this behavior in production code! + pushInt64(L, int64_t(floor(double(getInt64(L, 1)) / double(getInt64(L, 2))))); + return 1; + }, + nullptr); + lua_setfield(L, -2, "__idiv"); + // __mod lua_pushcfunction( L, diff --git a/tests/IrBuilder.test.cpp b/tests/IrBuilder.test.cpp index dff13342..a736f5cf 100644 --- a/tests/IrBuilder.test.cpp +++ b/tests/IrBuilder.test.cpp @@ -5,6 +5,7 @@ #include "Luau/IrUtils.h" #include "Luau/OptimizeConstProp.h" #include "Luau/OptimizeFinalX64.h" +#include "ScopedFlags.h" #include "doctest.h" @@ -1930,6 +1931,135 @@ bb_0: )"); } +TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateHashSlotChecks") +{ + ScopedFastFlag luauReuseHashSlots{"LuauReuseHashSlots2", true}; + + IrOp block = build.block(IrBlockKind::Internal); + IrOp fallback = build.block(IrBlockKind::Fallback); + + build.beginBlock(block); + + // This roughly corresponds to 'return t.a + t.a' + IrOp table1 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(1)); + IrOp slot1 = build.inst(IrCmd::GET_SLOT_NODE_ADDR, table1, build.constUint(3), build.vmConst(1)); + build.inst(IrCmd::CHECK_SLOT_MATCH, slot1, build.vmConst(1), fallback); + IrOp value1 = build.inst(IrCmd::LOAD_TVALUE, slot1, build.constInt(0)); + build.inst(IrCmd::STORE_TVALUE, build.vmReg(3), value1); + + IrOp slot1b = build.inst(IrCmd::GET_SLOT_NODE_ADDR, table1, build.constUint(8), build.vmConst(1)); // This will be removed + build.inst(IrCmd::CHECK_SLOT_MATCH, slot1b, build.vmConst(1), fallback); // Key will be replaced with undef here + IrOp value1b = build.inst(IrCmd::LOAD_TVALUE, slot1b, build.constInt(0)); + build.inst(IrCmd::STORE_TVALUE, build.vmReg(4), value1b); + + IrOp a = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(3)); + IrOp b = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(4)); + IrOp sum = build.inst(IrCmd::ADD_NUM, a, b); + build.inst(IrCmd::STORE_DOUBLE, build.vmReg(2), sum); + + build.inst(IrCmd::RETURN, build.vmReg(2), build.constUint(1)); + + build.beginBlock(fallback); + build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1)); + + updateUseCounts(build.function); + constPropInBlockChains(build, true); + + // In the future, we might even see duplicate identical TValue loads go away + // In the future, we might even see loads of different VM regs with the same value go away + CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( +bb_0: + %0 = LOAD_POINTER R1 + %1 = GET_SLOT_NODE_ADDR %0, 3u, K1 + CHECK_SLOT_MATCH %1, K1, bb_fallback_1 + %3 = LOAD_TVALUE %1, 0i + STORE_TVALUE R3, %3 + CHECK_NODE_VALUE %1, bb_fallback_1 + %7 = LOAD_TVALUE %1, 0i + STORE_TVALUE R4, %7 + %9 = LOAD_DOUBLE R3 + %10 = LOAD_DOUBLE R4 + %11 = ADD_NUM %9, %10 + STORE_DOUBLE R2, %11 + RETURN R2, 1u + +bb_fallback_1: + RETURN R0, 1u + +)"); +} + +TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateHashSlotChecksAvoidNil") +{ + ScopedFastFlag luauReuseHashSlots{"LuauReuseHashSlots2", true}; + + IrOp block = build.block(IrBlockKind::Internal); + IrOp fallback = build.block(IrBlockKind::Fallback); + + build.beginBlock(block); + + IrOp table1 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(1)); + IrOp slot1 = build.inst(IrCmd::GET_SLOT_NODE_ADDR, table1, build.constUint(3), build.vmConst(1)); + build.inst(IrCmd::CHECK_SLOT_MATCH, slot1, build.vmConst(1), fallback); + IrOp value1 = build.inst(IrCmd::LOAD_TVALUE, slot1, build.constInt(0)); + build.inst(IrCmd::STORE_TVALUE, build.vmReg(3), value1); + + IrOp table2 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(2)); + IrOp slot2 = build.inst(IrCmd::GET_SLOT_NODE_ADDR, table2, build.constUint(6), build.vmConst(1)); + build.inst(IrCmd::CHECK_SLOT_MATCH, slot2, build.vmConst(1), fallback); + build.inst(IrCmd::CHECK_READONLY, table2, fallback); + + build.inst(IrCmd::STORE_TAG, build.vmReg(4), build.constTag(tnil)); + IrOp valueNil = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(4)); + build.inst(IrCmd::STORE_TVALUE, slot2, valueNil, build.constInt(0)); + + // In the future, we might get to track that value became 'nil' and that fallback will be taken + IrOp slot1b = build.inst(IrCmd::GET_SLOT_NODE_ADDR, table1, build.constUint(8), build.vmConst(1)); // This will be removed + build.inst(IrCmd::CHECK_SLOT_MATCH, slot1b, build.vmConst(1), fallback); // Key will be replaced with undef here + IrOp value1b = build.inst(IrCmd::LOAD_TVALUE, slot1b, build.constInt(0)); + build.inst(IrCmd::STORE_TVALUE, build.vmReg(3), value1b); + + IrOp slot2b = build.inst(IrCmd::GET_SLOT_NODE_ADDR, table2, build.constUint(11), build.vmConst(1)); // This will be removed + build.inst(IrCmd::CHECK_SLOT_MATCH, slot2b, build.vmConst(1), fallback); // Key will be replaced with undef here + build.inst(IrCmd::CHECK_READONLY, table2, fallback); + + build.inst(IrCmd::STORE_SPLIT_TVALUE, slot2b, build.constTag(tnumber), build.constDouble(1), build.constInt(0)); + + build.inst(IrCmd::RETURN, build.vmReg(3), build.constUint(2)); + + build.beginBlock(fallback); + build.inst(IrCmd::RETURN, build.vmReg(1), build.constUint(2)); + + updateUseCounts(build.function); + constPropInBlockChains(build, true); + + CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( +bb_0: + %0 = LOAD_POINTER R1 + %1 = GET_SLOT_NODE_ADDR %0, 3u, K1 + CHECK_SLOT_MATCH %1, K1, bb_fallback_1 + %3 = LOAD_TVALUE %1, 0i + STORE_TVALUE R3, %3 + %5 = LOAD_POINTER R2 + %6 = GET_SLOT_NODE_ADDR %5, 6u, K1 + CHECK_SLOT_MATCH %6, K1, bb_fallback_1 + CHECK_READONLY %5, bb_fallback_1 + STORE_TAG R4, tnil + %10 = LOAD_TVALUE R4 + STORE_TVALUE %6, %10, 0i + CHECK_NODE_VALUE %1, bb_fallback_1 + %14 = LOAD_TVALUE %1, 0i + STORE_TVALUE R3, %14 + CHECK_NODE_VALUE %6, bb_fallback_1 + STORE_SPLIT_TVALUE %6, tnumber, 1, 0i + RETURN R3, 2u + +bb_fallback_1: + RETURN R1, 2u + +)"); +} + TEST_SUITE_END(); TEST_SUITE_BEGIN("Analysis"); diff --git a/tests/Parser.test.cpp b/tests/Parser.test.cpp index 26a157e4..2492c4a0 100644 --- a/tests/Parser.test.cpp +++ b/tests/Parser.test.cpp @@ -2970,4 +2970,40 @@ TEST_CASE_FIXTURE(Fixture, "unfinished_string_literal_types_get_reported_but_par CHECK_EQ(result.root->body.size, 2); } +TEST_CASE_FIXTURE(Fixture, "do_block_with_no_end") +{ + ParseResult result = tryParse(R"( + do + )"); + + REQUIRE_EQ(1, result.errors.size()); + + AstStatBlock* stat0 = result.root->body.data[0]->as(); + REQUIRE(stat0); + + CHECK(!stat0->hasEnd); +} + +TEST_CASE_FIXTURE(Fixture, "parse_interpolated_string_with_lookahead_involved") +{ + ScopedFastFlag sff{"LuauLexerLookaheadRemembersBraceType", true}; + + ParseResult result = tryParse(R"( + local x = `{ {y} }` + )"); + + REQUIRE_MESSAGE(result.errors.empty(), result.errors[0].getMessage()); +} + +TEST_CASE_FIXTURE(Fixture, "parse_interpolated_string_with_lookahead_involved2") +{ + ScopedFastFlag sff{"LuauLexerLookaheadRemembersBraceType", true}; + + ParseResult result = tryParse(R"( + local x = `{ { y{} } }` + )"); + + REQUIRE_MESSAGE(result.errors.empty(), result.errors[0].getMessage()); +} + TEST_SUITE_END(); diff --git a/tests/Subtyping.test.cpp b/tests/Subtyping.test.cpp index e1fa0e5a..23d05f91 100644 --- a/tests/Subtyping.test.cpp +++ b/tests/Subtyping.test.cpp @@ -52,6 +52,35 @@ struct SubtypeFixture : Fixture return arena.addType(TableType{std::move(props), std::nullopt, {}, TableState::Sealed}); } + // `&` + TypeId meet(TypeId a, TypeId b) + { + return arena.addType(IntersectionType{{a, b}}); + } + + // `|` + TypeId join(TypeId a, TypeId b) + { + return arena.addType(UnionType{{a, b}}); + } + + TypeId negate(TypeId ty) + { + return arena.addType(NegationType{ty}); + } + + TypeId cls(const std::string& name, std::optional parent = std::nullopt) + { + return arena.addType(ClassType{name, {}, parent.value_or(builtinTypes->classType), {}, {}, nullptr, ""}); + } + + TypeId cls(const std::string& name, ClassType::Props&& props) + { + TypeId ty = cls(name); + getMutable(ty)->props = std::move(props); + return ty; + } + TypeId cyclicTable(std::function&& cb) { TypeId res = arena.addType(GenericType{}); @@ -61,6 +90,11 @@ struct SubtypeFixture : Fixture return res; } + TypeId meta(TableType::Props&& metaProps, TableType::Props&& tableProps = {}) + { + return arena.addType(MetatableType{tbl(std::move(tableProps)), tbl(std::move(metaProps))}); + } + TypeId genericT = arena.addType(GenericType{"T"}); TypeId genericU = arena.addType(GenericType{"U"}); @@ -77,8 +111,34 @@ struct SubtypeFixture : Fixture TypeId helloType2 = arena.addType(SingletonType{StringSingleton{"hello"}}); TypeId worldType = arena.addType(SingletonType{StringSingleton{"world"}}); - TypeId helloOrWorldType = arena.addType(UnionType{{helloType, worldType}}); - TypeId trueOrFalseType = arena.addType(UnionType{{builtinTypes->trueType, builtinTypes->falseType}}); + TypeId helloOrWorldType = join(helloType, worldType); + TypeId trueOrFalseType = join(builtinTypes->trueType, builtinTypes->falseType); + + TypeId helloAndWorldType = meet(helloType, worldType); + TypeId booleanAndTrueType = meet(builtinTypes->booleanType, builtinTypes->trueType); + + /** + * class + * \- Root + * |- Child + * | |-GrandchildOne + * | \-GrandchildTwo + * \- AnotherChild + * |- AnotherGrandchildOne + * \- AnotherGrandchildTwo + */ + TypeId rootClass = cls("Root"); + TypeId childClass = cls("Child", rootClass); + TypeId grandchildOneClass = cls("GrandchildOne", childClass); + TypeId grandchildTwoClass = cls("GrandchildTwo", childClass); + TypeId anotherChildClass = cls("AnotherChild", rootClass); + TypeId anotherGrandchildOneClass = cls("AnotherGrandchildOne", anotherChildClass); + TypeId anotherGrandchildTwoClass = cls("AnotherGrandchildTwo", anotherChildClass); + + TypeId vec2Class = cls("Vec2", { + {"X", builtinTypes->numberType}, + {"Y", builtinTypes->numberType}, + }); // "hello" | "hello" TypeId helloOrHelloType = arena.addType(UnionType{{helloType, helloType}}); @@ -86,12 +146,6 @@ struct SubtypeFixture : Fixture // () -> () const TypeId nothingToNothingType = fn({}, {}); - // ("hello") -> "world" - TypeId helloAndWorldType = arena.addType(IntersectionType{{helloType, worldType}}); - - // (boolean) -> true - TypeId booleanAndTrueType = arena.addType(IntersectionType{{builtinTypes->booleanType, builtinTypes->trueType}}); - // (number) -> string const TypeId numberToStringType = fn( {builtinTypes->numberType}, @@ -247,6 +301,11 @@ struct SubtypeFixture : Fixture builtinTypes->emptyTypePack, genericAs }); + + // { lower : string -> string } + TypeId tableWithLower = tbl(TableType::Props{{"lower", fn({builtinTypes->stringType}, {builtinTypes->stringType})}}); + // { insaneThingNoScalarHas : () -> () } + TypeId tableWithoutScalarProp = tbl(TableType::Props{{"insaneThingNoScalarHas", fn({}, {})}}); }; #define CHECK_IS_SUBTYPE(left, right) \ @@ -620,6 +679,99 @@ TEST_CASE_FIXTURE(SubtypeFixture, "{x: (T) -> ()} <: {x: (U) -> ()}") ); } +TEST_CASE_FIXTURE(SubtypeFixture, "{ @metatable { x: number } } <: { @metatable {} }") +{ + CHECK_IS_SUBTYPE( + meta({{"x", builtinTypes->numberType}}), + meta({}) + ); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "{ @metatable { x: number } } numberType}}), + meta({{"x", builtinTypes->booleanType}}) + ); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "{ @metatable {} } booleanType}}) + ); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "{ @metatable {} } <: {}") +{ + CHECK_IS_SUBTYPE( + meta({}), + tbl({}) + ); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "{ @metatable { u: boolean }, x: number } <: { x: number }") +{ + CHECK_IS_SUBTYPE( + meta({{"u", builtinTypes->booleanType}}, {{"x", builtinTypes->numberType}}), + tbl({{"x", builtinTypes->numberType}}) + ); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "{ @metatable { x: number } } numberType}}), + tbl({{"x", builtinTypes->numberType}}) + ); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "Root <: class") +{ + CHECK_IS_SUBTYPE(rootClass, builtinTypes->classType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "Child | AnotherChild <: class") +{ + CHECK_IS_SUBTYPE(join(childClass, anotherChildClass), builtinTypes->classType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "Child | AnotherChild <: Child | AnotherChild") +{ + CHECK_IS_SUBTYPE(join(childClass, anotherChildClass), join(childClass, anotherChildClass)); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "Child | Root <: Root") +{ + CHECK_IS_SUBTYPE(join(childClass, rootClass), rootClass); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "Child & AnotherChild <: class") +{ + CHECK_IS_SUBTYPE(meet(childClass, anotherChildClass), builtinTypes->classType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "Child & Root <: class") +{ + CHECK_IS_SUBTYPE(meet(childClass, rootClass), builtinTypes->classType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "Child & ~Root <: class") +{ + CHECK_IS_SUBTYPE(meet(childClass, negate(rootClass)), builtinTypes->classType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "Child & AnotherChild <: number") +{ + CHECK_IS_SUBTYPE(meet(childClass, anotherChildClass), builtinTypes->numberType); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "Child & ~GrandchildOne numberType); +} + TEST_CASE_FIXTURE(SubtypeFixture, "t1 where t1 = {trim: (t1) -> string} <: t2 where t2 = {trim: (t2) -> string}") { TypeId t1 = cyclicTable([&](TypeId ty, TableType* tt) @@ -665,6 +817,84 @@ TEST_CASE_FIXTURE(SubtypeFixture, "t1 where t1 = {trim: (t1) -> t1} numberType}, + {"Y", builtinTypes->numberType}, + }); + + CHECK_IS_SUBTYPE(vec2Class, xy); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "Vec2 <: { X: number }") +{ + TypeId x = tbl({ + {"X", builtinTypes->numberType}, + }); + + CHECK_IS_SUBTYPE(vec2Class, x); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "{ X: number, Y: number } numberType}, + {"Y", builtinTypes->numberType}, + }); + + CHECK_IS_NOT_SUBTYPE(xy, vec2Class); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "{ X: number } numberType}, + }); + + CHECK_IS_NOT_SUBTYPE(x, vec2Class); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "table & { X: number, Y: number } numberType}, + {"Y", builtinTypes->numberType}, + }); + + CHECK_IS_NOT_SUBTYPE(meet(builtinTypes->tableType, x), vec2Class); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "Vec2 numberType}, + {"Y", builtinTypes->numberType}, + }); + + CHECK_IS_NOT_SUBTYPE(vec2Class, meet(builtinTypes->tableType, xy)); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "\"hello\" <: { lower : (string) -> string }") +{ + CHECK_IS_SUBTYPE(helloType, tableWithLower); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "\"hello\" () }") +{ + CHECK_IS_NOT_SUBTYPE(helloType, tableWithoutScalarProp); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "string <: { lower : (string) -> string }") +{ + CHECK_IS_SUBTYPE(builtinTypes->stringType, tableWithLower); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "string () }") +{ + CHECK_IS_NOT_SUBTYPE(builtinTypes->stringType, tableWithoutScalarProp); +} + /* * (A) -> A <: (X) -> X * A can be bound to X. diff --git a/tests/ToString.test.cpp b/tests/ToString.test.cpp index b0592b4c..cb1576f8 100644 --- a/tests/ToString.test.cpp +++ b/tests/ToString.test.cpp @@ -833,14 +833,6 @@ TEST_CASE_FIXTURE(Fixture, "tostring_unsee_ttv_if_array") TEST_CASE_FIXTURE(Fixture, "tostring_error_mismatch") { - ScopedFastFlag sff[] = { - {"LuauIndentTypeMismatch", true}, - }; - - ScopedFastInt sfi[] = { - {"LuauIndentTypeMismatchMaxTypeLength", 10}, - }; - CheckResult result = check(R"( --!strict function f1() : {a : number, b : string, c : { d : number}} diff --git a/tests/Transpiler.test.cpp b/tests/Transpiler.test.cpp index 87f0b2b8..ae7a925c 100644 --- a/tests/Transpiler.test.cpp +++ b/tests/Transpiler.test.cpp @@ -529,14 +529,17 @@ until c CHECK_EQ(code, transpile(code, {}, true).code); } -TEST_CASE_FIXTURE(Fixture, "transpile_compound_assignmenr") +TEST_CASE_FIXTURE(Fixture, "transpile_compound_assignment") { + ScopedFastFlag sffs{"LuauFloorDivision", true}; + std::string code = R"( local a = 1 a += 2 a -= 3 a *= 4 a /= 5 +a //= 5 a %= 6 a ^= 7 a ..= ' - result' diff --git a/tests/TypeInfer.aliases.test.cpp b/tests/TypeInfer.aliases.test.cpp index bebc8942..29dcff83 100644 --- a/tests/TypeInfer.aliases.test.cpp +++ b/tests/TypeInfer.aliases.test.cpp @@ -189,9 +189,7 @@ TEST_CASE_FIXTURE(Fixture, "generic_aliases") { ScopedFastFlag sff[] = { {"DebugLuauDeferredConstraintResolution", true}, - {"LuauIndentTypeMismatch", true}, }; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( type T = { v: a } local x: T = { v = 123 } @@ -212,10 +210,7 @@ TEST_CASE_FIXTURE(Fixture, "dependent_generic_aliases") { ScopedFastFlag sff[] = { {"DebugLuauDeferredConstraintResolution", true}, - {"LuauIndentTypeMismatch", true}, }; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( type T = { v: a } diff --git a/tests/TypeInfer.builtins.test.cpp b/tests/TypeInfer.builtins.test.cpp index e64bf9e9..de29812b 100644 --- a/tests/TypeInfer.builtins.test.cpp +++ b/tests/TypeInfer.builtins.test.cpp @@ -134,9 +134,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "sort_with_bad_predicate") { ScopedFastFlag sff[] = { {"LuauAlwaysCommitInferencesOfFunctionCalls", true}, - {"LuauIndentTypeMismatch", true}, }; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( --!strict diff --git a/tests/TypeInfer.classes.test.cpp b/tests/TypeInfer.classes.test.cpp index 7055c27e..412ec1d0 100644 --- a/tests/TypeInfer.classes.test.cpp +++ b/tests/TypeInfer.classes.test.cpp @@ -369,9 +369,7 @@ TEST_CASE_FIXTURE(ClassFixture, "detailed_class_unification_error") { ScopedFastFlag sff[] = { {"LuauAlwaysCommitInferencesOfFunctionCalls", true}, - {"LuauIndentTypeMismatch", true}, }; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( local function foo(v) return v.X :: number + string.len(v.Y) @@ -457,8 +455,6 @@ TEST_CASE_FIXTURE(ClassFixture, "index_instance_property_nonstrict") TEST_CASE_FIXTURE(ClassFixture, "type_mismatch_invariance_required_for_error") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( type A = { x: ChildClass } type B = { x: BaseClass } diff --git a/tests/TypeInfer.functions.test.cpp b/tests/TypeInfer.functions.test.cpp index 1c46b91a..dbd3f258 100644 --- a/tests/TypeInfer.functions.test.cpp +++ b/tests/TypeInfer.functions.test.cpp @@ -1095,9 +1095,6 @@ TEST_CASE_FIXTURE(Fixture, "return_type_by_overload") TEST_CASE_FIXTURE(BuiltinsFixture, "infer_anonymous_function_arguments") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - // Simple direct arg to arg propagation CheckResult result = check(R"( type Table = { x: number, y: number } @@ -1342,8 +1339,6 @@ end TEST_CASE_FIXTURE(Fixture, "error_detailed_function_mismatch_arg_count") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( type A = (number, number) -> string type B = (number) -> string @@ -1364,9 +1359,6 @@ caused by: TEST_CASE_FIXTURE(Fixture, "error_detailed_function_mismatch_arg") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( type A = (number, number) -> string type B = (number, string) -> string @@ -1388,9 +1380,6 @@ Type 'string' could not be converted into 'number')"; TEST_CASE_FIXTURE(Fixture, "error_detailed_function_mismatch_ret_count") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( type A = (number, number) -> (number) type B = (number, number) -> (number, boolean) @@ -1411,9 +1400,6 @@ caused by: TEST_CASE_FIXTURE(Fixture, "error_detailed_function_mismatch_ret") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( type A = (number, number) -> string type B = (number, number) -> number @@ -1435,9 +1421,6 @@ Type 'string' could not be converted into 'number')"; TEST_CASE_FIXTURE(Fixture, "error_detailed_function_mismatch_ret_mult") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( type A = (number, number) -> (number, string) type B = (number, number) -> (number, boolean) @@ -1563,9 +1546,6 @@ TEST_CASE_FIXTURE(Fixture, "inferred_higher_order_functions_are_quantified_at_th TEST_CASE_FIXTURE(BuiltinsFixture, "function_decl_non_self_unsealed_overwrite") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( local t = { f = nil :: ((x: number) -> number)? } @@ -1608,9 +1588,6 @@ TEST_CASE_FIXTURE(Fixture, "strict_mode_ok_with_missing_arguments") TEST_CASE_FIXTURE(Fixture, "function_statement_sealed_table_assignment_through_indexer") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( local t: {[string]: () -> number} = {} @@ -1819,9 +1796,6 @@ foo(string.find("hello", "e")) TEST_CASE_FIXTURE(Fixture, "luau_subtyping_is_np_hard") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( --!strict @@ -2017,10 +1991,8 @@ TEST_CASE_FIXTURE(Fixture, "function_exprs_are_generalized_at_signature_scope_no TEST_CASE_FIXTURE(BuiltinsFixture, "param_1_and_2_both_takes_the_same_generic_but_their_arguments_are_incompatible") { ScopedFastFlag sff[] = { - {"LuauIndentTypeMismatch", true}, {"LuauAlwaysCommitInferencesOfFunctionCalls", true}, }; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( local function foo(x: a, y: a?) diff --git a/tests/TypeInfer.generics.test.cpp b/tests/TypeInfer.generics.test.cpp index f7904d45..ea62f0bf 100644 --- a/tests/TypeInfer.generics.test.cpp +++ b/tests/TypeInfer.generics.test.cpp @@ -713,9 +713,6 @@ end TEST_CASE_FIXTURE(Fixture, "generic_functions_should_be_memory_safe") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( --!strict -- At one point this produced a UAF diff --git a/tests/TypeInfer.intersectionTypes.test.cpp b/tests/TypeInfer.intersectionTypes.test.cpp index c285edf9..9e685881 100644 --- a/tests/TypeInfer.intersectionTypes.test.cpp +++ b/tests/TypeInfer.intersectionTypes.test.cpp @@ -317,9 +317,6 @@ TEST_CASE_FIXTURE(Fixture, "table_intersection_write_sealed") TEST_CASE_FIXTURE(Fixture, "table_intersection_write_sealed_indirect") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( type X = { x: (number) -> number } type Y = { y: (string) -> string } @@ -350,9 +347,6 @@ caused by: TEST_CASE_FIXTURE(Fixture, "table_write_sealed_indirect") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - // After normalization, previous 'table_intersection_write_sealed_indirect' is identical to this one CheckResult result = check(R"( type XY = { x: (number) -> number, y: (string) -> string } @@ -392,9 +386,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_intersection_setmetatable") TEST_CASE_FIXTURE(Fixture, "error_detailed_intersection_part") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( type X = { x: number } type Y = { y: number } @@ -482,9 +473,6 @@ TEST_CASE_FIXTURE(Fixture, "intersect_false_and_bool_and_false") TEST_CASE_FIXTURE(Fixture, "intersect_saturate_overloaded_functions") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( local x : ((number?) -> number?) & ((string?) -> string?) local y : (nil) -> nil = x -- OK @@ -501,8 +489,6 @@ could not be converted into TEST_CASE_FIXTURE(Fixture, "union_saturate_overloaded_functions") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( local x : ((number) -> number) & ((string) -> string) @@ -520,9 +506,6 @@ could not be converted into TEST_CASE_FIXTURE(Fixture, "intersection_of_tables") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( local x : { p : number?, q : string? } & { p : number?, q : number?, r : number? } local y : { p : number?, q : nil, r : number? } = x -- OK @@ -539,9 +522,6 @@ could not be converted into TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_top_properties") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( local x : { p : number?, q : any } & { p : unknown, q : string? } local y : { p : number?, q : string? } = x -- OK @@ -594,9 +574,6 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_never_properties") TEST_CASE_FIXTURE(Fixture, "overloaded_functions_returning_intersections") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( local x : ((number?) -> ({ p : number } & { q : number })) & ((string?) -> ({ p : number } & { r : number })) local y : (nil) -> { p : number, q : number, r : number} = x -- OK @@ -613,9 +590,6 @@ could not be converted into TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( function f() local x : ((number?) -> (a | number)) & ((string?) -> (a | string)) @@ -634,9 +608,6 @@ could not be converted into TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generics") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( function f() local x : ((a?) -> (a | b)) & ((c?) -> (b | c)) @@ -655,9 +626,6 @@ could not be converted into TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic_packs") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( function f() local x : ((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...)) @@ -676,9 +644,6 @@ could not be converted into TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_result") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( function f() local x : ((number) -> number) & ((nil) -> unknown) @@ -697,9 +662,6 @@ could not be converted into TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_arguments") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( function f() local x : ((number) -> number?) & ((unknown) -> string?) @@ -718,9 +680,6 @@ could not be converted into TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_result") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( function f() local x : ((number) -> number) & ((nil) -> never) @@ -739,9 +698,6 @@ could not be converted into TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_arguments") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( function f() local x : ((number) -> number?) & ((never) -> string?) @@ -760,9 +716,6 @@ could not be converted into TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_overlapping_results_and_variadics") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( local x : ((string?) -> (string | number)) & ((number?) -> ...number) local y : ((nil) -> (number, number?)) = x -- OK @@ -809,9 +762,6 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_2") TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_3") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( function f() local x : (() -> a...) & (() -> (number?,a...)) @@ -830,9 +780,6 @@ could not be converted into TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_4") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( function f() local x : ((a...) -> ()) & ((number,a...) -> number) diff --git a/tests/TypeInfer.modules.test.cpp b/tests/TypeInfer.modules.test.cpp index 7c3d4808..08291b44 100644 --- a/tests/TypeInfer.modules.test.cpp +++ b/tests/TypeInfer.modules.test.cpp @@ -389,9 +389,6 @@ type Table = typeof(tbl) TEST_CASE_FIXTURE(BuiltinsFixture, "module_type_conflict") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - fileResolver.source["game/A"] = R"( export type T = { x: number } return {} @@ -420,9 +417,6 @@ Type 'number' could not be converted into 'string' in an invariant context)"; TEST_CASE_FIXTURE(BuiltinsFixture, "module_type_conflict_instantiated") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - fileResolver.source["game/A"] = R"( export type Wrap = { x: T } return {} diff --git a/tests/TypeInfer.operators.test.cpp b/tests/TypeInfer.operators.test.cpp index 0b7a8311..6a551811 100644 --- a/tests/TypeInfer.operators.test.cpp +++ b/tests/TypeInfer.operators.test.cpp @@ -12,6 +12,8 @@ #include "doctest.h" +#include "ScopedFlags.h" + using namespace Luau; LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution) @@ -143,6 +145,24 @@ TEST_CASE_FIXTURE(Fixture, "some_primitive_binary_ops") CHECK_EQ("number", toString(requireType("c"))); } +TEST_CASE_FIXTURE(Fixture, "floor_division_binary_op") +{ + ScopedFastFlag sffs{"LuauFloorDivision", true}; + + CheckResult result = check(R"( + local a = 4 // 8 + local b = -4 // 9 + local c = 9 + c //= -6.5 + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + + CHECK_EQ("number", toString(requireType("a"))); + CHECK_EQ("number", toString(requireType("b"))); + CHECK_EQ("number", toString(requireType("c"))); +} + TEST_CASE_FIXTURE(BuiltinsFixture, "typecheck_overloaded_multiply_that_is_an_intersection") { CheckResult result = check(R"( diff --git a/tests/TypeInfer.provisional.test.cpp b/tests/TypeInfer.provisional.test.cpp index c33fe170..794171fb 100644 --- a/tests/TypeInfer.provisional.test.cpp +++ b/tests/TypeInfer.provisional.test.cpp @@ -787,9 +787,6 @@ TEST_CASE_FIXTURE(IsSubtypeFixture, "functions_with_mismatching_arity_but_any_is TEST_CASE_FIXTURE(Fixture, "assign_table_with_refined_property_with_a_similar_type_is_illegal") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( local t: {x: number?} = {x = nil} @@ -1050,4 +1047,42 @@ tbl:f3() } } +// Ideally, unification with any will not cause a 2^n normalization of a function overload +TEST_CASE_FIXTURE(BuiltinsFixture, "normalization_limit_in_unify_with_any") +{ + ScopedFastFlag sff[] = { + {"LuauTransitiveSubtyping", true}, + {"DebugLuauDeferredConstraintResolution", true}, + }; + + // With default limit, this test will take 10 seconds in NoOpt + ScopedFastInt luauNormalizeCacheLimit{"LuauNormalizeCacheLimit", 1000}; + + // Build a function type with a large overload set + const int parts = 100; + std::string source; + + for (int i = 0; i < parts; i++) + formatAppend(source, "type T%d = { f%d: number }\n", i, i); + + source += "type Instance = { new: (('s0', extra: Instance?) -> T0)"; + + for (int i = 1; i < parts; i++) + formatAppend(source, " & (('s%d', extra: Instance?) -> T%d)", i, i); + + source += " }\n"; + + source += R"( +local Instance: Instance = {} :: any + +local function foo(a: typeof(Instance.new)) return if a then 2 else 3 end + +foo(1 :: any) +)"; + + CheckResult result = check(source); + + LUAU_REQUIRE_ERRORS(result); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.singletons.test.cpp b/tests/TypeInfer.singletons.test.cpp index 2156d1c6..d085f900 100644 --- a/tests/TypeInfer.singletons.test.cpp +++ b/tests/TypeInfer.singletons.test.cpp @@ -316,9 +316,6 @@ TEST_CASE_FIXTURE(Fixture, "table_properties_type_error_escapes") TEST_CASE_FIXTURE(Fixture, "error_detailed_tagged_union_mismatch_string") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( type Cat = { tag: 'cat', catfood: string } type Dog = { tag: 'dog', dogfood: string } @@ -337,9 +334,6 @@ Table type 'a' not compatible with type 'Cat' because the former is missing fiel TEST_CASE_FIXTURE(Fixture, "error_detailed_tagged_union_mismatch_bool") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( type Good = { success: true, result: string } type Bad = { success: false, error: string } @@ -360,9 +354,7 @@ TEST_CASE_FIXTURE(Fixture, "parametric_tagged_union_alias") { ScopedFastFlag sff[] = { {"DebugLuauDeferredConstraintResolution", true}, - {"LuauIndentTypeMismatch", true}, }; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( type Ok = {success: true, result: T} type Err = {success: false, error: T} diff --git a/tests/TypeInfer.tables.test.cpp b/tests/TypeInfer.tables.test.cpp index ca137b15..315798c2 100644 --- a/tests/TypeInfer.tables.test.cpp +++ b/tests/TypeInfer.tables.test.cpp @@ -2083,8 +2083,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_should_cope_with_optional_prope TEST_CASE_FIXTURE(Fixture, "error_detailed_prop") { - ScopedFastFlag sff[] = {{"LuauIndentTypeMismatch", true}}; - ScopedFastInt sfi[] = {{"LuauIndentTypeMismatchMaxTypeLength", 10}}; CheckResult result = check(R"( type A = { x: number, y: number } type B = { x: number, y: string } @@ -2103,8 +2101,6 @@ Type 'number' could not be converted into 'string' in an invariant context)"; TEST_CASE_FIXTURE(Fixture, "error_detailed_prop_nested") { - ScopedFastFlag sff[] = {{"LuauIndentTypeMismatch", true}}; - ScopedFastInt sfi[] = {{"LuauIndentTypeMismatchMaxTypeLength", 10}}; CheckResult result = check(R"( type AS = { x: number, y: number } type BS = { x: number, y: string } @@ -2129,9 +2125,6 @@ Type 'number' could not be converted into 'string' in an invariant context)"; TEST_CASE_FIXTURE(BuiltinsFixture, "error_detailed_metatable_prop") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( local a1 = setmetatable({ x = 2, y = 3 }, { __call = function(s) end }); local b1 = setmetatable({ x = 2, y = "hello" }, { __call = function(s) end }); @@ -2202,8 +2195,6 @@ could not be converted into TEST_CASE_FIXTURE(Fixture, "error_detailed_indexer_key") { - ScopedFastFlag sff[] = {{"LuauIndentTypeMismatch", true}}; - ScopedFastInt sfi[] = {{"LuauIndentTypeMismatchMaxTypeLength", 10}}; CheckResult result = check(R"( type A = { [number]: string } type B = { [string]: string } @@ -2222,8 +2213,6 @@ Type 'number' could not be converted into 'string' in an invariant context)"; TEST_CASE_FIXTURE(Fixture, "error_detailed_indexer_value") { - ScopedFastFlag sff[] = {{"LuauIndentTypeMismatch", true}}; - ScopedFastInt sfi[] = {{"LuauIndentTypeMismatchMaxTypeLength", 10}}; CheckResult result = check(R"( type A = { [number]: number } type B = { [number]: string } @@ -2257,8 +2246,6 @@ a.p = { x = 9 } TEST_CASE_FIXTURE(Fixture, "explicitly_typed_table_error") { - ScopedFastFlag sff[] = {{"LuauIndentTypeMismatch", true}}; - ScopedFastInt sfi[] = {{"LuauIndentTypeMismatchMaxTypeLength", 10}}; CheckResult result = check(R"( --!strict type Super = { x : number } @@ -3359,14 +3346,10 @@ TEST_CASE_FIXTURE(Fixture, "scalar_is_a_subtype_of_a_compatible_polymorphic_shap TEST_CASE_FIXTURE(Fixture, "scalar_is_not_a_subtype_of_a_compatible_polymorphic_shape_type") { - ScopedFastInt sfi[] = {{"LuauIndentTypeMismatchMaxTypeLength", 10}}; ScopedFastFlag sff[] = { {"LuauAlwaysCommitInferencesOfFunctionCalls", true}, - {"LuauIndentTypeMismatch", true}, }; - - CheckResult result = check(R"( local function f(s) return s:absolutely_no_scalar_has_this_method() @@ -3422,8 +3405,6 @@ TEST_CASE_FIXTURE(Fixture, "a_free_shape_can_turn_into_a_scalar_if_it_is_compati TEST_CASE_FIXTURE(Fixture, "a_free_shape_cannot_turn_into_a_scalar_if_it_is_not_compatible") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( local function f(s): string diff --git a/tests/TypeInfer.test.cpp b/tests/TypeInfer.test.cpp index cb7429aa..8af5c684 100644 --- a/tests/TypeInfer.test.cpp +++ b/tests/TypeInfer.test.cpp @@ -992,9 +992,6 @@ end TEST_CASE_FIXTURE(Fixture, "cli_50041_committing_txnlog_in_apollo_client_error") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( --!strict --!nolint diff --git a/tests/TypeInfer.tryUnify.test.cpp b/tests/TypeInfer.tryUnify.test.cpp index 7910cf6d..1f0b04c6 100644 --- a/tests/TypeInfer.tryUnify.test.cpp +++ b/tests/TypeInfer.tryUnify.test.cpp @@ -345,9 +345,7 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "metatables_unify_against_shape_of_free_table { ScopedFastFlag sff[] = { {"LuauTransitiveSubtyping", true}, - {"LuauIndentTypeMismatch", true}, }; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; TableType::Props freeProps{ {"foo", {builtinTypes->numberType}}, diff --git a/tests/TypeInfer.typePacks.cpp b/tests/TypeInfer.typePacks.cpp index 37aaea7a..92d736f8 100644 --- a/tests/TypeInfer.typePacks.cpp +++ b/tests/TypeInfer.typePacks.cpp @@ -872,9 +872,6 @@ type R = { m: F } TEST_CASE_FIXTURE(Fixture, "pack_tail_unification_check") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; - CheckResult result = check(R"( local a: () -> (number, ...string) local b: () -> (number, ...boolean) diff --git a/tests/TypeInfer.unionTypes.test.cpp b/tests/TypeInfer.unionTypes.test.cpp index 271841e9..b455903c 100644 --- a/tests/TypeInfer.unionTypes.test.cpp +++ b/tests/TypeInfer.unionTypes.test.cpp @@ -459,8 +459,6 @@ local oh : boolean = t.y TEST_CASE_FIXTURE(Fixture, "error_detailed_union_part") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( type X = { x: number } type Y = { y: number } @@ -498,8 +496,6 @@ local a: XYZ = { w = 4 } TEST_CASE_FIXTURE(Fixture, "error_detailed_optional") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( type X = { x: number } @@ -532,8 +528,6 @@ TEST_CASE_FIXTURE(Fixture, "dont_allow_cyclic_unions_to_be_inferred") TEST_CASE_FIXTURE(BuiltinsFixture, "table_union_write_indirect") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( type A = { x: number, y: (number) -> string } | { z: number, y: (number) -> string } @@ -620,8 +614,6 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_mentioning_generics") TEST_CASE_FIXTURE(Fixture, "union_of_functions_mentioning_generic_typepacks") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( function f() local x : (number, a...) -> (number?, a...) @@ -640,8 +632,6 @@ could not be converted into TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_arities") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( local x : (number) -> number? local y : ((number?) -> number) | ((number | string) -> nil) = x -- OK @@ -658,8 +648,6 @@ could not be converted into TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_arities") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( local x : () -> (number | string) @@ -677,8 +665,6 @@ could not be converted into TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_variadics") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( local x : (...nil) -> (...number?) @@ -696,8 +682,6 @@ could not be converted into TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_variadics") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( local x : (number) -> () @@ -715,8 +699,6 @@ could not be converted into TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_variadics") { - ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; - ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( local x : () -> (number?, ...number) diff --git a/tests/conformance/basic.lua b/tests/conformance/basic.lua index 07150ba8..42030c55 100644 --- a/tests/conformance/basic.lua +++ b/tests/conformance/basic.lua @@ -91,6 +91,15 @@ assert((function() local a = 1 a = a + 2 return a end)() == 3) assert((function() local a = 1 a = a - 2 return a end)() == -1) assert((function() local a = 1 a = a * 2 return a end)() == 2) assert((function() local a = 1 a = a / 2 return a end)() == 0.5) + +-- floor division should always round towards -Infinity +assert((function() local a = 1 a = a // 2 return a end)() == 0) +assert((function() local a = 3 a = a // 2 return a end)() == 1) +assert((function() local a = 3.5 a = a // 2 return a end)() == 1) +assert((function() local a = -1 a = a // 2 return a end)() == -1) +assert((function() local a = -3 a = a // 2 return a end)() == -2) +assert((function() local a = -3.5 a = a // 2 return a end)() == -2) + assert((function() local a = 5 a = a % 2 return a end)() == 1) assert((function() local a = 3 a = a ^ 2 return a end)() == 9) assert((function() local a = 3 a = a ^ 3 return a end)() == 27) @@ -494,6 +503,7 @@ local function vec3t(x, y, z) __sub = function(l, r) return vec3t(l.x - r.x, l.y - r.y, l.z - r.z) end, __mul = function(l, r) return type(r) == "number" and vec3t(l.x * r, l.y * r, l.z * r) or vec3t(l.x * r.x, l.y * r.y, l.z * r.z) end, __div = function(l, r) return type(r) == "number" and vec3t(l.x / r, l.y / r, l.z / r) or vec3t(l.x / r.x, l.y / r.y, l.z / r.z) end, + __idiv = function(l, r) return type(r) == "number" and vec3t(l.x // r, l.y // r, l.z // r) or vec3t(l.x // r.x, l.y // r.y, l.z // r.z) end, __unm = function(v) return vec3t(-v.x, -v.y, -v.z) end, __tostring = function(v) return string.format("%g, %g, %g", v.x, v.y, v.z) end }) @@ -504,10 +514,13 @@ assert((function() return tostring(vec3t(1,2,3) + vec3t(4,5,6)) end)() == "5, 7, assert((function() return tostring(vec3t(1,2,3) - vec3t(4,5,6)) end)() == "-3, -3, -3") assert((function() return tostring(vec3t(1,2,3) * vec3t(4,5,6)) end)() == "4, 10, 18") assert((function() return tostring(vec3t(1,2,3) / vec3t(2,4,8)) end)() == "0.5, 0.5, 0.375") +assert((function() return tostring(vec3t(1,2,3) // vec3t(2,4,2)) end)() == "0, 0, 1") +assert((function() return tostring(vec3t(1,2,3) // vec3t(-2,-4,-2)) end)() == "-1, -1, -2") -- reg vs constant assert((function() return tostring(vec3t(1,2,3) * 2) end)() == "2, 4, 6") assert((function() return tostring(vec3t(1,2,3) / 2) end)() == "0.5, 1, 1.5") +assert((function() return tostring(vec3t(1,2,3) // 2) end)() == "0, 1, 1") -- unary assert((function() return tostring(-vec3t(1,2,3)) end)() == "-1, -2, -3") diff --git a/tests/conformance/events.lua b/tests/conformance/events.lua index 94314c3f..4ee801f0 100644 --- a/tests/conformance/events.lua +++ b/tests/conformance/events.lua @@ -107,6 +107,7 @@ t.__add = f("add") t.__sub = f("sub") t.__mul = f("mul") t.__div = f("div") +t.__idiv = f("idiv") t.__mod = f("mod") t.__unm = f("unm") t.__pow = f("pow") @@ -128,6 +129,8 @@ assert(a*a == a) assert(cap[0] == "mul" and cap[1] == a and cap[2] == a and cap[3]==nil) assert(a/0 == a) assert(cap[0] == "div" and cap[1] == a and cap[2] == 0 and cap[3]==nil) +assert(a//0 == a) +assert(cap[0] == "idiv" and cap[1] == a and cap[2] == 0 and cap[3]==nil) assert(a%2 == a) assert(cap[0] == "mod" and cap[1] == a and cap[2] == 2 and cap[3]==nil) assert(-a == a) diff --git a/tests/conformance/math.lua b/tests/conformance/math.lua index 9e9ae384..6b5bfc5f 100644 --- a/tests/conformance/math.lua +++ b/tests/conformance/math.lua @@ -82,6 +82,7 @@ assert(not(1>1) and not(1>2) and (2>1)) assert(not('a'>'a') and not('a'>'b') and ('b'>'a')) assert((1>=1) and not(1>=2) and (2>=1)) assert(('a'>='a') and not('a'>='b') and ('b'>='a')) +assert((unk and unk > 0) == nil) -- validate precedence between and and > -- testing mod operator assert(-4%3 == 2) diff --git a/tests/conformance/native.lua b/tests/conformance/native.lua index 9b5bb884..7a77edec 100644 --- a/tests/conformance/native.lua +++ b/tests/conformance/native.lua @@ -130,4 +130,45 @@ end assert(pcall(fuzzfail13) == true) +local function arraySizeInv1() + local t = {1, 2, nil, nil, nil, nil, nil, nil, nil, true} + + table.insert(t, 3) + + return t[10] +end + +assert(arraySizeInv1() == true) + +local function arraySizeInv2() + local t = {1, 2, nil, nil, nil, nil, nil, nil, nil, true} + + local u = {a = t} + table.insert(u.a, 3) -- aliased modifiction of 't' register through other value + + return t[10] +end + +assert(arraySizeInv2() == true) + +local function nilInvalidatesSlot() + local function tabs() + local t = { x=1, y=2, z=3 } + setmetatable(t, { __index = function(t, k) return 42 end }) + return t, t + end + + local t1, t2 = tabs() + + for i=1,2 do + local a = t1.x + t2.x = nil + local b = t1.x + t2.x = 1 + assert(a == 1 and b == 42) + end +end + +nilInvalidatesSlot() + return('OK') diff --git a/tests/conformance/userdata.lua b/tests/conformance/userdata.lua index 98392e25..32759ad1 100644 --- a/tests/conformance/userdata.lua +++ b/tests/conformance/userdata.lua @@ -30,6 +30,12 @@ assert(int64(4) / 2 == int64(2)) assert(int64(4) % 3 == int64(1)) assert(int64(2) ^ 3 == int64(8)) +-- / and // round in different directions in our test implementation +assert(int64(5) / int64(2) == int64(2)) +assert(int64(5) // int64(2) == int64(2)) +assert(int64(-5) / int64(2) == int64(-2)) +assert(int64(-5) // int64(2) == int64(-3)) + -- tostring assert(tostring(int64(2)) == "2") diff --git a/tests/conformance/vector.lua b/tests/conformance/vector.lua index 6164e929..c9cc47aa 100644 --- a/tests/conformance/vector.lua +++ b/tests/conformance/vector.lua @@ -67,6 +67,18 @@ assert(vector(1, 2, 4) / '8' == vector(1/8, 1/4, 1/2)); assert(-vector(1, 2, 4) == vector(-1, -2, -4)); +-- test floor division +assert(vector(1, 3, 5) // 2 == vector(0, 1, 2)) +assert(vector(1, 3, 5) // val == vector(8, 24, 40)) + +if vector_size == 4 then + assert(10 // vector(1, 2, 3, 4) == vector(10, 5, 3, 2)) + assert(vector(10, 9, 8, 7) // vector(1, 2, 3, 4) == vector(10, 4, 2, 1)) +else + assert(10 // vector(1, 2, 3) == vector(10, 5, 3)) + assert(vector(10, 9, 8) // vector(1, 2, 3) == vector(10, 4, 2)) +end + -- test NaN comparison local nanv = vector(0/0, 0/0, 0/0) assert(nanv ~= nanv); From cb18b53396d80f69e57d28195ecc4aa70b37f5f0 Mon Sep 17 00:00:00 2001 From: Arseny Kapoulkine Date: Fri, 1 Sep 2023 14:09:18 -0700 Subject: [PATCH 2/5] Update compatibility.md Mark floor division operator as implemented --- docs/_pages/compatibility.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_pages/compatibility.md b/docs/_pages/compatibility.md index b57a9af6..94f8809b 100644 --- a/docs/_pages/compatibility.md +++ b/docs/_pages/compatibility.md @@ -87,7 +87,7 @@ Ephemeron tables may be implemented at some point since they do have valid uses | bitwise operators | ❌ | `bit32` library covers this in absence of 64-bit integers | | basic utf-8 support | ✔️ | we include `utf8` library and other UTF8 features | | functions for packing and unpacking values (string.pack/unpack/packsize) | ✔️ | | -| floor division | 🔜 | | +| floor division | ✔️ | | | `ipairs` and the `table` library respect metamethods | ❌ | no strong use cases, performance implications | | new function `table.move` | ✔️ | | | `collectgarbage("count")` now returns only one result | ✔️ | | From f0a2e79365462c2769e590a0a61849dbfe776d34 Mon Sep 17 00:00:00 2001 From: Arseny Kapoulkine Date: Fri, 1 Sep 2023 14:10:22 -0700 Subject: [PATCH 3/5] rfcs: Mark floor division RFC as implemented --- rfcs/syntax-floor-division-operator.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rfcs/syntax-floor-division-operator.md b/rfcs/syntax-floor-division-operator.md index 8ec3913f..b84ddd18 100644 --- a/rfcs/syntax-floor-division-operator.md +++ b/rfcs/syntax-floor-division-operator.md @@ -1,5 +1,7 @@ # Floor division operator +**Status**: Implemented + ## Summary Add floor division operator `//` to ease computing with integers. From 105f54b2330d0227059c08d2e03454efa934c5db Mon Sep 17 00:00:00 2001 From: Arseny Kapoulkine Date: Tue, 5 Sep 2023 10:22:35 -0700 Subject: [PATCH 4/5] fix build & test conformance issues on mingw32/gcc and mingw64/clang (#1034) CLI/Reduce.cpp: Never worked under MinGW as-is. Add check for MinGW as they're defined. VM/src/lmem.cpp: MinGW layout follows more closely MSVC. Checks before were only correct for 32-bit gcc / clang in non-Windows. NOTES: __MINGW32__ is defined on both 32-bit and 64-bit, __MINGW64__ is 64-bit only. 32-bit MinGW compilers (both Clang & GCC) have a floating point test error on math.noise, specifically math.lua:258. All other test cases / unit tests pass modulo stack size issues (so requires optimized build). --------- Co-authored-by: jdp_ <42700985+jdpatdiscord@users.noreply.github.com> --- CLI/Reduce.cpp | 2 +- VM/src/lmem.cpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CLI/Reduce.cpp b/CLI/Reduce.cpp index 38133e04..721bb51c 100644 --- a/CLI/Reduce.cpp +++ b/CLI/Reduce.cpp @@ -15,7 +15,7 @@ #define VERBOSE 0 // 1 - print out commandline invocations. 2 - print out stdout -#ifdef _WIN32 +#if defined(_WIN32) && !defined(__MINGW32__) const auto popen = &_popen; const auto pclose = &_pclose; diff --git a/VM/src/lmem.cpp b/VM/src/lmem.cpp index 3cbdafff..25ca999d 100644 --- a/VM/src/lmem.cpp +++ b/VM/src/lmem.cpp @@ -98,6 +98,8 @@ */ #if defined(__APPLE__) #define ABISWITCH(x64, ms32, gcc32) (sizeof(void*) == 8 ? x64 : gcc32) +#elif defined(__i386__) && defined(__MINGW32__) && !defined(__MINGW64__) +#define ABISWITCH(x64, ms32, gcc32) (ms32) #elif defined(__i386__) && !defined(_MSC_VER) #define ABISWITCH(x64, ms32, gcc32) (gcc32) #else From bf1fb8f1e4e4f21b229775c4d988cb2de624967f Mon Sep 17 00:00:00 2001 From: Arseny Kapoulkine Date: Wed, 6 Sep 2023 18:14:03 -0700 Subject: [PATCH 5/5] Update syntax.md (#1035) Add a note about floor division support. --- docs/_pages/syntax.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/_pages/syntax.md b/docs/_pages/syntax.md index 595a4804..42fbf897 100644 --- a/docs/_pages/syntax.md +++ b/docs/_pages/syntax.md @@ -273,3 +273,9 @@ This restriction is made to prevent developers using other programming languages Luau currently does not support backtick string literals as a type annotation, so `` type Foo = `Foo` `` is invalid. Function calls with a backtick string literal without parenthesis is not supported, so `` print`hello` `` is invalid. + +## Floor division (`//`) + +Luau implements support for floor division operator (`//`) for numbers as well as support for `__idiv` metamethod. The syntax and semantics follow [Lua 5.3](https://www.lua.org/manual/5.3/manual.html#3.4.1). + +For numbers, `a // b` is equal to `math.floor(a / b)`; when `b` is 0, `a // b` results in infinity or NaN as appropriate.