diff --git a/Analysis/include/Luau/Clone.h b/Analysis/include/Luau/Clone.h index 51f1e7a..b3cbe46 100644 --- a/Analysis/include/Luau/Clone.h +++ b/Analysis/include/Luau/Clone.h @@ -26,7 +26,4 @@ TypePackId clone(TypePackId tp, TypeArena& dest, CloneState& cloneState); TypeId clone(TypeId tp, TypeArena& dest, CloneState& cloneState); TypeFun clone(const TypeFun& typeFun, TypeArena& dest, CloneState& cloneState); -TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log, bool alwaysClone = false); -TypeId shallowClone(TypeId ty, NotNull dest); - } // namespace Luau diff --git a/Analysis/include/Luau/ControlFlow.h b/Analysis/include/Luau/ControlFlow.h index 8272bd5..566d77b 100644 --- a/Analysis/include/Luau/ControlFlow.h +++ b/Analysis/include/Luau/ControlFlow.h @@ -11,10 +11,10 @@ using ScopePtr = std::shared_ptr; enum class ControlFlow { - None = 0b00001, - Returns = 0b00010, - Throws = 0b00100, - Break = 0b01000, // Currently unused. + None = 0b00001, + Returns = 0b00010, + Throws = 0b00100, + Break = 0b01000, // Currently unused. Continue = 0b10000, // Currently unused. }; diff --git a/Analysis/include/Luau/Unifiable.h b/Analysis/include/Luau/Unifiable.h index 9c4f013..ae55f37 100644 --- a/Analysis/include/Luau/Unifiable.h +++ b/Analysis/include/Luau/Unifiable.h @@ -82,7 +82,7 @@ namespace Luau::Unifiable using Name = std::string; int freshIndex(); - + struct Free { explicit Free(TypeLevel level); diff --git a/Analysis/src/Clone.cpp b/Analysis/src/Clone.cpp index ff8e0c3..2645209 100644 --- a/Analysis/src/Clone.cpp +++ b/Analysis/src/Clone.cpp @@ -7,7 +7,7 @@ #include "Luau/Unifiable.h" LUAU_FASTFLAG(DebugLuauCopyBeforeNormalizing) -LUAU_FASTFLAG(LuauClonePublicInterfaceLess) +LUAU_FASTFLAG(LuauClonePublicInterfaceLess2) LUAU_FASTINTVARIABLE(LuauTypeCloneRecursionLimit, 300) @@ -422,86 +422,4 @@ TypeFun clone(const TypeFun& typeFun, TypeArena& dest, CloneState& cloneState) return result; } -TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log, bool alwaysClone) -{ - ty = log->follow(ty); - - TypeId result = ty; - - if (auto pty = log->pending(ty)) - ty = &pty->pending; - - if (const FunctionType* ftv = get(ty)) - { - FunctionType clone = FunctionType{ftv->level, ftv->scope, ftv->argTypes, ftv->retTypes, ftv->definition, ftv->hasSelf}; - clone.generics = ftv->generics; - clone.genericPacks = ftv->genericPacks; - clone.magicFunction = ftv->magicFunction; - clone.dcrMagicFunction = ftv->dcrMagicFunction; - clone.dcrMagicRefinement = ftv->dcrMagicRefinement; - clone.tags = ftv->tags; - clone.argNames = ftv->argNames; - result = dest.addType(std::move(clone)); - } - else if (const TableType* ttv = get(ty)) - { - LUAU_ASSERT(!ttv->boundTo); - TableType clone = TableType{ttv->props, ttv->indexer, ttv->level, ttv->scope, ttv->state}; - clone.definitionModuleName = ttv->definitionModuleName; - clone.definitionLocation = ttv->definitionLocation; - clone.name = ttv->name; - clone.syntheticName = ttv->syntheticName; - clone.instantiatedTypeParams = ttv->instantiatedTypeParams; - clone.instantiatedTypePackParams = ttv->instantiatedTypePackParams; - clone.tags = ttv->tags; - result = dest.addType(std::move(clone)); - } - else if (const MetatableType* mtv = get(ty)) - { - MetatableType clone = MetatableType{mtv->table, mtv->metatable}; - clone.syntheticName = mtv->syntheticName; - result = dest.addType(std::move(clone)); - } - else if (const UnionType* utv = get(ty)) - { - UnionType clone; - clone.options = utv->options; - result = dest.addType(std::move(clone)); - } - else if (const IntersectionType* itv = get(ty)) - { - IntersectionType clone; - clone.parts = itv->parts; - result = dest.addType(std::move(clone)); - } - else if (const PendingExpansionType* petv = get(ty)) - { - PendingExpansionType clone{petv->prefix, petv->name, petv->typeArguments, petv->packArguments}; - result = dest.addType(std::move(clone)); - } - else if (const ClassType* ctv = get(ty); FFlag::LuauClonePublicInterfaceLess && ctv && alwaysClone) - { - ClassType clone{ctv->name, ctv->props, ctv->parent, ctv->metatable, ctv->tags, ctv->userData, ctv->definitionModuleName}; - result = dest.addType(std::move(clone)); - } - else if (FFlag::LuauClonePublicInterfaceLess && alwaysClone) - { - result = dest.addType(*ty); - } - else if (const NegationType* ntv = get(ty)) - { - result = dest.addType(NegationType{ntv->ty}); - } - else - return result; - - asMutable(result)->documentationSymbol = ty->documentationSymbol; - return result; -} - -TypeId shallowClone(TypeId ty, NotNull dest) -{ - return shallowClone(ty, *dest, TxnLog::empty()); -} - } // namespace Luau diff --git a/Analysis/src/ConstraintGraphBuilder.cpp b/Analysis/src/ConstraintGraphBuilder.cpp index e90cb7d..474d392 100644 --- a/Analysis/src/ConstraintGraphBuilder.cpp +++ b/Analysis/src/ConstraintGraphBuilder.cpp @@ -23,7 +23,7 @@ LUAU_FASTFLAG(LuauNegatedClassTypes); namespace Luau { -bool doesCallError(const AstExprCall* call); // TypeInfer.cpp +bool doesCallError(const AstExprCall* call); // TypeInfer.cpp const AstStat* getFallthrough(const AstStat* node); // TypeInfer.cpp static std::optional matchRequire(const AstExprCall& call) @@ -1359,10 +1359,34 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCa if (argTail && args.size() < 2) argTailPack = extendTypePack(*arena, builtinTypes, *argTail, 2 - args.size()); - LUAU_ASSERT(args.size() + argTailPack.head.size() == 2); + TypeId target = nullptr; + TypeId mt = nullptr; - TypeId target = args.size() > 0 ? args[0] : argTailPack.head[0]; - TypeId mt = args.size() > 1 ? args[1] : argTailPack.head[args.size() == 0 ? 1 : 0]; + if (args.size() + argTailPack.head.size() == 2) + { + target = args.size() > 0 ? args[0] : argTailPack.head[0]; + mt = args.size() > 1 ? args[1] : argTailPack.head[args.size() == 0 ? 1 : 0]; + } + else + { + std::vector unpackedTypes; + if (args.size() > 0) + target = args[0]; + else + { + target = arena->addType(BlockedType{}); + unpackedTypes.emplace_back(target); + } + + mt = arena->addType(BlockedType{}); + unpackedTypes.emplace_back(mt); + TypePackId mtPack = arena->addTypePack(std::move(unpackedTypes)); + + addConstraint(scope, call->location, UnpackConstraint{mtPack, *argTail}); + } + + LUAU_ASSERT(target); + LUAU_ASSERT(mt); AstExpr* targetExpr = call->args.data[0]; @@ -2090,6 +2114,19 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS TypePack expectedArgPack; const FunctionType* expectedFunction = expectedType ? get(*expectedType) : nullptr; + // This check ensures that expectedType is precisely optional and not any (since any is also an optional type) + if (expectedType && isOptional(*expectedType) && !get(*expectedType)) + { + auto ut = get(*expectedType); + for (auto u : ut) + { + if (get(u) && !isNil(u)) + { + expectedFunction = get(u); + break; + } + } + } if (expectedFunction) { diff --git a/Analysis/src/ConstraintSolver.cpp b/Analysis/src/ConstraintSolver.cpp index 5662cf0..d585393 100644 --- a/Analysis/src/ConstraintSolver.cpp +++ b/Analysis/src/ConstraintSolver.cpp @@ -3,6 +3,7 @@ #include "Luau/Anyification.h" #include "Luau/ApplyTypeFunction.h" #include "Luau/Clone.h" +#include "Luau/Common.h" #include "Luau/ConstraintSolver.h" #include "Luau/DcrLogger.h" #include "Luau/Instantiation.h" @@ -221,17 +222,6 @@ void dump(ConstraintSolver* cs, ToStringOptions& opts) auto it = cs->blockedConstraints.find(c); int blockCount = it == cs->blockedConstraints.end() ? 0 : int(it->second); printf("\t%d\t%s\n", blockCount, toString(*c, opts).c_str()); - - for (NotNull dep : c->dependencies) - { - auto unsolvedIter = std::find(begin(cs->unsolvedConstraints), end(cs->unsolvedConstraints), dep); - if (unsolvedIter == cs->unsolvedConstraints.end()) - continue; - - auto it = cs->blockedConstraints.find(dep); - int blockCount = it == cs->blockedConstraints.end() ? 0 : int(it->second); - printf("\t%d\t\t%s\n", blockCount, toString(*dep, opts).c_str()); - } } } @@ -578,12 +568,16 @@ bool ConstraintSolver::tryDispatch(const UnaryConstraint& c, NotNullty.emplace(builtinTypes->booleanType); + + unblock(c.resultType); return true; } case AstExprUnary::Len: { // __len must return a number. asMutable(c.resultType)->ty.emplace(builtinTypes->numberType); + + unblock(c.resultType); return true; } case AstExprUnary::Minus: @@ -613,6 +607,7 @@ bool ConstraintSolver::tryDispatch(const UnaryConstraint& c, NotNullty.emplace(builtinTypes->errorRecoveryType()); } + unblock(c.resultType); return true; } } @@ -868,7 +863,7 @@ bool ConstraintSolver::tryDispatch(const IterableConstraint& c, NotNullanyType}; } - TypeId instantiatedTy = arena->addType(BlockedType{}); TypeId inferredTy = arena->addType(FunctionType{TypeLevel{}, constraint->scope.get(), argsPack, c.result}); - auto pushConstraintGreedy = [this, constraint](ConstraintV cv) -> Constraint* { - std::unique_ptr c = std::make_unique(constraint->scope, constraint->location, std::move(cv)); - NotNull borrow{c.get()}; + std::vector overloads = flattenIntersection(fn); - bool ok = tryDispatch(borrow, false); - if (ok) - return nullptr; + Instantiation inst(TxnLog::empty(), arena, TypeLevel{}, constraint->scope); - solverConstraints.push_back(std::move(c)); - unsolvedConstraints.push_back(borrow); + for (TypeId overload : overloads) + { + overload = follow(overload); - return borrow; - }; + std::optional instantiated = inst.substitute(overload); + LUAU_ASSERT(instantiated); // TODO FIXME HANDLE THIS - // HACK: We don't want other constraints to act on the free type pack - // created above until after these two constraints are solved, so we try to - // dispatch them directly. + Unifier u{normalizer, Mode::Strict, constraint->scope, Location{}, Covariant}; + u.useScopes = true; - auto ic = pushConstraintGreedy(InstantiationConstraint{instantiatedTy, fn}); - auto sc = pushConstraintGreedy(SubtypeConstraint{instantiatedTy, inferredTy}); + u.tryUnify(*instantiated, inferredTy, /* isFunctionCall */ true); - if (ic) - inheritBlocks(constraint, NotNull{ic}); + if (!u.blockedTypes.empty() || !u.blockedTypePacks.empty()) + { + for (TypeId bt : u.blockedTypes) + block(bt, constraint); + for (TypePackId btp : u.blockedTypePacks) + block(btp, constraint); + return false; + } - if (sc) - inheritBlocks(constraint, NotNull{sc}); + if (const auto& e = hasUnificationTooComplex(u.errors)) + reportError(*e); + + if (u.errors.empty()) + { + // We found a matching overload. + const auto [changedTypes, changedPacks] = u.log.getChanges(); + u.log.commit(); + unblock(changedTypes); + unblock(changedPacks); + + unblock(c.result); + return true; + } + } + + // We found no matching overloads. + Unifier u{normalizer, Mode::Strict, constraint->scope, Location{}, Covariant}; + u.useScopes = true; + + u.tryUnify(inferredTy, builtinTypes->anyType); + u.tryUnify(fn, builtinTypes->anyType); + + LUAU_ASSERT(u.errors.empty()); // unifying with any should never fail + + const auto [changedTypes, changedPacks] = u.log.getChanges(); + u.log.commit(); + + unblock(changedTypes); + unblock(changedPacks); unblock(c.result); return true; @@ -1291,6 +1314,7 @@ bool ConstraintSolver::tryDispatch(const PrimitiveTypeConstraint& c, NotNullty.emplace(bindTo); + unblock(c.resultType); return true; } @@ -1335,20 +1359,17 @@ static bool isUnsealedTable(TypeId ty) } /** - * Create a shallow copy of `ty` and its properties along `path`. Insert a new - * property (the last segment of `path`) into the tail table with the value `t`. + * Given a path into a set of nested unsealed tables `ty`, insert a new property `replaceTy` as the leaf-most property. * - * On success, returns the new outermost table type. If the root table or any - * of its subkeys are not unsealed tables, the function fails and returns - * std::nullopt. + * Fails and does nothing if every table along the way is not unsealed. * - * TODO: Prove that we completely give up in the face of indexers and - * metatables. + * Mutates the innermost table type in-place. */ -static std::optional updateTheTableType(NotNull arena, TypeId ty, const std::vector& path, TypeId replaceTy) +static void updateTheTableType( + NotNull builtinTypes, NotNull arena, TypeId ty, const std::vector& path, TypeId replaceTy) { if (path.empty()) - return std::nullopt; + return; // First walk the path and ensure that it's unsealed tables all the way // to the end. @@ -1357,12 +1378,12 @@ static std::optional updateTheTableType(NotNull arena, TypeId for (size_t i = 0; i < path.size() - 1; ++i) { if (!isUnsealedTable(t)) - return std::nullopt; + return; const TableType* tbl = get(t); auto it = tbl->props.find(path[i]); if (it == tbl->props.end()) - return std::nullopt; + return; t = follow(it->second.type); } @@ -1371,40 +1392,37 @@ static std::optional updateTheTableType(NotNull arena, TypeId // We are not changing property types. We are only admitting this one // new property to be appended. if (!isUnsealedTable(t)) - return std::nullopt; + return; const TableType* tbl = get(t); if (0 != tbl->props.count(path.back())) - return std::nullopt; + return; } - const TypeId res = shallowClone(ty, arena); - TypeId t = res; + TypeId t = ty; + ErrorVec dummy; for (size_t i = 0; i < path.size() - 1; ++i) { - const std::string segment = path[i]; + auto propTy = findTablePropertyRespectingMeta(builtinTypes, dummy, t, path[i], Location{}); + dummy.clear(); - TableType* ttv = getMutable(t); - LUAU_ASSERT(ttv); + if (!propTy) + return; - auto propIt = ttv->props.find(segment); - if (propIt != ttv->props.end()) - { - LUAU_ASSERT(isUnsealedTable(propIt->second.type)); - t = shallowClone(follow(propIt->second.type), arena); - ttv->props[segment].type = t; - } - else - return std::nullopt; + t = *propTy; } - TableType* ttv = getMutable(t); - LUAU_ASSERT(ttv); + const std::string& lastSegment = path.back(); - const std::string lastSegment = path.back(); - LUAU_ASSERT(0 == ttv->props.count(lastSegment)); - ttv->props[lastSegment] = Property{replaceTy}; - return res; + t = follow(t); + TableType* tt = getMutable(t); + if (auto mt = get(t)) + tt = getMutable(mt->table); + + if (!tt) + return; + + tt->props[lastSegment].type = replaceTy; } bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull constraint, bool force) @@ -1443,6 +1461,7 @@ bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNullscope); bind(c.resultType, c.subjectType); + unblock(c.resultType); return true; } @@ -1467,6 +1486,8 @@ bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull(subjectType)) @@ -1477,20 +1498,23 @@ bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNullprops[c.path[0]] = Property{c.propType}; bind(c.resultType, c.subjectType); + unblock(c.resultType); return true; } else if (ttv->state == TableState::Unsealed) { LUAU_ASSERT(!subjectType->persistent); - std::optional augmented = updateTheTableType(NotNull{arena}, subjectType, c.path, c.propType); - bind(c.resultType, augmented.value_or(subjectType)); - bind(subjectType, c.resultType); + updateTheTableType(builtinTypes, NotNull{arena}, subjectType, c.path, c.propType); + bind(c.resultType, c.subjectType); + unblock(subjectType); + unblock(c.resultType); return true; } else { bind(c.resultType, subjectType); + unblock(c.resultType); return true; } } @@ -1499,6 +1523,7 @@ bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull 0) - return LoadDefinitionFileResult{false, parseResult, {}, nullptr}; - - Luau::SourceModule module; - module.root = parseResult.root; - module.mode = Mode::Definition; - - ModulePtr checkedModule = typeChecker.check(module, Mode::Definition); - - if (checkedModule->errors.size() > 0) - return LoadDefinitionFileResult{false, parseResult, {}, checkedModule}; - - CloneState cloneState; - - std::vector typesToPersist; - typesToPersist.reserve(checkedModule->declaredGlobals.size() + checkedModule->exportedTypeBindings.size()); - - for (const auto& [name, ty] : checkedModule->declaredGlobals) - { - TypeId globalTy = clone(ty, globals.globalTypes, cloneState); - std::string documentationSymbol = packageName + "/global/" + name; - generateDocumentationSymbols(globalTy, documentationSymbol); - targetScope->bindings[globals.globalNames.names->getOrAdd(name.c_str())] = {globalTy, Location(), false, {}, documentationSymbol}; - - typesToPersist.push_back(globalTy); - } - - for (const auto& [name, ty] : checkedModule->exportedTypeBindings) - { - TypeFun globalTy = clone(ty, globals.globalTypes, cloneState); - std::string documentationSymbol = packageName + "/globaltype/" + name; - generateDocumentationSymbols(globalTy.type, documentationSymbol); - targetScope->exportedTypeBindings[name] = globalTy; - - typesToPersist.push_back(globalTy.type); - } - - for (TypeId ty : typesToPersist) - { - persist(ty); - } - - return LoadDefinitionFileResult{true, parseResult, {}, checkedModule}; -} - LoadDefinitionFileResult loadDefinitionFile(TypeChecker& typeChecker, GlobalTypes& globals, ScopePtr targetScope, std::string_view source, const std::string& packageName, bool captureComments) { - if (!FFlag::LuauDefinitionFileSourceModule) - return loadDefinitionFile_DEPRECATED(typeChecker, globals, targetScope, source, packageName); - LUAU_TIMETRACE_SCOPE("loadDefinitionFile", "Frontend"); Luau::SourceModule sourceModule; diff --git a/Analysis/src/Instantiation.cpp b/Analysis/src/Instantiation.cpp index 9c3ae07..7d0f0f7 100644 --- a/Analysis/src/Instantiation.cpp +++ b/Analysis/src/Instantiation.cpp @@ -127,7 +127,7 @@ TypeId ReplaceGenerics::clean(TypeId ty) TypePackId ReplaceGenerics::clean(TypePackId tp) { LUAU_ASSERT(isDirty(tp)); - return addTypePack(TypePackVar(FreeTypePack{level})); + return addTypePack(TypePackVar(FreeTypePack{scope, level})); } } // namespace Luau diff --git a/Analysis/src/Module.cpp b/Analysis/src/Module.cpp index b51b7c9..fd94840 100644 --- a/Analysis/src/Module.cpp +++ b/Analysis/src/Module.cpp @@ -16,7 +16,7 @@ #include LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution); -LUAU_FASTFLAGVARIABLE(LuauClonePublicInterfaceLess, false); +LUAU_FASTFLAGVARIABLE(LuauClonePublicInterfaceLess2, false); LUAU_FASTFLAG(LuauSubstitutionReentrant); LUAU_FASTFLAG(LuauClassTypeVarsInSubstitution); LUAU_FASTFLAG(LuauSubstitutionFixMissingFields); @@ -194,7 +194,7 @@ void Module::clonePublicInterface(NotNull builtinTypes, InternalEr TxnLog log; ClonePublicInterface clonePublicInterface{&log, builtinTypes, this}; - if (FFlag::LuauClonePublicInterfaceLess) + if (FFlag::LuauClonePublicInterfaceLess2) returnType = clonePublicInterface.cloneTypePack(returnType); else returnType = clone(returnType, interfaceTypes, cloneState); @@ -202,7 +202,7 @@ void Module::clonePublicInterface(NotNull builtinTypes, InternalEr moduleScope->returnType = returnType; if (varargPack) { - if (FFlag::LuauClonePublicInterfaceLess) + if (FFlag::LuauClonePublicInterfaceLess2) varargPack = clonePublicInterface.cloneTypePack(*varargPack); else varargPack = clone(*varargPack, interfaceTypes, cloneState); @@ -211,7 +211,7 @@ void Module::clonePublicInterface(NotNull builtinTypes, InternalEr for (auto& [name, tf] : moduleScope->exportedTypeBindings) { - if (FFlag::LuauClonePublicInterfaceLess) + if (FFlag::LuauClonePublicInterfaceLess2) tf = clonePublicInterface.cloneTypeFun(tf); else tf = clone(tf, interfaceTypes, cloneState); @@ -219,7 +219,7 @@ void Module::clonePublicInterface(NotNull builtinTypes, InternalEr for (auto& [name, ty] : declaredGlobals) { - if (FFlag::LuauClonePublicInterfaceLess) + if (FFlag::LuauClonePublicInterfaceLess2) ty = clonePublicInterface.cloneType(ty); else ty = clone(ty, interfaceTypes, cloneState); diff --git a/Analysis/src/Normalize.cpp b/Analysis/src/Normalize.cpp index f8f8b97..f383f5e 100644 --- a/Analysis/src/Normalize.cpp +++ b/Analysis/src/Normalize.cpp @@ -533,7 +533,7 @@ static bool areNormalizedClasses(const NormalizedClassType& tys) static bool isPlainTyvar(TypeId ty) { - return (get(ty) || get(ty) || (FFlag::LuauNormalizeBlockedTypes && get(ty))); + return (get(ty) || get(ty) || (FFlag::LuauNormalizeBlockedTypes && get(ty)) || get(ty)); } static bool isNormalizedTyvar(const NormalizedTyvars& tyvars) @@ -1380,7 +1380,8 @@ bool Normalizer::unionNormalWithTy(NormalizedType& here, TypeId there, int ignor } else if (FFlag::LuauTransitiveSubtyping && get(here.tops)) return true; - else if (get(there) || get(there) || (FFlag::LuauNormalizeBlockedTypes && get(there))) + else if (get(there) || get(there) || (FFlag::LuauNormalizeBlockedTypes && get(there)) || + get(there)) { if (tyvarIndex(there) <= ignoreSmallerTyvars) return true; @@ -1460,6 +1461,10 @@ bool Normalizer::unionNormalWithTy(NormalizedType& here, TypeId there, int ignor } else if (!FFlag::LuauNormalizeBlockedTypes && get(there)) LUAU_ASSERT(!"Internal error: Trying to normalize a BlockedType"); + else if (get(there)) + { + // nothing + } else LUAU_ASSERT(!"Unreachable"); @@ -2544,7 +2549,8 @@ bool Normalizer::intersectNormalWithTy(NormalizedType& here, TypeId there) return false; return true; } - else if (get(there) || get(there) || (FFlag::LuauNormalizeBlockedTypes && get(there))) + else if (get(there) || get(there) || (FFlag::LuauNormalizeBlockedTypes && get(there)) || + get(there)) { NormalizedType thereNorm{builtinTypes}; NormalizedType topNorm{builtinTypes}; @@ -2856,7 +2862,8 @@ bool isConsistentSubtype(TypeId subTy, TypeId superTy, NotNull scope, Not return ok; } -bool isConsistentSubtype(TypePackId subPack, TypePackId superPack, NotNull scope, NotNull builtinTypes, InternalErrorReporter& ice) +bool isConsistentSubtype( + TypePackId subPack, TypePackId superPack, NotNull scope, NotNull builtinTypes, InternalErrorReporter& ice) { UnifierSharedState sharedState{&ice}; TypeArena arena; diff --git a/Analysis/src/Substitution.cpp b/Analysis/src/Substitution.cpp index 160647a..935d85d 100644 --- a/Analysis/src/Substitution.cpp +++ b/Analysis/src/Substitution.cpp @@ -9,7 +9,7 @@ #include LUAU_FASTFLAGVARIABLE(LuauSubstitutionFixMissingFields, false) -LUAU_FASTFLAG(LuauClonePublicInterfaceLess) +LUAU_FASTFLAG(LuauClonePublicInterfaceLess2) LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 10000) LUAU_FASTFLAGVARIABLE(LuauClassTypeVarsInSubstitution, false) LUAU_FASTFLAGVARIABLE(LuauSubstitutionReentrant, false) @@ -17,6 +17,181 @@ LUAU_FASTFLAGVARIABLE(LuauSubstitutionReentrant, false) namespace Luau { +static TypeId DEPRECATED_shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log, bool alwaysClone) +{ + ty = log->follow(ty); + + TypeId result = ty; + + if (auto pty = log->pending(ty)) + ty = &pty->pending; + + if (const FunctionType* ftv = get(ty)) + { + FunctionType clone = FunctionType{ftv->level, ftv->scope, ftv->argTypes, ftv->retTypes, ftv->definition, ftv->hasSelf}; + clone.generics = ftv->generics; + clone.genericPacks = ftv->genericPacks; + clone.magicFunction = ftv->magicFunction; + clone.dcrMagicFunction = ftv->dcrMagicFunction; + clone.dcrMagicRefinement = ftv->dcrMagicRefinement; + clone.tags = ftv->tags; + clone.argNames = ftv->argNames; + result = dest.addType(std::move(clone)); + } + else if (const TableType* ttv = get(ty)) + { + LUAU_ASSERT(!ttv->boundTo); + TableType clone = TableType{ttv->props, ttv->indexer, ttv->level, ttv->scope, ttv->state}; + clone.definitionModuleName = ttv->definitionModuleName; + clone.definitionLocation = ttv->definitionLocation; + clone.name = ttv->name; + clone.syntheticName = ttv->syntheticName; + clone.instantiatedTypeParams = ttv->instantiatedTypeParams; + clone.instantiatedTypePackParams = ttv->instantiatedTypePackParams; + clone.tags = ttv->tags; + result = dest.addType(std::move(clone)); + } + else if (const MetatableType* mtv = get(ty)) + { + MetatableType clone = MetatableType{mtv->table, mtv->metatable}; + clone.syntheticName = mtv->syntheticName; + result = dest.addType(std::move(clone)); + } + else if (const UnionType* utv = get(ty)) + { + UnionType clone; + clone.options = utv->options; + result = dest.addType(std::move(clone)); + } + else if (const IntersectionType* itv = get(ty)) + { + IntersectionType clone; + clone.parts = itv->parts; + result = dest.addType(std::move(clone)); + } + else if (const PendingExpansionType* petv = get(ty)) + { + PendingExpansionType clone{petv->prefix, petv->name, petv->typeArguments, petv->packArguments}; + result = dest.addType(std::move(clone)); + } + else if (const NegationType* ntv = get(ty)) + { + result = dest.addType(NegationType{ntv->ty}); + } + else + return result; + + asMutable(result)->documentationSymbol = ty->documentationSymbol; + return result; +} + +static TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log, bool alwaysClone) +{ + if (!FFlag::LuauClonePublicInterfaceLess2) + return DEPRECATED_shallowClone(ty, dest, log, alwaysClone); + + auto go = [ty, &dest, alwaysClone](auto&& a) { + using T = std::decay_t; + + if constexpr (std::is_same_v) + return ty; + else if constexpr (std::is_same_v) + { + // This should never happen, but visit() cannot see it. + LUAU_ASSERT(!"shallowClone didn't follow its argument!"); + return dest.addType(BoundType{a.boundTo}); + } + else if constexpr (std::is_same_v) + return dest.addType(a); + else if constexpr (std::is_same_v) + return ty; + else if constexpr (std::is_same_v) + return ty; + else if constexpr (std::is_same_v) + return ty; + else if constexpr (std::is_same_v) + return ty; + else if constexpr (std::is_same_v) + return ty; + else if constexpr (std::is_same_v) + return ty; + else if constexpr (std::is_same_v) + return ty; + else if constexpr (std::is_same_v) + return ty; + else if constexpr (std::is_same_v) + return dest.addType(a); + else if constexpr (std::is_same_v) + { + FunctionType clone = FunctionType{a.level, a.scope, a.argTypes, a.retTypes, a.definition, a.hasSelf}; + clone.generics = a.generics; + clone.genericPacks = a.genericPacks; + clone.magicFunction = a.magicFunction; + clone.dcrMagicFunction = a.dcrMagicFunction; + clone.dcrMagicRefinement = a.dcrMagicRefinement; + clone.tags = a.tags; + clone.argNames = a.argNames; + return dest.addType(std::move(clone)); + } + else if constexpr (std::is_same_v) + { + LUAU_ASSERT(!a.boundTo); + TableType clone = TableType{a.props, a.indexer, a.level, a.scope, a.state}; + clone.definitionModuleName = a.definitionModuleName; + clone.definitionLocation = a.definitionLocation; + clone.name = a.name; + clone.syntheticName = a.syntheticName; + clone.instantiatedTypeParams = a.instantiatedTypeParams; + clone.instantiatedTypePackParams = a.instantiatedTypePackParams; + clone.tags = a.tags; + return dest.addType(std::move(clone)); + } + else if constexpr (std::is_same_v) + { + MetatableType clone = MetatableType{a.table, a.metatable}; + clone.syntheticName = a.syntheticName; + return dest.addType(std::move(clone)); + } + else if constexpr (std::is_same_v) + { + UnionType clone; + clone.options = a.options; + return dest.addType(std::move(clone)); + } + else if constexpr (std::is_same_v) + { + IntersectionType clone; + clone.parts = a.parts; + return dest.addType(std::move(clone)); + } + else if constexpr (std::is_same_v) + { + if (alwaysClone) + { + ClassType clone{a.name, a.props, a.parent, a.metatable, a.tags, a.userData, a.definitionModuleName}; + return dest.addType(std::move(clone)); + } + else + return ty; + } + else if constexpr (std::is_same_v) + return dest.addType(NegationType{a.ty}); + else + static_assert(always_false_v, "Non-exhaustive shallowClone switch"); + }; + + ty = log->follow(ty); + + if (auto pty = log->pending(ty)) + ty = &pty->pending; + + TypeId resTy = visit(go, ty->ty); + if (resTy != ty) + asMutable(resTy)->documentationSymbol = ty->documentationSymbol; + + return resTy; +} + void Tarjan::visitChildren(TypeId ty, int index) { LUAU_ASSERT(ty == log->follow(ty)); @@ -469,7 +644,7 @@ std::optional Substitution::substitute(TypePackId tp) TypeId Substitution::clone(TypeId ty) { - return shallowClone(ty, *arena, log, /* alwaysClone */ FFlag::LuauClonePublicInterfaceLess); + return shallowClone(ty, *arena, log, /* alwaysClone */ FFlag::LuauClonePublicInterfaceLess2); } TypePackId Substitution::clone(TypePackId tp) @@ -494,7 +669,7 @@ TypePackId Substitution::clone(TypePackId tp) clone.hidden = vtp->hidden; return addTypePack(std::move(clone)); } - else if (FFlag::LuauClonePublicInterfaceLess) + else if (FFlag::LuauClonePublicInterfaceLess2) { return addTypePack(*tp); } diff --git a/Analysis/src/ToString.cpp b/Analysis/src/ToString.cpp index d0c5398..5c0f48f 100644 --- a/Analysis/src/ToString.cpp +++ b/Analysis/src/ToString.cpp @@ -85,6 +85,11 @@ struct FindCyclicTypes final : TypeVisitor { return false; } + + bool visit(TypeId, const PendingExpansionType&) override + { + return false; + } }; template @@ -1518,7 +1523,7 @@ std::string toString(const Constraint& constraint, ToStringOptions& opts) } else if constexpr (std::is_same_v) { - return "call " + tos(c.fn) + " with { result = " + tos(c.result) + " }"; + return "call " + tos(c.fn) + "( " + tos(c.argsPack) + " )" + " with { result = " + tos(c.result) + " }"; } else if constexpr (std::is_same_v) { diff --git a/Analysis/src/Type.cpp b/Analysis/src/Type.cpp index 42fa40a..021d952 100644 --- a/Analysis/src/Type.cpp +++ b/Analysis/src/Type.cpp @@ -26,7 +26,6 @@ LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0) LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauNormalizeBlockedTypes) -LUAU_FASTFLAGVARIABLE(LuauMatchReturnsOptionalString, false); namespace Luau { @@ -432,7 +431,7 @@ bool hasLength(TypeId ty, DenseHashSet& seen, int* recursionCount) } BlockedType::BlockedType() - : index(FFlag::LuauNormalizeBlockedTypes ? Unifiable::freshIndex() : ++DEPRECATED_nextIndex) + : index(FFlag::LuauNormalizeBlockedTypes ? Unifiable::freshIndex() : ++DEPRECATED_nextIndex) { } @@ -1219,12 +1218,12 @@ static std::vector parsePatternString(NotNull builtinTypes if (i + 1 < size && data[i + 1] == ')') { i++; - result.push_back(FFlag::LuauMatchReturnsOptionalString ? builtinTypes->optionalNumberType : builtinTypes->numberType); + result.push_back(builtinTypes->optionalNumberType); continue; } ++depth; - result.push_back(FFlag::LuauMatchReturnsOptionalString ? builtinTypes->optionalStringType : builtinTypes->stringType); + result.push_back(builtinTypes->optionalStringType); } else if (data[i] == ')') { @@ -1242,7 +1241,7 @@ static std::vector parsePatternString(NotNull builtinTypes return std::vector(); if (result.empty()) - result.push_back(FFlag::LuauMatchReturnsOptionalString ? builtinTypes->optionalStringType : builtinTypes->stringType); + result.push_back(builtinTypes->optionalStringType); return result; } diff --git a/Analysis/src/TypeChecker2.cpp b/Analysis/src/TypeChecker2.cpp index a160a1d..ec71a58 100644 --- a/Analysis/src/TypeChecker2.cpp +++ b/Analysis/src/TypeChecker2.cpp @@ -568,6 +568,10 @@ struct TypeChecker2 { // nothing } + else if (isOptional(iteratorTy)) + { + reportError(OptionalValueAccess{iteratorTy}, forInStatement->values.data[0]->location); + } else if (std::optional iterMmTy = findMetatableEntry(builtinTypes, module->errors, iteratorTy, "__iter", forInStatement->values.data[0]->location)) { @@ -973,6 +977,12 @@ struct TypeChecker2 else if (auto utv = get(functionType)) { // Sometimes it's okay to call a union of functions, but only if all of the functions are the same. + // Another scenario we might run into it is if the union has a nil member. In this case, we want to throw an error + if (isOptional(functionType)) + { + reportError(OptionalValueAccess{functionType}, call->location); + return; + } std::optional fst; for (TypeId ty : utv) { @@ -1187,6 +1197,8 @@ struct TypeChecker2 else reportError(CannotExtendTable{exprType, CannotExtendTable::Indexer, "indexer??"}, indexExpr->location); } + else if (get(exprType) && isOptional(exprType)) + reportError(OptionalValueAccess{exprType}, indexExpr->location); } void visit(AstExprFunction* fn) @@ -1297,9 +1309,13 @@ struct TypeChecker2 DenseHashSet seen{nullptr}; int recursionCount = 0; + if (!hasLength(operandType, seen, &recursionCount)) { - reportError(NotATable{operandType}, expr->location); + if (isOptional(operandType)) + reportError(OptionalValueAccess{operandType}, expr->location); + else + reportError(NotATable{operandType}, expr->location); } } else if (expr->op == AstExprUnary::Op::Minus) diff --git a/Analysis/src/TypeInfer.cpp b/Analysis/src/TypeInfer.cpp index abc6528..48ff6a2 100644 --- a/Analysis/src/TypeInfer.cpp +++ b/Analysis/src/TypeInfer.cpp @@ -689,11 +689,10 @@ LUAU_NOINLINE void TypeChecker::checkBlockTypeAliases(const ScopePtr& scope, std if (duplicateTypeAliases.contains({typealias->exported, name})) continue; - TypeId type = bindings[name].type; - if (get(follow(type))) + TypeId type = follow(bindings[name].type); + if (get(type)) { - Type* mty = asMutable(follow(type)); - mty->reassign(*errorRecoveryType(anyType)); + asMutable(type)->ty.emplace(errorRecoveryType(anyType)); reportError(TypeError{typealias->location, OccursCheckFailed{}}); } diff --git a/Analysis/src/TypeReduction.cpp b/Analysis/src/TypeReduction.cpp index 6a9fadf..310df76 100644 --- a/Analysis/src/TypeReduction.cpp +++ b/Analysis/src/TypeReduction.cpp @@ -331,7 +331,7 @@ TypeId TypeReducer::reduce(TypeId ty) if (edge->irreducible) return edge->type; else - ty = edge->type; + ty = follow(edge->type); } else if (cyclics->contains(ty)) return ty; diff --git a/Analysis/src/Unifiable.cpp b/Analysis/src/Unifiable.cpp index dcb2d36..abdc6c3 100644 --- a/Analysis/src/Unifiable.cpp +++ b/Analysis/src/Unifiable.cpp @@ -12,7 +12,7 @@ int freshIndex() { return ++nextIndex; } - + Free::Free(TypeLevel level) : index(++nextIndex) , level(level) diff --git a/Analysis/src/Unifier.cpp b/Analysis/src/Unifier.cpp index 9f30d11..5f01a60 100644 --- a/Analysis/src/Unifier.cpp +++ b/Analysis/src/Unifier.cpp @@ -192,6 +192,18 @@ struct SkipCacheForType final : TypeOnceVisitor return false; } + bool visit(TypeId, const BlockedType&) override + { + result = true; + return false; + } + + bool visit(TypeId, const PendingExpansionType&) override + { + result = true; + return false; + } + bool visit(TypeId ty, const TableType&) override { // Types from other modules don't contain mutable elements and are ok to cache @@ -259,6 +271,12 @@ struct SkipCacheForType final : TypeOnceVisitor return false; } + bool visit(TypePackId tp, const BlockedTypePack&) override + { + result = true; + return false; + } + const DenseHashMap& skipCacheForType; const TypeArena* typeArena = nullptr; bool result = false; @@ -386,6 +404,12 @@ void Unifier::tryUnify(TypeId subTy, TypeId superTy, bool isFunctionCall, bool i tryUnify_(subTy, superTy, isFunctionCall, isIntersection); } +static bool isBlocked(const TxnLog& log, TypeId ty) +{ + ty = log.follow(ty); + return get(ty) || get(ty); +} + void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool isIntersection) { RecursionLimiter _ra(&sharedState.counters.recursionCount, sharedState.counters.recursionLimit); @@ -531,11 +555,15 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool size_t errorCount = errors.size(); - if (log.getMutable(subTy) && log.getMutable(superTy)) + if (isBlocked(log, subTy) && isBlocked(log, superTy)) { blockedTypes.push_back(subTy); blockedTypes.push_back(superTy); } + else if (isBlocked(log, subTy)) + blockedTypes.push_back(subTy); + else if (isBlocked(log, superTy)) + blockedTypes.push_back(superTy); else if (const UnionType* subUnion = log.getMutable(subTy)) { tryUnifyUnionWithType(subTy, subUnion, superTy); @@ -890,7 +918,8 @@ void Unifier::tryUnifyTypeWithUnion(TypeId subTy, TypeId superTy, const UnionTyp if (!subNorm || !superNorm) return reportError(location, UnificationTooComplex{}); else if ((failedOptionCount == 1 || foundHeuristic) && failedOption) - innerState.tryUnifyNormalizedTypes(subTy, superTy, *subNorm, *superNorm, "None of the union options are compatible. For example:", *failedOption); + innerState.tryUnifyNormalizedTypes( + subTy, superTy, *subNorm, *superNorm, "None of the union options are compatible. For example:", *failedOption); else innerState.tryUnifyNormalizedTypes(subTy, superTy, *subNorm, *superNorm, "none of the union options are compatible"); if (!innerState.failure) diff --git a/CodeGen/include/Luau/AssemblyBuilderA64.h b/CodeGen/include/Luau/AssemblyBuilderA64.h index 0179967..1190e97 100644 --- a/CodeGen/include/Luau/AssemblyBuilderA64.h +++ b/CodeGen/include/Luau/AssemblyBuilderA64.h @@ -24,20 +24,24 @@ public: // Moves void mov(RegisterA64 dst, RegisterA64 src); - void mov(RegisterA64 dst, uint16_t src, int shift = 0); + void mov(RegisterA64 dst, int src); // macro + + // Moves of 32-bit immediates get decomposed into one or more of these + void movz(RegisterA64 dst, uint16_t src, int shift = 0); + void movn(RegisterA64 dst, uint16_t src, int shift = 0); void movk(RegisterA64 dst, uint16_t src, int shift = 0); // Arithmetics void add(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2, int shift = 0); - void add(RegisterA64 dst, RegisterA64 src1, int src2); + void add(RegisterA64 dst, RegisterA64 src1, uint16_t src2); void sub(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2, int shift = 0); - void sub(RegisterA64 dst, RegisterA64 src1, int src2); + void sub(RegisterA64 dst, RegisterA64 src1, uint16_t src2); void neg(RegisterA64 dst, RegisterA64 src); // Comparisons // Note: some arithmetic instructions also have versions that update flags (ADDS etc) but we aren't using them atm void cmp(RegisterA64 src1, RegisterA64 src2); - void cmp(RegisterA64 src1, int src2); + void cmp(RegisterA64 src1, uint16_t src2); // Bitwise // Note: shifted-register support and bitfield operations are omitted for simplicity @@ -63,11 +67,13 @@ public: void ldrsb(RegisterA64 dst, AddressA64 src); void ldrsh(RegisterA64 dst, AddressA64 src); void ldrsw(RegisterA64 dst, AddressA64 src); + void ldp(RegisterA64 dst1, RegisterA64 dst2, AddressA64 src); // Store void str(RegisterA64 src, AddressA64 dst); void strb(RegisterA64 src, AddressA64 dst); void strh(RegisterA64 src, AddressA64 dst); + void stp(RegisterA64 src1, RegisterA64 src2, AddressA64 dst); // Control flow // Note: tbz/tbnz are currently not supported because they have 15-bit offsets and we don't support branch thunks @@ -84,6 +90,9 @@ public: void adr(RegisterA64 dst, uint64_t value); void adr(RegisterA64 dst, double value); + // Address of code (label) + void adr(RegisterA64 dst, Label& label); + // Run final checks bool finalize(); @@ -113,6 +122,9 @@ public: const bool logText = false; + // Maximum immediate argument to functions like add/sub/cmp + static constexpr size_t kMaxImmediate = (1 << 12) - 1; + private: // Instruction archetypes void place0(const char* name, uint32_t word); @@ -127,6 +139,8 @@ private: void placeBCR(const char* name, Label& label, uint8_t op, RegisterA64 cond); void placeBR(const char* name, RegisterA64 src, uint32_t op); void placeADR(const char* name, RegisterA64 src, uint8_t op); + void placeADR(const char* name, RegisterA64 src, uint8_t op, Label& label); + void placeP(const char* name, RegisterA64 dst1, RegisterA64 dst2, AddressA64 src, uint8_t op, uint8_t size); void place(uint32_t word); @@ -146,6 +160,7 @@ private: LUAU_NOINLINE void log(const char* opcode, RegisterA64 dst, RegisterA64 src); LUAU_NOINLINE void log(const char* opcode, RegisterA64 dst, int src, int shift = 0); LUAU_NOINLINE void log(const char* opcode, RegisterA64 dst, AddressA64 src); + LUAU_NOINLINE void log(const char* opcode, RegisterA64 dst1, RegisterA64 dst2, AddressA64 src); LUAU_NOINLINE void log(const char* opcode, RegisterA64 src, Label label); LUAU_NOINLINE void log(const char* opcode, RegisterA64 src); LUAU_NOINLINE void log(const char* opcode, Label label); diff --git a/CodeGen/include/Luau/IrAnalysis.h b/CodeGen/include/Luau/IrAnalysis.h index 5c2bc4d..470690b 100644 --- a/CodeGen/include/Luau/IrAnalysis.h +++ b/CodeGen/include/Luau/IrAnalysis.h @@ -52,8 +52,8 @@ void computeCfgInfo(IrFunction& function); struct BlockIteratorWrapper { - uint32_t* itBegin = nullptr; - uint32_t* itEnd = nullptr; + const uint32_t* itBegin = nullptr; + const uint32_t* itEnd = nullptr; bool empty() const { @@ -65,19 +65,19 @@ struct BlockIteratorWrapper return size_t(itEnd - itBegin); } - uint32_t* begin() const + const uint32_t* begin() const { return itBegin; } - uint32_t* end() const + const uint32_t* end() const { return itEnd; } }; -BlockIteratorWrapper predecessors(CfgInfo& cfg, uint32_t blockIdx); -BlockIteratorWrapper successors(CfgInfo& cfg, uint32_t blockIdx); +BlockIteratorWrapper predecessors(const CfgInfo& cfg, uint32_t blockIdx); +BlockIteratorWrapper successors(const CfgInfo& cfg, uint32_t blockIdx); } // namespace CodeGen } // namespace Luau diff --git a/CodeGen/include/Luau/IrData.h b/CodeGen/include/Luau/IrData.h index 67e7063..e8b2bc6 100644 --- a/CodeGen/include/Luau/IrData.h +++ b/CodeGen/include/Luau/IrData.h @@ -385,17 +385,15 @@ enum class IrCmd : uint8_t LOP_SETLIST, // Call specified function - // A: unsigned int (bytecode instruction index) - // B: Rn (function, followed by arguments) - // C: int (argument count or -1 to use all arguments up to stack top) - // D: int (result count or -1 to preserve all results and adjust stack top) - // Note: return values are placed starting from Rn specified in 'B' + // A: Rn (function, followed by arguments) + // B: int (argument count or -1 to use all arguments up to stack top) + // C: int (result count or -1 to preserve all results and adjust stack top) + // Note: return values are placed starting from Rn specified in 'A' LOP_CALL, // Return specified values from the function - // A: unsigned int (bytecode instruction index) - // B: Rn (value start) - // C: int (result count or -1 to return all values up to stack top) + // A: Rn (value start) + // B: int (result count or -1 to return all values up to stack top) LOP_RETURN, // Adjust loop variables for one iteration of a generic for loop, jump back to the loop header if loop needs to continue @@ -421,10 +419,9 @@ enum class IrCmd : uint8_t LOP_FORGPREP_XNEXT_FALLBACK, // Perform `and` or `or` operation (selecting lhs or rhs based on whether the lhs is truthy) and put the result into target register - // A: unsigned int (bytecode instruction index) - // B: Rn (target) - // C: Rn (lhs) - // D: Rn or Kn (rhs) + // A: Rn (target) + // B: Rn (lhs) + // C: Rn or Kn (rhs) LOP_AND, LOP_ANDK, LOP_OR, @@ -790,12 +787,6 @@ struct IrFunction return value.valueDouble; } - IrCondition conditionOp(IrOp op) - { - LUAU_ASSERT(op.kind == IrOpKind::Condition); - return IrCondition(op.index); - } - uint32_t getBlockIndex(const IrBlock& block) { // Can only be called with blocks from our vector @@ -804,5 +795,29 @@ struct IrFunction } }; +inline IrCondition conditionOp(IrOp op) +{ + LUAU_ASSERT(op.kind == IrOpKind::Condition); + return IrCondition(op.index); +} + +inline int vmRegOp(IrOp op) +{ + LUAU_ASSERT(op.kind == IrOpKind::VmReg); + return op.index; +} + +inline int vmConstOp(IrOp op) +{ + LUAU_ASSERT(op.kind == IrOpKind::VmConst); + return op.index; +} + +inline int vmUpvalueOp(IrOp op) +{ + LUAU_ASSERT(op.kind == IrOpKind::VmUpvalue); + return op.index; +} + } // namespace CodeGen } // namespace Luau diff --git a/CodeGen/include/Luau/IrDump.h b/CodeGen/include/Luau/IrDump.h index ae517e8..1bc31d9 100644 --- a/CodeGen/include/Luau/IrDump.h +++ b/CodeGen/include/Luau/IrDump.h @@ -19,9 +19,9 @@ const char* getBlockKindName(IrBlockKind kind); struct IrToStringContext { std::string& result; - std::vector& blocks; - std::vector& constants; - CfgInfo& cfg; + const std::vector& blocks; + const std::vector& constants; + const CfgInfo& cfg; }; void toString(IrToStringContext& ctx, const IrInst& inst, uint32_t index); @@ -33,13 +33,13 @@ void toString(std::string& result, IrConst constant); void toStringDetailed(IrToStringContext& ctx, const IrInst& inst, uint32_t index, bool includeUseInfo); void toStringDetailed(IrToStringContext& ctx, const IrBlock& block, uint32_t index, bool includeUseInfo); // Block title -std::string toString(IrFunction& function, bool includeUseInfo); +std::string toString(const IrFunction& function, bool includeUseInfo); -std::string dump(IrFunction& function); +std::string dump(const IrFunction& function); -std::string toDot(IrFunction& function, bool includeInst); +std::string toDot(const IrFunction& function, bool includeInst); -std::string dumpDot(IrFunction& function, bool includeInst); +std::string dumpDot(const IrFunction& function, bool includeInst); } // namespace CodeGen } // namespace Luau diff --git a/CodeGen/src/AssemblyBuilderA64.cpp b/CodeGen/src/AssemblyBuilderA64.cpp index 308747d..bedd274 100644 --- a/CodeGen/src/AssemblyBuilderA64.cpp +++ b/CodeGen/src/AssemblyBuilderA64.cpp @@ -45,9 +45,30 @@ void AssemblyBuilderA64::mov(RegisterA64 dst, RegisterA64 src) placeSR2("mov", dst, src, 0b01'01010); } -void AssemblyBuilderA64::mov(RegisterA64 dst, uint16_t src, int shift) +void AssemblyBuilderA64::mov(RegisterA64 dst, int src) { - placeI16("mov", dst, src, 0b10'100101, shift); + if (src >= 0) + { + movz(dst, src & 0xffff); + if (src > 0xffff) + movk(dst, src >> 16, 16); + } + else + { + movn(dst, ~src & 0xffff); + if (src < -0x10000) + movk(dst, (src >> 16) & 0xffff, 16); + } +} + +void AssemblyBuilderA64::movz(RegisterA64 dst, uint16_t src, int shift) +{ + placeI16("movz", dst, src, 0b10'100101, shift); +} + +void AssemblyBuilderA64::movn(RegisterA64 dst, uint16_t src, int shift) +{ + placeI16("movn", dst, src, 0b00'100101, shift); } void AssemblyBuilderA64::movk(RegisterA64 dst, uint16_t src, int shift) @@ -60,7 +81,7 @@ void AssemblyBuilderA64::add(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2 placeSR3("add", dst, src1, src2, 0b00'01011, shift); } -void AssemblyBuilderA64::add(RegisterA64 dst, RegisterA64 src1, int src2) +void AssemblyBuilderA64::add(RegisterA64 dst, RegisterA64 src1, uint16_t src2) { placeI12("add", dst, src1, src2, 0b00'10001); } @@ -70,7 +91,7 @@ void AssemblyBuilderA64::sub(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2 placeSR3("sub", dst, src1, src2, 0b10'01011, shift); } -void AssemblyBuilderA64::sub(RegisterA64 dst, RegisterA64 src1, int src2) +void AssemblyBuilderA64::sub(RegisterA64 dst, RegisterA64 src1, uint16_t src2) { placeI12("sub", dst, src1, src2, 0b10'10001); } @@ -87,7 +108,7 @@ void AssemblyBuilderA64::cmp(RegisterA64 src1, RegisterA64 src2) placeSR3("cmp", dst, src1, src2, 0b11'01011); } -void AssemblyBuilderA64::cmp(RegisterA64 src1, int src2) +void AssemblyBuilderA64::cmp(RegisterA64 src1, uint16_t src2) { RegisterA64 dst = src1.kind == KindA64::x ? xzr : wzr; @@ -186,6 +207,14 @@ void AssemblyBuilderA64::ldrsw(RegisterA64 dst, AddressA64 src) placeA("ldrsw", dst, src, 0b11100010, 0b10); } +void AssemblyBuilderA64::ldp(RegisterA64 dst1, RegisterA64 dst2, AddressA64 src) +{ + LUAU_ASSERT(dst1.kind == KindA64::x || dst1.kind == KindA64::w); + LUAU_ASSERT(dst1.kind == dst2.kind); + + placeP("ldp", dst1, dst2, src, 0b101'0'010'1, 0b10 | uint8_t(dst1.kind == KindA64::x)); +} + void AssemblyBuilderA64::str(RegisterA64 src, AddressA64 dst) { LUAU_ASSERT(src.kind == KindA64::x || src.kind == KindA64::w); @@ -207,6 +236,14 @@ void AssemblyBuilderA64::strh(RegisterA64 src, AddressA64 dst) placeA("strh", src, dst, 0b11100000, 0b01); } +void AssemblyBuilderA64::stp(RegisterA64 src1, RegisterA64 src2, AddressA64 dst) +{ + LUAU_ASSERT(src1.kind == KindA64::x || src1.kind == KindA64::w); + LUAU_ASSERT(src1.kind == src2.kind); + + placeP("stp", src1, src2, dst, 0b101'0'010'0, 0b10 | uint8_t(src1.kind == KindA64::x)); +} + void AssemblyBuilderA64::b(Label& label) { // Note: we aren't using 'b' form since it has a 26-bit immediate which requires custom fixup logic @@ -276,6 +313,11 @@ void AssemblyBuilderA64::adr(RegisterA64 dst, double value) patchImm19(location, -int(location) - int((data.size() - pos) / 4)); } +void AssemblyBuilderA64::adr(RegisterA64 dst, Label& label) +{ + placeADR("adr", dst, 0b10000, label); +} + bool AssemblyBuilderA64::finalize() { code.resize(codePos - code.data()); @@ -511,6 +553,32 @@ void AssemblyBuilderA64::placeADR(const char* name, RegisterA64 dst, uint8_t op) commit(); } +void AssemblyBuilderA64::placeADR(const char* name, RegisterA64 dst, uint8_t op, Label& label) +{ + LUAU_ASSERT(dst.kind == KindA64::x); + + place(dst.index | (op << 24)); + commit(); + + patchLabel(label); + + if (logText) + log(name, dst, label); +} + +void AssemblyBuilderA64::placeP(const char* name, RegisterA64 src1, RegisterA64 src2, AddressA64 dst, uint8_t op, uint8_t size) +{ + if (logText) + log(name, src1, src2, dst); + + LUAU_ASSERT(dst.kind == AddressKindA64::imm); + LUAU_ASSERT(dst.data >= -128 * (1 << size) && dst.data <= 127 * (1 << size)); + LUAU_ASSERT(dst.data % (1 << size) == 0); + + place(src1.index | (dst.base.index << 5) | (src2.index << 10) | (((dst.data >> size) & 127) << 15) | (op << 22) | (size << 31)); + commit(); +} + void AssemblyBuilderA64::place(uint32_t word) { LUAU_ASSERT(codePos < codeEnd); @@ -628,6 +696,17 @@ void AssemblyBuilderA64::log(const char* opcode, RegisterA64 dst, AddressA64 src text.append("\n"); } +void AssemblyBuilderA64::log(const char* opcode, RegisterA64 dst1, RegisterA64 dst2, AddressA64 src) +{ + logAppend(" %-12s", opcode); + log(dst1); + text.append(","); + log(dst2); + text.append(","); + log(src); + text.append("\n"); +} + void AssemblyBuilderA64::log(const char* opcode, RegisterA64 dst, RegisterA64 src) { logAppend(" %-12s", opcode); diff --git a/CodeGen/src/CodeGen.cpp b/CodeGen/src/CodeGen.cpp index ce490f9..5ef5ba6 100644 --- a/CodeGen/src/CodeGen.cpp +++ b/CodeGen/src/CodeGen.cpp @@ -6,6 +6,8 @@ #include "Luau/CodeBlockUnwind.h" #include "Luau/IrAnalysis.h" #include "Luau/IrBuilder.h" +#include "Luau/IrDump.h" +#include "Luau/IrUtils.h" #include "Luau/OptimizeConstProp.h" #include "Luau/OptimizeFinalX64.h" @@ -13,19 +15,24 @@ #include "Luau/UnwindBuilderDwarf2.h" #include "Luau/UnwindBuilderWin.h" -#include "Luau/AssemblyBuilderX64.h" #include "Luau/AssemblyBuilderA64.h" +#include "Luau/AssemblyBuilderX64.h" #include "CustomExecUtils.h" -#include "CodeGenX64.h" +#include "NativeState.h" + #include "CodeGenA64.h" +#include "EmitCommonA64.h" +#include "IrLoweringA64.h" + +#include "CodeGenX64.h" #include "EmitCommonX64.h" #include "EmitInstructionX64.h" #include "IrLoweringX64.h" -#include "NativeState.h" #include "lapi.h" +#include #include #if defined(__x86_64__) || defined(_M_X64) @@ -60,6 +67,148 @@ static NativeProto* createNativeProto(Proto* proto, const IrBuilder& ir) return result; } +template +static void lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction& function, int bytecodeid, AssemblyOptions options) +{ + // While we will need a better block ordering in the future, right now we want to mostly preserve build order with fallbacks outlined + std::vector sortedBlocks; + sortedBlocks.reserve(function.blocks.size()); + for (uint32_t i = 0; i < function.blocks.size(); i++) + sortedBlocks.push_back(i); + + std::sort(sortedBlocks.begin(), sortedBlocks.end(), [&](uint32_t idxA, uint32_t idxB) { + const IrBlock& a = function.blocks[idxA]; + const IrBlock& b = function.blocks[idxB]; + + // Place fallback blocks at the end + if ((a.kind == IrBlockKind::Fallback) != (b.kind == IrBlockKind::Fallback)) + return (a.kind == IrBlockKind::Fallback) < (b.kind == IrBlockKind::Fallback); + + // Try to order by instruction order + return a.start < b.start; + }); + + DenseHashMap bcLocations{~0u}; + + // Create keys for IR assembly locations that original bytecode instruction are interested in + for (const auto& [irLocation, asmLocation] : function.bcMapping) + { + if (irLocation != ~0u) + bcLocations[irLocation] = 0; + } + + DenseHashMap indexIrToBc{~0u}; + bool outputEnabled = options.includeAssembly || options.includeIr; + + if (outputEnabled && options.annotator) + { + // Create reverse mapping from IR location to bytecode location + for (size_t i = 0; i < function.bcMapping.size(); ++i) + { + uint32_t irLocation = function.bcMapping[i].irLocation; + + if (irLocation != ~0u) + indexIrToBc[irLocation] = uint32_t(i); + } + } + + IrToStringContext ctx{build.text, function.blocks, function.constants, function.cfg}; + + // We use this to skip outlined fallback blocks from IR/asm text output + size_t textSize = build.text.length(); + uint32_t codeSize = build.getCodeSize(); + bool seenFallback = false; + + IrBlock dummy; + dummy.start = ~0u; + + for (size_t i = 0; i < sortedBlocks.size(); ++i) + { + uint32_t blockIndex = sortedBlocks[i]; + + IrBlock& block = function.blocks[blockIndex]; + + if (block.kind == IrBlockKind::Dead) + continue; + + LUAU_ASSERT(block.start != ~0u); + LUAU_ASSERT(block.finish != ~0u); + + // If we want to skip fallback code IR/asm, we'll record when those blocks start once we see them + if (block.kind == IrBlockKind::Fallback && !seenFallback) + { + textSize = build.text.length(); + codeSize = build.getCodeSize(); + seenFallback = true; + } + + if (options.includeIr) + { + build.logAppend("# "); + toStringDetailed(ctx, block, blockIndex, /* includeUseInfo */ true); + } + + build.setLabel(block.label); + + for (uint32_t index = block.start; index <= block.finish; index++) + { + LUAU_ASSERT(index < function.instructions.size()); + + // If IR instruction is the first one for the original bytecode, we can annotate it with source code text + if (outputEnabled && options.annotator) + { + if (uint32_t* bcIndex = indexIrToBc.find(index)) + options.annotator(options.annotatorContext, build.text, bytecodeid, *bcIndex); + } + + // If bytecode needs the location of this instruction for jumps, record it + if (uint32_t* bcLocation = bcLocations.find(index)) + { + Label label = (index == block.start) ? block.label : build.setLabel(); + *bcLocation = build.getLabelOffset(label); + } + + IrInst& inst = function.instructions[index]; + + // Skip pseudo instructions, but make sure they are not used at this stage + // This also prevents them from getting into text output when that's enabled + if (isPseudo(inst.cmd)) + { + LUAU_ASSERT(inst.useCount == 0); + continue; + } + + if (options.includeIr) + { + build.logAppend("# "); + toStringDetailed(ctx, inst, index, /* includeUseInfo */ true); + } + + IrBlock& next = i + 1 < sortedBlocks.size() ? function.blocks[sortedBlocks[i + 1]] : dummy; + + lowering.lowerInst(inst, index, next); + } + + if (options.includeIr) + build.logAppend("#\n"); + } + + if (outputEnabled && !options.includeOutlinedCode && seenFallback) + { + build.text.resize(textSize); + + if (options.includeAssembly) + build.logAppend("; skipping %u bytes of outlined code\n", unsigned((build.getCodeSize() - codeSize) * sizeof(build.code[0]))); + } + + // Copy assembly locations of IR instructions that are mapped to bytecode instructions + for (auto& [irLocation, asmLocation] : function.bcMapping) + { + if (irLocation != ~0u) + asmLocation = bcLocations[irLocation]; + } +} + [[maybe_unused]] static void lowerIr( X64::AssemblyBuilderX64& build, IrBuilder& ir, NativeState& data, ModuleHelpers& helpers, Proto* proto, AssemblyOptions options) { @@ -69,24 +218,34 @@ static NativeProto* createNativeProto(Proto* proto, const IrBuilder& ir) build.align(kFunctionAlignment, X64::AlignmentDataX64::Ud2); - X64::IrLoweringX64 lowering(build, helpers, data, proto, ir.function); + X64::IrLoweringX64 lowering(build, helpers, data, ir.function); - lowering.lower(options); + lowerImpl(build, lowering, ir.function, proto->bytecodeid, options); } [[maybe_unused]] static void lowerIr( A64::AssemblyBuilderA64& build, IrBuilder& ir, NativeState& data, ModuleHelpers& helpers, Proto* proto, AssemblyOptions options) { - Label start = build.setLabel(); + if (A64::IrLoweringA64::canLower(ir.function)) + { + A64::IrLoweringA64 lowering(build, helpers, data, proto, ir.function); - build.mov(A64::x0, 1); // finish function in VM - build.ret(); + lowerImpl(build, lowering, ir.function, proto->bytecodeid, options); + } + else + { + // TODO: This is only needed while we don't support all IR opcodes + // When we can't translate some parts of the function, we instead encode a dummy assembly sequence that hands off control to VM + // In the future we could return nullptr from assembleFunction and handle it because there may be other reasons for why we refuse to assemble. + Label start = build.setLabel(); - // TODO: This is only needed while we don't support all IR opcodes - // When we can't translate some parts of the function, we instead encode a dummy assembly sequence that hands off control to VM - // In the future we could return nullptr from assembleFunction and handle it because there may be other reasons for why we refuse to assemble. - for (int i = 0; i < proto->sizecode; i++) - ir.function.bcMapping[i].asmLocation = build.getLabelOffset(start); + build.mov(A64::x0, 1); // finish function in VM + build.ldr(A64::x1, A64::mem(A64::rNativeContext, offsetof(NativeContext, gateExit))); + build.br(A64::x1); + + for (int i = 0; i < proto->sizecode; i++) + ir.function.bcMapping[i].asmLocation = build.getLabelOffset(start); + } } template @@ -123,15 +282,13 @@ static NativeProto* assembleFunction(AssemblyBuilder& build, NativeState& data, IrBuilder ir; ir.buildFunctionIr(proto); + computeCfgInfo(ir.function); + if (!FFlag::DebugCodegenNoOpt) { constPropInBlockChains(ir); } - // TODO: cfg info has to be computed earlier to use in optimizations - // It's done here to appear in text output and to measure performance impact on code generation - computeCfgInfo(ir.function); - lowerIr(build, ir, data, helpers, proto, options); if (build.logText) @@ -217,7 +374,8 @@ bool isSupported() return true; #elif defined(__aarch64__) - return true; + // TODO: A64 codegen does not generate correct unwind info at the moment so it requires longjmp instead of C++ exceptions + return bool(LUA_USE_LONGJMP); #else return false; #endif @@ -300,7 +458,9 @@ void compile(lua_State* L, int idx) gatherFunctions(protos, clvalue(func)->l.p); ModuleHelpers helpers; -#if !defined(__aarch64__) +#if defined(__aarch64__) + A64::assembleHelpers(build, helpers); +#else X64::assembleHelpers(build, helpers); #endif @@ -359,7 +519,9 @@ std::string getAssembly(lua_State* L, int idx, AssemblyOptions options) gatherFunctions(protos, clvalue(func)->l.p); ModuleHelpers helpers; -#if !defined(__aarch64__) +#if defined(__aarch64__) + A64::assembleHelpers(build, helpers); +#else X64::assembleHelpers(build, helpers); #endif @@ -373,8 +535,7 @@ std::string getAssembly(lua_State* L, int idx, AssemblyOptions options) build.finalize(); if (options.outputBinary) - return std::string( - reinterpret_cast(build.code.data()), reinterpret_cast(build.code.data() + build.code.size())) + + return std::string(reinterpret_cast(build.code.data()), reinterpret_cast(build.code.data() + build.code.size())) + std::string(build.data.begin(), build.data.end()); else return build.text; diff --git a/CodeGen/src/CodeGenA64.cpp b/CodeGen/src/CodeGenA64.cpp index 94d6f2e..028b332 100644 --- a/CodeGen/src/CodeGenA64.cpp +++ b/CodeGen/src/CodeGenA64.cpp @@ -6,6 +6,7 @@ #include "CustomExecUtils.h" #include "NativeState.h" +#include "EmitCommonA64.h" #include "lstate.h" @@ -21,26 +22,50 @@ bool initEntryFunction(NativeState& data) AssemblyBuilderA64 build(/* logText= */ false); UnwindBuilder& unwind = *data.unwindBuilder.get(); - unwind.start(); - unwind.allocStack(8); // TODO: this is only necessary to align stack by 16 bytes, as start() allocates 8b return pointer + // Arguments: x0 = lua_State*, x1 = Proto*, x2 = native code pointer to jump to, x3 = NativeContext* - // TODO: prologue goes here + unwind.start(); + unwind.allocStack(8); // TODO: this is just a hack to make UnwindBuilder assertions cooperate + + // prologue + build.sub(sp, sp, kStackSize); + build.stp(x29, x30, mem(sp)); // fp, lr + + // stash non-volatile registers used for execution environment + build.stp(x19, x20, mem(sp, 16)); + build.stp(x21, x22, mem(sp, 32)); + build.stp(x23, x24, mem(sp, 48)); + + build.mov(x29, sp); // this is only necessary if we maintain frame pointers, which we do in the JIT for now unwind.finish(); size_t prologueSize = build.setLabel().location; // Setup native execution environment - // TODO: figure out state layout + build.mov(rState, x0); + build.mov(rNativeContext, x3); - // Jump to the specified instruction; further control flow will be handled with custom ABI with register setup from EmitCommonX64.h + build.ldr(rBase, mem(x0, offsetof(lua_State, base))); // L->base + build.ldr(rConstants, mem(x1, offsetof(Proto, k))); // proto->k + build.ldr(rCode, mem(x1, offsetof(Proto, code))); // proto->code + + build.ldr(x9, mem(x0, offsetof(lua_State, ci))); // L->ci + build.ldr(x9, mem(x9, offsetof(CallInfo, func))); // L->ci->func + build.ldr(rClosure, mem(x9, offsetof(TValue, value.gc))); // L->ci->func->value.gc aka cl + + // Jump to the specified instruction; further control flow will be handled with custom ABI with register setup from EmitCommonA64.h build.br(x2); // Even though we jumped away, we will return here in the end Label returnOff = build.setLabel(); // Cleanup and exit - // TODO: epilogue + build.ldp(x23, x24, mem(sp, 48)); + build.ldp(x21, x22, mem(sp, 32)); + build.ldp(x19, x20, mem(sp, 16)); + build.ldp(x29, x30, mem(sp)); // fp, lr + build.add(sp, sp, kStackSize); build.ret(); @@ -59,11 +84,24 @@ bool initEntryFunction(NativeState& data) // specified by the unwind information of the entry function unwind.setBeginOffset(prologueSize); - data.context.gateExit = data.context.gateEntry + returnOff.location; + data.context.gateExit = data.context.gateEntry + build.getLabelOffset(returnOff); return true; } +void assembleHelpers(AssemblyBuilderA64& build, ModuleHelpers& helpers) +{ + if (build.logText) + build.logAppend("; exitContinueVm\n"); + helpers.exitContinueVm = build.setLabel(); + emitExit(build, /* continueInVm */ true); + + if (build.logText) + build.logAppend("; exitNoContinueVm\n"); + helpers.exitNoContinueVm = build.setLabel(); + emitExit(build, /* continueInVm */ false); +} + } // namespace A64 } // namespace CodeGen } // namespace Luau diff --git a/CodeGen/src/CodeGenA64.h b/CodeGen/src/CodeGenA64.h index 5043e5c..7b792cc 100644 --- a/CodeGen/src/CodeGenA64.h +++ b/CodeGen/src/CodeGenA64.h @@ -7,11 +7,15 @@ namespace CodeGen { struct NativeState; +struct ModuleHelpers; namespace A64 { +class AssemblyBuilderA64; + bool initEntryFunction(NativeState& data); +void assembleHelpers(AssemblyBuilderA64& build, ModuleHelpers& helpers); } // namespace A64 } // namespace CodeGen diff --git a/CodeGen/src/CodeGenUtils.cpp b/CodeGen/src/CodeGenUtils.cpp index 77047a7..26568c3 100644 --- a/CodeGen/src/CodeGenUtils.cpp +++ b/CodeGen/src/CodeGenUtils.cpp @@ -126,5 +126,42 @@ void callEpilogC(lua_State* L, int nresults, int n) L->top = (nresults == LUA_MULTRET) ? res : cip->top; } +const Instruction* returnFallback(lua_State* L, StkId ra, int n) +{ + // ci is our callinfo, cip is our parent + CallInfo* ci = L->ci; + CallInfo* cip = ci - 1; + + StkId res = ci->func; // note: we assume CALL always puts func+args and expects results to start at func + + StkId vali = ra; + StkId valend = (n == LUA_MULTRET) ? L->top : ra + n; // copy as much as possible for MULTRET calls, and only as much as needed otherwise + + int nresults = ci->nresults; + + // copy return values into parent stack (but only up to nresults!), fill the rest with nil + // note: in MULTRET context nresults starts as -1 so i != 0 condition never activates intentionally + int i; + for (i = nresults; i != 0 && vali < valend; i--) + setobj2s(L, res++, vali++); + while (i-- > 0) + setnilvalue(res++); + + // pop the stack frame + L->ci = cip; + L->base = cip->base; + L->top = (nresults == LUA_MULTRET) ? res : cip->top; + + // we're done! + if (LUAU_UNLIKELY(ci->flags & LUA_CALLINFO_RETURN)) + { + L->top = res; + return NULL; + } + + LUAU_ASSERT(isLua(cip)); + return cip->savedpc; +} + } // namespace CodeGen } // namespace Luau diff --git a/CodeGen/src/CodeGenUtils.h b/CodeGen/src/CodeGenUtils.h index ca19021..5d37bfd 100644 --- a/CodeGen/src/CodeGenUtils.h +++ b/CodeGen/src/CodeGenUtils.h @@ -16,5 +16,7 @@ void forgPrepXnextFallback(lua_State* L, TValue* ra, int pc); Closure* callProlog(lua_State* L, TValue* ra, StkId argtop, int nresults); void callEpilogC(lua_State* L, int nresults, int n); +const Instruction* returnFallback(lua_State* L, StkId ra, int n); + } // namespace CodeGen } // namespace Luau diff --git a/CodeGen/src/EmitCommonA64.cpp b/CodeGen/src/EmitCommonA64.cpp new file mode 100644 index 0000000..66810d3 --- /dev/null +++ b/CodeGen/src/EmitCommonA64.cpp @@ -0,0 +1,75 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#include "EmitCommonA64.h" + +#include "NativeState.h" +#include "CustomExecUtils.h" + +namespace Luau +{ +namespace CodeGen +{ +namespace A64 +{ + +void emitExit(AssemblyBuilderA64& build, bool continueInVm) +{ + build.mov(x0, continueInVm); + build.ldr(x1, mem(rNativeContext, offsetof(NativeContext, gateExit))); + build.br(x1); +} + +void emitUpdateBase(AssemblyBuilderA64& build) +{ + build.ldr(rBase, mem(rState, offsetof(lua_State, base))); +} + +void emitSetSavedPc(AssemblyBuilderA64& build, int pcpos) +{ + if (pcpos * sizeof(Instruction) <= AssemblyBuilderA64::kMaxImmediate) + { + build.add(x0, rCode, uint16_t(pcpos * sizeof(Instruction))); + } + else + { + build.mov(x0, pcpos * sizeof(Instruction)); + build.add(x0, rCode, x0); + } + + build.ldr(x1, mem(rState, offsetof(lua_State, ci))); + build.str(x0, mem(x1, offsetof(CallInfo, savedpc))); +} + +void emitInterrupt(AssemblyBuilderA64& build, int pcpos) +{ + Label skip; + + build.ldr(x2, mem(rState, offsetof(lua_State, global))); + build.ldr(x2, mem(x2, offsetof(global_State, cb.interrupt))); + build.cbz(x2, skip); + + emitSetSavedPc(build, pcpos + 1); // uses x0/x1 + + // Call interrupt + // TODO: This code should be outlined so that it can be shared by multiple interruptible instructions + build.mov(x0, rState); + build.mov(w1, -1); + build.blr(x2); + + // Check if we need to exit + build.ldrb(w0, mem(rState, offsetof(lua_State, status))); + build.cbz(w0, skip); + + // L->ci->savedpc-- + build.ldr(x0, mem(rState, offsetof(lua_State, ci))); + build.ldr(x1, mem(x0, offsetof(CallInfo, savedpc))); + build.sub(x1, x1, sizeof(Instruction)); + build.str(x1, mem(x0, offsetof(CallInfo, savedpc))); + + emitExit(build, /* continueInVm */ false); + + build.setLabel(skip); +} + +} // namespace A64 +} // namespace CodeGen +} // namespace Luau diff --git a/CodeGen/src/EmitCommonA64.h b/CodeGen/src/EmitCommonA64.h new file mode 100644 index 0000000..251f6a3 --- /dev/null +++ b/CodeGen/src/EmitCommonA64.h @@ -0,0 +1,77 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#pragma once + +#include "Luau/AssemblyBuilderA64.h" + +#include "EmitCommon.h" + +#include "lobject.h" +#include "ltm.h" + +// AArch64 ABI reminder: +// Arguments: x0-x7, v0-v7 +// Return: x0, v0 (or x8 that points to the address of the resulting structure) +// Volatile: x9-x14, v16-v31 ("caller-saved", any call may change them) +// Non-volatile: x19-x28, v8-v15 ("callee-saved", preserved after calls, only bottom half of SIMD registers is preserved!) +// Reserved: x16-x18: reserved for linker/platform use; x29: frame pointer (unless omitted); x30: link register; x31: stack pointer + +namespace Luau +{ +namespace CodeGen +{ + +struct NativeState; + +namespace A64 +{ + +// Data that is very common to access is placed in non-volatile registers +constexpr RegisterA64 rState = x19; // lua_State* L +constexpr RegisterA64 rBase = x20; // StkId base +constexpr RegisterA64 rNativeContext = x21; // NativeContext* context +constexpr RegisterA64 rConstants = x22; // TValue* k +constexpr RegisterA64 rClosure = x23; // Closure* cl +constexpr RegisterA64 rCode = x24; // Instruction* code + +// Native code is as stackless as the interpreter, so we can place some data on the stack once and have it accessible at any point +// See CodeGenA64.cpp for layout +constexpr unsigned kStackSize = 64; // 8 stashed registers + +inline AddressA64 luauReg(int ri) +{ + return mem(rBase, ri * sizeof(TValue)); +} + +inline AddressA64 luauRegValue(int ri) +{ + return mem(rBase, ri * sizeof(TValue) + offsetof(TValue, value)); +} + +inline AddressA64 luauRegTag(int ri) +{ + return mem(rBase, ri * sizeof(TValue) + offsetof(TValue, tt)); +} + +inline AddressA64 luauConstant(int ki) +{ + return mem(rConstants, ki * sizeof(TValue)); +} + +inline AddressA64 luauConstantTag(int ki) +{ + return mem(rConstants, ki * sizeof(TValue) + offsetof(TValue, tt)); +} + +inline AddressA64 luauConstantValue(int ki) +{ + return mem(rConstants, ki * sizeof(TValue) + offsetof(TValue, value)); +} + +void emitExit(AssemblyBuilderA64& build, bool continueInVm); +void emitUpdateBase(AssemblyBuilderA64& build); +void emitSetSavedPc(AssemblyBuilderA64& build, int pcpos); // invalidates x0/x1 +void emitInterrupt(AssemblyBuilderA64& build, int pcpos); + +} // namespace A64 +} // namespace CodeGen +} // namespace Luau diff --git a/CodeGen/src/EmitInstructionA64.cpp b/CodeGen/src/EmitInstructionA64.cpp new file mode 100644 index 0000000..8289ee2 --- /dev/null +++ b/CodeGen/src/EmitInstructionA64.cpp @@ -0,0 +1,59 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#include "EmitInstructionA64.h" + +#include "Luau/AssemblyBuilderA64.h" + +#include "EmitCommonA64.h" +#include "NativeState.h" +#include "CustomExecUtils.h" + +namespace Luau +{ +namespace CodeGen +{ +namespace A64 +{ + +void emitInstReturn(AssemblyBuilderA64& build, ModuleHelpers& helpers, int ra, int n) +{ + // callFallback(L, ra, n) + build.mov(x0, rState); + build.add(x1, rBase, uint16_t(ra * sizeof(TValue))); + build.mov(w2, n); + build.ldr(x3, mem(rNativeContext, offsetof(NativeContext, returnFallback))); + build.blr(x3); + + emitUpdateBase(build); + + // If the fallback requested an exit, we need to do this right away + build.cbz(x0, helpers.exitNoContinueVm); + + // Need to update state of the current function before we jump away + build.ldr(x1, mem(rState, offsetof(lua_State, ci))); // L->ci + build.ldr(x1, mem(x1, offsetof(CallInfo, func))); // L->ci->func + build.ldr(rClosure, mem(x1, offsetof(TValue, value.gc))); // L->ci->func->value.gc aka cl + + build.ldr(x1, mem(rClosure, offsetof(Closure, l.p))); // cl->l.p aka proto + + build.ldr(rConstants, mem(x1, offsetof(Proto, k))); // proto->k + build.ldr(rCode, mem(x1, offsetof(Proto, code))); // proto->code + + // Get instruction index from instruction pointer + // To get instruction index from instruction pointer, we need to divide byte offset by 4 + // But we will actually need to scale instruction index by 8 back to byte offset later so it cancels out + build.sub(x2, x0, rCode); + build.add(x2, x2, x2); // TODO: this would not be necessary if we supported shifted register offsets in loads + + // We need to check if the new function can be executed natively + build.ldr(x1, mem(x1, offsetofProtoExecData)); + build.cbz(x1, helpers.exitContinueVm); + + // Get new instruction location and jump to it + build.ldr(x1, mem(x1, offsetof(NativeProto, instTargets))); + build.ldr(x1, mem(x1, x2)); + build.br(x1); +} + +} // namespace A64 +} // namespace CodeGen +} // namespace Luau diff --git a/CodeGen/src/EmitInstructionA64.h b/CodeGen/src/EmitInstructionA64.h new file mode 100644 index 0000000..7f15d81 --- /dev/null +++ b/CodeGen/src/EmitInstructionA64.h @@ -0,0 +1,20 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#pragma once + +namespace Luau +{ +namespace CodeGen +{ + +struct ModuleHelpers; + +namespace A64 +{ + +class AssemblyBuilderA64; + +void emitInstReturn(AssemblyBuilderA64& build, ModuleHelpers& helpers, int ra, int n); + +} // namespace A64 +} // namespace CodeGen +} // namespace Luau diff --git a/CodeGen/src/EmitInstructionX64.cpp b/CodeGen/src/EmitInstructionX64.cpp index e8f61eb..649498f 100644 --- a/CodeGen/src/EmitInstructionX64.cpp +++ b/CodeGen/src/EmitInstructionX64.cpp @@ -4,12 +4,7 @@ #include "Luau/AssemblyBuilderX64.h" #include "CustomExecUtils.h" -#include "EmitBuiltinsX64.h" #include "EmitCommonX64.h" -#include "NativeState.h" - -#include "lobject.h" -#include "ltm.h" namespace Luau { @@ -18,16 +13,8 @@ namespace CodeGen namespace X64 { -void emitInstCall(AssemblyBuilderX64& build, ModuleHelpers& helpers, const Instruction* pc, int pcpos) +void emitInstCall(AssemblyBuilderX64& build, ModuleHelpers& helpers, int ra, int nparams, int nresults) { - int ra = LUAU_INSN_A(*pc); - int nparams = LUAU_INSN_B(*pc) - 1; - int nresults = LUAU_INSN_C(*pc) - 1; - - emitInterrupt(build, pcpos); - - emitSetSavedPc(build, pcpos + 1); - build.mov(rArg1, rState); build.lea(rArg2, luauRegAddress(ra)); @@ -171,13 +158,8 @@ void emitInstCall(AssemblyBuilderX64& build, ModuleHelpers& helpers, const Instr } } -void emitInstReturn(AssemblyBuilderX64& build, ModuleHelpers& helpers, const Instruction* pc, int pcpos) +void emitInstReturn(AssemblyBuilderX64& build, ModuleHelpers& helpers, int ra, int actualResults) { - emitInterrupt(build, pcpos); - - int ra = LUAU_INSN_A(*pc); - int b = LUAU_INSN_B(*pc) - 1; - RegisterX64 ci = r8; RegisterX64 cip = r9; RegisterX64 res = rdi; @@ -196,7 +178,7 @@ void emitInstReturn(AssemblyBuilderX64& build, ModuleHelpers& helpers, const Ins RegisterX64 counter = ecx; - if (b == 0) + if (actualResults == 0) { // Our instruction doesn't have any results, so just fill results expected in parent with 'nil' build.test(nresults, nresults); // test here will set SF=1 for a negative number, ZF=1 for zero and OF=0 @@ -210,7 +192,7 @@ void emitInstReturn(AssemblyBuilderX64& build, ModuleHelpers& helpers, const Ins build.dec(counter); build.jcc(ConditionX64::NotZero, repeatNilLoop); } - else if (b == 1) + else if (actualResults == 1) { // Try setting our 1 result build.test(nresults, nresults); @@ -245,10 +227,10 @@ void emitInstReturn(AssemblyBuilderX64& build, ModuleHelpers& helpers, const Ins build.lea(vali, luauRegAddress(ra)); // Copy as much as possible for MULTRET calls, and only as much as needed otherwise - if (b == LUA_MULTRET) + if (actualResults == LUA_MULTRET) build.mov(valend, qword[rState + offsetof(lua_State, top)]); // valend = L->top else - build.lea(valend, luauRegAddress(ra + b)); // valend = ra + b + build.lea(valend, luauRegAddress(ra + actualResults)); // valend = ra + actualResults build.mov(counter, nresults); @@ -333,24 +315,19 @@ void emitInstReturn(AssemblyBuilderX64& build, ModuleHelpers& helpers, const Ins build.jmp(qword[rdx + rax * 2]); } -void emitInstSetList(AssemblyBuilderX64& build, const Instruction* pc, Label& next) +void emitInstSetList(AssemblyBuilderX64& build, Label& next, int ra, int rb, int count, uint32_t index) { - int ra = LUAU_INSN_A(*pc); - int rb = LUAU_INSN_B(*pc); - int c = LUAU_INSN_C(*pc) - 1; - uint32_t index = pc[1]; + OperandX64 last = index + count - 1; - OperandX64 last = index + c - 1; - - // Using non-volatile 'rbx' for dynamic 'c' value (for LUA_MULTRET) to skip later recomputation - // We also keep 'c' scaled by sizeof(TValue) here as it helps in the loop below + // Using non-volatile 'rbx' for dynamic 'count' value (for LUA_MULTRET) to skip later recomputation + // We also keep 'count' scaled by sizeof(TValue) here as it helps in the loop below RegisterX64 cscaled = rbx; - if (c == LUA_MULTRET) + if (count == LUA_MULTRET) { RegisterX64 tmp = rax; - // c = L->top - rb + // count = L->top - rb build.mov(cscaled, qword[rState + offsetof(lua_State, top)]); build.lea(tmp, luauRegAddress(rb)); build.sub(cscaled, tmp); // Using byte difference @@ -360,7 +337,7 @@ void emitInstSetList(AssemblyBuilderX64& build, const Instruction* pc, Label& ne build.mov(tmp, qword[tmp + offsetof(CallInfo, top)]); build.mov(qword[rState + offsetof(lua_State, top)], tmp); - // last = index + c - 1; + // last = index + count - 1; last = edx; build.mov(last, dwordReg(cscaled)); build.shr(last, kTValueSizeLog2); @@ -394,9 +371,9 @@ void emitInstSetList(AssemblyBuilderX64& build, const Instruction* pc, Label& ne const int kUnrollSetListLimit = 4; - if (c != LUA_MULTRET && c <= kUnrollSetListLimit) + if (count != LUA_MULTRET && count <= kUnrollSetListLimit) { - for (int i = 0; i < c; ++i) + for (int i = 0; i < count; ++i) { // setobj2t(L, &array[index + i - 1], rb + i); build.vmovups(xmm0, luauRegValue(rb + i)); @@ -405,17 +382,17 @@ void emitInstSetList(AssemblyBuilderX64& build, const Instruction* pc, Label& ne } else { - LUAU_ASSERT(c != 0); + LUAU_ASSERT(count != 0); build.xor_(offset, offset); if (index != 1) build.add(arrayDst, (index - 1) * sizeof(TValue)); Label repeatLoop, endLoop; - OperandX64 limit = c == LUA_MULTRET ? cscaled : OperandX64(c * sizeof(TValue)); + OperandX64 limit = count == LUA_MULTRET ? cscaled : OperandX64(count * sizeof(TValue)); // If c is static, we will always do at least one iteration - if (c == LUA_MULTRET) + if (count == LUA_MULTRET) { build.cmp(offset, limit); build.jcc(ConditionX64::NotBelow, endLoop); @@ -556,14 +533,14 @@ static void emitInstAndX(AssemblyBuilderX64& build, int ra, int rb, OperandX64 c } } -void emitInstAnd(AssemblyBuilderX64& build, const Instruction* pc) +void emitInstAnd(AssemblyBuilderX64& build, int ra, int rb, int rc) { - emitInstAndX(build, LUAU_INSN_A(*pc), LUAU_INSN_B(*pc), luauReg(LUAU_INSN_C(*pc))); + emitInstAndX(build, ra, rb, luauReg(rc)); } -void emitInstAndK(AssemblyBuilderX64& build, const Instruction* pc) +void emitInstAndK(AssemblyBuilderX64& build, int ra, int rb, int kc) { - emitInstAndX(build, LUAU_INSN_A(*pc), LUAU_INSN_B(*pc), luauConstant(LUAU_INSN_C(*pc))); + emitInstAndX(build, ra, rb, luauConstant(kc)); } static void emitInstOrX(AssemblyBuilderX64& build, int ra, int rb, OperandX64 c) @@ -594,14 +571,14 @@ static void emitInstOrX(AssemblyBuilderX64& build, int ra, int rb, OperandX64 c) } } -void emitInstOr(AssemblyBuilderX64& build, const Instruction* pc) +void emitInstOr(AssemblyBuilderX64& build, int ra, int rb, int rc) { - emitInstOrX(build, LUAU_INSN_A(*pc), LUAU_INSN_B(*pc), luauReg(LUAU_INSN_C(*pc))); + emitInstOrX(build, ra, rb, luauReg(rc)); } -void emitInstOrK(AssemblyBuilderX64& build, const Instruction* pc) +void emitInstOrK(AssemblyBuilderX64& build, int ra, int rb, int kc) { - emitInstOrX(build, LUAU_INSN_A(*pc), LUAU_INSN_B(*pc), luauConstant(LUAU_INSN_C(*pc))); + emitInstOrX(build, ra, rb, luauConstant(kc)); } void emitInstGetImportFallback(AssemblyBuilderX64& build, int ra, uint32_t aux) diff --git a/CodeGen/src/EmitInstructionX64.h b/CodeGen/src/EmitInstructionX64.h index 6a8a3c0..880c9fa 100644 --- a/CodeGen/src/EmitInstructionX64.h +++ b/CodeGen/src/EmitInstructionX64.h @@ -3,11 +3,6 @@ #include -#include "ltm.h" - -typedef uint32_t Instruction; -typedef struct lua_TValue TValue; - namespace Luau { namespace CodeGen @@ -21,16 +16,16 @@ namespace X64 class AssemblyBuilderX64; -void emitInstCall(AssemblyBuilderX64& build, ModuleHelpers& helpers, const Instruction* pc, int pcpos); -void emitInstReturn(AssemblyBuilderX64& build, ModuleHelpers& helpers, const Instruction* pc, int pcpos); -void emitInstSetList(AssemblyBuilderX64& build, const Instruction* pc, Label& next); +void emitInstCall(AssemblyBuilderX64& build, ModuleHelpers& helpers, int ra, int nparams, int nresults); +void emitInstReturn(AssemblyBuilderX64& build, ModuleHelpers& helpers, int ra, int actualResults); +void emitInstSetList(AssemblyBuilderX64& build, Label& next, int ra, int rb, int count, uint32_t index); void emitinstForGLoop(AssemblyBuilderX64& build, int ra, int aux, Label& loopRepeat, Label& loopExit); void emitinstForGLoopFallback(AssemblyBuilderX64& build, int pcpos, int ra, int aux, Label& loopRepeat); void emitInstForGPrepXnextFallback(AssemblyBuilderX64& build, int pcpos, int ra, Label& target); -void emitInstAnd(AssemblyBuilderX64& build, const Instruction* pc); -void emitInstAndK(AssemblyBuilderX64& build, const Instruction* pc); -void emitInstOr(AssemblyBuilderX64& build, const Instruction* pc); -void emitInstOrK(AssemblyBuilderX64& build, const Instruction* pc); +void emitInstAnd(AssemblyBuilderX64& build, int ra, int rb, int rc); +void emitInstAndK(AssemblyBuilderX64& build, int ra, int rb, int kc); +void emitInstOr(AssemblyBuilderX64& build, int ra, int rb, int rc); +void emitInstOrK(AssemblyBuilderX64& build, int ra, int rb, int kc); void emitInstGetImportFallback(AssemblyBuilderX64& build, int ra, uint32_t aux); void emitInstCoverage(AssemblyBuilderX64& build, int pcpos); diff --git a/CodeGen/src/IrAnalysis.cpp b/CodeGen/src/IrAnalysis.cpp index b998487..6e77dfe 100644 --- a/CodeGen/src/IrAnalysis.cpp +++ b/CodeGen/src/IrAnalysis.cpp @@ -244,12 +244,16 @@ static RegisterSet computeBlockLiveInRegSet(IrFunction& function, const IrBlock& // A <- B, C case IrCmd::DO_ARITH: case IrCmd::GET_TABLE: - case IrCmd::SET_TABLE: use(inst.b); maybeUse(inst.c); // Argument can also be a VmConst def(inst.a); break; + case IrCmd::SET_TABLE: + use(inst.a); + use(inst.b); + maybeUse(inst.c); // Argument can also be a VmConst + break; // A <- B case IrCmd::DO_LEN: use(inst.b); @@ -301,13 +305,13 @@ static RegisterSet computeBlockLiveInRegSet(IrFunction& function, const IrBlock& useRange(inst.c.index, function.intOp(inst.d)); break; case IrCmd::LOP_CALL: - use(inst.b); - useRange(inst.b.index + 1, function.intOp(inst.c)); + use(inst.a); + useRange(inst.a.index + 1, function.intOp(inst.b)); - defRange(inst.b.index, function.intOp(inst.d)); + defRange(inst.a.index, function.intOp(inst.c)); break; case IrCmd::LOP_RETURN: - useRange(inst.b.index, function.intOp(inst.c)); + useRange(inst.a.index, function.intOp(inst.b)); break; case IrCmd::FASTCALL: case IrCmd::INVOKE_FASTCALL: @@ -333,7 +337,9 @@ static RegisterSet computeBlockLiveInRegSet(IrFunction& function, const IrBlock& useVarargs(inst.c.index); } - defRange(inst.b.index, function.intOp(inst.f)); + // Multiple return sequences (count == -1) are defined by ADJUST_STACK_TO_REG + if (int count = function.intOp(inst.f); count != -1) + defRange(inst.b.index, count); break; case IrCmd::LOP_FORGLOOP: // First register is not used by instruction, we check that it's still 'nil' with CHECK_TAG @@ -352,20 +358,20 @@ static RegisterSet computeBlockLiveInRegSet(IrFunction& function, const IrBlock& case IrCmd::LOP_FORGPREP_XNEXT_FALLBACK: use(inst.b); break; - // B <- C, D + // A <- B, C case IrCmd::LOP_AND: case IrCmd::LOP_OR: + use(inst.b); use(inst.c); - use(inst.d); - def(inst.b); + def(inst.a); break; - // B <- C + // A <- B case IrCmd::LOP_ANDK: case IrCmd::LOP_ORK: - use(inst.c); + use(inst.b); - def(inst.b); + def(inst.a); break; case IrCmd::FALLBACK_GETGLOBAL: def(inst.b); @@ -405,8 +411,10 @@ static RegisterSet computeBlockLiveInRegSet(IrFunction& function, const IrBlock& defRange(inst.b.index, 3); break; case IrCmd::ADJUST_STACK_TO_REG: + defRange(inst.a.index, -1); + break; case IrCmd::ADJUST_STACK_TO_TOP: - // While these can be considered as vararg producers and consumers, it is already handled in fastcall instruction + // While this can be considered to be a vararg consumer, it is already handled in fastcall instructions break; default: @@ -626,7 +634,7 @@ void computeCfgInfo(IrFunction& function) computeCfgLiveInOutRegSets(function); } -BlockIteratorWrapper predecessors(CfgInfo& cfg, uint32_t blockIdx) +BlockIteratorWrapper predecessors(const CfgInfo& cfg, uint32_t blockIdx) { LUAU_ASSERT(blockIdx < cfg.predecessorsOffsets.size()); @@ -636,7 +644,7 @@ BlockIteratorWrapper predecessors(CfgInfo& cfg, uint32_t blockIdx) return BlockIteratorWrapper{cfg.predecessors.data() + start, cfg.predecessors.data() + end}; } -BlockIteratorWrapper successors(CfgInfo& cfg, uint32_t blockIdx) +BlockIteratorWrapper successors(const CfgInfo& cfg, uint32_t blockIdx) { LUAU_ASSERT(blockIdx < cfg.successorsOffsets.size()); diff --git a/CodeGen/src/IrBuilder.cpp b/CodeGen/src/IrBuilder.cpp index f1099cf..239f7a8 100644 --- a/CodeGen/src/IrBuilder.cpp +++ b/CodeGen/src/IrBuilder.cpp @@ -132,7 +132,10 @@ void IrBuilder::translateInst(LuauOpcode op, const Instruction* pc, int i) translateInstSetGlobal(*this, pc, i); break; case LOP_CALL: - inst(IrCmd::LOP_CALL, constUint(i), vmReg(LUAU_INSN_A(*pc)), constInt(LUAU_INSN_B(*pc) - 1), constInt(LUAU_INSN_C(*pc) - 1)); + inst(IrCmd::INTERRUPT, constUint(i)); + inst(IrCmd::SET_SAVEDPC, constUint(i + 1)); + + inst(IrCmd::LOP_CALL, vmReg(LUAU_INSN_A(*pc)), constInt(LUAU_INSN_B(*pc) - 1), constInt(LUAU_INSN_C(*pc) - 1)); if (activeFastcallFallback) { @@ -144,7 +147,9 @@ void IrBuilder::translateInst(LuauOpcode op, const Instruction* pc, int i) } break; case LOP_RETURN: - inst(IrCmd::LOP_RETURN, constUint(i), vmReg(LUAU_INSN_A(*pc)), constInt(LUAU_INSN_B(*pc) - 1)); + inst(IrCmd::INTERRUPT, constUint(i)); + + inst(IrCmd::LOP_RETURN, vmReg(LUAU_INSN_A(*pc)), constInt(LUAU_INSN_B(*pc) - 1)); break; case LOP_GETTABLE: translateInstGetTable(*this, pc, i); @@ -358,16 +363,16 @@ void IrBuilder::translateInst(LuauOpcode op, const Instruction* pc, int i) translateInstForGPrepInext(*this, pc, i); break; case LOP_AND: - inst(IrCmd::LOP_AND, constUint(i), vmReg(LUAU_INSN_A(*pc)), vmReg(LUAU_INSN_B(*pc)), vmReg(LUAU_INSN_C(*pc))); + inst(IrCmd::LOP_AND, vmReg(LUAU_INSN_A(*pc)), vmReg(LUAU_INSN_B(*pc)), vmReg(LUAU_INSN_C(*pc))); break; case LOP_ANDK: - inst(IrCmd::LOP_ANDK, constUint(i), vmReg(LUAU_INSN_A(*pc)), vmReg(LUAU_INSN_B(*pc)), vmConst(LUAU_INSN_C(*pc))); + inst(IrCmd::LOP_ANDK, vmReg(LUAU_INSN_A(*pc)), vmReg(LUAU_INSN_B(*pc)), vmConst(LUAU_INSN_C(*pc))); break; case LOP_OR: - inst(IrCmd::LOP_OR, constUint(i), vmReg(LUAU_INSN_A(*pc)), vmReg(LUAU_INSN_B(*pc)), vmReg(LUAU_INSN_C(*pc))); + inst(IrCmd::LOP_OR, vmReg(LUAU_INSN_A(*pc)), vmReg(LUAU_INSN_B(*pc)), vmReg(LUAU_INSN_C(*pc))); break; case LOP_ORK: - inst(IrCmd::LOP_ORK, constUint(i), vmReg(LUAU_INSN_A(*pc)), vmReg(LUAU_INSN_B(*pc)), vmConst(LUAU_INSN_C(*pc))); + inst(IrCmd::LOP_ORK, vmReg(LUAU_INSN_A(*pc)), vmReg(LUAU_INSN_B(*pc)), vmConst(LUAU_INSN_C(*pc))); break; case LOP_COVERAGE: inst(IrCmd::LOP_COVERAGE, constUint(i)); diff --git a/CodeGen/src/IrDump.cpp b/CodeGen/src/IrDump.cpp index 3c4e420..53654d6 100644 --- a/CodeGen/src/IrDump.cpp +++ b/CodeGen/src/IrDump.cpp @@ -455,7 +455,7 @@ void toStringDetailed(IrToStringContext& ctx, const IrBlock& block, uint32_t ind } // Predecessor list - if (!ctx.cfg.predecessors.empty()) + if (index < ctx.cfg.predecessorsOffsets.size()) { BlockIteratorWrapper pred = predecessors(ctx.cfg, index); @@ -469,7 +469,7 @@ void toStringDetailed(IrToStringContext& ctx, const IrBlock& block, uint32_t ind } // Successor list - if (!ctx.cfg.successors.empty()) + if (index < ctx.cfg.successorsOffsets.size()) { BlockIteratorWrapper succ = successors(ctx.cfg, index); @@ -509,14 +509,14 @@ void toStringDetailed(IrToStringContext& ctx, const IrBlock& block, uint32_t ind } } -std::string toString(IrFunction& function, bool includeUseInfo) +std::string toString(const IrFunction& function, bool includeUseInfo) { std::string result; IrToStringContext ctx{result, function.blocks, function.constants, function.cfg}; for (size_t i = 0; i < function.blocks.size(); i++) { - IrBlock& block = function.blocks[i]; + const IrBlock& block = function.blocks[i]; if (block.kind == IrBlockKind::Dead) continue; @@ -532,7 +532,7 @@ std::string toString(IrFunction& function, bool includeUseInfo) // To allow dumping blocks that are still being constructed, we can't rely on terminator and need a bounds check for (uint32_t index = block.start; index <= block.finish && index < uint32_t(function.instructions.size()); index++) { - IrInst& inst = function.instructions[index]; + const IrInst& inst = function.instructions[index]; // Skip pseudo instructions unless they are still referenced if (isPseudo(inst.cmd) && inst.useCount == 0) @@ -548,7 +548,7 @@ std::string toString(IrFunction& function, bool includeUseInfo) return result; } -std::string dump(IrFunction& function) +std::string dump(const IrFunction& function) { std::string result = toString(function, /* includeUseInfo */ true); @@ -557,12 +557,12 @@ std::string dump(IrFunction& function) return result; } -std::string toDot(IrFunction& function, bool includeInst) +std::string toDot(const IrFunction& function, bool includeInst) { std::string result; IrToStringContext ctx{result, function.blocks, function.constants, function.cfg}; - auto appendLabelRegset = [&ctx](std::vector& regSets, size_t blockIdx, const char* name) { + auto appendLabelRegset = [&ctx](const std::vector& regSets, size_t blockIdx, const char* name) { if (blockIdx < regSets.size()) { const RegisterSet& rs = regSets[blockIdx]; @@ -581,7 +581,7 @@ std::string toDot(IrFunction& function, bool includeInst) for (size_t i = 0; i < function.blocks.size(); i++) { - IrBlock& block = function.blocks[i]; + const IrBlock& block = function.blocks[i]; append(ctx.result, "b%u [", unsigned(i)); @@ -599,7 +599,7 @@ std::string toDot(IrFunction& function, bool includeInst) { for (uint32_t instIdx = block.start; instIdx <= block.finish; instIdx++) { - IrInst& inst = function.instructions[instIdx]; + const IrInst& inst = function.instructions[instIdx]; // Skip pseudo instructions unless they are still referenced if (isPseudo(inst.cmd) && inst.useCount == 0) @@ -618,14 +618,14 @@ std::string toDot(IrFunction& function, bool includeInst) for (size_t i = 0; i < function.blocks.size(); i++) { - IrBlock& block = function.blocks[i]; + const IrBlock& block = function.blocks[i]; if (block.start == ~0u) continue; for (uint32_t instIdx = block.start; instIdx != ~0u && instIdx <= block.finish; instIdx++) { - IrInst& inst = function.instructions[instIdx]; + const IrInst& inst = function.instructions[instIdx]; auto checkOp = [&](IrOp op) { if (op.kind == IrOpKind::Block) @@ -651,7 +651,7 @@ std::string toDot(IrFunction& function, bool includeInst) return result; } -std::string dumpDot(IrFunction& function, bool includeInst) +std::string dumpDot(const IrFunction& function, bool includeInst) { std::string result = toDot(function, includeInst); diff --git a/CodeGen/src/IrLoweringA64.cpp b/CodeGen/src/IrLoweringA64.cpp new file mode 100644 index 0000000..ae4bc01 --- /dev/null +++ b/CodeGen/src/IrLoweringA64.cpp @@ -0,0 +1,137 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#include "IrLoweringA64.h" + +#include "Luau/CodeGen.h" +#include "Luau/DenseHash.h" +#include "Luau/IrAnalysis.h" +#include "Luau/IrDump.h" +#include "Luau/IrUtils.h" + +#include "EmitCommonA64.h" +#include "EmitInstructionA64.h" +#include "NativeState.h" + +#include "lstate.h" + +namespace Luau +{ +namespace CodeGen +{ +namespace A64 +{ + +IrLoweringA64::IrLoweringA64(AssemblyBuilderA64& build, ModuleHelpers& helpers, NativeState& data, Proto* proto, IrFunction& function) + : build(build) + , helpers(helpers) + , data(data) + , proto(proto) + , function(function) +{ + // In order to allocate registers during lowering, we need to know where instruction results are last used + updateLastUseLocations(function); +} + +// TODO: Eventually this can go away +bool IrLoweringA64::canLower(const IrFunction& function) +{ + for (const IrInst& inst : function.instructions) + { + switch (inst.cmd) + { + case IrCmd::NOP: + case IrCmd::SUBSTITUTE: + case IrCmd::INTERRUPT: + case IrCmd::LOP_RETURN: + continue; + default: + return false; + } + } + + return true; +} + +void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) +{ + switch (inst.cmd) + { + case IrCmd::INTERRUPT: + { + emitInterrupt(build, uintOp(inst.a)); + break; + } + case IrCmd::LOP_RETURN: + { + emitInstReturn(build, helpers, vmRegOp(inst.a), intOp(inst.b)); + break; + } + default: + LUAU_ASSERT(!"Not supported yet"); + break; + } + + // TODO + // regs.freeLastUseRegs(inst, index); +} + +bool IrLoweringA64::isFallthroughBlock(IrBlock target, IrBlock next) +{ + return target.start == next.start; +} + +void IrLoweringA64::jumpOrFallthrough(IrBlock& target, IrBlock& next) +{ + if (!isFallthroughBlock(target, next)) + build.b(target.label); +} + +RegisterA64 IrLoweringA64::regOp(IrOp op) const +{ + IrInst& inst = function.instOp(op); + LUAU_ASSERT(inst.regA64 != noreg); + return inst.regA64; +} + +IrConst IrLoweringA64::constOp(IrOp op) const +{ + return function.constOp(op); +} + +uint8_t IrLoweringA64::tagOp(IrOp op) const +{ + return function.tagOp(op); +} + +bool IrLoweringA64::boolOp(IrOp op) const +{ + return function.boolOp(op); +} + +int IrLoweringA64::intOp(IrOp op) const +{ + return function.intOp(op); +} + +unsigned IrLoweringA64::uintOp(IrOp op) const +{ + return function.uintOp(op); +} + +double IrLoweringA64::doubleOp(IrOp op) const +{ + return function.doubleOp(op); +} + +IrBlock& IrLoweringA64::blockOp(IrOp op) const +{ + return function.blockOp(op); +} + +Label& IrLoweringA64::labelOp(IrOp op) const +{ + return blockOp(op).label; +} + +} // namespace A64 +} // namespace CodeGen +} // namespace Luau diff --git a/CodeGen/src/IrLoweringA64.h b/CodeGen/src/IrLoweringA64.h new file mode 100644 index 0000000..aa9eba4 --- /dev/null +++ b/CodeGen/src/IrLoweringA64.h @@ -0,0 +1,60 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#pragma once + +#include "Luau/AssemblyBuilderA64.h" +#include "Luau/IrData.h" + +#include + +struct Proto; + +namespace Luau +{ +namespace CodeGen +{ + +struct ModuleHelpers; +struct NativeState; +struct AssemblyOptions; + +namespace A64 +{ + +struct IrLoweringA64 +{ + IrLoweringA64(AssemblyBuilderA64& build, ModuleHelpers& helpers, NativeState& data, Proto* proto, IrFunction& function); + + static bool canLower(const IrFunction& function); + + void lowerInst(IrInst& inst, uint32_t index, IrBlock& next); + + bool isFallthroughBlock(IrBlock target, IrBlock next); + void jumpOrFallthrough(IrBlock& target, IrBlock& next); + + // Operand data lookup helpers + RegisterA64 regOp(IrOp op) const; + + IrConst constOp(IrOp op) const; + uint8_t tagOp(IrOp op) const; + bool boolOp(IrOp op) const; + int intOp(IrOp op) const; + unsigned uintOp(IrOp op) const; + double doubleOp(IrOp op) const; + + IrBlock& blockOp(IrOp op) const; + Label& labelOp(IrOp op) const; + + AssemblyBuilderA64& build; + ModuleHelpers& helpers; + NativeState& data; + Proto* proto = nullptr; // Temporarily required to provide 'Instruction* pc' to old emitInst* methods + + IrFunction& function; + + // TODO: + // IrRegAllocA64 regs; +}; + +} // namespace A64 +} // namespace CodeGen +} // namespace Luau diff --git a/CodeGen/src/IrLoweringX64.cpp b/CodeGen/src/IrLoweringX64.cpp index b45ce22..1cc56fe 100644 --- a/CodeGen/src/IrLoweringX64.cpp +++ b/CodeGen/src/IrLoweringX64.cpp @@ -14,8 +14,6 @@ #include "lstate.h" -#include - namespace Luau { namespace CodeGen @@ -23,11 +21,10 @@ namespace CodeGen namespace X64 { -IrLoweringX64::IrLoweringX64(AssemblyBuilderX64& build, ModuleHelpers& helpers, NativeState& data, Proto* proto, IrFunction& function) +IrLoweringX64::IrLoweringX64(AssemblyBuilderX64& build, ModuleHelpers& helpers, NativeState& data, IrFunction& function) : build(build) , helpers(helpers) , data(data) - , proto(proto) , function(function) , regs(function) { @@ -35,146 +32,6 @@ IrLoweringX64::IrLoweringX64(AssemblyBuilderX64& build, ModuleHelpers& helpers, updateLastUseLocations(function); } -void IrLoweringX64::lower(AssemblyOptions options) -{ - // While we will need a better block ordering in the future, right now we want to mostly preserve build order with fallbacks outlined - std::vector sortedBlocks; - sortedBlocks.reserve(function.blocks.size()); - for (uint32_t i = 0; i < function.blocks.size(); i++) - sortedBlocks.push_back(i); - - std::sort(sortedBlocks.begin(), sortedBlocks.end(), [&](uint32_t idxA, uint32_t idxB) { - const IrBlock& a = function.blocks[idxA]; - const IrBlock& b = function.blocks[idxB]; - - // Place fallback blocks at the end - if ((a.kind == IrBlockKind::Fallback) != (b.kind == IrBlockKind::Fallback)) - return (a.kind == IrBlockKind::Fallback) < (b.kind == IrBlockKind::Fallback); - - // Try to order by instruction order - return a.start < b.start; - }); - - DenseHashMap bcLocations{~0u}; - - // Create keys for IR assembly locations that original bytecode instruction are interested in - for (const auto& [irLocation, asmLocation] : function.bcMapping) - { - if (irLocation != ~0u) - bcLocations[irLocation] = 0; - } - - DenseHashMap indexIrToBc{~0u}; - bool outputEnabled = options.includeAssembly || options.includeIr; - - if (outputEnabled && options.annotator) - { - // Create reverse mapping from IR location to bytecode location - for (size_t i = 0; i < function.bcMapping.size(); ++i) - { - uint32_t irLocation = function.bcMapping[i].irLocation; - - if (irLocation != ~0u) - indexIrToBc[irLocation] = uint32_t(i); - } - } - - IrToStringContext ctx{build.text, function.blocks, function.constants, function.cfg}; - - // We use this to skip outlined fallback blocks from IR/asm text output - size_t textSize = build.text.length(); - uint32_t codeSize = build.getCodeSize(); - bool seenFallback = false; - - IrBlock dummy; - dummy.start = ~0u; - - for (size_t i = 0; i < sortedBlocks.size(); ++i) - { - uint32_t blockIndex = sortedBlocks[i]; - - IrBlock& block = function.blocks[blockIndex]; - - if (block.kind == IrBlockKind::Dead) - continue; - - LUAU_ASSERT(block.start != ~0u); - LUAU_ASSERT(block.finish != ~0u); - - // If we want to skip fallback code IR/asm, we'll record when those blocks start once we see them - if (block.kind == IrBlockKind::Fallback && !seenFallback) - { - textSize = build.text.length(); - codeSize = build.getCodeSize(); - seenFallback = true; - } - - if (options.includeIr) - { - build.logAppend("# "); - toStringDetailed(ctx, block, blockIndex, /* includeUseInfo */ true); - } - - build.setLabel(block.label); - - for (uint32_t index = block.start; index <= block.finish; index++) - { - LUAU_ASSERT(index < function.instructions.size()); - - // If IR instruction is the first one for the original bytecode, we can annotate it with source code text - if (outputEnabled && options.annotator) - { - if (uint32_t* bcIndex = indexIrToBc.find(index)) - options.annotator(options.annotatorContext, build.text, proto->bytecodeid, *bcIndex); - } - - // If bytecode needs the location of this instruction for jumps, record it - if (uint32_t* bcLocation = bcLocations.find(index)) - *bcLocation = build.getCodeSize(); - - IrInst& inst = function.instructions[index]; - - // Skip pseudo instructions, but make sure they are not used at this stage - // This also prevents them from getting into text output when that's enabled - if (isPseudo(inst.cmd)) - { - LUAU_ASSERT(inst.useCount == 0); - continue; - } - - if (options.includeIr) - { - build.logAppend("# "); - toStringDetailed(ctx, inst, index, /* includeUseInfo */ true); - } - - IrBlock& next = i + 1 < sortedBlocks.size() ? function.blocks[sortedBlocks[i + 1]] : dummy; - - lowerInst(inst, index, next); - - regs.freeLastUseRegs(inst, index); - } - - if (options.includeIr) - build.logAppend("#\n"); - } - - if (outputEnabled && !options.includeOutlinedCode && seenFallback) - { - build.text.resize(textSize); - - if (options.includeAssembly) - build.logAppend("; skipping %u bytes of outlined code\n", build.getCodeSize() - codeSize); - } - - // Copy assembly locations of IR instructions that are mapped to bytecode instructions - for (auto& [irLocation, asmLocation] : function.bcMapping) - { - if (irLocation != ~0u) - asmLocation = bcLocations[irLocation]; - } -} - void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) { switch (inst.cmd) @@ -183,9 +40,9 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) inst.regX64 = regs.allocGprReg(SizeX64::dword); if (inst.a.kind == IrOpKind::VmReg) - build.mov(inst.regX64, luauRegTag(inst.a.index)); + build.mov(inst.regX64, luauRegTag(vmRegOp(inst.a))); else if (inst.a.kind == IrOpKind::VmConst) - build.mov(inst.regX64, luauConstantTag(inst.a.index)); + build.mov(inst.regX64, luauConstantTag(vmConstOp(inst.a))); // If we have a register, we assume it's a pointer to TValue // We might introduce explicit operand types in the future to make this more robust else if (inst.a.kind == IrOpKind::Inst) @@ -197,9 +54,9 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) inst.regX64 = regs.allocGprReg(SizeX64::qword); if (inst.a.kind == IrOpKind::VmReg) - build.mov(inst.regX64, luauRegValue(inst.a.index)); + build.mov(inst.regX64, luauRegValue(vmRegOp(inst.a))); else if (inst.a.kind == IrOpKind::VmConst) - build.mov(inst.regX64, luauConstantValue(inst.a.index)); + build.mov(inst.regX64, luauConstantValue(vmConstOp(inst.a))); // If we have a register, we assume it's a pointer to TValue // We might introduce explicit operand types in the future to make this more robust else if (inst.a.kind == IrOpKind::Inst) @@ -211,26 +68,24 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) inst.regX64 = regs.allocXmmReg(); if (inst.a.kind == IrOpKind::VmReg) - build.vmovsd(inst.regX64, luauRegValue(inst.a.index)); + build.vmovsd(inst.regX64, luauRegValue(vmRegOp(inst.a))); else if (inst.a.kind == IrOpKind::VmConst) - build.vmovsd(inst.regX64, luauConstantValue(inst.a.index)); + build.vmovsd(inst.regX64, luauConstantValue(vmConstOp(inst.a))); else LUAU_ASSERT(!"Unsupported instruction form"); break; case IrCmd::LOAD_INT: - LUAU_ASSERT(inst.a.kind == IrOpKind::VmReg); - inst.regX64 = regs.allocGprReg(SizeX64::dword); - build.mov(inst.regX64, luauRegValueInt(inst.a.index)); + build.mov(inst.regX64, luauRegValueInt(vmRegOp(inst.a))); break; case IrCmd::LOAD_TVALUE: inst.regX64 = regs.allocXmmReg(); if (inst.a.kind == IrOpKind::VmReg) - build.vmovups(inst.regX64, luauReg(inst.a.index)); + build.vmovups(inst.regX64, luauReg(vmRegOp(inst.a))); else if (inst.a.kind == IrOpKind::VmConst) - build.vmovups(inst.regX64, luauConstant(inst.a.index)); + build.vmovups(inst.regX64, luauConstant(vmConstOp(inst.a))); else if (inst.a.kind == IrOpKind::Inst) build.vmovups(inst.regX64, xmmword[regOp(inst.a)]); else @@ -301,31 +156,25 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) break; }; case IrCmd::STORE_TAG: - LUAU_ASSERT(inst.a.kind == IrOpKind::VmReg); - if (inst.b.kind == IrOpKind::Constant) - build.mov(luauRegTag(inst.a.index), tagOp(inst.b)); + build.mov(luauRegTag(vmRegOp(inst.a)), tagOp(inst.b)); else LUAU_ASSERT(!"Unsupported instruction form"); break; case IrCmd::STORE_POINTER: - LUAU_ASSERT(inst.a.kind == IrOpKind::VmReg); - - build.mov(luauRegValue(inst.a.index), regOp(inst.b)); + build.mov(luauRegValue(vmRegOp(inst.a)), regOp(inst.b)); break; case IrCmd::STORE_DOUBLE: - LUAU_ASSERT(inst.a.kind == IrOpKind::VmReg); - if (inst.b.kind == IrOpKind::Constant) { ScopedRegX64 tmp{regs, SizeX64::xmmword}; build.vmovsd(tmp.reg, build.f64(doubleOp(inst.b))); - build.vmovsd(luauRegValue(inst.a.index), tmp.reg); + build.vmovsd(luauRegValue(vmRegOp(inst.a)), tmp.reg); } else if (inst.b.kind == IrOpKind::Inst) { - build.vmovsd(luauRegValue(inst.a.index), regOp(inst.b)); + build.vmovsd(luauRegValue(vmRegOp(inst.a)), regOp(inst.b)); } else { @@ -334,19 +183,17 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) break; case IrCmd::STORE_INT: { - LUAU_ASSERT(inst.a.kind == IrOpKind::VmReg); - if (inst.b.kind == IrOpKind::Constant) - build.mov(luauRegValueInt(inst.a.index), intOp(inst.b)); + build.mov(luauRegValueInt(vmRegOp(inst.a)), intOp(inst.b)); else if (inst.b.kind == IrOpKind::Inst) - build.mov(luauRegValueInt(inst.a.index), regOp(inst.b)); + build.mov(luauRegValueInt(vmRegOp(inst.a)), regOp(inst.b)); else LUAU_ASSERT(!"Unsupported instruction form"); break; } case IrCmd::STORE_TVALUE: if (inst.a.kind == IrOpKind::VmReg) - build.vmovups(luauReg(inst.a.index), regOp(inst.b)); + build.vmovups(luauReg(vmRegOp(inst.a)), regOp(inst.b)); else if (inst.a.kind == IrOpKind::Inst) build.vmovups(xmmword[regOp(inst.a)], regOp(inst.b)); else @@ -642,15 +489,11 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) jumpOrFallthrough(blockOp(inst.a), next); break; case IrCmd::JUMP_IF_TRUTHY: - LUAU_ASSERT(inst.a.kind == IrOpKind::VmReg); - - jumpIfTruthy(build, inst.a.index, labelOp(inst.b), labelOp(inst.c)); + jumpIfTruthy(build, vmRegOp(inst.a), labelOp(inst.b), labelOp(inst.c)); jumpOrFallthrough(blockOp(inst.c), next); break; case IrCmd::JUMP_IF_FALSY: - LUAU_ASSERT(inst.a.kind == IrOpKind::VmReg); - - jumpIfFalsy(build, inst.a.index, labelOp(inst.b), labelOp(inst.c)); + jumpIfFalsy(build, vmRegOp(inst.a), labelOp(inst.b), labelOp(inst.c)); jumpOrFallthrough(blockOp(inst.c), next); break; case IrCmd::JUMP_EQ_TAG: @@ -686,9 +529,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) break; case IrCmd::JUMP_CMP_NUM: { - LUAU_ASSERT(inst.c.kind == IrOpKind::Condition); - - IrCondition cond = IrCondition(inst.c.index); + IrCondition cond = conditionOp(inst.c); ScopedRegX64 tmp{regs, SizeX64::xmmword}; @@ -698,24 +539,14 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) break; } case IrCmd::JUMP_CMP_ANY: - { - LUAU_ASSERT(inst.a.kind == IrOpKind::VmReg); - LUAU_ASSERT(inst.b.kind == IrOpKind::VmReg); - LUAU_ASSERT(inst.c.kind == IrOpKind::Condition); - - IrCondition cond = IrCondition(inst.c.index); - - jumpOnAnyCmpFallback(build, inst.a.index, inst.b.index, cond, labelOp(inst.d)); + jumpOnAnyCmpFallback(build, vmRegOp(inst.a), vmRegOp(inst.b), conditionOp(inst.c), labelOp(inst.d)); jumpOrFallthrough(blockOp(inst.e), next); break; - } case IrCmd::JUMP_SLOT_MATCH: { - LUAU_ASSERT(inst.b.kind == IrOpKind::VmConst); - ScopedRegX64 tmp{regs, SizeX64::qword}; - jumpIfNodeKeyNotInExpectedSlot(build, tmp.reg, regOp(inst.a), luauConstantValue(inst.b.index), labelOp(inst.d)); + jumpIfNodeKeyNotInExpectedSlot(build, tmp.reg, regOp(inst.a), luauConstantValue(vmConstOp(inst.b)), labelOp(inst.d)); jumpOrFallthrough(blockOp(inst.c), next); break; } @@ -774,13 +605,11 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) break; case IrCmd::ADJUST_STACK_TO_REG: { - LUAU_ASSERT(inst.a.kind == IrOpKind::VmReg); - if (inst.b.kind == IrOpKind::Constant) { ScopedRegX64 tmp{regs, SizeX64::qword}; - build.lea(tmp.reg, addr[rBase + (inst.a.index + intOp(inst.b)) * sizeof(TValue)]); + build.lea(tmp.reg, addr[rBase + (vmRegOp(inst.a) + intOp(inst.b)) * sizeof(TValue)]); build.mov(qword[rState + offsetof(lua_State, top)], tmp.reg); } else if (inst.b.kind == IrOpKind::Inst) @@ -788,7 +617,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) ScopedRegX64 tmp(regs, regs.allocGprRegOrReuse(SizeX64::dword, index, {inst.b})); build.shl(qwordReg(tmp.reg), kTValueSizeLog2); - build.lea(qwordReg(tmp.reg), addr[rBase + qwordReg(tmp.reg) + inst.a.index * sizeof(TValue)]); + build.lea(qwordReg(tmp.reg), addr[rBase + qwordReg(tmp.reg) + vmRegOp(inst.a) * sizeof(TValue)]); build.mov(qword[rState + offsetof(lua_State, top)], qwordReg(tmp.reg)); } else @@ -807,28 +636,23 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) } case IrCmd::FASTCALL: - LUAU_ASSERT(inst.b.kind == IrOpKind::VmReg); - LUAU_ASSERT(inst.c.kind == IrOpKind::VmReg); - emitBuiltin(regs, build, uintOp(inst.a), inst.b.index, inst.c.index, inst.d, intOp(inst.e), intOp(inst.f)); + emitBuiltin(regs, build, uintOp(inst.a), vmRegOp(inst.b), vmRegOp(inst.c), inst.d, intOp(inst.e), intOp(inst.f)); break; case IrCmd::INVOKE_FASTCALL: { - LUAU_ASSERT(inst.b.kind == IrOpKind::VmReg); - LUAU_ASSERT(inst.c.kind == IrOpKind::VmReg); - unsigned bfid = uintOp(inst.a); OperandX64 args = 0; if (inst.d.kind == IrOpKind::VmReg) - args = luauRegAddress(inst.d.index); + args = luauRegAddress(vmRegOp(inst.d)); else if (inst.d.kind == IrOpKind::VmConst) - args = luauConstantAddress(inst.d.index); + args = luauConstantAddress(vmConstOp(inst.d)); else LUAU_ASSERT(boolOp(inst.d) == false); - int ra = inst.b.index; - int arg = inst.c.index; + int ra = vmRegOp(inst.b); + int arg = vmRegOp(inst.c); int nparams = intOp(inst.e); int nresults = intOp(inst.f); @@ -889,34 +713,24 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) break; } case IrCmd::DO_ARITH: - LUAU_ASSERT(inst.a.kind == IrOpKind::VmReg); - LUAU_ASSERT(inst.b.kind == IrOpKind::VmReg); - LUAU_ASSERT(inst.c.kind == IrOpKind::VmReg || inst.c.kind == IrOpKind::VmConst); - if (inst.c.kind == IrOpKind::VmReg) - callArithHelper(build, inst.a.index, inst.b.index, luauRegAddress(inst.c.index), TMS(intOp(inst.d))); + callArithHelper(build, vmRegOp(inst.a), vmRegOp(inst.b), luauRegAddress(vmRegOp(inst.c)), TMS(intOp(inst.d))); else - callArithHelper(build, inst.a.index, inst.b.index, luauConstantAddress(inst.c.index), TMS(intOp(inst.d))); + callArithHelper(build, vmRegOp(inst.a), vmRegOp(inst.b), luauConstantAddress(vmConstOp(inst.c)), TMS(intOp(inst.d))); break; case IrCmd::DO_LEN: - LUAU_ASSERT(inst.a.kind == IrOpKind::VmReg); - LUAU_ASSERT(inst.b.kind == IrOpKind::VmReg); - - callLengthHelper(build, inst.a.index, inst.b.index); + callLengthHelper(build, vmRegOp(inst.a), vmRegOp(inst.b)); break; case IrCmd::GET_TABLE: - LUAU_ASSERT(inst.a.kind == IrOpKind::VmReg); - LUAU_ASSERT(inst.b.kind == IrOpKind::VmReg); - if (inst.c.kind == IrOpKind::VmReg) { - callGetTable(build, inst.b.index, luauRegAddress(inst.c.index), inst.a.index); + callGetTable(build, vmRegOp(inst.b), luauRegAddress(vmRegOp(inst.c)), vmRegOp(inst.a)); } else if (inst.c.kind == IrOpKind::Constant) { TValue n; setnvalue(&n, uintOp(inst.c)); - callGetTable(build, inst.b.index, build.bytes(&n, sizeof(n)), inst.a.index); + callGetTable(build, vmRegOp(inst.b), build.bytes(&n, sizeof(n)), vmRegOp(inst.a)); } else { @@ -924,18 +738,15 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) } break; case IrCmd::SET_TABLE: - LUAU_ASSERT(inst.a.kind == IrOpKind::VmReg); - LUAU_ASSERT(inst.b.kind == IrOpKind::VmReg); - if (inst.c.kind == IrOpKind::VmReg) { - callSetTable(build, inst.b.index, luauRegAddress(inst.c.index), inst.a.index); + callSetTable(build, vmRegOp(inst.b), luauRegAddress(vmRegOp(inst.c)), vmRegOp(inst.a)); } else if (inst.c.kind == IrOpKind::Constant) { TValue n; setnvalue(&n, uintOp(inst.c)); - callSetTable(build, inst.b.index, build.bytes(&n, sizeof(n)), inst.a.index); + callSetTable(build, vmRegOp(inst.b), build.bytes(&n, sizeof(n)), vmRegOp(inst.a)); } else { @@ -943,30 +754,23 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) } break; case IrCmd::GET_IMPORT: - LUAU_ASSERT(inst.a.kind == IrOpKind::VmReg); - - emitInstGetImportFallback(build, inst.a.index, uintOp(inst.b)); + emitInstGetImportFallback(build, vmRegOp(inst.a), uintOp(inst.b)); break; case IrCmd::CONCAT: - LUAU_ASSERT(inst.a.kind == IrOpKind::VmReg); - build.mov(rArg1, rState); build.mov(dwordReg(rArg2), uintOp(inst.b)); - build.mov(dwordReg(rArg3), inst.a.index + uintOp(inst.b) - 1); + build.mov(dwordReg(rArg3), vmRegOp(inst.a) + uintOp(inst.b) - 1); build.call(qword[rNativeContext + offsetof(NativeContext, luaV_concat)]); emitUpdateBase(build); break; case IrCmd::GET_UPVALUE: { - LUAU_ASSERT(inst.a.kind == IrOpKind::VmReg); - LUAU_ASSERT(inst.b.kind == IrOpKind::VmUpvalue); - ScopedRegX64 tmp1{regs, SizeX64::qword}; ScopedRegX64 tmp2{regs, SizeX64::xmmword}; build.mov(tmp1.reg, sClosure); - build.add(tmp1.reg, offsetof(Closure, l.uprefs) + sizeof(TValue) * inst.b.index); + build.add(tmp1.reg, offsetof(Closure, l.uprefs) + sizeof(TValue) * vmUpvalueOp(inst.b)); // uprefs[] is either an actual value, or it points to UpVal object which has a pointer to value Label skip; @@ -981,32 +785,29 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) build.setLabel(skip); build.vmovups(tmp2.reg, xmmword[tmp1.reg]); - build.vmovups(luauReg(inst.a.index), tmp2.reg); + build.vmovups(luauReg(vmRegOp(inst.a)), tmp2.reg); break; } case IrCmd::SET_UPVALUE: { - LUAU_ASSERT(inst.a.kind == IrOpKind::VmUpvalue); - LUAU_ASSERT(inst.b.kind == IrOpKind::VmReg); - Label next; ScopedRegX64 tmp1{regs, SizeX64::qword}; ScopedRegX64 tmp2{regs, SizeX64::qword}; ScopedRegX64 tmp3{regs, SizeX64::xmmword}; build.mov(tmp1.reg, sClosure); - build.mov(tmp2.reg, qword[tmp1.reg + offsetof(Closure, l.uprefs) + sizeof(TValue) * inst.a.index + offsetof(TValue, value.gc)]); + build.mov(tmp2.reg, qword[tmp1.reg + offsetof(Closure, l.uprefs) + sizeof(TValue) * vmUpvalueOp(inst.a) + offsetof(TValue, value.gc)]); build.mov(tmp1.reg, qword[tmp2.reg + offsetof(UpVal, v)]); - build.vmovups(tmp3.reg, luauReg(inst.b.index)); + build.vmovups(tmp3.reg, luauReg(vmRegOp(inst.b))); build.vmovups(xmmword[tmp1.reg], tmp3.reg); - callBarrierObject(build, tmp1.reg, tmp2.reg, inst.b.index, next); + callBarrierObject(build, tmp1.reg, tmp2.reg, vmRegOp(inst.b), next); build.setLabel(next); break; } case IrCmd::PREPARE_FORN: - callPrepareForN(build, inst.a.index, inst.b.index, inst.c.index); + callPrepareForN(build, vmRegOp(inst.a), vmRegOp(inst.b), vmRegOp(inst.c)); break; case IrCmd::CHECK_TAG: if (inst.a.kind == IrOpKind::Inst) @@ -1016,11 +817,11 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) } else if (inst.a.kind == IrOpKind::VmReg) { - jumpIfTagIsNot(build, inst.a.index, lua_Type(tagOp(inst.b)), labelOp(inst.c)); + jumpIfTagIsNot(build, vmRegOp(inst.a), lua_Type(tagOp(inst.b)), labelOp(inst.c)); } else if (inst.a.kind == IrOpKind::VmConst) { - build.cmp(luauConstantTag(inst.a.index), tagOp(inst.b)); + build.cmp(luauConstantTag(vmConstOp(inst.a)), tagOp(inst.b)); build.jcc(ConditionX64::NotEqual, labelOp(inst.c)); } else @@ -1053,11 +854,9 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) break; case IrCmd::CHECK_SLOT_MATCH: { - LUAU_ASSERT(inst.b.kind == IrOpKind::VmConst); - ScopedRegX64 tmp{regs, SizeX64::qword}; - jumpIfNodeKeyNotInExpectedSlot(build, tmp.reg, regOp(inst.a), luauConstantValue(inst.b.index), labelOp(inst.c)); + jumpIfNodeKeyNotInExpectedSlot(build, tmp.reg, regOp(inst.a), luauConstantValue(vmConstOp(inst.b)), labelOp(inst.c)); break; } case IrCmd::CHECK_NODE_NO_NEXT: @@ -1075,12 +874,10 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) } case IrCmd::BARRIER_OBJ: { - LUAU_ASSERT(inst.b.kind == IrOpKind::VmReg); - Label skip; ScopedRegX64 tmp{regs, SizeX64::qword}; - callBarrierObject(build, tmp.reg, regOp(inst.a), inst.b.index, skip); + callBarrierObject(build, tmp.reg, regOp(inst.a), vmRegOp(inst.b), skip); build.setLabel(skip); break; } @@ -1094,12 +891,10 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) } case IrCmd::BARRIER_TABLE_FORWARD: { - LUAU_ASSERT(inst.b.kind == IrOpKind::VmReg); - Label skip; ScopedRegX64 tmp{regs, SizeX64::qword}; - callBarrierTable(build, tmp.reg, regOp(inst.a), inst.b.index, skip); + callBarrierTable(build, tmp.reg, regOp(inst.a), vmRegOp(inst.b), skip); build.setLabel(skip); break; } @@ -1117,8 +912,6 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) } case IrCmd::CLOSE_UPVALS: { - LUAU_ASSERT(inst.a.kind == IrOpKind::VmReg); - Label next; ScopedRegX64 tmp1{regs, SizeX64::qword}; ScopedRegX64 tmp2{regs, SizeX64::qword}; @@ -1129,7 +922,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) build.jcc(ConditionX64::Zero, next); // ra <= L->openuval->v - build.lea(tmp2.reg, addr[rBase + inst.a.index * sizeof(TValue)]); + build.lea(tmp2.reg, addr[rBase + vmRegOp(inst.a) * sizeof(TValue)]); build.cmp(tmp2.reg, qword[tmp1.reg + offsetof(UpVal, v)]); build.jcc(ConditionX64::Above, next); @@ -1149,60 +942,38 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) // Fallbacks to non-IR instruction implementations case IrCmd::LOP_SETLIST: { - const Instruction* pc = proto->code + uintOp(inst.a); - LUAU_ASSERT(inst.b.kind == IrOpKind::VmReg); - LUAU_ASSERT(inst.c.kind == IrOpKind::VmReg); - LUAU_ASSERT(inst.d.kind == IrOpKind::Constant); - LUAU_ASSERT(inst.e.kind == IrOpKind::Constant); - Label next; - emitInstSetList(build, pc, next); + emitInstSetList(build, next, vmRegOp(inst.b), vmRegOp(inst.c), intOp(inst.d), uintOp(inst.e)); build.setLabel(next); break; } case IrCmd::LOP_CALL: - { - const Instruction* pc = proto->code + uintOp(inst.a); - LUAU_ASSERT(inst.b.kind == IrOpKind::VmReg); - LUAU_ASSERT(inst.c.kind == IrOpKind::Constant); - LUAU_ASSERT(inst.d.kind == IrOpKind::Constant); - - emitInstCall(build, helpers, pc, uintOp(inst.a)); + emitInstCall(build, helpers, vmRegOp(inst.a), intOp(inst.b), intOp(inst.c)); break; - } case IrCmd::LOP_RETURN: - { - const Instruction* pc = proto->code + uintOp(inst.a); - LUAU_ASSERT(inst.b.kind == IrOpKind::VmReg); - LUAU_ASSERT(inst.c.kind == IrOpKind::Constant); - - emitInstReturn(build, helpers, pc, uintOp(inst.a)); + emitInstReturn(build, helpers, vmRegOp(inst.a), intOp(inst.b)); break; - } case IrCmd::LOP_FORGLOOP: - LUAU_ASSERT(inst.a.kind == IrOpKind::VmReg); - emitinstForGLoop(build, inst.a.index, intOp(inst.b), labelOp(inst.c), labelOp(inst.d)); + emitinstForGLoop(build, vmRegOp(inst.a), intOp(inst.b), labelOp(inst.c), labelOp(inst.d)); break; case IrCmd::LOP_FORGLOOP_FALLBACK: - LUAU_ASSERT(inst.b.kind == IrOpKind::VmReg); - emitinstForGLoopFallback(build, uintOp(inst.a), inst.b.index, intOp(inst.c), labelOp(inst.d)); + emitinstForGLoopFallback(build, uintOp(inst.a), vmRegOp(inst.b), intOp(inst.c), labelOp(inst.d)); build.jmp(labelOp(inst.e)); break; case IrCmd::LOP_FORGPREP_XNEXT_FALLBACK: - LUAU_ASSERT(inst.b.kind == IrOpKind::VmReg); - emitInstForGPrepXnextFallback(build, uintOp(inst.a), inst.b.index, labelOp(inst.c)); + emitInstForGPrepXnextFallback(build, uintOp(inst.a), vmRegOp(inst.b), labelOp(inst.c)); break; case IrCmd::LOP_AND: - emitInstAnd(build, proto->code + uintOp(inst.a)); + emitInstAnd(build, vmRegOp(inst.a), vmRegOp(inst.b), vmRegOp(inst.c)); break; case IrCmd::LOP_ANDK: - emitInstAndK(build, proto->code + uintOp(inst.a)); + emitInstAndK(build, vmRegOp(inst.a), vmRegOp(inst.b), vmConstOp(inst.c)); break; case IrCmd::LOP_OR: - emitInstOr(build, proto->code + uintOp(inst.a)); + emitInstOr(build, vmRegOp(inst.a), vmRegOp(inst.b), vmRegOp(inst.c)); break; case IrCmd::LOP_ORK: - emitInstOrK(build, proto->code + uintOp(inst.a)); + emitInstOrK(build, vmRegOp(inst.a), vmRegOp(inst.b), vmConstOp(inst.c)); break; case IrCmd::LOP_COVERAGE: emitInstCoverage(build, uintOp(inst.a)); @@ -1272,6 +1043,8 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) LUAU_ASSERT(!"Not supported yet"); break; } + + regs.freeLastUseRegs(inst, index); } bool IrLoweringX64::isFallthroughBlock(IrBlock target, IrBlock next) @@ -1294,9 +1067,9 @@ OperandX64 IrLoweringX64::memRegDoubleOp(IrOp op) const case IrOpKind::Constant: return build.f64(doubleOp(op)); case IrOpKind::VmReg: - return luauRegValue(op.index); + return luauRegValue(vmRegOp(op)); case IrOpKind::VmConst: - return luauConstantValue(op.index); + return luauConstantValue(vmConstOp(op)); default: LUAU_ASSERT(!"Unsupported operand kind"); } @@ -1311,9 +1084,9 @@ OperandX64 IrLoweringX64::memRegTagOp(IrOp op) const case IrOpKind::Inst: return regOp(op); case IrOpKind::VmReg: - return luauRegTag(op.index); + return luauRegTag(vmRegOp(op)); case IrOpKind::VmConst: - return luauConstantTag(op.index); + return luauConstantTag(vmConstOp(op)); default: LUAU_ASSERT(!"Unsupported operand kind"); } @@ -1323,7 +1096,9 @@ OperandX64 IrLoweringX64::memRegTagOp(IrOp op) const RegisterX64 IrLoweringX64::regOp(IrOp op) const { - return function.instOp(op).regX64; + IrInst& inst = function.instOp(op); + LUAU_ASSERT(inst.regX64 != noreg); + return inst.regX64; } IrConst IrLoweringX64::constOp(IrOp op) const diff --git a/CodeGen/src/IrLoweringX64.h b/CodeGen/src/IrLoweringX64.h index a0ad3ea..c8ebd1f 100644 --- a/CodeGen/src/IrLoweringX64.h +++ b/CodeGen/src/IrLoweringX64.h @@ -24,10 +24,7 @@ namespace X64 struct IrLoweringX64 { - // Some of these arguments are only required while we re-use old direct bytecode to x64 lowering - IrLoweringX64(AssemblyBuilderX64& build, ModuleHelpers& helpers, NativeState& data, Proto* proto, IrFunction& function); - - void lower(AssemblyOptions options); + IrLoweringX64(AssemblyBuilderX64& build, ModuleHelpers& helpers, NativeState& data, IrFunction& function); void lowerInst(IrInst& inst, uint32_t index, IrBlock& next); @@ -52,7 +49,6 @@ struct IrLoweringX64 AssemblyBuilderX64& build; ModuleHelpers& helpers; NativeState& data; - Proto* proto = nullptr; // Temporarily required to provide 'Instruction* pc' to old emitInst* methods IrFunction& function; diff --git a/CodeGen/src/IrTranslateBuiltins.cpp b/CodeGen/src/IrTranslateBuiltins.cpp index d9f935c..cb8e414 100644 --- a/CodeGen/src/IrTranslateBuiltins.cpp +++ b/CodeGen/src/IrTranslateBuiltins.cpp @@ -240,6 +240,10 @@ BuiltinImplResult translateBuiltinTypeof(IrBuilder& build, int nparams, int ra, BuiltinImplResult translateBuiltin(IrBuilder& build, int bfid, int ra, int arg, IrOp args, int nparams, int nresults, IrOp fallback) { + // Builtins are not allowed to handle variadic arguments + if (nparams == LUA_MULTRET) + return {BuiltinImplType::None, -1}; + switch (bfid) { case LBF_ASSERT: diff --git a/CodeGen/src/IrTranslation.cpp b/CodeGen/src/IrTranslation.cpp index 28c6aca..d90841c 100644 --- a/CodeGen/src/IrTranslation.cpp +++ b/CodeGen/src/IrTranslation.cpp @@ -501,10 +501,10 @@ void translateFastCallN(IrBuilder& build, const Instruction* pc, int pcpos, bool if (br.type == BuiltinImplType::UsesFallback) { + LUAU_ASSERT(nparams != LUA_MULTRET && "builtins are not allowed to handle variadic arguments"); + if (nresults == LUA_MULTRET) build.inst(IrCmd::ADJUST_STACK_TO_REG, build.vmReg(ra), build.constInt(br.actualResultCount)); - else if (nparams == LUA_MULTRET) - build.inst(IrCmd::ADJUST_STACK_TO_TOP); } else { diff --git a/CodeGen/src/IrUtils.cpp b/CodeGen/src/IrUtils.cpp index e29a5b0..b28ce59 100644 --- a/CodeGen/src/IrUtils.cpp +++ b/CodeGen/src/IrUtils.cpp @@ -354,7 +354,7 @@ void foldConstants(IrBuilder& build, IrFunction& function, IrBlock& block, uint3 case IrCmd::JUMP_CMP_NUM: if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant) { - if (compare(function.doubleOp(inst.a), function.doubleOp(inst.b), function.conditionOp(inst.c))) + if (compare(function.doubleOp(inst.a), function.doubleOp(inst.b), conditionOp(inst.c))) replace(function, block, index, {IrCmd::JUMP, inst.d}); else replace(function, block, index, {IrCmd::JUMP, inst.e}); diff --git a/CodeGen/src/NativeState.cpp b/CodeGen/src/NativeState.cpp index f79bcab..f149789 100644 --- a/CodeGen/src/NativeState.cpp +++ b/CodeGen/src/NativeState.cpp @@ -109,6 +109,7 @@ void initHelperFunctions(NativeState& data) data.context.forgPrepXnextFallback = forgPrepXnextFallback; data.context.callProlog = callProlog; data.context.callEpilogC = callEpilogC; + data.context.returnFallback = returnFallback; } } // namespace CodeGen diff --git a/CodeGen/src/NativeState.h b/CodeGen/src/NativeState.h index bebf421..6d83318 100644 --- a/CodeGen/src/NativeState.h +++ b/CodeGen/src/NativeState.h @@ -47,12 +47,6 @@ struct NativeContext uint8_t* gateEntry = nullptr; uint8_t* gateExit = nullptr; - // Opcode fallbacks, implemented in C - NativeFallback fallback[LOP__COUNT] = {}; - - // Fast call methods, implemented in C - luau_FastFunction luauF_table[256] = {}; - // Helper functions, implemented in C int (*luaV_lessthan)(lua_State* L, const TValue* l, const TValue* r) = nullptr; int (*luaV_lessequal)(lua_State* L, const TValue* l, const TValue* r) = nullptr; @@ -107,6 +101,13 @@ struct NativeContext void (*forgPrepXnextFallback)(lua_State* L, TValue* ra, int pc) = nullptr; Closure* (*callProlog)(lua_State* L, TValue* ra, StkId argtop, int nresults) = nullptr; void (*callEpilogC)(lua_State* L, int nresults, int n) = nullptr; + const Instruction* (*returnFallback)(lua_State* L, StkId ra, int n) = nullptr; + + // Opcode fallbacks, implemented in C + NativeFallback fallback[LOP__COUNT] = {}; + + // Fast call methods, implemented in C + luau_FastFunction luauF_table[256] = {}; }; struct NativeState diff --git a/CodeGen/src/OptimizeConstProp.cpp b/CodeGen/src/OptimizeConstProp.cpp index b12a9b9..6723647 100644 --- a/CodeGen/src/OptimizeConstProp.cpp +++ b/CodeGen/src/OptimizeConstProp.cpp @@ -42,6 +42,11 @@ struct RegisterLink // Data we know about the current VM state struct ConstPropState { + ConstPropState(const IrFunction& function) + : function(function) + { + } + uint8_t tryGetTag(IrOp op) { if (RegisterInfo* info = tryGetRegisterInfo(op)) @@ -107,14 +112,29 @@ struct ConstPropState invalidate(regs[regOp.index], /* invalidateTag */ true, /* invalidateValue */ true); } - void invalidateRegistersFrom(uint32_t firstReg) + void invalidateRegistersFrom(int firstReg) { - for (int i = int(firstReg); i <= maxReg; ++i) + for (int i = firstReg; i <= maxReg; ++i) invalidate(regs[i], /* invalidateTag */ true, /* invalidateValue */ true); maxReg = int(firstReg) - 1; } + void invalidateRegisterRange(int firstReg, int count) + { + for (int i = firstReg; i < firstReg + count && i <= maxReg; ++i) + invalidate(regs[i], /* invalidateTag */ true, /* invalidateValue */ true); + } + + void invalidateCapturedRegisters() + { + for (int i = 0; i <= maxReg; ++i) + { + if (function.cfg.captured.regs.test(i)) + invalidate(regs[i], /* invalidateTag */ true, /* invalidateValue */ true); + } + } + void invalidateHeap() { for (int i = 0; i <= maxReg; ++i) @@ -127,10 +147,10 @@ struct ConstPropState reg.knownNoMetatable = false; } - void invalidateAll() + void invalidateUserCall() { - // Invalidating registers also invalidates what we know about the heap (stored in RegisterInfo) - invalidateRegistersFrom(0u); + invalidateHeap(); + invalidateCapturedRegisters(); inSafeEnv = false; } @@ -175,6 +195,8 @@ struct ConstPropState return nullptr; } + const IrFunction& function; + RegisterInfo regs[256]; // For range/full invalidations, we only want to visit a limited number of data that we have recorded @@ -411,7 +433,7 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& if (valueA && valueB) { - if (compare(*valueA, *valueB, function.conditionOp(inst.c))) + if (compare(*valueA, *valueB, conditionOp(inst.c))) replace(function, block, index, {IrCmd::JUMP, inst.d}); else replace(function, block, index, {IrCmd::JUMP, inst.e}); @@ -485,7 +507,7 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& case IrCmd::LOP_ANDK: case IrCmd::LOP_OR: case IrCmd::LOP_ORK: - state.invalidate(inst.b); + state.invalidate(inst.a); break; case IrCmd::FASTCALL: case IrCmd::INVOKE_FASTCALL: @@ -538,35 +560,93 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& case IrCmd::CHECK_FASTCALL_RES: // Changes stack top, but not the values break; - // We don't model the following instructions, so we just clear all the knowledge we have built up - // Many of these call user functions that can change memory and captured registers - // Some of these might yield with similar effects case IrCmd::JUMP_CMP_ANY: + state.invalidateUserCall(); // TODO: if arguments are strings, there will be no user calls + break; case IrCmd::DO_ARITH: + state.invalidate(inst.a); + state.invalidateUserCall(); + break; case IrCmd::DO_LEN: + state.invalidate(inst.a); + state.invalidateUserCall(); // TODO: if argument is a string, there will be no user call + + state.saveTag(inst.a, LUA_TNUMBER); + break; case IrCmd::GET_TABLE: + state.invalidate(inst.a); + state.invalidateUserCall(); + break; case IrCmd::SET_TABLE: + state.invalidateUserCall(); + break; case IrCmd::GET_IMPORT: + state.invalidate(inst.a); + state.invalidateUserCall(); + break; case IrCmd::CONCAT: + state.invalidateRegisterRange(inst.a.index, function.uintOp(inst.b)); + state.invalidateUserCall(); // TODO: if only strings and numbers are concatenated, there will be no user calls + break; case IrCmd::PREPARE_FORN: - case IrCmd::INTERRUPT: // TODO: it will be important to keep tag/value state, but we have to track register capture + state.invalidateValue(inst.a); + state.saveTag(inst.a, LUA_TNUMBER); + state.invalidateValue(inst.b); + state.saveTag(inst.b, LUA_TNUMBER); + state.invalidateValue(inst.c); + state.saveTag(inst.c, LUA_TNUMBER); + break; + case IrCmd::INTERRUPT: + state.invalidateUserCall(); + break; case IrCmd::LOP_CALL: + state.invalidateRegistersFrom(inst.a.index); + state.invalidateUserCall(); + break; case IrCmd::LOP_FORGLOOP: + state.invalidateRegistersFrom(inst.a.index + 2); // Rn and Rn+1 are not modified + break; case IrCmd::LOP_FORGLOOP_FALLBACK: + state.invalidateRegistersFrom(inst.b.index + 2); // Rn and Rn+1 are not modified + state.invalidateUserCall(); + break; case IrCmd::LOP_FORGPREP_XNEXT_FALLBACK: + // This fallback only conditionally throws an exception + break; case IrCmd::FALLBACK_GETGLOBAL: + state.invalidate(inst.b); + state.invalidateUserCall(); + break; case IrCmd::FALLBACK_SETGLOBAL: + state.invalidateUserCall(); + break; case IrCmd::FALLBACK_GETTABLEKS: + state.invalidate(inst.b); + state.invalidateUserCall(); + break; case IrCmd::FALLBACK_SETTABLEKS: + state.invalidateUserCall(); + break; case IrCmd::FALLBACK_NAMECALL: + state.invalidate(IrOp{inst.b.kind, inst.b.index + 0u}); + state.invalidate(IrOp{inst.b.kind, inst.b.index + 1u}); + state.invalidateUserCall(); + break; case IrCmd::FALLBACK_PREPVARARGS: + break; case IrCmd::FALLBACK_GETVARARGS: + state.invalidateRegistersFrom(inst.b.index); + break; case IrCmd::FALLBACK_NEWCLOSURE: + state.invalidate(inst.b); + break; case IrCmd::FALLBACK_DUPCLOSURE: + state.invalidate(inst.b); + break; case IrCmd::FALLBACK_FORGPREP: - // TODO: this is very conservative, some of there instructions can be tracked better - // TODO: non-captured register tags and values should not be cleared here - state.invalidateAll(); + state.invalidate(IrOp{inst.b.kind, inst.b.index + 0u}); + state.invalidate(IrOp{inst.b.kind, inst.b.index + 1u}); + state.invalidate(IrOp{inst.b.kind, inst.b.index + 2u}); break; } } @@ -592,7 +672,7 @@ static void constPropInBlockChain(IrBuilder& build, std::vector& visite { IrFunction& function = build.function; - ConstPropState state; + ConstPropState state{function}; while (block) { @@ -698,7 +778,7 @@ static void tryCreateLinearBlock(IrBuilder& build, std::vector& visited return; // Initialize state with the knowledge of our current block - ConstPropState state; + ConstPropState state{function}; constPropInBlock(build, startingBlock, state); // Veryfy that target hasn't changed diff --git a/Makefile b/Makefile index 5851229..bbc66c2 100644 --- a/Makefile +++ b/Makefile @@ -117,6 +117,11 @@ ifneq ($(native),) TESTS_ARGS+=--codegen endif +ifneq ($(nativelj),) + CXXFLAGS+=-DLUA_CUSTOM_EXECUTION=1 -DLUA_USE_LONGJMP=1 + TESTS_ARGS+=--codegen +endif + # target-specific flags $(AST_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include $(COMPILER_OBJECTS): CXXFLAGS+=-std=c++17 -ICompiler/include -ICommon/include -IAst/include diff --git a/Sources.cmake b/Sources.cmake index 6e0a32e..3f32aab 100644 --- a/Sources.cmake +++ b/Sources.cmake @@ -84,14 +84,18 @@ target_sources(Luau.CodeGen PRIVATE CodeGen/src/CodeBlockUnwind.cpp CodeGen/src/CodeGen.cpp CodeGen/src/CodeGenUtils.cpp + CodeGen/src/CodeGenA64.cpp CodeGen/src/CodeGenX64.cpp CodeGen/src/EmitBuiltinsX64.cpp + CodeGen/src/EmitCommonA64.cpp CodeGen/src/EmitCommonX64.cpp + CodeGen/src/EmitInstructionA64.cpp CodeGen/src/EmitInstructionX64.cpp CodeGen/src/Fallbacks.cpp CodeGen/src/IrAnalysis.cpp CodeGen/src/IrBuilder.cpp CodeGen/src/IrDump.cpp + CodeGen/src/IrLoweringA64.cpp CodeGen/src/IrLoweringX64.cpp CodeGen/src/IrRegAllocX64.cpp CodeGen/src/IrTranslateBuiltins.cpp @@ -106,13 +110,17 @@ target_sources(Luau.CodeGen PRIVATE CodeGen/src/ByteUtils.h CodeGen/src/CustomExecUtils.h CodeGen/src/CodeGenUtils.h + CodeGen/src/CodeGenA64.h CodeGen/src/CodeGenX64.h CodeGen/src/EmitBuiltinsX64.h CodeGen/src/EmitCommon.h + CodeGen/src/EmitCommonA64.h CodeGen/src/EmitCommonX64.h + CodeGen/src/EmitInstructionA64.h CodeGen/src/EmitInstructionX64.h CodeGen/src/Fallbacks.h CodeGen/src/FallbacksProlog.h + CodeGen/src/IrLoweringA64.h CodeGen/src/IrLoweringX64.h CodeGen/src/IrRegAllocX64.h CodeGen/src/IrTranslateBuiltins.h diff --git a/VM/src/lstate.h b/VM/src/lstate.h index 1d32489..32a240b 100644 --- a/VM/src/lstate.h +++ b/VM/src/lstate.h @@ -208,14 +208,14 @@ typedef struct global_State uint64_t rngstate; // PCG random number generator state uint64_t ptrenckey[4]; // pointer encoding key for display - void (*udatagc[LUA_UTAG_LIMIT])(lua_State*, void*); // for each userdata tag, a gc callback to be called immediately before freeing memory - lua_Callbacks cb; #if LUA_CUSTOM_EXECUTION lua_ExecutionCallbacks ecb; #endif + void (*udatagc[LUA_UTAG_LIMIT])(lua_State*, void*); // for each userdata tag, a gc callback to be called immediately before freeing memory + GCStats gcstats; #ifdef LUAI_GCMETRICS diff --git a/tests/AssemblyBuilderA64.test.cpp b/tests/AssemblyBuilderA64.test.cpp index e23b965..a68932b 100644 --- a/tests/AssemblyBuilderA64.test.cpp +++ b/tests/AssemblyBuilderA64.test.cpp @@ -120,6 +120,10 @@ TEST_CASE_FIXTURE(AssemblyBuilderA64Fixture, "Loads") SINGLE_COMPARE(ldrsh(x0, x1), 0x79800020); SINGLE_COMPARE(ldrsh(w0, x1), 0x79C00020); SINGLE_COMPARE(ldrsw(x0, x1), 0xB9800020); + + // paired loads + SINGLE_COMPARE(ldp(x0, x1, mem(x2, 8)), 0xA9408440); + SINGLE_COMPARE(ldp(w0, w1, mem(x2, -8)), 0x297F0440); } TEST_CASE_FIXTURE(AssemblyBuilderA64Fixture, "Stores") @@ -135,15 +139,58 @@ TEST_CASE_FIXTURE(AssemblyBuilderA64Fixture, "Stores") SINGLE_COMPARE(str(w0, x1), 0xB9000020); SINGLE_COMPARE(strb(w0, x1), 0x39000020); SINGLE_COMPARE(strh(w0, x1), 0x79000020); + + // paired stores + SINGLE_COMPARE(stp(x0, x1, mem(x2, 8)), 0xA9008440); + SINGLE_COMPARE(stp(w0, w1, mem(x2, -8)), 0x293F0440); } TEST_CASE_FIXTURE(AssemblyBuilderA64Fixture, "Moves") { SINGLE_COMPARE(mov(x0, x1), 0xAA0103E0); SINGLE_COMPARE(mov(w0, w1), 0x2A0103E0); - SINGLE_COMPARE(mov(x0, 42), 0xD2800540); - SINGLE_COMPARE(mov(w0, 42), 0x52800540); + + SINGLE_COMPARE(movz(x0, 42), 0xD2800540); + SINGLE_COMPARE(movz(w0, 42), 0x52800540); + SINGLE_COMPARE(movn(x0, 42), 0x92800540); + SINGLE_COMPARE(movn(w0, 42), 0x12800540); SINGLE_COMPARE(movk(x0, 42, 16), 0xF2A00540); + + CHECK(check( + [](AssemblyBuilderA64& build) { + build.mov(x0, 42); + }, + {0xD2800540})); + + CHECK(check( + [](AssemblyBuilderA64& build) { + build.mov(x0, 424242); + }, + {0xD28F2640, 0xF2A000C0})); + + CHECK(check( + [](AssemblyBuilderA64& build) { + build.mov(x0, -42); + }, + {0x92800520})); + + CHECK(check( + [](AssemblyBuilderA64& build) { + build.mov(x0, -424242); + }, + {0x928F2620, 0xF2BFFF20})); + + CHECK(check( + [](AssemblyBuilderA64& build) { + build.mov(x0, -65536); + }, + {0x929FFFE0})); + + CHECK(check( + [](AssemblyBuilderA64& build) { + build.mov(x0, -65537); + }, + {0x92800000, 0xF2BFFFC0})); } TEST_CASE_FIXTURE(AssemblyBuilderA64Fixture, "ControlFlow") @@ -222,6 +269,22 @@ TEST_CASE_FIXTURE(AssemblyBuilderA64Fixture, "Constants") // clang-format on } +TEST_CASE_FIXTURE(AssemblyBuilderA64Fixture, "AddressOfLabel") +{ + // clang-format off + CHECK(check( + [](AssemblyBuilderA64& build) { + Label label; + build.adr(x0, label); + build.add(x0, x0, x0); + build.setLabel(label); + }, + { + 0x10000040, 0x8b000000, + })); + // clang-format on +} + TEST_CASE("LogTest") { AssemblyBuilderA64 build(/* logText= */ true); @@ -243,6 +306,9 @@ TEST_CASE("LogTest") build.b(ConditionA64::Plus, l); build.cbz(x7, l); + build.ldp(x0, x1, mem(x8, 8)); + build.adr(x0, l); + build.setLabel(l); build.ret(); @@ -263,6 +329,8 @@ TEST_CASE("LogTest") blr x0 b.pl .L1 cbz x7,.L1 + ldp x0,x1,[x8,#8] + adr x0,.L1 .L1: ret )"; diff --git a/tests/CodeAllocator.test.cpp b/tests/CodeAllocator.test.cpp index a6ed96f..359f2ba 100644 --- a/tests/CodeAllocator.test.cpp +++ b/tests/CodeAllocator.test.cpp @@ -465,6 +465,7 @@ TEST_CASE("GeneratedCodeExecutionA64") build.add(x1, x1, 2); build.add(x0, x0, x1, /* LSL */ 1); + build.ret(); build.finalize(); diff --git a/tests/Conformance.test.cpp b/tests/Conformance.test.cpp index 7d5f41a..1072b95 100644 --- a/tests/Conformance.test.cpp +++ b/tests/Conformance.test.cpp @@ -1250,7 +1250,9 @@ TEST_CASE("Interrupt") 13, 13, 16, - 20, + 23, + 21, + 25, }; static int index; diff --git a/tests/IrBuilder.test.cpp b/tests/IrBuilder.test.cpp index 37c12dc..f4c9cdc 100644 --- a/tests/IrBuilder.test.cpp +++ b/tests/IrBuilder.test.cpp @@ -764,12 +764,14 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "ConcatInvalidation") build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber)); build.inst(IrCmd::STORE_INT, build.vmReg(1), build.constInt(10)); build.inst(IrCmd::STORE_DOUBLE, build.vmReg(2), build.constDouble(0.5)); + build.inst(IrCmd::STORE_DOUBLE, build.vmReg(3), build.constDouble(2.0)); - build.inst(IrCmd::CONCAT, build.vmReg(0), build.vmReg(3)); // Concat invalidates more than the target register + build.inst(IrCmd::CONCAT, build.vmReg(0), build.constUint(3)); - build.inst(IrCmd::STORE_TAG, build.vmReg(3), build.inst(IrCmd::LOAD_TAG, build.vmReg(0))); - build.inst(IrCmd::STORE_INT, build.vmReg(4), build.inst(IrCmd::LOAD_INT, build.vmReg(1))); - build.inst(IrCmd::STORE_DOUBLE, build.vmReg(5), build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(2))); + build.inst(IrCmd::STORE_TAG, build.vmReg(4), build.inst(IrCmd::LOAD_TAG, build.vmReg(0))); + build.inst(IrCmd::STORE_INT, build.vmReg(5), build.inst(IrCmd::LOAD_INT, build.vmReg(1))); + build.inst(IrCmd::STORE_DOUBLE, build.vmReg(6), build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(2))); + build.inst(IrCmd::STORE_DOUBLE, build.vmReg(7), build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(3))); build.inst(IrCmd::LOP_RETURN, build.constUint(0)); @@ -781,13 +783,15 @@ bb_0: STORE_TAG R0, tnumber STORE_INT R1, 10i STORE_DOUBLE R2, 0.5 - CONCAT R0, R3 - %4 = LOAD_TAG R0 - STORE_TAG R3, %4 - %6 = LOAD_INT R1 - STORE_INT R4, %6 - %8 = LOAD_DOUBLE R2 - STORE_DOUBLE R5, %8 + STORE_DOUBLE R3, 2 + CONCAT R0, 3u + %5 = LOAD_TAG R0 + STORE_TAG R4, %5 + %7 = LOAD_INT R1 + STORE_INT R5, %7 + %9 = LOAD_DOUBLE R2 + STORE_DOUBLE R6, %9 + STORE_DOUBLE R7, 2 LOP_RETURN 0u )"); @@ -1179,7 +1183,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "EntryBlockUseRemoval") build.inst(IrCmd::JUMP_IF_TRUTHY, build.vmReg(0), exit, repeat); build.beginBlock(exit); - build.inst(IrCmd::LOP_RETURN, build.constUint(0), build.vmReg(0), build.constInt(0)); + build.inst(IrCmd::LOP_RETURN, build.vmReg(0), build.constInt(0)); build.beginBlock(repeat); build.inst(IrCmd::INTERRUPT, build.constUint(0)); @@ -1194,7 +1198,7 @@ bb_0: JUMP bb_1 bb_1: - LOP_RETURN 0u, R0, 0i + LOP_RETURN R0, 0i )"); } @@ -1207,14 +1211,14 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "RecursiveSccUseRemoval1") IrOp repeat = build.block(IrBlockKind::Internal); build.beginBlock(entry); - build.inst(IrCmd::LOP_RETURN, build.constUint(0), build.vmReg(0), build.constInt(0)); + build.inst(IrCmd::LOP_RETURN, build.vmReg(0), build.constInt(0)); build.beginBlock(block); build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber)); build.inst(IrCmd::JUMP_IF_TRUTHY, build.vmReg(0), exit, repeat); build.beginBlock(exit); - build.inst(IrCmd::LOP_RETURN, build.constUint(0), build.vmReg(0), build.constInt(0)); + build.inst(IrCmd::LOP_RETURN, build.vmReg(0), build.constInt(0)); build.beginBlock(repeat); build.inst(IrCmd::INTERRUPT, build.constUint(0)); @@ -1225,14 +1229,14 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "RecursiveSccUseRemoval1") CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( bb_0: - LOP_RETURN 0u, R0, 0i + LOP_RETURN R0, 0i bb_1: STORE_TAG R0, tnumber JUMP bb_2 bb_2: - LOP_RETURN 0u, R0, 0i + LOP_RETURN R0, 0i )"); } @@ -1249,14 +1253,14 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "RecursiveSccUseRemoval2") build.inst(IrCmd::JUMP_EQ_INT, build.constInt(0), build.constInt(1), block, exit1); build.beginBlock(exit1); - build.inst(IrCmd::LOP_RETURN, build.constUint(0), build.vmReg(0), build.constInt(0)); + build.inst(IrCmd::LOP_RETURN, build.vmReg(0), build.constInt(0)); build.beginBlock(block); build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber)); build.inst(IrCmd::JUMP_IF_TRUTHY, build.vmReg(0), exit2, repeat); build.beginBlock(exit2); - build.inst(IrCmd::LOP_RETURN, build.constUint(0), build.vmReg(0), build.constInt(0)); + build.inst(IrCmd::LOP_RETURN, build.vmReg(0), build.constInt(0)); build.beginBlock(repeat); build.inst(IrCmd::INTERRUPT, build.constUint(0)); @@ -1270,14 +1274,14 @@ bb_0: JUMP bb_1 bb_1: - LOP_RETURN 0u, R0, 0i + LOP_RETURN R0, 0i bb_2: STORE_TAG R0, tnumber JUMP bb_3 bb_3: - LOP_RETURN 0u, R0, 0i + LOP_RETURN R0, 0i )"); } @@ -1318,7 +1322,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "SimplePathExtraction") build.inst(IrCmd::JUMP, block4); build.beginBlock(block4); - build.inst(IrCmd::LOP_RETURN, build.constUint(0), build.vmReg(0), build.constInt(0)); + build.inst(IrCmd::LOP_RETURN, build.vmReg(0), build.constInt(0)); updateUseCounts(build.function); constPropInBlockChains(build); @@ -1346,10 +1350,10 @@ bb_4: JUMP bb_5 bb_5: - LOP_RETURN 0u, R0, 0i + LOP_RETURN R0, 0i bb_linear_6: - LOP_RETURN 0u, R0, 0i + LOP_RETURN R0, 0i )"); } @@ -1389,11 +1393,11 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "NoPathExtractionForBlocksWithLiveOutValues" build.beginBlock(block4a); build.inst(IrCmd::STORE_TAG, build.vmReg(0), tag3a); - build.inst(IrCmd::LOP_RETURN, build.constUint(0), build.vmReg(0), build.constInt(0)); + build.inst(IrCmd::LOP_RETURN, build.vmReg(0), build.constInt(0)); build.beginBlock(block4b); build.inst(IrCmd::STORE_TAG, build.vmReg(0), tag3a); - build.inst(IrCmd::LOP_RETURN, build.constUint(0), build.vmReg(0), build.constInt(0)); + build.inst(IrCmd::LOP_RETURN, build.vmReg(0), build.constInt(0)); updateUseCounts(build.function); constPropInBlockChains(build); @@ -1423,11 +1427,11 @@ bb_4: bb_5: STORE_TAG R0, %10 - LOP_RETURN 0u, R0, 0i + LOP_RETURN R0, 0i bb_6: STORE_TAG R0, %10 - LOP_RETURN 0u, R0, 0i + LOP_RETURN R0, 0i )"); } @@ -1484,7 +1488,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "SimpleDiamond") build.inst(IrCmd::JUMP, exit); build.beginBlock(exit); - build.inst(IrCmd::LOP_RETURN, build.constUint(0), build.vmReg(2), build.constInt(2)); + build.inst(IrCmd::LOP_RETURN, build.vmReg(2), build.constInt(2)); updateUseCounts(build.function); computeCfgInfo(build.function); @@ -1518,7 +1522,7 @@ bb_2: bb_3: ; predecessors: bb_1, bb_2 ; in regs: R2, R3 - LOP_RETURN 0u, R2, 2i + LOP_RETURN R2, 2i )"); } @@ -1530,11 +1534,11 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "ImplicitFixedRegistersInVarargCall") build.beginBlock(entry); build.inst(IrCmd::FALLBACK_GETVARARGS, build.constUint(0), build.vmReg(3), build.constInt(-1)); - build.inst(IrCmd::LOP_CALL, build.constUint(0), build.vmReg(0), build.constInt(-1), build.constInt(5)); + build.inst(IrCmd::LOP_CALL, build.vmReg(0), build.constInt(-1), build.constInt(5)); build.inst(IrCmd::JUMP, exit); build.beginBlock(exit); - build.inst(IrCmd::LOP_RETURN, build.constUint(0), build.vmReg(0), build.constInt(5)); + build.inst(IrCmd::LOP_RETURN, build.vmReg(0), build.constInt(5)); updateUseCounts(build.function); computeCfgInfo(build.function); @@ -1545,13 +1549,13 @@ bb_0: ; in regs: R0, R1, R2 ; out regs: R0, R1, R2, R3, R4 FALLBACK_GETVARARGS 0u, R3, -1i - LOP_CALL 0u, R0, -1i, 5i + LOP_CALL R0, -1i, 5i JUMP bb_1 bb_1: ; predecessors: bb_0 ; in regs: R0, R1, R2, R3, R4 - LOP_RETURN 0u, R0, 5i + LOP_RETURN R0, 5i )"); } @@ -1563,11 +1567,13 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "ExplicitUseOfRegisterInVarargSequence") build.beginBlock(entry); build.inst(IrCmd::FALLBACK_GETVARARGS, build.constUint(0), build.vmReg(1), build.constInt(-1)); - build.inst(IrCmd::INVOKE_FASTCALL, build.constUint(0), build.vmReg(0), build.vmReg(1), build.vmReg(2), build.constInt(-1), build.constInt(-1)); + IrOp results = build.inst( + IrCmd::INVOKE_FASTCALL, build.constUint(0), build.vmReg(0), build.vmReg(1), build.vmReg(2), build.constInt(-1), build.constInt(-1)); + build.inst(IrCmd::ADJUST_STACK_TO_REG, build.vmReg(0), results); build.inst(IrCmd::JUMP, exit); build.beginBlock(exit); - build.inst(IrCmd::LOP_RETURN, build.constUint(0), build.vmReg(0), build.constInt(-1)); + build.inst(IrCmd::LOP_RETURN, build.vmReg(0), build.constInt(-1)); updateUseCounts(build.function); computeCfgInfo(build.function); @@ -1578,12 +1584,13 @@ bb_0: ; out regs: R0... FALLBACK_GETVARARGS 0u, R1, -1i %1 = INVOKE_FASTCALL 0u, R0, R1, R2, -1i, -1i + ADJUST_STACK_TO_REG R0, %1 JUMP bb_1 bb_1: ; predecessors: bb_0 ; in regs: R0... - LOP_RETURN 0u, R0, -1i + LOP_RETURN R0, -1i )"); } @@ -1594,12 +1601,12 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "VariadicSequenceRestart") IrOp exit = build.block(IrBlockKind::Internal); build.beginBlock(entry); - build.inst(IrCmd::LOP_CALL, build.constUint(0), build.vmReg(1), build.constInt(0), build.constInt(-1)); - build.inst(IrCmd::LOP_CALL, build.constUint(0), build.vmReg(0), build.constInt(-1), build.constInt(-1)); + build.inst(IrCmd::LOP_CALL, build.vmReg(1), build.constInt(0), build.constInt(-1)); + build.inst(IrCmd::LOP_CALL, build.vmReg(0), build.constInt(-1), build.constInt(-1)); build.inst(IrCmd::JUMP, exit); build.beginBlock(exit); - build.inst(IrCmd::LOP_RETURN, build.constUint(0), build.vmReg(0), build.constInt(-1)); + build.inst(IrCmd::LOP_RETURN, build.vmReg(0), build.constInt(-1)); updateUseCounts(build.function); computeCfgInfo(build.function); @@ -1609,14 +1616,14 @@ bb_0: ; successors: bb_1 ; in regs: R0, R1 ; out regs: R0... - LOP_CALL 0u, R1, 0i, -1i - LOP_CALL 0u, R0, -1i, -1i + LOP_CALL R1, 0i, -1i + LOP_CALL R0, -1i, -1i JUMP bb_1 bb_1: ; predecessors: bb_0 ; in regs: R0... - LOP_RETURN 0u, R0, -1i + LOP_RETURN R0, -1i )"); } @@ -1630,15 +1637,15 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "FallbackDoesNotFlowUp") build.beginBlock(entry); build.inst(IrCmd::FALLBACK_GETVARARGS, build.constUint(0), build.vmReg(1), build.constInt(-1)); build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(0)), build.constTag(tnumber), fallback); - build.inst(IrCmd::LOP_CALL, build.constUint(0), build.vmReg(0), build.constInt(-1), build.constInt(-1)); + build.inst(IrCmd::LOP_CALL, build.vmReg(0), build.constInt(-1), build.constInt(-1)); build.inst(IrCmd::JUMP, exit); build.beginBlock(fallback); - build.inst(IrCmd::LOP_CALL, build.constUint(0), build.vmReg(0), build.constInt(-1), build.constInt(-1)); + build.inst(IrCmd::LOP_CALL, build.vmReg(0), build.constInt(-1), build.constInt(-1)); build.inst(IrCmd::JUMP, exit); build.beginBlock(exit); - build.inst(IrCmd::LOP_RETURN, build.constUint(0), build.vmReg(0), build.constInt(-1)); + build.inst(IrCmd::LOP_RETURN, build.vmReg(0), build.constInt(-1)); updateUseCounts(build.function); computeCfgInfo(build.function); @@ -1651,7 +1658,7 @@ bb_0: FALLBACK_GETVARARGS 0u, R1, -1i %1 = LOAD_TAG R0 CHECK_TAG %1, tnumber, bb_fallback_1 - LOP_CALL 0u, R0, -1i, -1i + LOP_CALL R0, -1i, -1i JUMP bb_2 bb_fallback_1: @@ -1659,13 +1666,13 @@ bb_fallback_1: ; successors: bb_2 ; in regs: R0, R1... ; out regs: R0... - LOP_CALL 0u, R0, -1i, -1i + LOP_CALL R0, -1i, -1i JUMP bb_2 bb_2: ; predecessors: bb_0, bb_fallback_1 ; in regs: R0... - LOP_RETURN 0u, R0, -1i + LOP_RETURN R0, -1i )"); } @@ -1690,7 +1697,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "VariadicSequencePeeling") build.inst(IrCmd::JUMP, exit); build.beginBlock(exit); - build.inst(IrCmd::LOP_RETURN, build.constUint(0), build.vmReg(2), build.constInt(-1)); + build.inst(IrCmd::LOP_RETURN, build.vmReg(2), build.constInt(-1)); updateUseCounts(build.function); computeCfgInfo(build.function); @@ -1725,7 +1732,65 @@ bb_2: bb_3: ; predecessors: bb_1, bb_2 ; in regs: R2... - LOP_RETURN 0u, R2, -1i + LOP_RETURN R2, -1i + +)"); +} + +TEST_CASE_FIXTURE(IrBuilderFixture, "BuiltinVariadicStart") +{ + IrOp entry = build.block(IrBlockKind::Internal); + IrOp exit = build.block(IrBlockKind::Internal); + + build.beginBlock(entry); + build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), build.constDouble(1.0)); + build.inst(IrCmd::STORE_DOUBLE, build.vmReg(2), build.constDouble(2.0)); + build.inst(IrCmd::ADJUST_STACK_TO_REG, build.vmReg(2), build.constInt(1)); + build.inst(IrCmd::LOP_CALL, build.vmReg(1), build.constInt(-1), build.constInt(1)); + build.inst(IrCmd::JUMP, exit); + + build.beginBlock(exit); + build.inst(IrCmd::LOP_RETURN, build.vmReg(0), build.constInt(2)); + + updateUseCounts(build.function); + computeCfgInfo(build.function); + + CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( +bb_0: +; successors: bb_1 +; in regs: R0 +; out regs: R0, R1 + STORE_DOUBLE R1, 1 + STORE_DOUBLE R2, 2 + ADJUST_STACK_TO_REG R2, 1i + LOP_CALL R1, -1i, 1i + JUMP bb_1 + +bb_1: +; predecessors: bb_0 +; in regs: R0, R1 + LOP_RETURN R0, 2i + +)"); +} + + +TEST_CASE_FIXTURE(IrBuilderFixture, "SetTable") +{ + IrOp entry = build.block(IrBlockKind::Internal); + + build.beginBlock(entry); + build.inst(IrCmd::SET_TABLE, build.vmReg(0), build.vmReg(1), build.constUint(1)); + build.inst(IrCmd::LOP_RETURN, build.vmReg(0), build.constInt(1)); + + updateUseCounts(build.function); + computeCfgInfo(build.function); + + CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( +bb_0: +; in regs: R0, R1 + SET_TABLE R0, R1, 1u + LOP_RETURN R0, 1i )"); } diff --git a/tests/Module.test.cpp b/tests/Module.test.cpp index d2796b6..7e61235 100644 --- a/tests/Module.test.cpp +++ b/tests/Module.test.cpp @@ -338,7 +338,7 @@ type B = A TEST_CASE_FIXTURE(BuiltinsFixture, "do_not_clone_reexports") { ScopedFastFlag flags[] = { - {"LuauClonePublicInterfaceLess", true}, + {"LuauClonePublicInterfaceLess2", true}, {"LuauSubstitutionReentrant", true}, {"LuauClassTypeVarsInSubstitution", true}, {"LuauSubstitutionFixMissingFields", true}, @@ -376,7 +376,7 @@ return {} TEST_CASE_FIXTURE(BuiltinsFixture, "do_not_clone_types_of_reexported_values") { ScopedFastFlag flags[] = { - {"LuauClonePublicInterfaceLess", true}, + {"LuauClonePublicInterfaceLess2", true}, {"LuauSubstitutionReentrant", true}, {"LuauClassTypeVarsInSubstitution", true}, {"LuauSubstitutionFixMissingFields", true}, diff --git a/tests/Normalize.test.cpp b/tests/Normalize.test.cpp index 384a39f..a495ee2 100644 --- a/tests/Normalize.test.cpp +++ b/tests/Normalize.test.cpp @@ -802,7 +802,7 @@ TEST_CASE_FIXTURE(NormalizeFixture, "negations_of_tables") TEST_CASE_FIXTURE(NormalizeFixture, "normalize_blocked_types") { - ScopedFastFlag sff[] { + ScopedFastFlag sff[]{ {"LuauNormalizeBlockedTypes", true}, }; @@ -813,4 +813,14 @@ TEST_CASE_FIXTURE(NormalizeFixture, "normalize_blocked_types") CHECK_EQ(normalizer.typeFromNormal(*norm), &blocked); } +TEST_CASE_FIXTURE(NormalizeFixture, "normalize_pending_expansion_types") +{ + AstName name; + Type pending{PendingExpansionType{std::nullopt, name, {}, {}}}; + + const NormalizedType* norm = normalizer.normalize(&pending); + + CHECK_EQ(normalizer.typeFromNormal(*norm), &pending); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.aliases.test.cpp b/tests/TypeInfer.aliases.test.cpp index b55c774..022abea 100644 --- a/tests/TypeInfer.aliases.test.cpp +++ b/tests/TypeInfer.aliases.test.cpp @@ -947,4 +947,71 @@ TEST_CASE_FIXTURE(Fixture, "type_alias_locations") CHECK(mod->scopes[3].second->typeAliasNameLocations["X"] == Location(Position(5, 17), 1)); } +/* + * We had a bug in DCR where substitution would improperly clone a + * PendingExpansionType. + * + * This cloned type did not have a matching constraint to expand it, so it was + * left dangling and unexpanded forever. + * + * We must also delay the dispatch a constraint if doing so would require + * unifying a PendingExpansionType. + */ +TEST_CASE_FIXTURE(BuiltinsFixture, "dont_lose_track_of_PendingExpansionTypes_after_substitution") +{ + fileResolver.source["game/ReactCurrentDispatcher"] = R"( + export type BasicStateAction = ((S) -> S) | S + export type Dispatch = (A) -> () + + export type Dispatcher = { + useState: (initialState: (() -> S) | S) -> (S, Dispatch>), + } + + return {} + )"; + + // Note: This script path is actually as short as it can be. Any shorter + // and we somehow fail to surface the bug. + fileResolver.source["game/React/React/ReactHooks"] = R"( + local RCD = require(script.Parent.Parent.Parent.ReactCurrentDispatcher) + + local function resolveDispatcher(): RCD.Dispatcher + return (nil :: any) :: RCD.Dispatcher + end + + function useState( + initialState: (() -> S) | S + ): (S, RCD.Dispatch>) + local dispatcher = resolveDispatcher() + return dispatcher.useState(initialState) + end + )"; + + CheckResult result = frontend.check("game/React/React/ReactHooks"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(Fixture, "another_thing_from_roact") +{ + CheckResult result = check(R"( + type Map = { [K]: V } + type Set = { [T]: boolean } + + type FiberRoot = { + pingCache: Map | Map>)> | nil, + } + + type Wakeable = { + andThen: (self: Wakeable) -> nil | Wakeable, + } + + local function attachPingListener(root: FiberRoot, wakeable: Wakeable, lanes: number) + local pingCache: Map | Map>)> | nil = root.pingCache + end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.builtins.test.cpp b/tests/TypeInfer.builtins.test.cpp index 49209a4..79d9108 100644 --- a/tests/TypeInfer.builtins.test.cpp +++ b/tests/TypeInfer.builtins.test.cpp @@ -9,7 +9,6 @@ using namespace Luau; LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution); -LUAU_FASTFLAG(LuauMatchReturnsOptionalString); TEST_SUITE_BEGIN("BuiltinTests"); @@ -1064,10 +1063,7 @@ TEST_CASE_FIXTURE(Fixture, "string_match") )"); LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::LuauMatchReturnsOptionalString) - CHECK_EQ(toString(requireType("p")), "string?"); - else - CHECK_EQ(toString(requireType("p")), "string"); + CHECK_EQ(toString(requireType("p")), "string?"); } TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types") @@ -1078,18 +1074,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types") LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::LuauMatchReturnsOptionalString) - { - CHECK_EQ(toString(requireType("a")), "string?"); - CHECK_EQ(toString(requireType("b")), "number?"); - CHECK_EQ(toString(requireType("c")), "string?"); - } - else - { - CHECK_EQ(toString(requireType("a")), "string"); - CHECK_EQ(toString(requireType("b")), "number"); - CHECK_EQ(toString(requireType("c")), "string"); - } + CHECK_EQ(toString(requireType("a")), "string?"); + CHECK_EQ(toString(requireType("b")), "number?"); + CHECK_EQ(toString(requireType("c")), "string?"); } TEST_CASE_FIXTURE(Fixture, "gmatch_capture_types2") @@ -1100,18 +1087,9 @@ TEST_CASE_FIXTURE(Fixture, "gmatch_capture_types2") LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::LuauMatchReturnsOptionalString) - { - CHECK_EQ(toString(requireType("a")), "string?"); - CHECK_EQ(toString(requireType("b")), "number?"); - CHECK_EQ(toString(requireType("c")), "string?"); - } - else - { - CHECK_EQ(toString(requireType("a")), "string"); - CHECK_EQ(toString(requireType("b")), "number"); - CHECK_EQ(toString(requireType("c")), "string"); - } + CHECK_EQ(toString(requireType("a")), "string?"); + CHECK_EQ(toString(requireType("b")), "number?"); + CHECK_EQ(toString(requireType("c")), "string?"); } TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_default_capture") @@ -1128,10 +1106,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_default_capture") CHECK_EQ(acm->expected, 1); CHECK_EQ(acm->actual, 4); - if (FFlag::LuauMatchReturnsOptionalString) - CHECK_EQ(toString(requireType("a")), "string?"); - else - CHECK_EQ(toString(requireType("a")), "string"); + CHECK_EQ(toString(requireType("a")), "string?"); } TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_balanced_escaped_parens") @@ -1148,18 +1123,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_balanced_escaped_parens CHECK_EQ(acm->expected, 3); CHECK_EQ(acm->actual, 4); - if (FFlag::LuauMatchReturnsOptionalString) - { - CHECK_EQ(toString(requireType("a")), "string?"); - CHECK_EQ(toString(requireType("b")), "string?"); - CHECK_EQ(toString(requireType("c")), "number?"); - } - else - { - CHECK_EQ(toString(requireType("a")), "string"); - CHECK_EQ(toString(requireType("b")), "string"); - CHECK_EQ(toString(requireType("c")), "number"); - } + CHECK_EQ(toString(requireType("a")), "string?"); + CHECK_EQ(toString(requireType("b")), "string?"); + CHECK_EQ(toString(requireType("c")), "number?"); } TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_parens_in_sets_are_ignored") @@ -1176,16 +1142,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_parens_in_sets_are_igno CHECK_EQ(acm->expected, 2); CHECK_EQ(acm->actual, 3); - if (FFlag::LuauMatchReturnsOptionalString) - { - CHECK_EQ(toString(requireType("a")), "string?"); - CHECK_EQ(toString(requireType("b")), "number?"); - } - else - { - CHECK_EQ(toString(requireType("a")), "string"); - CHECK_EQ(toString(requireType("b")), "number"); - } + CHECK_EQ(toString(requireType("a")), "string?"); + CHECK_EQ(toString(requireType("b")), "number?"); } TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_set_containing_lbracket") @@ -1196,16 +1154,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_set_containing_lbracket LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::LuauMatchReturnsOptionalString) - { - CHECK_EQ(toString(requireType("a")), "number?"); - CHECK_EQ(toString(requireType("b")), "string?"); - } - else - { - CHECK_EQ(toString(requireType("a")), "number"); - CHECK_EQ(toString(requireType("b")), "string"); - } + CHECK_EQ(toString(requireType("a")), "number?"); + CHECK_EQ(toString(requireType("b")), "string?"); } TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_leading_end_bracket_is_part_of_set") @@ -1253,18 +1203,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "match_capture_types") LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::LuauMatchReturnsOptionalString) - { - CHECK_EQ(toString(requireType("a")), "string?"); - CHECK_EQ(toString(requireType("b")), "number?"); - CHECK_EQ(toString(requireType("c")), "string?"); - } - else - { - CHECK_EQ(toString(requireType("a")), "string"); - CHECK_EQ(toString(requireType("b")), "number"); - CHECK_EQ(toString(requireType("c")), "string"); - } + CHECK_EQ(toString(requireType("a")), "string?"); + CHECK_EQ(toString(requireType("b")), "number?"); + CHECK_EQ(toString(requireType("c")), "string?"); } TEST_CASE_FIXTURE(BuiltinsFixture, "match_capture_types2") @@ -1280,18 +1221,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "match_capture_types2") CHECK_EQ(toString(tm->wantedType), "number?"); CHECK_EQ(toString(tm->givenType), "string"); - if (FFlag::LuauMatchReturnsOptionalString) - { - CHECK_EQ(toString(requireType("a")), "string?"); - CHECK_EQ(toString(requireType("b")), "number?"); - CHECK_EQ(toString(requireType("c")), "string?"); - } - else - { - CHECK_EQ(toString(requireType("a")), "string"); - CHECK_EQ(toString(requireType("b")), "number"); - CHECK_EQ(toString(requireType("c")), "string"); - } + CHECK_EQ(toString(requireType("a")), "string?"); + CHECK_EQ(toString(requireType("b")), "number?"); + CHECK_EQ(toString(requireType("c")), "string?"); } TEST_CASE_FIXTURE(BuiltinsFixture, "find_capture_types") @@ -1302,18 +1234,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "find_capture_types") LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::LuauMatchReturnsOptionalString) - { - CHECK_EQ(toString(requireType("a")), "string?"); - CHECK_EQ(toString(requireType("b")), "number?"); - CHECK_EQ(toString(requireType("c")), "string?"); - } - else - { - CHECK_EQ(toString(requireType("a")), "string"); - CHECK_EQ(toString(requireType("b")), "number"); - CHECK_EQ(toString(requireType("c")), "string"); - } + CHECK_EQ(toString(requireType("a")), "string?"); + CHECK_EQ(toString(requireType("b")), "number?"); + CHECK_EQ(toString(requireType("c")), "string?"); CHECK_EQ(toString(requireType("d")), "number?"); CHECK_EQ(toString(requireType("e")), "number?"); } @@ -1331,18 +1254,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "find_capture_types2") CHECK_EQ(toString(tm->wantedType), "number?"); CHECK_EQ(toString(tm->givenType), "string"); - if (FFlag::LuauMatchReturnsOptionalString) - { - CHECK_EQ(toString(requireType("a")), "string?"); - CHECK_EQ(toString(requireType("b")), "number?"); - CHECK_EQ(toString(requireType("c")), "string?"); - } - else - { - CHECK_EQ(toString(requireType("a")), "string"); - CHECK_EQ(toString(requireType("b")), "number"); - CHECK_EQ(toString(requireType("c")), "string"); - } + CHECK_EQ(toString(requireType("a")), "string?"); + CHECK_EQ(toString(requireType("b")), "number?"); + CHECK_EQ(toString(requireType("c")), "string?"); CHECK_EQ(toString(requireType("d")), "number?"); CHECK_EQ(toString(requireType("e")), "number?"); } @@ -1360,18 +1274,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "find_capture_types3") CHECK_EQ(toString(tm->wantedType), "boolean?"); CHECK_EQ(toString(tm->givenType), "string"); - if (FFlag::LuauMatchReturnsOptionalString) - { - CHECK_EQ(toString(requireType("a")), "string?"); - CHECK_EQ(toString(requireType("b")), "number?"); - CHECK_EQ(toString(requireType("c")), "string?"); - } - else - { - CHECK_EQ(toString(requireType("a")), "string"); - CHECK_EQ(toString(requireType("b")), "number"); - CHECK_EQ(toString(requireType("c")), "string"); - } + CHECK_EQ(toString(requireType("a")), "string?"); + CHECK_EQ(toString(requireType("b")), "number?"); + CHECK_EQ(toString(requireType("c")), "string?"); CHECK_EQ(toString(requireType("d")), "number?"); CHECK_EQ(toString(requireType("e")), "number?"); } diff --git a/tests/TypeInfer.functions.test.cpp b/tests/TypeInfer.functions.test.cpp index 482a6b7..c7f9684 100644 --- a/tests/TypeInfer.functions.test.cpp +++ b/tests/TypeInfer.functions.test.cpp @@ -1860,7 +1860,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "dont_assert_when_the_tarjan_limit_is_exceede ScopedFastInt sfi{"LuauTarjanChildLimit", 2}; ScopedFastFlag sff[] = { {"DebugLuauDeferredConstraintResolution", true}, - {"LuauClonePublicInterfaceLess", true}, + {"LuauClonePublicInterfaceLess2", true}, {"LuauSubstitutionReentrant", true}, {"LuauSubstitutionFixMissingFields", true}, }; @@ -1880,4 +1880,33 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "dont_assert_when_the_tarjan_limit_is_exceede CHECK(Location({0, 0}, {4, 4}) == result.errors[1].location); } +/* We had a bug under DCR where instantiated type packs had a nullptr scope. + * + * This caused an issue with promotion. + */ +TEST_CASE_FIXTURE(Fixture, "instantiated_type_packs_must_have_a_non_null_scope") +{ + CheckResult result = check(R"( + function pcall(...: A...): R... + end + + type Dispatch = (A) -> () + + function mountReducer() + dispatchAction() + return nil :: any + end + + function dispatchAction() + end + + function useReducer(): Dispatch + local result, setResult = pcall(mountReducer) + return setResult + end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.intersectionTypes.test.cpp b/tests/TypeInfer.intersectionTypes.test.cpp index f6d04a9..b682e5f 100644 --- a/tests/TypeInfer.intersectionTypes.test.cpp +++ b/tests/TypeInfer.intersectionTypes.test.cpp @@ -327,7 +327,12 @@ TEST_CASE_FIXTURE(Fixture, "table_intersection_write_sealed") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), "Cannot add property 'z' to table 'X & Y'"); + auto e = toString(result.errors[0]); + // In DCR, because of type normalization, we print a different error message + if (FFlag::DebugLuauDeferredConstraintResolution) + CHECK_EQ("Cannot add property 'z' to table '{| x: number, y: number |}'", e); + else + CHECK_EQ("Cannot add property 'z' to table 'X & Y'", e); } TEST_CASE_FIXTURE(Fixture, "table_intersection_write_sealed_indirect") diff --git a/tests/TypeInfer.oop.test.cpp b/tests/TypeInfer.oop.test.cpp index 0f540f6..eb4937f 100644 --- a/tests/TypeInfer.oop.test.cpp +++ b/tests/TypeInfer.oop.test.cpp @@ -326,4 +326,59 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "flag_when_index_metamethod_returns_0_values" CHECK("nil" == toString(requireType("p"))); } +TEST_CASE_FIXTURE(BuiltinsFixture, "augmenting_an_unsealed_table_with_a_metatable") +{ + CheckResult result = check(R"( + local A = {number = 8} + + local B = setmetatable({}, A) + + function B:method() + return "hello!!" + end + )"); + + CHECK("{ @metatable { number: number }, { method: (a) -> string } }" == toString(requireType("B"), {true})); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "react_style_oo") +{ + CheckResult result = check(R"( + local Prototype = {} + + local ClassMetatable = { + __index = Prototype + } + + local BaseClass = (setmetatable({}, ClassMetatable)) + + function BaseClass:extend(name) + local class = { + name=name + } + + class.__index = class + + function class.ctor(props) + return setmetatable({props=props}, class) + end + + return setmetatable(class, getmetatable(self)) + end + + local C = BaseClass:extend('C') + local i = C.ctor({hello='world'}) + + local iName = i.name + local cName = C.name + local hello = i.props.hello + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + + CHECK("string" == toString(requireType("iName"))); + CHECK("string" == toString(requireType("cName"))); + CHECK("string" == toString(requireType("hello"))); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.operators.test.cpp b/tests/TypeInfer.operators.test.cpp index 720784c..8c289c7 100644 --- a/tests/TypeInfer.operators.test.cpp +++ b/tests/TypeInfer.operators.test.cpp @@ -526,7 +526,17 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "typecheck_unary_minus_error") LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ("string", toString(requireType("a"))); + if (FFlag::DebugLuauDeferredConstraintResolution) + { + // Under DCR, this currently functions as a failed overload resolution, and so we can't say + // anything about the result type of the unary minus. + CHECK_EQ("any", toString(requireType("a"))); + } + else + { + + CHECK_EQ("string", toString(requireType("a"))); + } TypeMismatch* tm = get(result.errors[0]); REQUIRE_EQ(*tm->wantedType, *builtinTypes->booleanType); diff --git a/tests/TypeInfer.unionTypes.test.cpp b/tests/TypeInfer.unionTypes.test.cpp index d49f004..19a19e4 100644 --- a/tests/TypeInfer.unionTypes.test.cpp +++ b/tests/TypeInfer.unionTypes.test.cpp @@ -196,7 +196,6 @@ TEST_CASE_FIXTURE(Fixture, "index_on_a_union_type_with_missing_property") REQUIRE(bTy); CHECK_EQ(mup->missing[0], *bTy); CHECK_EQ(mup->key, "x"); - CHECK_EQ("*error-type*", toString(requireType("r"))); } @@ -354,7 +353,11 @@ a.x = 2 )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ("Value of type '({| x: number |} & {| y: number |})?' could be nil", toString(result.errors[0])); + auto s = toString(result.errors[0]); + if (FFlag::DebugLuauDeferredConstraintResolution) + CHECK_EQ("Value of type '{| x: number, y: number |}?' could be nil", s); + else + CHECK_EQ("Value of type '({| x: number |} & {| y: number |})?' could be nil", s); } TEST_CASE_FIXTURE(Fixture, "optional_length_error") diff --git a/tests/conformance/interrupt.lua b/tests/conformance/interrupt.lua index d4b7c80..c07f57e 100644 --- a/tests/conformance/interrupt.lua +++ b/tests/conformance/interrupt.lua @@ -17,4 +17,9 @@ end bar() +function baz() +end + +baz() + return "OK" diff --git a/tests/conformance/math.lua b/tests/conformance/math.lua index 18ed137..ea3b5c8 100644 --- a/tests/conformance/math.lua +++ b/tests/conformance/math.lua @@ -345,5 +345,7 @@ assert(math.round("1.8") == 2) assert(select('#', math.floor(1.4)) == 1) assert(select('#', math.ceil(1.6)) == 1) assert(select('#', math.sqrt(9)) == 1) +assert(select('#', math.deg(9)) == 1) +assert(select('#', math.rad(9)) == 1) return('OK') diff --git a/tests/main.cpp b/tests/main.cpp index 82ce4e1..5395e7c 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -59,6 +59,25 @@ static bool debuggerPresent() int ret = sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, nullptr, 0); // debugger is attached if the P_TRACED flag is set return ret == 0 && (info.kp_proc.p_flag & P_TRACED) != 0; +#elif defined(__linux__) + FILE* st = fopen("/proc/self/status", "r"); + if (!st) + return false; // assume no debugger is attached. + + int tpid = 0; + char buf[256]; + + while (fgets(buf, sizeof(buf), st)) + { + if (strncmp(buf, "TracerPid:\t", 11) == 0) + { + tpid = atoi(buf + 11); + break; + } + } + + fclose(st); + return tpid != 0; #else return false; // assume no debugger is attached. #endif @@ -67,7 +86,7 @@ static bool debuggerPresent() static int testAssertionHandler(const char* expr, const char* file, int line, const char* function) { if (debuggerPresent()) - LUAU_DEBUGBREAK(); + return 1; // LUAU_ASSERT will trigger LUAU_DEBUGBREAK for a more convenient debugging experience ADD_FAIL_AT(file, line, "Assertion failed: ", std::string(expr)); return 1; diff --git a/tools/faillist.txt b/tools/faillist.txt index d513af1..76e5972 100644 --- a/tools/faillist.txt +++ b/tools/faillist.txt @@ -25,7 +25,6 @@ BuiltinTests.string_format_correctly_ordered_types BuiltinTests.string_format_report_all_type_errors_at_correct_positions BuiltinTests.string_format_tostring_specifier_type_constraint BuiltinTests.string_format_use_correct_argument2 -BuiltinTests.table_insert_correctly_infers_type_of_array_3_args_overload BuiltinTests.table_pack BuiltinTests.table_pack_reduce BuiltinTests.table_pack_variadic @@ -49,9 +48,9 @@ GenericsTests.infer_generic_lib_function_function_argument GenericsTests.instantiated_function_argument_names GenericsTests.no_stack_overflow_from_quantifying GenericsTests.self_recursive_instantiated_param -IntersectionTypes.table_intersection_write_sealed IntersectionTypes.table_intersection_write_sealed_indirect IntersectionTypes.table_write_sealed_indirect +isSubtype.any_is_unknown_union_error ProvisionalTests.assign_table_with_refined_property_with_a_similar_type_is_illegal ProvisionalTests.bail_early_if_unification_is_too_complicated ProvisionalTests.do_not_ice_when_trying_to_pick_first_of_generic_type_pack @@ -70,7 +69,6 @@ RefinementTest.typeguard_in_assert_position RefinementTest.x_as_any_if_x_is_instance_elseif_x_is_table RuntimeLimits.typescript_port_of_Result_type TableTests.a_free_shape_cannot_turn_into_a_scalar_if_it_is_not_compatible -TableTests.casting_tables_with_props_into_table_with_indexer3 TableTests.checked_prop_too_early TableTests.disallow_indexing_into_an_unsealed_table_with_no_indexer_in_strict_mode TableTests.dont_crash_when_setmetatable_does_not_produce_a_metatabletypevar @@ -123,6 +121,7 @@ ToString.toStringNamedFunction_generic_pack TryUnifyTests.members_of_failed_typepack_unification_are_unified_with_errorType TryUnifyTests.result_of_failed_typepack_unification_is_constrained TryUnifyTests.typepack_unification_should_trim_free_tails +TypeAliases.dont_lose_track_of_PendingExpansionTypes_after_substitution TypeAliases.generic_param_remap TypeAliases.mismatched_generic_type_param TypeAliases.mutually_recursive_types_restriction_not_ok_1 @@ -218,10 +217,5 @@ TypeSingletons.widen_the_supertype_if_it_is_free_and_subtype_has_singleton TypeSingletons.widening_happens_almost_everywhere UnionTypes.generic_function_with_optional_arg UnionTypes.index_on_a_union_type_with_missing_property -UnionTypes.optional_assignment_errors -UnionTypes.optional_call_error -UnionTypes.optional_index_error -UnionTypes.optional_iteration -UnionTypes.optional_length_error UnionTypes.optional_union_follow UnionTypes.table_union_write_indirect