Sync to upstream/release/571 (#895)

* `table.sort` was improved further. It now guarentees N*log(N) time
complexity in the worst case.
* Fix https://github.com/Roblox/luau/issues/880

We are also working on fixing final bugs and crashes in the new type
solver.

On the CodeGen front we have a few things going on:
* We have a smarter register allocator for the x86 JIT
* We lower more instructions on arm64
* The vector constructor builtin is now translated to IR

---------

Co-authored-by: Arseny Kapoulkine <arseny.kapoulkine@gmail.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
This commit is contained in:
Andy Friesen 2023-04-07 14:01:29 -07:00 committed by GitHub
parent d148d7d574
commit ba67fb275e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
77 changed files with 2555 additions and 1146 deletions

View File

@ -16,9 +16,7 @@ struct TypeArena;
void registerBuiltinTypes(GlobalTypes& globals); void registerBuiltinTypes(GlobalTypes& globals);
void registerBuiltinGlobals(TypeChecker& typeChecker, GlobalTypes& globals); void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeCheckForAutocomplete = false);
void registerBuiltinGlobals(Frontend& frontend);
TypeId makeUnion(TypeArena& arena, std::vector<TypeId>&& types); TypeId makeUnion(TypeArena& arena, std::vector<TypeId>&& types);
TypeId makeIntersection(TypeArena& arena, std::vector<TypeId>&& types); TypeId makeIntersection(TypeArena& arena, std::vector<TypeId>&& types);

View File

@ -8,7 +8,6 @@
#include "Luau/Scope.h" #include "Luau/Scope.h"
#include "Luau/TypeInfer.h" #include "Luau/TypeInfer.h"
#include "Luau/Variant.h" #include "Luau/Variant.h"
#include <string> #include <string>
#include <vector> #include <vector>
#include <optional> #include <optional>
@ -36,9 +35,6 @@ struct LoadDefinitionFileResult
ModulePtr module; ModulePtr module;
}; };
LoadDefinitionFileResult loadDefinitionFile(TypeChecker& typeChecker, GlobalTypes& globals, ScopePtr targetScope, std::string_view definition,
const std::string& packageName, bool captureComments);
std::optional<Mode> parseMode(const std::vector<HotComment>& hotcomments); std::optional<Mode> parseMode(const std::vector<HotComment>& hotcomments);
std::vector<std::string_view> parsePathExpr(const AstExpr& pathExpr); std::vector<std::string_view> parsePathExpr(const AstExpr& pathExpr);
@ -55,7 +51,9 @@ std::optional<ModuleName> pathExprToModuleName(const ModuleName& currentModuleNa
* error when we try during typechecking. * error when we try during typechecking.
*/ */
std::optional<ModuleName> pathExprToModuleName(const ModuleName& currentModuleName, const AstExpr& expr); std::optional<ModuleName> pathExprToModuleName(const ModuleName& currentModuleName, const AstExpr& expr);
// TODO: Deprecate this code path when we move away from the old solver
LoadDefinitionFileResult loadDefinitionFileNoDCR(TypeChecker& typeChecker, GlobalTypes& globals, ScopePtr targetScope, std::string_view definition,
const std::string& packageName, bool captureComments);
struct SourceNode struct SourceNode
{ {
bool hasDirtySourceModule() const bool hasDirtySourceModule() const
@ -140,10 +138,6 @@ struct Frontend
CheckResult check(const ModuleName& name, std::optional<FrontendOptions> optionOverride = {}); // new shininess CheckResult check(const ModuleName& name, std::optional<FrontendOptions> optionOverride = {}); // new shininess
// Use 'check' with 'runLintChecks' set to true in FrontendOptions (enabledLintWarnings be set there as well)
LintResult lint_DEPRECATED(const ModuleName& name, std::optional<LintOptions> enabledLintWarnings = {});
LintResult lint_DEPRECATED(const SourceModule& module, std::optional<LintOptions> enabledLintWarnings = {});
bool isDirty(const ModuleName& name, bool forAutocomplete = false) const; bool isDirty(const ModuleName& name, bool forAutocomplete = false) const;
void markDirty(const ModuleName& name, std::vector<ModuleName>* markedDirty = nullptr); void markDirty(const ModuleName& name, std::vector<ModuleName>* markedDirty = nullptr);
@ -164,10 +158,11 @@ struct Frontend
ScopePtr addEnvironment(const std::string& environmentName); ScopePtr addEnvironment(const std::string& environmentName);
ScopePtr getEnvironmentScope(const std::string& environmentName) const; ScopePtr getEnvironmentScope(const std::string& environmentName) const;
void registerBuiltinDefinition(const std::string& name, std::function<void(TypeChecker&, GlobalTypes&, ScopePtr)>); void registerBuiltinDefinition(const std::string& name, std::function<void(Frontend&, GlobalTypes&, ScopePtr)>);
void applyBuiltinDefinitionToEnvironment(const std::string& environmentName, const std::string& definitionName); void applyBuiltinDefinitionToEnvironment(const std::string& environmentName, const std::string& definitionName);
LoadDefinitionFileResult loadDefinitionFile(std::string_view source, const std::string& packageName, bool captureComments); LoadDefinitionFileResult loadDefinitionFile(GlobalTypes& globals, ScopePtr targetScope, std::string_view source, const std::string& packageName,
bool captureComments, bool typeCheckForAutocomplete = false);
private: private:
ModulePtr check(const SourceModule& sourceModule, Mode mode, std::vector<RequireCycle> requireCycles, bool forAutocomplete = false, bool recordJsonLog = false); ModulePtr check(const SourceModule& sourceModule, Mode mode, std::vector<RequireCycle> requireCycles, bool forAutocomplete = false, bool recordJsonLog = false);
@ -182,7 +177,7 @@ private:
ScopePtr getModuleEnvironment(const SourceModule& module, const Config& config, bool forAutocomplete) const; ScopePtr getModuleEnvironment(const SourceModule& module, const Config& config, bool forAutocomplete) const;
std::unordered_map<std::string, ScopePtr> environments; std::unordered_map<std::string, ScopePtr> environments;
std::unordered_map<std::string, std::function<void(TypeChecker&, GlobalTypes&, ScopePtr)>> builtinDefinitions; std::unordered_map<std::string, std::function<void(Frontend&, GlobalTypes&, ScopePtr)>> builtinDefinitions;
BuiltinTypes builtinTypes_; BuiltinTypes builtinTypes_;

View File

@ -75,14 +75,45 @@ using TypeId = const Type*;
using Name = std::string; using Name = std::string;
// A free type var is one whose exact shape has yet to be fully determined. // A free type var is one whose exact shape has yet to be fully determined.
using FreeType = Unifiable::Free; struct FreeType
{
explicit FreeType(TypeLevel level);
explicit FreeType(Scope* scope);
FreeType(Scope* scope, TypeLevel level);
// When a free type var is unified with any other, it is then "bound" int index;
// to that type var, indicating that the two types are actually the same type. TypeLevel level;
Scope* scope = nullptr;
// True if this free type variable is part of a mutually
// recursive type alias whose definitions haven't been
// resolved yet.
bool forwardedTypeAlias = false;
};
struct GenericType
{
// By default, generics are global, with a synthetic name
GenericType();
explicit GenericType(TypeLevel level);
explicit GenericType(const Name& name);
explicit GenericType(Scope* scope);
GenericType(TypeLevel level, const Name& name);
GenericType(Scope* scope, const Name& name);
int index;
TypeLevel level;
Scope* scope = nullptr;
Name name;
bool explicitName = false;
};
// When an equality constraint is found, it is then "bound" to that type,
// indicating that the two types are actually the same type.
using BoundType = Unifiable::Bound<TypeId>; using BoundType = Unifiable::Bound<TypeId>;
using GenericType = Unifiable::Generic;
using Tags = std::vector<std::string>; using Tags = std::vector<std::string>;
using ModuleName = std::string; using ModuleName = std::string;
@ -395,9 +426,11 @@ struct TableType
// Represents a metatable attached to a table type. Somewhat analogous to a bound type. // Represents a metatable attached to a table type. Somewhat analogous to a bound type.
struct MetatableType struct MetatableType
{ {
// Always points to a TableType. // Should always be a TableType.
TypeId table; TypeId table;
// Always points to either a TableType or a MetatableType. // Should almost always either be a TableType or another MetatableType,
// though it is possible for other types (like AnyType and ErrorType) to
// find their way here sometimes.
TypeId metatable; TypeId metatable;
std::optional<std::string> syntheticName; std::optional<std::string> syntheticName;
@ -536,8 +569,8 @@ struct NegationType
using ErrorType = Unifiable::Error; using ErrorType = Unifiable::Error;
using TypeVariant = Unifiable::Variant<TypeId, PrimitiveType, BlockedType, PendingExpansionType, SingletonType, FunctionType, TableType, using TypeVariant = Unifiable::Variant<TypeId, FreeType, GenericType, PrimitiveType, BlockedType, PendingExpansionType, SingletonType, FunctionType,
MetatableType, ClassType, AnyType, UnionType, IntersectionType, LazyType, UnknownType, NeverType, NegationType>; TableType, MetatableType, ClassType, AnyType, UnionType, IntersectionType, LazyType, UnknownType, NeverType, NegationType>;
struct Type final struct Type final
{ {

View File

@ -12,20 +12,48 @@ namespace Luau
{ {
struct TypeArena; struct TypeArena;
struct TxnLog;
struct TypePack; struct TypePack;
struct VariadicTypePack; struct VariadicTypePack;
struct BlockedTypePack; struct BlockedTypePack;
struct TypePackVar; struct TypePackVar;
struct TxnLog;
using TypePackId = const TypePackVar*; using TypePackId = const TypePackVar*;
using FreeTypePack = Unifiable::Free;
struct FreeTypePack
{
explicit FreeTypePack(TypeLevel level);
explicit FreeTypePack(Scope* scope);
FreeTypePack(Scope* scope, TypeLevel level);
int index;
TypeLevel level;
Scope* scope = nullptr;
};
struct GenericTypePack
{
// By default, generics are global, with a synthetic name
GenericTypePack();
explicit GenericTypePack(TypeLevel level);
explicit GenericTypePack(const Name& name);
explicit GenericTypePack(Scope* scope);
GenericTypePack(TypeLevel level, const Name& name);
GenericTypePack(Scope* scope, const Name& name);
int index;
TypeLevel level;
Scope* scope = nullptr;
Name name;
bool explicitName = false;
};
using BoundTypePack = Unifiable::Bound<TypePackId>; using BoundTypePack = Unifiable::Bound<TypePackId>;
using GenericTypePack = Unifiable::Generic;
using TypePackVariant = Unifiable::Variant<TypePackId, TypePack, VariadicTypePack, BlockedTypePack>; using ErrorTypePack = Unifiable::Error;
using TypePackVariant = Unifiable::Variant<TypePackId, FreeTypePack, GenericTypePack, TypePack, VariadicTypePack, BlockedTypePack>;
/* A TypePack is a rope-like string of TypeIds. We use this structure to encode /* A TypePack is a rope-like string of TypeIds. We use this structure to encode
* notions like packs of unknown length and packs of any length, as well as more * notions like packs of unknown length and packs of any length, as well as more

View File

@ -83,24 +83,6 @@ using Name = std::string;
int freshIndex(); int freshIndex();
struct Free
{
explicit Free(TypeLevel level);
explicit Free(Scope* scope);
explicit Free(Scope* scope, TypeLevel level);
int index;
TypeLevel level;
Scope* scope = nullptr;
// True if this free type variable is part of a mutually
// recursive type alias whose definitions haven't been
// resolved yet.
bool forwardedTypeAlias = false;
private:
static int DEPRECATED_nextIndex;
};
template<typename Id> template<typename Id>
struct Bound struct Bound
{ {
@ -112,26 +94,6 @@ struct Bound
Id boundTo; Id boundTo;
}; };
struct Generic
{
// By default, generics are global, with a synthetic name
Generic();
explicit Generic(TypeLevel level);
explicit Generic(const Name& name);
explicit Generic(Scope* scope);
Generic(TypeLevel level, const Name& name);
Generic(Scope* scope, const Name& name);
int index;
TypeLevel level;
Scope* scope = nullptr;
Name name;
bool explicitName = false;
private:
static int DEPRECATED_nextIndex;
};
struct Error struct Error
{ {
// This constructor has to be public, since it's used in Type and TypePack, // This constructor has to be public, since it's used in Type and TypePack,
@ -145,6 +107,6 @@ private:
}; };
template<typename Id, typename... Value> template<typename Id, typename... Value>
using Variant = Luau::Variant<Free, Bound<Id>, Generic, Error, Value...>; using Variant = Luau::Variant<Bound<Id>, Error, Value...>;
} // namespace Luau::Unifiable } // namespace Luau::Unifiable

View File

@ -341,10 +341,10 @@ struct GenericTypeVisitor
traverse(btv->boundTo); traverse(btv->boundTo);
} }
else if (auto ftv = get<Unifiable::Free>(tp)) else if (auto ftv = get<FreeTypePack>(tp))
visit(tp, *ftv); visit(tp, *ftv);
else if (auto gtv = get<Unifiable::Generic>(tp)) else if (auto gtv = get<GenericTypePack>(tp))
visit(tp, *gtv); visit(tp, *gtv);
else if (auto etv = get<Unifiable::Error>(tp)) else if (auto etv = get<Unifiable::Error>(tp))

View File

@ -13,8 +13,6 @@
#include <unordered_set> #include <unordered_set>
#include <utility> #include <utility>
LUAU_FASTFLAGVARIABLE(LuauAutocompleteSkipNormalization, false);
static const std::unordered_set<std::string> kStatementStartingKeywords = { static const std::unordered_set<std::string> kStatementStartingKeywords = {
"while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"}; "while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"};
@ -143,12 +141,9 @@ static bool checkTypeMatch(TypeId subTy, TypeId superTy, NotNull<Scope> scope, T
Normalizer normalizer{typeArena, builtinTypes, NotNull{&unifierState}}; Normalizer normalizer{typeArena, builtinTypes, NotNull{&unifierState}};
Unifier unifier(NotNull<Normalizer>{&normalizer}, Mode::Strict, scope, Location(), Variance::Covariant); Unifier unifier(NotNull<Normalizer>{&normalizer}, Mode::Strict, scope, Location(), Variance::Covariant);
if (FFlag::LuauAutocompleteSkipNormalization) // Cost of normalization can be too high for autocomplete response time requirements
{ unifier.normalize = false;
// Cost of normalization can be too high for autocomplete response time requirements unifier.checkInhabited = false;
unifier.normalize = false;
unifier.checkInhabited = false;
}
return unifier.canUnify(subTy, superTy).empty(); return unifier.canUnify(subTy, superTy).empty();
} }

View File

@ -212,7 +212,7 @@ void registerBuiltinTypes(GlobalTypes& globals)
globals.globalScope->addBuiltinTypeBinding("never", TypeFun{{}, globals.builtinTypes->neverType}); globals.globalScope->addBuiltinTypeBinding("never", TypeFun{{}, globals.builtinTypes->neverType});
} }
void registerBuiltinGlobals(TypeChecker& typeChecker, GlobalTypes& globals) void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeCheckForAutocomplete)
{ {
LUAU_ASSERT(!globals.globalTypes.types.isFrozen()); LUAU_ASSERT(!globals.globalTypes.types.isFrozen());
LUAU_ASSERT(!globals.globalTypes.typePacks.isFrozen()); LUAU_ASSERT(!globals.globalTypes.typePacks.isFrozen());
@ -220,8 +220,8 @@ void registerBuiltinGlobals(TypeChecker& typeChecker, GlobalTypes& globals)
TypeArena& arena = globals.globalTypes; TypeArena& arena = globals.globalTypes;
NotNull<BuiltinTypes> builtinTypes = globals.builtinTypes; NotNull<BuiltinTypes> builtinTypes = globals.builtinTypes;
LoadDefinitionFileResult loadResult = LoadDefinitionFileResult loadResult = frontend.loadDefinitionFile(
Luau::loadDefinitionFile(typeChecker, globals, globals.globalScope, getBuiltinDefinitionSource(), "@luau", /* captureComments */ false); globals, globals.globalScope, getBuiltinDefinitionSource(), "@luau", /* captureComments */ false, typeCheckForAutocomplete);
LUAU_ASSERT(loadResult.success); LUAU_ASSERT(loadResult.success);
TypeId genericK = arena.addType(GenericType{"K"}); TypeId genericK = arena.addType(GenericType{"K"});
@ -309,106 +309,6 @@ void registerBuiltinGlobals(TypeChecker& typeChecker, GlobalTypes& globals)
attachDcrMagicFunction(getGlobalBinding(globals, "require"), dcrMagicFunctionRequire); attachDcrMagicFunction(getGlobalBinding(globals, "require"), dcrMagicFunctionRequire);
} }
void registerBuiltinGlobals(Frontend& frontend)
{
GlobalTypes& globals = frontend.globals;
LUAU_ASSERT(!globals.globalTypes.types.isFrozen());
LUAU_ASSERT(!globals.globalTypes.typePacks.isFrozen());
registerBuiltinTypes(globals);
TypeArena& arena = globals.globalTypes;
NotNull<BuiltinTypes> builtinTypes = globals.builtinTypes;
LoadDefinitionFileResult loadResult = frontend.loadDefinitionFile(getBuiltinDefinitionSource(), "@luau", /* captureComments */ false);
LUAU_ASSERT(loadResult.success);
TypeId genericK = arena.addType(GenericType{"K"});
TypeId genericV = arena.addType(GenericType{"V"});
TypeId mapOfKtoV = arena.addType(TableType{{}, TableIndexer(genericK, genericV), globals.globalScope->level, TableState::Generic});
std::optional<TypeId> stringMetatableTy = getMetatable(builtinTypes->stringType, builtinTypes);
LUAU_ASSERT(stringMetatableTy);
const TableType* stringMetatableTable = get<TableType>(follow(*stringMetatableTy));
LUAU_ASSERT(stringMetatableTable);
auto it = stringMetatableTable->props.find("__index");
LUAU_ASSERT(it != stringMetatableTable->props.end());
addGlobalBinding(globals, "string", it->second.type, "@luau");
// next<K, V>(t: Table<K, V>, i: K?) -> (K?, V)
TypePackId nextArgsTypePack = arena.addTypePack(TypePack{{mapOfKtoV, makeOption(builtinTypes, arena, genericK)}});
TypePackId nextRetsTypePack = arena.addTypePack(TypePack{{makeOption(builtinTypes, arena, genericK), genericV}});
addGlobalBinding(globals, "next", arena.addType(FunctionType{{genericK, genericV}, {}, nextArgsTypePack, nextRetsTypePack}), "@luau");
TypePackId pairsArgsTypePack = arena.addTypePack({mapOfKtoV});
TypeId pairsNext = arena.addType(FunctionType{nextArgsTypePack, nextRetsTypePack});
TypePackId pairsReturnTypePack = arena.addTypePack(TypePack{{pairsNext, mapOfKtoV, builtinTypes->nilType}});
// pairs<K, V>(t: Table<K, V>) -> ((Table<K, V>, K?) -> (K?, V), Table<K, V>, nil)
addGlobalBinding(globals, "pairs", arena.addType(FunctionType{{genericK, genericV}, {}, pairsArgsTypePack, pairsReturnTypePack}), "@luau");
TypeId genericMT = arena.addType(GenericType{"MT"});
TableType tab{TableState::Generic, globals.globalScope->level};
TypeId tabTy = arena.addType(tab);
TypeId tableMetaMT = arena.addType(MetatableType{tabTy, genericMT});
addGlobalBinding(globals, "getmetatable", makeFunction(arena, std::nullopt, {genericMT}, {}, {tableMetaMT}, {genericMT}), "@luau");
// clang-format off
// setmetatable<T: {}, MT>(T, MT) -> { @metatable MT, T }
addGlobalBinding(globals, "setmetatable",
arena.addType(
FunctionType{
{genericMT},
{},
arena.addTypePack(TypePack{{tabTy, genericMT}}),
arena.addTypePack(TypePack{{tableMetaMT}})
}
), "@luau"
);
// clang-format on
for (const auto& pair : globals.globalScope->bindings)
{
persist(pair.second.typeId);
if (TableType* ttv = getMutable<TableType>(pair.second.typeId))
{
if (!ttv->name)
ttv->name = "typeof(" + toString(pair.first) + ")";
}
}
attachMagicFunction(getGlobalBinding(globals, "assert"), magicFunctionAssert);
attachMagicFunction(getGlobalBinding(globals, "setmetatable"), magicFunctionSetMetaTable);
attachMagicFunction(getGlobalBinding(globals, "select"), magicFunctionSelect);
attachDcrMagicFunction(getGlobalBinding(globals, "select"), dcrMagicFunctionSelect);
if (TableType* ttv = getMutable<TableType>(getGlobalBinding(globals, "table")))
{
// tabTy is a generic table type which we can't express via declaration syntax yet
ttv->props["freeze"] = makeProperty(makeFunction(arena, std::nullopt, {tabTy}, {tabTy}), "@luau/global/table.freeze");
ttv->props["clone"] = makeProperty(makeFunction(arena, std::nullopt, {tabTy}, {tabTy}), "@luau/global/table.clone");
ttv->props["getn"].deprecated = true;
ttv->props["getn"].deprecatedSuggestion = "#";
ttv->props["foreach"].deprecated = true;
ttv->props["foreachi"].deprecated = true;
attachMagicFunction(ttv->props["pack"].type, magicFunctionPack);
attachDcrMagicFunction(ttv->props["pack"].type, dcrMagicFunctionPack);
}
attachMagicFunction(getGlobalBinding(globals, "require"), magicFunctionRequire);
attachDcrMagicFunction(getGlobalBinding(globals, "require"), dcrMagicFunctionRequire);
}
static std::optional<WithPredicate<TypePackId>> magicFunctionSelect( static std::optional<WithPredicate<TypePackId>> magicFunctionSelect(
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate) TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate)
{ {

View File

@ -44,10 +44,10 @@ struct TypeCloner
template<typename T> template<typename T>
void defaultClone(const T& t); void defaultClone(const T& t);
void operator()(const Unifiable::Free& t); void operator()(const FreeType& t);
void operator()(const Unifiable::Generic& t); void operator()(const GenericType& t);
void operator()(const Unifiable::Bound<TypeId>& t); void operator()(const BoundType& t);
void operator()(const Unifiable::Error& t); void operator()(const ErrorType& t);
void operator()(const BlockedType& t); void operator()(const BlockedType& t);
void operator()(const PendingExpansionType& t); void operator()(const PendingExpansionType& t);
void operator()(const PrimitiveType& t); void operator()(const PrimitiveType& t);
@ -89,15 +89,15 @@ struct TypePackCloner
seenTypePacks[typePackId] = cloned; seenTypePacks[typePackId] = cloned;
} }
void operator()(const Unifiable::Free& t) void operator()(const FreeTypePack& t)
{ {
defaultClone(t); defaultClone(t);
} }
void operator()(const Unifiable::Generic& t) void operator()(const GenericTypePack& t)
{ {
defaultClone(t); defaultClone(t);
} }
void operator()(const Unifiable::Error& t) void operator()(const ErrorTypePack& t)
{ {
defaultClone(t); defaultClone(t);
} }
@ -145,12 +145,12 @@ void TypeCloner::defaultClone(const T& t)
seenTypes[typeId] = cloned; seenTypes[typeId] = cloned;
} }
void TypeCloner::operator()(const Unifiable::Free& t) void TypeCloner::operator()(const FreeType& t)
{ {
defaultClone(t); defaultClone(t);
} }
void TypeCloner::operator()(const Unifiable::Generic& t) void TypeCloner::operator()(const GenericType& t)
{ {
defaultClone(t); defaultClone(t);
} }

View File

@ -29,7 +29,6 @@ LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTINT(LuauTarjanChildLimit) LUAU_FASTINT(LuauTarjanChildLimit)
LUAU_FASTFLAG(LuauInferInNoCheckMode) LUAU_FASTFLAG(LuauInferInNoCheckMode)
LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3, false) LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3, false)
LUAU_FASTFLAGVARIABLE(LuauLintInTypecheck, false)
LUAU_FASTINTVARIABLE(LuauAutocompleteCheckTimeoutMs, 100) LUAU_FASTINTVARIABLE(LuauAutocompleteCheckTimeoutMs, 100)
LUAU_FASTFLAGVARIABLE(DebugLuauDeferredConstraintResolution, false) LUAU_FASTFLAGVARIABLE(DebugLuauDeferredConstraintResolution, false)
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson, false); LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson, false);
@ -84,89 +83,20 @@ static void generateDocumentationSymbols(TypeId ty, const std::string& rootName)
} }
} }
LoadDefinitionFileResult Frontend::loadDefinitionFile(std::string_view source, const std::string& packageName, bool captureComments) static ParseResult parseSourceForModule(std::string_view source, Luau::SourceModule& sourceModule, bool captureComments)
{ {
if (!FFlag::DebugLuauDeferredConstraintResolution)
return Luau::loadDefinitionFile(typeChecker, globals, globals.globalScope, source, packageName, captureComments);
LUAU_TIMETRACE_SCOPE("loadDefinitionFile", "Frontend");
Luau::SourceModule sourceModule;
ParseOptions options; ParseOptions options;
options.allowDeclarationSyntax = true; options.allowDeclarationSyntax = true;
options.captureComments = captureComments; options.captureComments = captureComments;
Luau::ParseResult parseResult = Luau::Parser::parse(source.data(), source.size(), *sourceModule.names, *sourceModule.allocator, options); Luau::ParseResult parseResult = Luau::Parser::parse(source.data(), source.size(), *sourceModule.names, *sourceModule.allocator, options);
if (parseResult.errors.size() > 0)
return LoadDefinitionFileResult{false, parseResult, sourceModule, nullptr};
sourceModule.root = parseResult.root; sourceModule.root = parseResult.root;
sourceModule.mode = Mode::Definition; sourceModule.mode = Mode::Definition;
return parseResult;
ModulePtr checkedModule = check(sourceModule, Mode::Definition, {});
if (checkedModule->errors.size() > 0)
return LoadDefinitionFileResult{false, parseResult, sourceModule, checkedModule};
CloneState cloneState;
std::vector<TypeId> 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);
globals.globalScope->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);
globals.globalScope->exportedTypeBindings[name] = globalTy;
typesToPersist.push_back(globalTy.type);
}
for (TypeId ty : typesToPersist)
{
persist(ty);
}
return LoadDefinitionFileResult{true, parseResult, sourceModule, checkedModule};
} }
LoadDefinitionFileResult loadDefinitionFile(TypeChecker& typeChecker, GlobalTypes& globals, ScopePtr targetScope, std::string_view source, static void persistCheckedTypes(ModulePtr checkedModule, GlobalTypes& globals, ScopePtr targetScope, const std::string& packageName)
const std::string& packageName, bool captureComments)
{ {
LUAU_TIMETRACE_SCOPE("loadDefinitionFile", "Frontend");
Luau::SourceModule sourceModule;
ParseOptions options;
options.allowDeclarationSyntax = true;
options.captureComments = captureComments;
Luau::ParseResult parseResult = Luau::Parser::parse(source.data(), source.size(), *sourceModule.names, *sourceModule.allocator, options);
if (parseResult.errors.size() > 0)
return LoadDefinitionFileResult{false, parseResult, sourceModule, nullptr};
sourceModule.root = parseResult.root;
sourceModule.mode = Mode::Definition;
ModulePtr checkedModule = typeChecker.check(sourceModule, Mode::Definition);
if (checkedModule->errors.size() > 0)
return LoadDefinitionFileResult{false, parseResult, sourceModule, checkedModule};
CloneState cloneState; CloneState cloneState;
std::vector<TypeId> typesToPersist; std::vector<TypeId> typesToPersist;
@ -196,6 +126,49 @@ LoadDefinitionFileResult loadDefinitionFile(TypeChecker& typeChecker, GlobalType
{ {
persist(ty); persist(ty);
} }
}
LoadDefinitionFileResult Frontend::loadDefinitionFile(GlobalTypes& globals, ScopePtr targetScope, std::string_view source,
const std::string& packageName, bool captureComments, bool typeCheckForAutocomplete)
{
if (!FFlag::DebugLuauDeferredConstraintResolution)
return Luau::loadDefinitionFileNoDCR(typeCheckForAutocomplete ? typeCheckerForAutocomplete : typeChecker,
typeCheckForAutocomplete ? globalsForAutocomplete : globals, targetScope, source, packageName, captureComments);
LUAU_TIMETRACE_SCOPE("loadDefinitionFile", "Frontend");
Luau::SourceModule sourceModule;
Luau::ParseResult parseResult = parseSourceForModule(source, sourceModule, captureComments);
if (parseResult.errors.size() > 0)
return LoadDefinitionFileResult{false, parseResult, sourceModule, nullptr};
ModulePtr checkedModule = check(sourceModule, Mode::Definition, {});
if (checkedModule->errors.size() > 0)
return LoadDefinitionFileResult{false, parseResult, sourceModule, checkedModule};
persistCheckedTypes(checkedModule, globals, targetScope, packageName);
return LoadDefinitionFileResult{true, parseResult, sourceModule, checkedModule};
}
LoadDefinitionFileResult loadDefinitionFileNoDCR(TypeChecker& typeChecker, GlobalTypes& globals, ScopePtr targetScope, std::string_view source,
const std::string& packageName, bool captureComments)
{
LUAU_TIMETRACE_SCOPE("loadDefinitionFile", "Frontend");
Luau::SourceModule sourceModule;
Luau::ParseResult parseResult = parseSourceForModule(source, sourceModule, captureComments);
if (parseResult.errors.size() > 0)
return LoadDefinitionFileResult{false, parseResult, sourceModule, nullptr};
ModulePtr checkedModule = typeChecker.check(sourceModule, Mode::Definition);
if (checkedModule->errors.size() > 0)
return LoadDefinitionFileResult{false, parseResult, sourceModule, checkedModule};
persistCheckedTypes(checkedModule, globals, targetScope, packageName);
return LoadDefinitionFileResult{true, parseResult, sourceModule, checkedModule}; return LoadDefinitionFileResult{true, parseResult, sourceModule, checkedModule};
} }
@ -316,8 +289,6 @@ static ErrorVec accumulateErrors(
static void filterLintOptions(LintOptions& lintOptions, const std::vector<HotComment>& hotcomments, Mode mode) static void filterLintOptions(LintOptions& lintOptions, const std::vector<HotComment>& hotcomments, Mode mode)
{ {
LUAU_ASSERT(FFlag::LuauLintInTypecheck);
uint64_t ignoreLints = LintWarning::parseMask(hotcomments); uint64_t ignoreLints = LintWarning::parseMask(hotcomments);
lintOptions.warningMask &= ~ignoreLints; lintOptions.warningMask &= ~ignoreLints;
@ -472,24 +443,16 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
throw InternalCompilerError("Frontend::modules does not have data for " + name, name); throw InternalCompilerError("Frontend::modules does not have data for " + name, name);
} }
if (FFlag::LuauLintInTypecheck) std::unordered_map<ModuleName, ModulePtr>& modules =
{ frontendOptions.forAutocomplete ? moduleResolverForAutocomplete.modules : moduleResolver.modules;
std::unordered_map<ModuleName, ModulePtr>& modules =
frontendOptions.forAutocomplete ? moduleResolverForAutocomplete.modules : moduleResolver.modules;
checkResult.errors = accumulateErrors(sourceNodes, modules, name); checkResult.errors = accumulateErrors(sourceNodes, modules, name);
// Get lint result only for top checked module // Get lint result only for top checked module
if (auto it = modules.find(name); it != modules.end()) if (auto it = modules.find(name); it != modules.end())
checkResult.lintResult = it->second->lintResult; checkResult.lintResult = it->second->lintResult;
return checkResult; return checkResult;
}
else
{
return CheckResult{accumulateErrors(
sourceNodes, frontendOptions.forAutocomplete ? moduleResolverForAutocomplete.modules : moduleResolver.modules, name)};
}
} }
std::vector<ModuleName> buildQueue; std::vector<ModuleName> buildQueue;
@ -553,9 +516,10 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
else else
typeCheckerForAutocomplete.unifierIterationLimit = std::nullopt; typeCheckerForAutocomplete.unifierIterationLimit = std::nullopt;
ModulePtr moduleForAutocomplete = (FFlag::DebugLuauDeferredConstraintResolution && mode == Mode::Strict) ModulePtr moduleForAutocomplete =
? check(sourceModule, mode, requireCycles, /*forAutocomplete*/ true, /*recordJsonLog*/ false) FFlag::DebugLuauDeferredConstraintResolution
: typeCheckerForAutocomplete.check(sourceModule, Mode::Strict, environmentScope); ? check(sourceModule, Mode::Strict, requireCycles, /*forAutocomplete*/ true, /*recordJsonLog*/ false)
: typeCheckerForAutocomplete.check(sourceModule, Mode::Strict, environmentScope);
moduleResolverForAutocomplete.modules[moduleName] = moduleForAutocomplete; moduleResolverForAutocomplete.modules[moduleName] = moduleForAutocomplete;
@ -601,8 +565,6 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
{ {
LUAU_TIMETRACE_SCOPE("lint", "Frontend"); LUAU_TIMETRACE_SCOPE("lint", "Frontend");
LUAU_ASSERT(FFlag::LuauLintInTypecheck);
LintOptions lintOptions = frontendOptions.enabledLintWarnings.value_or(config.enabledLint); LintOptions lintOptions = frontendOptions.enabledLintWarnings.value_or(config.enabledLint);
filterLintOptions(lintOptions, sourceModule.hotcomments, mode); filterLintOptions(lintOptions, sourceModule.hotcomments, mode);
@ -662,15 +624,12 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
sourceNode.dirtyModule = false; sourceNode.dirtyModule = false;
} }
if (FFlag::LuauLintInTypecheck) // Get lint result only for top checked module
{ std::unordered_map<ModuleName, ModulePtr>& modules =
// Get lint result only for top checked module frontendOptions.forAutocomplete ? moduleResolverForAutocomplete.modules : moduleResolver.modules;
std::unordered_map<ModuleName, ModulePtr>& modules =
frontendOptions.forAutocomplete ? moduleResolverForAutocomplete.modules : moduleResolver.modules;
if (auto it = modules.find(name); it != modules.end()) if (auto it = modules.find(name); it != modules.end())
checkResult.lintResult = it->second->lintResult; checkResult.lintResult = it->second->lintResult;
}
return checkResult; return checkResult;
} }
@ -800,59 +759,6 @@ ScopePtr Frontend::getModuleEnvironment(const SourceModule& module, const Config
return result; return result;
} }
LintResult Frontend::lint_DEPRECATED(const ModuleName& name, std::optional<Luau::LintOptions> enabledLintWarnings)
{
LUAU_ASSERT(!FFlag::LuauLintInTypecheck);
LUAU_TIMETRACE_SCOPE("Frontend::lint", "Frontend");
LUAU_TIMETRACE_ARGUMENT("name", name.c_str());
auto [_sourceNode, sourceModule] = getSourceNode(name);
if (!sourceModule)
return LintResult{}; // FIXME: We really should do something a bit more obvious when a file is too broken to lint.
return lint_DEPRECATED(*sourceModule, enabledLintWarnings);
}
LintResult Frontend::lint_DEPRECATED(const SourceModule& module, std::optional<Luau::LintOptions> enabledLintWarnings)
{
LUAU_ASSERT(!FFlag::LuauLintInTypecheck);
LUAU_TIMETRACE_SCOPE("Frontend::lint", "Frontend");
LUAU_TIMETRACE_ARGUMENT("module", module.name.c_str());
const Config& config = configResolver->getConfig(module.name);
uint64_t ignoreLints = LintWarning::parseMask(module.hotcomments);
LintOptions options = enabledLintWarnings.value_or(config.enabledLint);
options.warningMask &= ~ignoreLints;
Mode mode = module.mode.value_or(config.mode);
if (mode != Mode::NoCheck)
{
options.disableWarning(Luau::LintWarning::Code_UnknownGlobal);
}
if (mode == Mode::Strict)
{
options.disableWarning(Luau::LintWarning::Code_ImplicitReturn);
}
ScopePtr environmentScope = getModuleEnvironment(module, config, /*forAutocomplete*/ false);
ModulePtr modulePtr = moduleResolver.getModule(module.name);
double timestamp = getTimestamp();
std::vector<LintWarning> warnings = Luau::lint(module.root, *module.names, environmentScope, modulePtr.get(), module.hotcomments, options);
stats.timeLint += getTimestamp() - timestamp;
return classifyLints(warnings, config);
}
bool Frontend::isDirty(const ModuleName& name, bool forAutocomplete) const bool Frontend::isDirty(const ModuleName& name, bool forAutocomplete) const
{ {
auto it = sourceNodes.find(name); auto it = sourceNodes.find(name);
@ -1195,7 +1101,7 @@ ScopePtr Frontend::getEnvironmentScope(const std::string& environmentName) const
return {}; return {};
} }
void Frontend::registerBuiltinDefinition(const std::string& name, std::function<void(TypeChecker&, GlobalTypes&, ScopePtr)> applicator) void Frontend::registerBuiltinDefinition(const std::string& name, std::function<void(Frontend&, GlobalTypes&, ScopePtr)> applicator)
{ {
LUAU_ASSERT(builtinDefinitions.count(name) == 0); LUAU_ASSERT(builtinDefinitions.count(name) == 0);
@ -1208,7 +1114,7 @@ void Frontend::applyBuiltinDefinitionToEnvironment(const std::string& environmen
LUAU_ASSERT(builtinDefinitions.count(definitionName) > 0); LUAU_ASSERT(builtinDefinitions.count(definitionName) > 0);
if (builtinDefinitions.count(definitionName) > 0) if (builtinDefinitions.count(definitionName) > 0)
builtinDefinitions[definitionName](typeChecker, globals, getEnvironmentScope(environmentName)); builtinDefinitions[definitionName](*this, globals, getEnvironmentScope(environmentName));
} }
LintResult Frontend::classifyLints(const std::vector<LintWarning>& warnings, const Config& config) LintResult Frontend::classifyLints(const std::vector<LintWarning>& warnings, const Config& config)

View File

@ -20,6 +20,7 @@ LUAU_FASTINTVARIABLE(LuauNormalizeCacheLimit, 100000);
LUAU_FASTFLAGVARIABLE(LuauNegatedClassTypes, false); LUAU_FASTFLAGVARIABLE(LuauNegatedClassTypes, false);
LUAU_FASTFLAGVARIABLE(LuauNegatedTableTypes, false); LUAU_FASTFLAGVARIABLE(LuauNegatedTableTypes, false);
LUAU_FASTFLAGVARIABLE(LuauNormalizeBlockedTypes, false); LUAU_FASTFLAGVARIABLE(LuauNormalizeBlockedTypes, false);
LUAU_FASTFLAGVARIABLE(LuauNormalizeMetatableFixes, false);
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution) LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
LUAU_FASTFLAG(LuauUninhabitedSubAnything2) LUAU_FASTFLAG(LuauUninhabitedSubAnything2)
LUAU_FASTFLAG(LuauTransitiveSubtyping) LUAU_FASTFLAG(LuauTransitiveSubtyping)
@ -2062,6 +2063,18 @@ std::optional<TypeId> Normalizer::intersectionOfTables(TypeId here, TypeId there
else if (isPrim(there, PrimitiveType::Table)) else if (isPrim(there, PrimitiveType::Table))
return here; return here;
if (FFlag::LuauNormalizeMetatableFixes)
{
if (get<NeverType>(here))
return there;
else if (get<NeverType>(there))
return here;
else if (get<AnyType>(here))
return there;
else if (get<AnyType>(there))
return here;
}
TypeId htable = here; TypeId htable = here;
TypeId hmtable = nullptr; TypeId hmtable = nullptr;
if (const MetatableType* hmtv = get<MetatableType>(here)) if (const MetatableType* hmtv = get<MetatableType>(here))
@ -2078,9 +2091,23 @@ std::optional<TypeId> Normalizer::intersectionOfTables(TypeId here, TypeId there
} }
const TableType* httv = get<TableType>(htable); const TableType* httv = get<TableType>(htable);
LUAU_ASSERT(httv); if (FFlag::LuauNormalizeMetatableFixes)
{
if (!httv)
return std::nullopt;
}
else
LUAU_ASSERT(httv);
const TableType* tttv = get<TableType>(ttable); const TableType* tttv = get<TableType>(ttable);
LUAU_ASSERT(tttv); if (FFlag::LuauNormalizeMetatableFixes)
{
if (!tttv)
return std::nullopt;
}
else
LUAU_ASSERT(tttv);
if (httv->state == TableState::Free || tttv->state == TableState::Free) if (httv->state == TableState::Free || tttv->state == TableState::Free)
return std::nullopt; return std::nullopt;

View File

@ -14,7 +14,6 @@
#include <stdexcept> #include <stdexcept>
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution) LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
LUAU_FASTFLAGVARIABLE(LuauFunctionReturnStringificationFixup, false)
/* /*
* Prefix generic typenames with gen- * Prefix generic typenames with gen-
@ -369,7 +368,7 @@ struct TypeStringifier
state.emit(">"); state.emit(">");
} }
void operator()(TypeId ty, const Unifiable::Free& ftv) void operator()(TypeId ty, const FreeType& ftv)
{ {
state.result.invalid = true; state.result.invalid = true;
if (FFlag::DebugLuauVerboseTypeNames) if (FFlag::DebugLuauVerboseTypeNames)

View File

@ -430,6 +430,69 @@ bool hasLength(TypeId ty, DenseHashSet<TypeId>& seen, int* recursionCount)
return false; return false;
} }
FreeType::FreeType(TypeLevel level)
: index(Unifiable::freshIndex())
, level(level)
, scope(nullptr)
{
}
FreeType::FreeType(Scope* scope)
: index(Unifiable::freshIndex())
, level{}
, scope(scope)
{
}
FreeType::FreeType(Scope* scope, TypeLevel level)
: index(Unifiable::freshIndex())
, level(level)
, scope(scope)
{
}
GenericType::GenericType()
: index(Unifiable::freshIndex())
, name("g" + std::to_string(index))
{
}
GenericType::GenericType(TypeLevel level)
: index(Unifiable::freshIndex())
, level(level)
, name("g" + std::to_string(index))
{
}
GenericType::GenericType(const Name& name)
: index(Unifiable::freshIndex())
, name(name)
, explicitName(true)
{
}
GenericType::GenericType(Scope* scope)
: index(Unifiable::freshIndex())
, scope(scope)
{
}
GenericType::GenericType(TypeLevel level, const Name& name)
: index(Unifiable::freshIndex())
, level(level)
, name(name)
, explicitName(true)
{
}
GenericType::GenericType(Scope* scope, const Name& name)
: index(Unifiable::freshIndex())
, scope(scope)
, name(name)
, explicitName(true)
{
}
BlockedType::BlockedType() BlockedType::BlockedType()
: index(FFlag::LuauNormalizeBlockedTypes ? Unifiable::freshIndex() : ++DEPRECATED_nextIndex) : index(FFlag::LuauNormalizeBlockedTypes ? Unifiable::freshIndex() : ++DEPRECATED_nextIndex)
{ {
@ -971,7 +1034,7 @@ const TypeLevel* getLevel(TypeId ty)
{ {
ty = follow(ty); ty = follow(ty);
if (auto ftv = get<Unifiable::Free>(ty)) if (auto ftv = get<FreeType>(ty))
return &ftv->level; return &ftv->level;
else if (auto ttv = get<TableType>(ty)) else if (auto ttv = get<TableType>(ty))
return &ttv->level; return &ttv->level;
@ -990,7 +1053,7 @@ std::optional<TypeLevel> getLevel(TypePackId tp)
{ {
tp = follow(tp); tp = follow(tp);
if (auto ftv = get<Unifiable::Free>(tp)) if (auto ftv = get<FreeTypePack>(tp))
return ftv->level; return ftv->level;
else else
return std::nullopt; return std::nullopt;

View File

@ -35,7 +35,21 @@ using SyntheticNames = std::unordered_map<const void*, char*>;
namespace Luau namespace Luau
{ {
static const char* getName(Allocator* allocator, SyntheticNames* syntheticNames, const Unifiable::Generic& gen) static const char* getName(Allocator* allocator, SyntheticNames* syntheticNames, const GenericType& gen)
{
size_t s = syntheticNames->size();
char*& n = (*syntheticNames)[&gen];
if (!n)
{
std::string str = gen.explicitName ? gen.name : generateName(s);
n = static_cast<char*>(allocator->allocate(str.size() + 1));
strcpy(n, str.c_str());
}
return n;
}
static const char* getName(Allocator* allocator, SyntheticNames* syntheticNames, const GenericTypePack& gen)
{ {
size_t s = syntheticNames->size(); size_t s = syntheticNames->size();
char*& n = (*syntheticNames)[&gen]; char*& n = (*syntheticNames)[&gen];
@ -237,7 +251,7 @@ public:
size_t numGenericPacks = 0; size_t numGenericPacks = 0;
for (auto it = ftv.genericPacks.begin(); it != ftv.genericPacks.end(); ++it) for (auto it = ftv.genericPacks.begin(); it != ftv.genericPacks.end(); ++it)
{ {
if (auto gtv = get<GenericType>(*it)) if (auto gtv = get<GenericTypePack>(*it))
genericPacks.data[numGenericPacks++] = {AstName(gtv->name.c_str()), Location(), nullptr}; genericPacks.data[numGenericPacks++] = {AstName(gtv->name.c_str()), Location(), nullptr};
} }

View File

@ -1020,7 +1020,7 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatAssign& assig
right = errorRecoveryType(scope); right = errorRecoveryType(scope);
else if (auto vtp = get<VariadicTypePack>(tailPack)) else if (auto vtp = get<VariadicTypePack>(tailPack))
right = vtp->ty; right = vtp->ty;
else if (get<Unifiable::Free>(tailPack)) else if (get<FreeTypePack>(tailPack))
{ {
*asMutable(tailPack) = TypePack{{left}}; *asMutable(tailPack) = TypePack{{left}};
growingPack = getMutable<TypePack>(tailPack); growingPack = getMutable<TypePack>(tailPack);
@ -1281,7 +1281,7 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatForIn& forin)
callRetPack = checkExprPack(scope, *exprCall).type; callRetPack = checkExprPack(scope, *exprCall).type;
callRetPack = follow(callRetPack); callRetPack = follow(callRetPack);
if (get<Unifiable::Free>(callRetPack)) if (get<FreeTypePack>(callRetPack))
{ {
iterTy = freshType(scope); iterTy = freshType(scope);
unify(callRetPack, addTypePack({{iterTy}, freshTypePack(scope)}), scope, forin.location); unify(callRetPack, addTypePack({{iterTy}, freshTypePack(scope)}), scope, forin.location);
@ -1951,7 +1951,7 @@ WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExp
return WithPredicate{errorRecoveryType(scope)}; return WithPredicate{errorRecoveryType(scope)};
else if (auto vtp = get<VariadicTypePack>(varargPack)) else if (auto vtp = get<VariadicTypePack>(varargPack))
return WithPredicate{vtp->ty}; return WithPredicate{vtp->ty};
else if (get<Unifiable::Generic>(varargPack)) else if (get<GenericTypePack>(varargPack))
{ {
// TODO: Better error? // TODO: Better error?
reportError(expr.location, GenericError{"Trying to get a type from a variadic type parameter"}); reportError(expr.location, GenericError{"Trying to get a type from a variadic type parameter"});
@ -1970,7 +1970,7 @@ WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExp
{ {
return {pack->head.empty() ? nilType : pack->head[0], std::move(result.predicates)}; return {pack->head.empty() ? nilType : pack->head[0], std::move(result.predicates)};
} }
else if (const FreeTypePack* ftp = get<Unifiable::Free>(retPack)) else if (const FreeTypePack* ftp = get<FreeTypePack>(retPack))
{ {
TypeId head = freshType(scope->level); TypeId head = freshType(scope->level);
TypePackId pack = addTypePack(TypePackVar{TypePack{{head}, freshTypePack(scope->level)}}); TypePackId pack = addTypePack(TypePackVar{TypePack{{head}, freshTypePack(scope->level)}});
@ -1981,7 +1981,7 @@ WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExp
return {errorRecoveryType(scope), std::move(result.predicates)}; return {errorRecoveryType(scope), std::move(result.predicates)};
else if (auto vtp = get<VariadicTypePack>(retPack)) else if (auto vtp = get<VariadicTypePack>(retPack))
return {vtp->ty, std::move(result.predicates)}; return {vtp->ty, std::move(result.predicates)};
else if (get<Unifiable::Generic>(retPack)) else if (get<GenericTypePack>(retPack))
{ {
if (FFlag::LuauReturnAnyInsteadOfICE) if (FFlag::LuauReturnAnyInsteadOfICE)
return {anyType, std::move(result.predicates)}; return {anyType, std::move(result.predicates)};
@ -3838,7 +3838,7 @@ void TypeChecker::checkArgumentList(const ScopePtr& scope, const AstExpr& funNam
if (argTail) if (argTail)
{ {
if (state.log.getMutable<Unifiable::Free>(state.log.follow(*argTail))) if (state.log.getMutable<FreeTypePack>(state.log.follow(*argTail)))
{ {
if (paramTail) if (paramTail)
state.tryUnify(*paramTail, *argTail); state.tryUnify(*paramTail, *argTail);
@ -3853,7 +3853,7 @@ void TypeChecker::checkArgumentList(const ScopePtr& scope, const AstExpr& funNam
else if (paramTail) else if (paramTail)
{ {
// argTail is definitely empty // argTail is definitely empty
if (state.log.getMutable<Unifiable::Free>(state.log.follow(*paramTail))) if (state.log.getMutable<FreeTypePack>(state.log.follow(*paramTail)))
state.log.replace(*paramTail, TypePackVar(TypePack{{}})); state.log.replace(*paramTail, TypePackVar(TypePack{{}}));
} }
@ -5570,7 +5570,7 @@ GenericTypeDefinitions TypeChecker::createGenericTypes(const ScopePtr& scope, st
} }
else else
{ {
g = addType(Unifiable::Generic{level, n}); g = addType(GenericType{level, n});
} }
generics.push_back({g, defaultValue}); generics.push_back({g, defaultValue});
@ -5598,7 +5598,7 @@ GenericTypeDefinitions TypeChecker::createGenericTypes(const ScopePtr& scope, st
TypePackId& cached = scope->parent->typeAliasTypePackParameters[n]; TypePackId& cached = scope->parent->typeAliasTypePackParameters[n];
if (!cached) if (!cached)
cached = addTypePack(TypePackVar{Unifiable::Generic{level, n}}); cached = addTypePack(TypePackVar{GenericTypePack{level, n}});
genericPacks.push_back({cached, defaultValue}); genericPacks.push_back({cached, defaultValue});
scope->privateTypePackBindings[n] = cached; scope->privateTypePackBindings[n] = cached;

View File

@ -9,6 +9,69 @@
namespace Luau namespace Luau
{ {
FreeTypePack::FreeTypePack(TypeLevel level)
: index(Unifiable::freshIndex())
, level(level)
, scope(nullptr)
{
}
FreeTypePack::FreeTypePack(Scope* scope)
: index(Unifiable::freshIndex())
, level{}
, scope(scope)
{
}
FreeTypePack::FreeTypePack(Scope* scope, TypeLevel level)
: index(Unifiable::freshIndex())
, level(level)
, scope(scope)
{
}
GenericTypePack::GenericTypePack()
: index(Unifiable::freshIndex())
, name("g" + std::to_string(index))
{
}
GenericTypePack::GenericTypePack(TypeLevel level)
: index(Unifiable::freshIndex())
, level(level)
, name("g" + std::to_string(index))
{
}
GenericTypePack::GenericTypePack(const Name& name)
: index(Unifiable::freshIndex())
, name(name)
, explicitName(true)
{
}
GenericTypePack::GenericTypePack(Scope* scope)
: index(Unifiable::freshIndex())
, scope(scope)
{
}
GenericTypePack::GenericTypePack(TypeLevel level, const Name& name)
: index(Unifiable::freshIndex())
, level(level)
, name(name)
, explicitName(true)
{
}
GenericTypePack::GenericTypePack(Scope* scope, const Name& name)
: index(Unifiable::freshIndex())
, scope(scope)
, name(name)
, explicitName(true)
{
}
BlockedTypePack::BlockedTypePack() BlockedTypePack::BlockedTypePack()
: index(++nextIndex) : index(++nextIndex)
{ {
@ -160,8 +223,8 @@ bool areEqual(SeenSet& seen, const TypePackVar& lhs, const TypePackVar& rhs)
TypePackId rhsTail = *rhsIter.tail(); TypePackId rhsTail = *rhsIter.tail();
{ {
const Unifiable::Free* lf = get_if<Unifiable::Free>(&lhsTail->ty); const FreeTypePack* lf = get_if<FreeTypePack>(&lhsTail->ty);
const Unifiable::Free* rf = get_if<Unifiable::Free>(&rhsTail->ty); const FreeTypePack* rf = get_if<FreeTypePack>(&rhsTail->ty);
if (lf && rf) if (lf && rf)
return lf->index == rf->index; return lf->index == rf->index;
} }
@ -174,8 +237,8 @@ bool areEqual(SeenSet& seen, const TypePackVar& lhs, const TypePackVar& rhs)
} }
{ {
const Unifiable::Generic* lg = get_if<Unifiable::Generic>(&lhsTail->ty); const GenericTypePack* lg = get_if<GenericTypePack>(&lhsTail->ty);
const Unifiable::Generic* rg = get_if<Unifiable::Generic>(&rhsTail->ty); const GenericTypePack* rg = get_if<GenericTypePack>(&rhsTail->ty);
if (lg && rg) if (lg && rg)
return lg->index == rg->index; return lg->index == rg->index;
} }

View File

@ -13,71 +13,6 @@ int freshIndex()
return ++nextIndex; return ++nextIndex;
} }
Free::Free(TypeLevel level)
: index(++nextIndex)
, level(level)
{
}
Free::Free(Scope* scope)
: index(++nextIndex)
, scope(scope)
{
}
Free::Free(Scope* scope, TypeLevel level)
: index(++nextIndex)
, level(level)
, scope(scope)
{
}
int Free::DEPRECATED_nextIndex = 0;
Generic::Generic()
: index(++nextIndex)
, name("g" + std::to_string(index))
{
}
Generic::Generic(TypeLevel level)
: index(++nextIndex)
, level(level)
, name("g" + std::to_string(index))
{
}
Generic::Generic(const Name& name)
: index(++nextIndex)
, name(name)
, explicitName(true)
{
}
Generic::Generic(Scope* scope)
: index(++nextIndex)
, scope(scope)
{
}
Generic::Generic(TypeLevel level, const Name& name)
: index(++nextIndex)
, level(level)
, name(name)
, explicitName(true)
{
}
Generic::Generic(Scope* scope, const Name& name)
: index(++nextIndex)
, scope(scope)
, name(name)
, explicitName(true)
{
}
int Generic::DEPRECATED_nextIndex = 0;
Error::Error() Error::Error()
: index(++nextIndex) : index(++nextIndex)
{ {

View File

@ -1489,7 +1489,7 @@ struct WeirdIter
bool canGrow() const bool canGrow() const
{ {
return nullptr != log.getMutable<Unifiable::Free>(packId); return nullptr != log.getMutable<FreeTypePack>(packId);
} }
void grow(TypePackId newTail) void grow(TypePackId newTail)
@ -1497,7 +1497,7 @@ struct WeirdIter
LUAU_ASSERT(canGrow()); LUAU_ASSERT(canGrow());
LUAU_ASSERT(log.getMutable<TypePack>(newTail)); LUAU_ASSERT(log.getMutable<TypePack>(newTail));
auto freePack = log.getMutable<Unifiable::Free>(packId); auto freePack = log.getMutable<FreeTypePack>(packId);
level = freePack->level; level = freePack->level;
if (FFlag::LuauMaintainScopesInUnifier && freePack->scope != nullptr) if (FFlag::LuauMaintainScopesInUnifier && freePack->scope != nullptr)
@ -1591,7 +1591,7 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
if (log.haveSeen(superTp, subTp)) if (log.haveSeen(superTp, subTp))
return; return;
if (log.getMutable<Unifiable::Free>(superTp)) if (log.getMutable<FreeTypePack>(superTp))
{ {
if (!occursCheck(superTp, subTp)) if (!occursCheck(superTp, subTp))
{ {
@ -1599,7 +1599,7 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
log.replace(superTp, Unifiable::Bound<TypePackId>(widen(subTp))); log.replace(superTp, Unifiable::Bound<TypePackId>(widen(subTp)));
} }
} }
else if (log.getMutable<Unifiable::Free>(subTp)) else if (log.getMutable<FreeTypePack>(subTp))
{ {
if (!occursCheck(subTp, superTp)) if (!occursCheck(subTp, superTp))
{ {
@ -2567,9 +2567,9 @@ static void queueTypePack(std::vector<TypeId>& queue, DenseHashSet<TypePackId>&
break; break;
seenTypePacks.insert(a); seenTypePacks.insert(a);
if (state.log.getMutable<Unifiable::Free>(a)) if (state.log.getMutable<FreeTypePack>(a))
{ {
state.log.replace(a, Unifiable::Bound{anyTypePack}); state.log.replace(a, BoundTypePack{anyTypePack});
} }
else if (auto tp = state.log.getMutable<TypePack>(a)) else if (auto tp = state.log.getMutable<TypePack>(a))
{ {
@ -2617,7 +2617,7 @@ void Unifier::tryUnifyVariadics(TypePackId subTp, TypePackId superTp, bool rever
{ {
tryUnify_(vtp->ty, superVariadic->ty); tryUnify_(vtp->ty, superVariadic->ty);
} }
else if (get<Unifiable::Generic>(tail)) else if (get<GenericTypePack>(tail))
{ {
reportError(location, GenericError{"Cannot unify variadic and generic packs"}); reportError(location, GenericError{"Cannot unify variadic and generic packs"});
} }
@ -2777,10 +2777,10 @@ bool Unifier::occursCheck(DenseHashSet<TypeId>& seen, TypeId needle, TypeId hays
seen.insert(haystack); seen.insert(haystack);
if (log.getMutable<Unifiable::Error>(needle)) if (log.getMutable<ErrorType>(needle))
return false; return false;
if (!log.getMutable<Unifiable::Free>(needle)) if (!log.getMutable<FreeType>(needle))
ice("Expected needle to be free"); ice("Expected needle to be free");
if (needle == haystack) if (needle == haystack)
@ -2824,10 +2824,10 @@ bool Unifier::occursCheck(DenseHashSet<TypePackId>& seen, TypePackId needle, Typ
seen.insert(haystack); seen.insert(haystack);
if (log.getMutable<Unifiable::Error>(needle)) if (log.getMutable<ErrorTypePack>(needle))
return false; return false;
if (!log.getMutable<Unifiable::Free>(needle)) if (!log.getMutable<FreeTypePack>(needle))
ice("Expected needle pack to be free"); ice("Expected needle pack to be free");
RecursionLimiter _ra(&sharedState.counters.recursionCount, sharedState.counters.recursionLimit); RecursionLimiter _ra(&sharedState.counters.recursionCount, sharedState.counters.recursionLimit);

View File

@ -14,7 +14,6 @@
#endif #endif
LUAU_FASTFLAG(DebugLuauTimeTracing) LUAU_FASTFLAG(DebugLuauTimeTracing)
LUAU_FASTFLAG(LuauLintInTypecheck)
enum class ReportFormat enum class ReportFormat
{ {
@ -81,12 +80,10 @@ static bool analyzeFile(Luau::Frontend& frontend, const char* name, ReportFormat
for (auto& error : cr.errors) for (auto& error : cr.errors)
reportError(frontend, format, error); reportError(frontend, format, error);
Luau::LintResult lr = FFlag::LuauLintInTypecheck ? cr.lintResult : frontend.lint_DEPRECATED(name);
std::string humanReadableName = frontend.fileResolver->getHumanReadableModuleName(name); std::string humanReadableName = frontend.fileResolver->getHumanReadableModuleName(name);
for (auto& error : lr.errors) for (auto& error : cr.lintResult.errors)
reportWarning(format, humanReadableName.c_str(), error); reportWarning(format, humanReadableName.c_str(), error);
for (auto& warning : lr.warnings) for (auto& warning : cr.lintResult.warnings)
reportWarning(format, humanReadableName.c_str(), warning); reportWarning(format, humanReadableName.c_str(), warning);
if (annotate) if (annotate)
@ -101,7 +98,7 @@ static bool analyzeFile(Luau::Frontend& frontend, const char* name, ReportFormat
printf("%s", annotated.c_str()); printf("%s", annotated.c_str());
} }
return cr.errors.empty() && lr.errors.empty(); return cr.errors.empty() && cr.lintResult.errors.empty();
} }
static void displayHelp(const char* argv0) static void displayHelp(const char* argv0)
@ -264,13 +261,13 @@ int main(int argc, char** argv)
Luau::FrontendOptions frontendOptions; Luau::FrontendOptions frontendOptions;
frontendOptions.retainFullTypeGraphs = annotate; frontendOptions.retainFullTypeGraphs = annotate;
frontendOptions.runLintChecks = FFlag::LuauLintInTypecheck; frontendOptions.runLintChecks = true;
CliFileResolver fileResolver; CliFileResolver fileResolver;
CliConfigResolver configResolver(mode); CliConfigResolver configResolver(mode);
Luau::Frontend frontend(&fileResolver, &configResolver, frontendOptions); Luau::Frontend frontend(&fileResolver, &configResolver, frontendOptions);
Luau::registerBuiltinGlobals(frontend.typeChecker, frontend.globals); Luau::registerBuiltinGlobals(frontend, frontend.globals);
Luau::freeze(frontend.globals.globalTypes); Luau::freeze(frontend.globals.globalTypes);
#ifdef CALLGRIND #ifdef CALLGRIND

View File

@ -37,6 +37,7 @@ public:
void movk(RegisterA64 dst, uint16_t src, int shift = 0); void movk(RegisterA64 dst, uint16_t src, int shift = 0);
// Arithmetics // Arithmetics
// TODO: support various kinds of shifts
void add(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2, int shift = 0); void add(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2, int shift = 0);
void add(RegisterA64 dst, RegisterA64 src1, uint16_t 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, RegisterA64 src2, int shift = 0);
@ -50,8 +51,10 @@ public:
void csel(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2, ConditionA64 cond); void csel(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2, ConditionA64 cond);
// Bitwise // Bitwise
// Note: shifted-register support and bitfield operations are omitted for simplicity
// TODO: support immediate arguments (they have odd encoding and forbid many values) // TODO: support immediate arguments (they have odd encoding and forbid many values)
// TODO: support bic (andnot)
// TODO: support shifts
// TODO: support bitfield ops
void and_(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2); void and_(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2);
void orr(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2); void orr(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2);
void eor(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2); void eor(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2);
@ -82,7 +85,7 @@ public:
void stp(RegisterA64 src1, RegisterA64 src2, AddressA64 dst); void stp(RegisterA64 src1, RegisterA64 src2, AddressA64 dst);
// Control flow // Control flow
// Note: tbz/tbnz are currently not supported because they have 15-bit offsets and we don't support branch thunks // TODO: support tbz/tbnz; they have 15-bit offsets but they can be useful in constrained cases
void b(Label& label); void b(Label& label);
void b(ConditionA64 cond, Label& label); void b(ConditionA64 cond, Label& label);
void cbz(RegisterA64 src, Label& label); void cbz(RegisterA64 src, Label& label);

View File

@ -121,6 +121,7 @@ public:
void vcvttsd2si(OperandX64 dst, OperandX64 src); void vcvttsd2si(OperandX64 dst, OperandX64 src);
void vcvtsi2sd(OperandX64 dst, OperandX64 src1, OperandX64 src2); void vcvtsi2sd(OperandX64 dst, OperandX64 src1, OperandX64 src2);
void vcvtsd2ss(OperandX64 dst, OperandX64 src1, OperandX64 src2);
void vroundsd(OperandX64 dst, OperandX64 src1, OperandX64 src2, RoundingModeX64 roundingMode); // inexact void vroundsd(OperandX64 dst, OperandX64 src1, OperandX64 src2, RoundingModeX64 roundingMode); // inexact

View File

@ -19,6 +19,8 @@ void updateUseCounts(IrFunction& function);
void updateLastUseLocations(IrFunction& function); void updateLastUseLocations(IrFunction& function);
uint32_t getNextInstUse(IrFunction& function, uint32_t targetInstIdx, uint32_t startInstIdx);
// Returns how many values are coming into the block (live in) and how many are coming out of the block (live out) // Returns how many values are coming into the block (live in) and how many are coming out of the block (live out)
std::pair<uint32_t, uint32_t> getLiveInOutValueCount(IrFunction& function, IrBlock& block); std::pair<uint32_t, uint32_t> getLiveInOutValueCount(IrFunction& function, IrBlock& block);
uint32_t getLiveInValueCount(IrFunction& function, IrBlock& block); uint32_t getLiveInValueCount(IrFunction& function, IrBlock& block);

View File

@ -17,10 +17,6 @@ namespace CodeGen
namespace X64 namespace X64
{ {
// When IrInst operands are used, current instruction index is required to track lifetime
// In all other calls it is ok to omit the argument
constexpr uint32_t kInvalidInstIdx = ~0u;
struct IrRegAllocX64; struct IrRegAllocX64;
struct ScopedRegX64; struct ScopedRegX64;
@ -61,6 +57,7 @@ private:
void renameRegister(RegisterX64& target, RegisterX64 reg, RegisterX64 replacement); void renameRegister(RegisterX64& target, RegisterX64 reg, RegisterX64 replacement);
void renameSourceRegisters(RegisterX64 reg, RegisterX64 replacement); void renameSourceRegisters(RegisterX64 reg, RegisterX64 replacement);
RegisterX64 findConflictingTarget() const; RegisterX64 findConflictingTarget() const;
void renameConflictingRegister(RegisterX64 conflict);
int getRegisterUses(RegisterX64 reg) const; int getRegisterUses(RegisterX64 reg) const;
void addRegisterUse(RegisterX64 reg); void addRegisterUse(RegisterX64 reg);

View File

@ -62,11 +62,12 @@ enum class IrCmd : uint8_t
// Get pointer (LuaNode) to table node element at the active cached slot index // Get pointer (LuaNode) to table node element at the active cached slot index
// A: pointer (Table) // A: pointer (Table)
// B: unsigned int (pcpos)
GET_SLOT_NODE_ADDR, GET_SLOT_NODE_ADDR,
// Get pointer (LuaNode) to table node element at the main position of the specified key hash // Get pointer (LuaNode) to table node element at the main position of the specified key hash
// A: pointer (Table) // A: pointer (Table)
// B: unsigned int // B: unsigned int (hash)
GET_HASH_NODE_ADDR, GET_HASH_NODE_ADDR,
// Store a tag into TValue // Store a tag into TValue
@ -89,6 +90,13 @@ enum class IrCmd : uint8_t
// B: int // B: int
STORE_INT, STORE_INT,
// Store a vector into TValue
// A: Rn
// B: double (x)
// C: double (y)
// D: double (z)
STORE_VECTOR,
// Store a TValue into memory // Store a TValue into memory
// A: Rn or pointer (TValue) // A: Rn or pointer (TValue)
// B: TValue // B: TValue
@ -438,15 +446,6 @@ enum class IrCmd : uint8_t
// C: block (forgloop location) // C: block (forgloop location)
FORGPREP_XNEXT_FALLBACK, 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: Rn (target)
// B: Rn (lhs)
// C: Rn or Kn (rhs)
AND,
ANDK,
OR,
ORK,
// Increment coverage data (saturating 24 bit add) // Increment coverage data (saturating 24 bit add)
// A: unsigned int (bytecode instruction index) // A: unsigned int (bytecode instruction index)
COVERAGE, COVERAGE,
@ -622,6 +621,17 @@ struct IrOp
static_assert(sizeof(IrOp) == 4); static_assert(sizeof(IrOp) == 4);
enum class IrValueKind : uint8_t
{
Unknown, // Used by SUBSTITUTE, argument has to be checked to get type
None,
Tag,
Int,
Pointer,
Double,
Tvalue,
};
struct IrInst struct IrInst
{ {
IrCmd cmd; IrCmd cmd;
@ -641,8 +651,12 @@ struct IrInst
X64::RegisterX64 regX64 = X64::noreg; X64::RegisterX64 regX64 = X64::noreg;
A64::RegisterA64 regA64 = A64::noreg; A64::RegisterA64 regA64 = A64::noreg;
bool reusedReg = false; bool reusedReg = false;
bool spilled = false;
}; };
// When IrInst operands are used, current instruction index is often required to track lifetime
constexpr uint32_t kInvalidInstIdx = ~0u;
enum class IrBlockKind : uint8_t enum class IrBlockKind : uint8_t
{ {
Bytecode, Bytecode,
@ -821,6 +835,13 @@ struct IrFunction
LUAU_ASSERT(&block >= blocks.data() && &block <= blocks.data() + blocks.size()); LUAU_ASSERT(&block >= blocks.data() && &block <= blocks.data() + blocks.size());
return uint32_t(&block - blocks.data()); return uint32_t(&block - blocks.data());
} }
uint32_t getInstIndex(const IrInst& inst)
{
// Can only be called with instructions from our vector
LUAU_ASSERT(&inst >= instructions.data() && &inst <= instructions.data() + instructions.size());
return uint32_t(&inst - instructions.data());
}
}; };
inline IrCondition conditionOp(IrOp op) inline IrCondition conditionOp(IrOp op)

View File

@ -1,6 +1,7 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once #pragma once
#include "Luau/AssemblyBuilderX64.h"
#include "Luau/IrData.h" #include "Luau/IrData.h"
#include "Luau/RegisterX64.h" #include "Luau/RegisterX64.h"
@ -14,33 +15,66 @@ namespace CodeGen
namespace X64 namespace X64
{ {
constexpr uint8_t kNoStackSlot = 0xff;
struct IrSpillX64
{
uint32_t instIdx = 0;
bool useDoubleSlot = 0;
// Spill location can be a stack location or be empty
// When it's empty, it means that instruction value can be rematerialized
uint8_t stackSlot = kNoStackSlot;
RegisterX64 originalLoc = noreg;
};
struct IrRegAllocX64 struct IrRegAllocX64
{ {
IrRegAllocX64(IrFunction& function); IrRegAllocX64(AssemblyBuilderX64& build, IrFunction& function);
RegisterX64 allocGprReg(SizeX64 preferredSize); RegisterX64 allocGprReg(SizeX64 preferredSize, uint32_t instIdx);
RegisterX64 allocXmmReg(); RegisterX64 allocXmmReg(uint32_t instIdx);
RegisterX64 allocGprRegOrReuse(SizeX64 preferredSize, uint32_t index, std::initializer_list<IrOp> oprefs); RegisterX64 allocGprRegOrReuse(SizeX64 preferredSize, uint32_t instIdx, std::initializer_list<IrOp> oprefs);
RegisterX64 allocXmmRegOrReuse(uint32_t index, std::initializer_list<IrOp> oprefs); RegisterX64 allocXmmRegOrReuse(uint32_t instIdx, std::initializer_list<IrOp> oprefs);
RegisterX64 takeReg(RegisterX64 reg); RegisterX64 takeReg(RegisterX64 reg, uint32_t instIdx);
void freeReg(RegisterX64 reg); void freeReg(RegisterX64 reg);
void freeLastUseReg(IrInst& target, uint32_t index); void freeLastUseReg(IrInst& target, uint32_t instIdx);
void freeLastUseRegs(const IrInst& inst, uint32_t index); void freeLastUseRegs(const IrInst& inst, uint32_t instIdx);
bool isLastUseReg(const IrInst& target, uint32_t index) const; bool isLastUseReg(const IrInst& target, uint32_t instIdx) const;
bool shouldFreeGpr(RegisterX64 reg) const; bool shouldFreeGpr(RegisterX64 reg) const;
// Register used by instruction is about to be freed, have to find a way to restore value later
void preserve(IrInst& inst);
void restore(IrInst& inst, bool intoOriginalLocation);
void preserveAndFreeInstValues();
uint32_t findInstructionWithFurthestNextUse(const std::array<uint32_t, 16>& regInstUsers) const;
void assertFree(RegisterX64 reg) const; void assertFree(RegisterX64 reg) const;
void assertAllFree() const; void assertAllFree() const;
void assertNoSpills() const;
AssemblyBuilderX64& build;
IrFunction& function; IrFunction& function;
uint32_t currInstIdx = ~0u;
std::array<bool, 16> freeGprMap; std::array<bool, 16> freeGprMap;
std::array<uint32_t, 16> gprInstUsers;
std::array<bool, 16> freeXmmMap; std::array<bool, 16> freeXmmMap;
std::array<uint32_t, 16> xmmInstUsers;
std::bitset<256> usedSpillSlots;
unsigned maxUsedSlot = 0;
std::vector<IrSpillX64> spills;
}; };
struct ScopedRegX64 struct ScopedRegX64
@ -62,6 +96,23 @@ struct ScopedRegX64
RegisterX64 reg; RegisterX64 reg;
}; };
// When IR instruction makes a call under a condition that's not reflected as a real branch in IR,
// spilled values have to be restored to their exact original locations, so that both after a call
// and after the skip, values are found in the same place
struct ScopedSpills
{
explicit ScopedSpills(IrRegAllocX64& owner);
~ScopedSpills();
ScopedSpills(const ScopedSpills&) = delete;
ScopedSpills& operator=(const ScopedSpills&) = delete;
bool wasSpilledBefore(const IrSpillX64& spill) const;
IrRegAllocX64& owner;
std::vector<IrSpillX64> snapshot;
};
} // namespace X64 } // namespace X64
} // namespace CodeGen } // namespace CodeGen
} // namespace Luau } // namespace Luau

View File

@ -175,6 +175,8 @@ inline bool isPseudo(IrCmd cmd)
return cmd == IrCmd::NOP || cmd == IrCmd::SUBSTITUTE; return cmd == IrCmd::NOP || cmd == IrCmd::SUBSTITUTE;
} }
IrValueKind getCmdValueKind(IrCmd cmd);
bool isGCO(uint8_t tag); bool isGCO(uint8_t tag);
// Manually add or remove use of an operand // Manually add or remove use of an operand

View File

@ -37,6 +37,15 @@ struct RegisterA64
} }
}; };
constexpr RegisterA64 castReg(KindA64 kind, RegisterA64 reg)
{
LUAU_ASSERT(kind != reg.kind);
LUAU_ASSERT(kind != KindA64::none && reg.kind != KindA64::none);
LUAU_ASSERT((kind == KindA64::w || kind == KindA64::x) == (reg.kind == KindA64::w || reg.kind == KindA64::x));
return RegisterA64{kind, reg.index};
}
constexpr RegisterA64 noreg{KindA64::none, 0}; constexpr RegisterA64 noreg{KindA64::none, 0};
constexpr RegisterA64 w0{KindA64::w, 0}; constexpr RegisterA64 w0{KindA64::w, 0};

View File

@ -676,6 +676,16 @@ void AssemblyBuilderX64::vcvtsi2sd(OperandX64 dst, OperandX64 src1, OperandX64 s
placeAvx("vcvtsi2sd", dst, src1, src2, 0x2a, (src2.cat == CategoryX64::reg ? src2.base.size : src2.memSize) == SizeX64::qword, AVX_0F, AVX_F2); placeAvx("vcvtsi2sd", dst, src1, src2, 0x2a, (src2.cat == CategoryX64::reg ? src2.base.size : src2.memSize) == SizeX64::qword, AVX_0F, AVX_F2);
} }
void AssemblyBuilderX64::vcvtsd2ss(OperandX64 dst, OperandX64 src1, OperandX64 src2)
{
if (src2.cat == CategoryX64::reg)
LUAU_ASSERT(src2.base.size == SizeX64::xmmword);
else
LUAU_ASSERT(src2.memSize == SizeX64::qword);
placeAvx("vcvtsd2ss", dst, src1, src2, 0x5a, (src2.cat == CategoryX64::reg ? src2.base.size : src2.memSize) == SizeX64::qword, AVX_0F, AVX_F2);
}
void AssemblyBuilderX64::vroundsd(OperandX64 dst, OperandX64 src1, OperandX64 src2, RoundingModeX64 roundingMode) void AssemblyBuilderX64::vroundsd(OperandX64 dst, OperandX64 src1, OperandX64 src2, RoundingModeX64 roundingMode)
{ {
placeAvx("vroundsd", dst, src1, src2, uint8_t(roundingMode) | kRoundingPrecisionInexact, 0x0b, false, AVX_0F3A, AVX_66); placeAvx("vroundsd", dst, src1, src2, uint8_t(roundingMode) | kRoundingPrecisionInexact, 0x0b, false, AVX_0F3A, AVX_66);

View File

@ -74,7 +74,7 @@ static NativeProto* createNativeProto(Proto* proto, const IrBuilder& ir)
} }
template<typename AssemblyBuilder, typename IrLowering> template<typename AssemblyBuilder, typename IrLowering>
static void lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction& function, int bytecodeid, AssemblyOptions options) static bool 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 // 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<uint32_t> sortedBlocks; std::vector<uint32_t> sortedBlocks;
@ -193,6 +193,9 @@ static void lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction&
IrBlock& next = i + 1 < sortedBlocks.size() ? function.blocks[sortedBlocks[i + 1]] : dummy; IrBlock& next = i + 1 < sortedBlocks.size() ? function.blocks[sortedBlocks[i + 1]] : dummy;
lowering.lowerInst(inst, index, next); lowering.lowerInst(inst, index, next);
if (lowering.hasError())
return false;
} }
if (options.includeIr) if (options.includeIr)
@ -213,6 +216,8 @@ static void lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction&
if (irLocation != ~0u) if (irLocation != ~0u)
asmLocation = bcLocations[irLocation]; asmLocation = bcLocations[irLocation];
} }
return true;
} }
[[maybe_unused]] static bool lowerIr( [[maybe_unused]] static bool lowerIr(
@ -226,9 +231,7 @@ static void lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction&
X64::IrLoweringX64 lowering(build, helpers, data, ir.function); X64::IrLoweringX64 lowering(build, helpers, data, ir.function);
lowerImpl(build, lowering, ir.function, proto->bytecodeid, options); return lowerImpl(build, lowering, ir.function, proto->bytecodeid, options);
return true;
} }
[[maybe_unused]] static bool lowerIr( [[maybe_unused]] static bool lowerIr(
@ -239,9 +242,7 @@ static void lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction&
A64::IrLoweringA64 lowering(build, helpers, data, proto, ir.function); A64::IrLoweringA64 lowering(build, helpers, data, proto, ir.function);
lowerImpl(build, lowering, ir.function, proto->bytecodeid, options); return lowerImpl(build, lowering, ir.function, proto->bytecodeid, options);
return true;
} }
template<typename AssemblyBuilder> template<typename AssemblyBuilder>

View File

@ -11,8 +11,6 @@
#include "lstate.h" #include "lstate.h"
// TODO: LBF_MATH_FREXP and LBF_MATH_MODF can work for 1 result case if second store is removed
namespace Luau namespace Luau
{ {
namespace CodeGen namespace CodeGen
@ -176,8 +174,11 @@ void emitBuiltinMathFrexp(IrRegAllocX64& regs, AssemblyBuilderX64& build, int np
build.vmovsd(luauRegValue(ra), xmm0); build.vmovsd(luauRegValue(ra), xmm0);
build.vcvtsi2sd(xmm0, xmm0, dword[sTemporarySlot + 0]); if (nresults > 1)
build.vmovsd(luauRegValue(ra + 1), xmm0); {
build.vcvtsi2sd(xmm0, xmm0, dword[sTemporarySlot + 0]);
build.vmovsd(luauRegValue(ra + 1), xmm0);
}
} }
void emitBuiltinMathModf(IrRegAllocX64& regs, AssemblyBuilderX64& build, int nparams, int ra, int arg, OperandX64 args, int nresults) void emitBuiltinMathModf(IrRegAllocX64& regs, AssemblyBuilderX64& build, int nparams, int ra, int arg, OperandX64 args, int nresults)
@ -190,7 +191,8 @@ void emitBuiltinMathModf(IrRegAllocX64& regs, AssemblyBuilderX64& build, int npa
build.vmovsd(xmm1, qword[sTemporarySlot + 0]); build.vmovsd(xmm1, qword[sTemporarySlot + 0]);
build.vmovsd(luauRegValue(ra), xmm1); build.vmovsd(luauRegValue(ra), xmm1);
build.vmovsd(luauRegValue(ra + 1), xmm0); if (nresults > 1)
build.vmovsd(luauRegValue(ra + 1), xmm0);
} }
void emitBuiltinMathSign(IrRegAllocX64& regs, AssemblyBuilderX64& build, int nparams, int ra, int arg, OperandX64 args, int nresults) void emitBuiltinMathSign(IrRegAllocX64& regs, AssemblyBuilderX64& build, int nparams, int ra, int arg, OperandX64 args, int nresults)
@ -248,9 +250,9 @@ void emitBuiltin(IrRegAllocX64& regs, AssemblyBuilderX64& build, int bfid, int r
OperandX64 argsOp = 0; OperandX64 argsOp = 0;
if (args.kind == IrOpKind::VmReg) if (args.kind == IrOpKind::VmReg)
argsOp = luauRegAddress(args.index); argsOp = luauRegAddress(vmRegOp(args));
else if (args.kind == IrOpKind::VmConst) else if (args.kind == IrOpKind::VmConst)
argsOp = luauConstantAddress(args.index); argsOp = luauConstantAddress(vmConstOp(args));
switch (bfid) switch (bfid)
{ {

View File

@ -101,6 +101,30 @@ void emitReentry(AssemblyBuilderA64& build, ModuleHelpers& helpers)
build.br(x1); build.br(x1);
} }
void emitFallback(AssemblyBuilderA64& build, int op, int pcpos)
{
// fallback(L, instruction, base, k)
build.mov(x0, rState);
// TODO: refactor into a common helper
if (pcpos * sizeof(Instruction) <= AssemblyBuilderA64::kMaxImmediate)
{
build.add(x1, rCode, uint16_t(pcpos * sizeof(Instruction)));
}
else
{
build.mov(x1, pcpos * sizeof(Instruction));
build.add(x1, rCode, x1);
}
build.mov(x2, rBase);
build.mov(x3, rConstants);
build.ldr(x4, mem(rNativeContext, offsetof(NativeContext, fallback) + op * sizeof(NativeFallback) + offsetof(NativeFallback, fallback)));
build.blr(x4);
emitUpdateBase(build);
}
} // namespace A64 } // namespace A64
} // namespace CodeGen } // namespace CodeGen
} // namespace Luau } // namespace Luau

View File

@ -46,6 +46,7 @@ void emitUpdateBase(AssemblyBuilderA64& build);
void emitExit(AssemblyBuilderA64& build, bool continueInVm); void emitExit(AssemblyBuilderA64& build, bool continueInVm);
void emitInterrupt(AssemblyBuilderA64& build); void emitInterrupt(AssemblyBuilderA64& build);
void emitReentry(AssemblyBuilderA64& build, ModuleHelpers& helpers); void emitReentry(AssemblyBuilderA64& build, ModuleHelpers& helpers);
void emitFallback(AssemblyBuilderA64& build, int op, int pcpos);
} // namespace A64 } // namespace A64
} // namespace CodeGen } // namespace CodeGen

View File

@ -196,33 +196,51 @@ void checkObjectBarrierConditions(AssemblyBuilderX64& build, RegisterX64 tmp, Re
build.jcc(ConditionX64::Zero, skip); build.jcc(ConditionX64::Zero, skip);
} }
void callBarrierObject(IrRegAllocX64& regs, AssemblyBuilderX64& build, RegisterX64 object, IrOp objectOp, int ra, Label& skip) void callBarrierObject(IrRegAllocX64& regs, AssemblyBuilderX64& build, RegisterX64 object, IrOp objectOp, int ra)
{ {
Label skip;
ScopedRegX64 tmp{regs, SizeX64::qword}; ScopedRegX64 tmp{regs, SizeX64::qword};
checkObjectBarrierConditions(build, tmp.reg, object, ra, skip); checkObjectBarrierConditions(build, tmp.reg, object, ra, skip);
IrCallWrapperX64 callWrap(regs, build); {
callWrap.addArgument(SizeX64::qword, rState); ScopedSpills spillGuard(regs);
callWrap.addArgument(SizeX64::qword, object, objectOp);
callWrap.addArgument(SizeX64::qword, tmp); IrCallWrapperX64 callWrap(regs, build);
callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaC_barrierf)]); callWrap.addArgument(SizeX64::qword, rState);
callWrap.addArgument(SizeX64::qword, object, objectOp);
callWrap.addArgument(SizeX64::qword, tmp);
callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaC_barrierf)]);
}
build.setLabel(skip);
} }
void callBarrierTableFast(IrRegAllocX64& regs, AssemblyBuilderX64& build, RegisterX64 table, IrOp tableOp, Label& skip) void callBarrierTableFast(IrRegAllocX64& regs, AssemblyBuilderX64& build, RegisterX64 table, IrOp tableOp)
{ {
Label skip;
// isblack(obj2gco(t)) // isblack(obj2gco(t))
build.test(byte[table + offsetof(GCheader, marked)], bitmask(BLACKBIT)); build.test(byte[table + offsetof(GCheader, marked)], bitmask(BLACKBIT));
build.jcc(ConditionX64::Zero, skip); build.jcc(ConditionX64::Zero, skip);
IrCallWrapperX64 callWrap(regs, build); {
callWrap.addArgument(SizeX64::qword, rState); ScopedSpills spillGuard(regs);
callWrap.addArgument(SizeX64::qword, table, tableOp);
callWrap.addArgument(SizeX64::qword, addr[table + offsetof(Table, gclist)]); IrCallWrapperX64 callWrap(regs, build);
callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaC_barrierback)]); callWrap.addArgument(SizeX64::qword, rState);
callWrap.addArgument(SizeX64::qword, table, tableOp);
callWrap.addArgument(SizeX64::qword, addr[table + offsetof(Table, gclist)]);
callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaC_barrierback)]);
}
build.setLabel(skip);
} }
void callCheckGc(IrRegAllocX64& regs, AssemblyBuilderX64& build, Label& skip) void callStepGc(IrRegAllocX64& regs, AssemblyBuilderX64& build)
{ {
Label skip;
{ {
ScopedRegX64 tmp1{regs, SizeX64::qword}; ScopedRegX64 tmp1{regs, SizeX64::qword};
ScopedRegX64 tmp2{regs, SizeX64::qword}; ScopedRegX64 tmp2{regs, SizeX64::qword};
@ -233,11 +251,17 @@ void callCheckGc(IrRegAllocX64& regs, AssemblyBuilderX64& build, Label& skip)
build.jcc(ConditionX64::Below, skip); build.jcc(ConditionX64::Below, skip);
} }
IrCallWrapperX64 callWrap(regs, build); {
callWrap.addArgument(SizeX64::qword, rState); ScopedSpills spillGuard(regs);
callWrap.addArgument(SizeX64::dword, 1);
callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaC_step)]); IrCallWrapperX64 callWrap(regs, build);
emitUpdateBase(build); callWrap.addArgument(SizeX64::qword, rState);
callWrap.addArgument(SizeX64::dword, 1);
callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaC_step)]);
emitUpdateBase(build);
}
build.setLabel(skip);
} }
void emitExit(AssemblyBuilderX64& build, bool continueInVm) void emitExit(AssemblyBuilderX64& build, bool continueInVm)
@ -256,7 +280,7 @@ void emitUpdateBase(AssemblyBuilderX64& build)
} }
// Note: only uses rax/rdx, the caller may use other registers // Note: only uses rax/rdx, the caller may use other registers
void emitSetSavedPc(AssemblyBuilderX64& build, int pcpos) static void emitSetSavedPc(AssemblyBuilderX64& build, int pcpos)
{ {
build.mov(rdx, sCode); build.mov(rdx, sCode);
build.add(rdx, pcpos * sizeof(Instruction)); build.add(rdx, pcpos * sizeof(Instruction));
@ -298,9 +322,6 @@ void emitInterrupt(AssemblyBuilderX64& build, int pcpos)
void emitFallback(AssemblyBuilderX64& build, NativeState& data, int op, int pcpos) void emitFallback(AssemblyBuilderX64& build, NativeState& data, int op, int pcpos)
{ {
if (op == LOP_CAPTURE)
return;
NativeFallback& opinfo = data.context.fallback[op]; NativeFallback& opinfo = data.context.fallback[op];
LUAU_ASSERT(opinfo.fallback); LUAU_ASSERT(opinfo.fallback);

View File

@ -42,12 +42,14 @@ constexpr RegisterX64 rConstants = r12; // TValue* k
// 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 // 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 CodeGenX64.cpp for layout // See CodeGenX64.cpp for layout
constexpr unsigned kStackSize = 32 + 16; // 4 home locations for registers, 16 bytes for additional function call arguments constexpr unsigned kStackSize = 32 + 16; // 4 home locations for registers, 16 bytes for additional function call arguments
constexpr unsigned kLocalsSize = 24; // 3 extra slots for our custom locals (also aligns the stack to 16 byte boundary) constexpr unsigned kSpillSlots = 4; // locations for register allocator to spill data into
constexpr unsigned kLocalsSize = 24 + 8 * kSpillSlots; // 3 extra slots for our custom locals (also aligns the stack to 16 byte boundary)
constexpr OperandX64 sClosure = qword[rsp + kStackSize + 0]; // Closure* cl constexpr OperandX64 sClosure = qword[rsp + kStackSize + 0]; // Closure* cl
constexpr OperandX64 sCode = qword[rsp + kStackSize + 8]; // Instruction* code constexpr OperandX64 sCode = qword[rsp + kStackSize + 8]; // Instruction* code
constexpr OperandX64 sTemporarySlot = addr[rsp + kStackSize + 16]; constexpr OperandX64 sTemporarySlot = addr[rsp + kStackSize + 16];
constexpr OperandX64 sSpillArea = addr[rsp + kStackSize + 24];
// TODO: These should be replaced with a portable call function that checks the ABI at runtime and reorders moves accordingly to avoid conflicts // TODO: These should be replaced with a portable call function that checks the ABI at runtime and reorders moves accordingly to avoid conflicts
#if defined(_WIN32) #if defined(_WIN32)
@ -99,6 +101,11 @@ inline OperandX64 luauRegValueInt(int ri)
return dword[rBase + ri * sizeof(TValue) + offsetof(TValue, value)]; return dword[rBase + ri * sizeof(TValue) + offsetof(TValue, value)];
} }
inline OperandX64 luauRegValueVector(int ri, int index)
{
return dword[rBase + ri * sizeof(TValue) + offsetof(TValue, value) + (sizeof(float) * index)];
}
inline OperandX64 luauConstant(int ki) inline OperandX64 luauConstant(int ki)
{ {
return xmmword[rConstants + ki * sizeof(TValue)]; return xmmword[rConstants + ki * sizeof(TValue)];
@ -247,13 +254,12 @@ void callPrepareForN(IrRegAllocX64& regs, AssemblyBuilderX64& build, int limit,
void callGetTable(IrRegAllocX64& regs, AssemblyBuilderX64& build, int rb, OperandX64 c, int ra); void callGetTable(IrRegAllocX64& regs, AssemblyBuilderX64& build, int rb, OperandX64 c, int ra);
void callSetTable(IrRegAllocX64& regs, AssemblyBuilderX64& build, int rb, OperandX64 c, int ra); void callSetTable(IrRegAllocX64& regs, AssemblyBuilderX64& build, int rb, OperandX64 c, int ra);
void checkObjectBarrierConditions(AssemblyBuilderX64& build, RegisterX64 tmp, RegisterX64 object, int ra, Label& skip); void checkObjectBarrierConditions(AssemblyBuilderX64& build, RegisterX64 tmp, RegisterX64 object, int ra, Label& skip);
void callBarrierObject(IrRegAllocX64& regs, AssemblyBuilderX64& build, RegisterX64 object, IrOp objectOp, int ra, Label& skip); void callBarrierObject(IrRegAllocX64& regs, AssemblyBuilderX64& build, RegisterX64 object, IrOp objectOp, int ra);
void callBarrierTableFast(IrRegAllocX64& regs, AssemblyBuilderX64& build, RegisterX64 table, IrOp tableOp, Label& skip); void callBarrierTableFast(IrRegAllocX64& regs, AssemblyBuilderX64& build, RegisterX64 table, IrOp tableOp);
void callCheckGc(IrRegAllocX64& regs, AssemblyBuilderX64& build, Label& skip); void callStepGc(IrRegAllocX64& regs, AssemblyBuilderX64& build);
void emitExit(AssemblyBuilderX64& build, bool continueInVm); void emitExit(AssemblyBuilderX64& build, bool continueInVm);
void emitUpdateBase(AssemblyBuilderX64& build); void emitUpdateBase(AssemblyBuilderX64& build);
void emitSetSavedPc(AssemblyBuilderX64& build, int pcpos); // Note: only uses rax/rdx, the caller may use other registers
void emitInterrupt(AssemblyBuilderX64& build, int pcpos); void emitInterrupt(AssemblyBuilderX64& build, int pcpos);
void emitFallback(AssemblyBuilderX64& build, NativeState& data, int op, int pcpos); void emitFallback(AssemblyBuilderX64& build, NativeState& data, int op, int pcpos);

View File

@ -316,7 +316,7 @@ void emitInstReturn(AssemblyBuilderX64& build, ModuleHelpers& helpers, int ra, i
build.jmp(qword[rdx + rax * 2]); build.jmp(qword[rdx + rax * 2]);
} }
void emitInstSetList(IrRegAllocX64& regs, AssemblyBuilderX64& build, Label& next, int ra, int rb, int count, uint32_t index) void emitInstSetList(IrRegAllocX64& regs, AssemblyBuilderX64& build, int ra, int rb, int count, uint32_t index)
{ {
OperandX64 last = index + count - 1; OperandX64 last = index + count - 1;
@ -347,7 +347,7 @@ void emitInstSetList(IrRegAllocX64& regs, AssemblyBuilderX64& build, Label& next
Label skipResize; Label skipResize;
RegisterX64 table = regs.takeReg(rax); RegisterX64 table = regs.takeReg(rax, kInvalidInstIdx);
build.mov(table, luauRegValue(ra)); build.mov(table, luauRegValue(ra));
@ -412,7 +412,7 @@ void emitInstSetList(IrRegAllocX64& regs, AssemblyBuilderX64& build, Label& next
build.setLabel(endLoop); build.setLabel(endLoop);
} }
callBarrierTableFast(regs, build, table, {}, next); callBarrierTableFast(regs, build, table, {});
} }
void emitinstForGLoop(AssemblyBuilderX64& build, int ra, int aux, Label& loopRepeat, Label& loopExit) void emitinstForGLoop(AssemblyBuilderX64& build, int ra, int aux, Label& loopRepeat, Label& loopExit)
@ -504,82 +504,6 @@ void emitInstForGPrepXnextFallback(AssemblyBuilderX64& build, int pcpos, int ra,
build.jmp(target); build.jmp(target);
} }
static void emitInstAndX(AssemblyBuilderX64& build, int ra, int rb, OperandX64 c)
{
Label target, fallthrough;
jumpIfFalsy(build, rb, target, fallthrough);
build.setLabel(fallthrough);
build.vmovups(xmm0, c);
build.vmovups(luauReg(ra), xmm0);
if (ra == rb)
{
build.setLabel(target);
}
else
{
Label exit;
build.jmp(exit);
build.setLabel(target);
build.vmovups(xmm0, luauReg(rb));
build.vmovups(luauReg(ra), xmm0);
build.setLabel(exit);
}
}
void emitInstAnd(AssemblyBuilderX64& build, int ra, int rb, int rc)
{
emitInstAndX(build, ra, rb, luauReg(rc));
}
void emitInstAndK(AssemblyBuilderX64& build, int ra, int rb, int kc)
{
emitInstAndX(build, ra, rb, luauConstant(kc));
}
static void emitInstOrX(AssemblyBuilderX64& build, int ra, int rb, OperandX64 c)
{
Label target, fallthrough;
jumpIfTruthy(build, rb, target, fallthrough);
build.setLabel(fallthrough);
build.vmovups(xmm0, c);
build.vmovups(luauReg(ra), xmm0);
if (ra == rb)
{
build.setLabel(target);
}
else
{
Label exit;
build.jmp(exit);
build.setLabel(target);
build.vmovups(xmm0, luauReg(rb));
build.vmovups(luauReg(ra), xmm0);
build.setLabel(exit);
}
}
void emitInstOr(AssemblyBuilderX64& build, int ra, int rb, int rc)
{
emitInstOrX(build, ra, rb, luauReg(rc));
}
void emitInstOrK(AssemblyBuilderX64& build, int ra, int rb, int kc)
{
emitInstOrX(build, ra, rb, luauConstant(kc));
}
void emitInstGetImportFallback(AssemblyBuilderX64& build, int ra, uint32_t aux) void emitInstGetImportFallback(AssemblyBuilderX64& build, int ra, uint32_t aux)
{ {
build.mov(rax, sClosure); build.mov(rax, sClosure);

View File

@ -19,14 +19,10 @@ struct IrRegAllocX64;
void emitInstCall(AssemblyBuilderX64& build, ModuleHelpers& helpers, int ra, int nparams, int nresults); void emitInstCall(AssemblyBuilderX64& build, ModuleHelpers& helpers, int ra, int nparams, int nresults);
void emitInstReturn(AssemblyBuilderX64& build, ModuleHelpers& helpers, int ra, int actualResults); void emitInstReturn(AssemblyBuilderX64& build, ModuleHelpers& helpers, int ra, int actualResults);
void emitInstSetList(IrRegAllocX64& regs, AssemblyBuilderX64& build, Label& next, int ra, int rb, int count, uint32_t index); void emitInstSetList(IrRegAllocX64& regs, AssemblyBuilderX64& build, int ra, int rb, int count, uint32_t index);
void emitinstForGLoop(AssemblyBuilderX64& build, int ra, int aux, Label& loopRepeat, Label& loopExit); void emitinstForGLoop(AssemblyBuilderX64& build, int ra, int aux, Label& loopRepeat, Label& loopExit);
void emitinstForGLoopFallback(AssemblyBuilderX64& build, int ra, int aux, Label& loopRepeat); void emitinstForGLoopFallback(AssemblyBuilderX64& build, int ra, int aux, Label& loopRepeat);
void emitInstForGPrepXnextFallback(AssemblyBuilderX64& build, int pcpos, int ra, Label& target); void emitInstForGPrepXnextFallback(AssemblyBuilderX64& build, int pcpos, int ra, Label& target);
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 emitInstGetImportFallback(AssemblyBuilderX64& build, int ra, uint32_t aux);
void emitInstCoverage(AssemblyBuilderX64& build, int pcpos); void emitInstCoverage(AssemblyBuilderX64& build, int pcpos);

View File

@ -69,6 +69,9 @@ void updateLastUseLocations(IrFunction& function)
instructions[op.index].lastUse = uint32_t(instIdx); instructions[op.index].lastUse = uint32_t(instIdx);
}; };
if (isPseudo(inst.cmd))
continue;
checkOp(inst.a); checkOp(inst.a);
checkOp(inst.b); checkOp(inst.b);
checkOp(inst.c); checkOp(inst.c);
@ -78,6 +81,42 @@ void updateLastUseLocations(IrFunction& function)
} }
} }
uint32_t getNextInstUse(IrFunction& function, uint32_t targetInstIdx, uint32_t startInstIdx)
{
LUAU_ASSERT(startInstIdx < function.instructions.size());
IrInst& targetInst = function.instructions[targetInstIdx];
for (uint32_t i = startInstIdx; i <= targetInst.lastUse; i++)
{
IrInst& inst = function.instructions[i];
if (isPseudo(inst.cmd))
continue;
if (inst.a.kind == IrOpKind::Inst && inst.a.index == targetInstIdx)
return i;
if (inst.b.kind == IrOpKind::Inst && inst.b.index == targetInstIdx)
return i;
if (inst.c.kind == IrOpKind::Inst && inst.c.index == targetInstIdx)
return i;
if (inst.d.kind == IrOpKind::Inst && inst.d.index == targetInstIdx)
return i;
if (inst.e.kind == IrOpKind::Inst && inst.e.index == targetInstIdx)
return i;
if (inst.f.kind == IrOpKind::Inst && inst.f.index == targetInstIdx)
return i;
}
// There must be a next use since there is the last use location
LUAU_ASSERT(!"failed to find next use");
return targetInst.lastUse;
}
std::pair<uint32_t, uint32_t> getLiveInOutValueCount(IrFunction& function, IrBlock& block) std::pair<uint32_t, uint32_t> getLiveInOutValueCount(IrFunction& function, IrBlock& block)
{ {
uint32_t liveIns = 0; uint32_t liveIns = 0;
@ -97,6 +136,9 @@ std::pair<uint32_t, uint32_t> getLiveInOutValueCount(IrFunction& function, IrBlo
{ {
IrInst& inst = function.instructions[instIdx]; IrInst& inst = function.instructions[instIdx];
if (isPseudo(inst.cmd))
continue;
liveOuts += inst.useCount; liveOuts += inst.useCount;
checkOp(inst.a); checkOp(inst.a);
@ -149,26 +191,24 @@ static RegisterSet computeBlockLiveInRegSet(IrFunction& function, const IrBlock&
RegisterSet inRs; RegisterSet inRs;
auto def = [&](IrOp op, int offset = 0) { auto def = [&](IrOp op, int offset = 0) {
LUAU_ASSERT(op.kind == IrOpKind::VmReg); defRs.regs.set(vmRegOp(op) + offset, true);
defRs.regs.set(op.index + offset, true);
}; };
auto use = [&](IrOp op, int offset = 0) { auto use = [&](IrOp op, int offset = 0) {
LUAU_ASSERT(op.kind == IrOpKind::VmReg); if (!defRs.regs.test(vmRegOp(op) + offset))
if (!defRs.regs.test(op.index + offset)) inRs.regs.set(vmRegOp(op) + offset, true);
inRs.regs.set(op.index + offset, true);
}; };
auto maybeDef = [&](IrOp op) { auto maybeDef = [&](IrOp op) {
if (op.kind == IrOpKind::VmReg) if (op.kind == IrOpKind::VmReg)
defRs.regs.set(op.index, true); defRs.regs.set(vmRegOp(op), true);
}; };
auto maybeUse = [&](IrOp op) { auto maybeUse = [&](IrOp op) {
if (op.kind == IrOpKind::VmReg) if (op.kind == IrOpKind::VmReg)
{ {
if (!defRs.regs.test(op.index)) if (!defRs.regs.test(vmRegOp(op)))
inRs.regs.set(op.index, true); inRs.regs.set(vmRegOp(op), true);
} }
}; };
@ -230,6 +270,7 @@ static RegisterSet computeBlockLiveInRegSet(IrFunction& function, const IrBlock&
case IrCmd::STORE_POINTER: case IrCmd::STORE_POINTER:
case IrCmd::STORE_DOUBLE: case IrCmd::STORE_DOUBLE:
case IrCmd::STORE_INT: case IrCmd::STORE_INT:
case IrCmd::STORE_VECTOR:
case IrCmd::STORE_TVALUE: case IrCmd::STORE_TVALUE:
maybeDef(inst.a); // Argument can also be a pointer value maybeDef(inst.a); // Argument can also be a pointer value
break; break;
@ -264,9 +305,9 @@ static RegisterSet computeBlockLiveInRegSet(IrFunction& function, const IrBlock&
def(inst.a); def(inst.a);
break; break;
case IrCmd::CONCAT: case IrCmd::CONCAT:
useRange(inst.a.index, function.uintOp(inst.b)); useRange(vmRegOp(inst.a), function.uintOp(inst.b));
defRange(inst.a.index, function.uintOp(inst.b)); defRange(vmRegOp(inst.a), function.uintOp(inst.b));
break; break;
case IrCmd::GET_UPVALUE: case IrCmd::GET_UPVALUE:
def(inst.a); def(inst.a);
@ -298,20 +339,20 @@ static RegisterSet computeBlockLiveInRegSet(IrFunction& function, const IrBlock&
maybeUse(inst.a); maybeUse(inst.a);
if (function.boolOp(inst.b)) if (function.boolOp(inst.b))
capturedRegs.set(inst.a.index, true); capturedRegs.set(vmRegOp(inst.a), true);
break; break;
case IrCmd::SETLIST: case IrCmd::SETLIST:
use(inst.b); use(inst.b);
useRange(inst.c.index, function.intOp(inst.d)); useRange(vmRegOp(inst.c), function.intOp(inst.d));
break; break;
case IrCmd::CALL: case IrCmd::CALL:
use(inst.a); use(inst.a);
useRange(inst.a.index + 1, function.intOp(inst.b)); useRange(vmRegOp(inst.a) + 1, function.intOp(inst.b));
defRange(inst.a.index, function.intOp(inst.c)); defRange(vmRegOp(inst.a), function.intOp(inst.c));
break; break;
case IrCmd::RETURN: case IrCmd::RETURN:
useRange(inst.a.index, function.intOp(inst.b)); useRange(vmRegOp(inst.a), function.intOp(inst.b));
break; break;
case IrCmd::FASTCALL: case IrCmd::FASTCALL:
case IrCmd::INVOKE_FASTCALL: case IrCmd::INVOKE_FASTCALL:
@ -319,9 +360,9 @@ static RegisterSet computeBlockLiveInRegSet(IrFunction& function, const IrBlock&
{ {
if (count >= 3) if (count >= 3)
{ {
LUAU_ASSERT(inst.d.kind == IrOpKind::VmReg && inst.d.index == inst.c.index + 1); LUAU_ASSERT(inst.d.kind == IrOpKind::VmReg && vmRegOp(inst.d) == vmRegOp(inst.c) + 1);
useRange(inst.c.index, count); useRange(vmRegOp(inst.c), count);
} }
else else
{ {
@ -334,12 +375,12 @@ static RegisterSet computeBlockLiveInRegSet(IrFunction& function, const IrBlock&
} }
else else
{ {
useVarargs(inst.c.index); useVarargs(vmRegOp(inst.c));
} }
// Multiple return sequences (count == -1) are defined by ADJUST_STACK_TO_REG // Multiple return sequences (count == -1) are defined by ADJUST_STACK_TO_REG
if (int count = function.intOp(inst.f); count != -1) if (int count = function.intOp(inst.f); count != -1)
defRange(inst.b.index, count); defRange(vmRegOp(inst.b), count);
break; break;
case IrCmd::FORGLOOP: case IrCmd::FORGLOOP:
// First register is not used by instruction, we check that it's still 'nil' with CHECK_TAG // First register is not used by instruction, we check that it's still 'nil' with CHECK_TAG
@ -347,32 +388,17 @@ static RegisterSet computeBlockLiveInRegSet(IrFunction& function, const IrBlock&
use(inst.a, 2); use(inst.a, 2);
def(inst.a, 2); def(inst.a, 2);
defRange(inst.a.index + 3, function.intOp(inst.b)); defRange(vmRegOp(inst.a) + 3, function.intOp(inst.b));
break; break;
case IrCmd::FORGLOOP_FALLBACK: case IrCmd::FORGLOOP_FALLBACK:
useRange(inst.a.index, 3); useRange(vmRegOp(inst.a), 3);
def(inst.a, 2); def(inst.a, 2);
defRange(inst.a.index + 3, uint8_t(function.intOp(inst.b))); // ignore most significant bit defRange(vmRegOp(inst.a) + 3, uint8_t(function.intOp(inst.b))); // ignore most significant bit
break; break;
case IrCmd::FORGPREP_XNEXT_FALLBACK: case IrCmd::FORGPREP_XNEXT_FALLBACK:
use(inst.b); use(inst.b);
break; break;
// A <- B, C
case IrCmd::AND:
case IrCmd::OR:
use(inst.b);
use(inst.c);
def(inst.a);
break;
// A <- B
case IrCmd::ANDK:
case IrCmd::ORK:
use(inst.b);
def(inst.a);
break;
case IrCmd::FALLBACK_GETGLOBAL: case IrCmd::FALLBACK_GETGLOBAL:
def(inst.b); def(inst.b);
break; break;
@ -391,13 +417,13 @@ static RegisterSet computeBlockLiveInRegSet(IrFunction& function, const IrBlock&
case IrCmd::FALLBACK_NAMECALL: case IrCmd::FALLBACK_NAMECALL:
use(inst.c); use(inst.c);
defRange(inst.b.index, 2); defRange(vmRegOp(inst.b), 2);
break; break;
case IrCmd::FALLBACK_PREPVARARGS: case IrCmd::FALLBACK_PREPVARARGS:
// No effect on explicitly referenced registers // No effect on explicitly referenced registers
break; break;
case IrCmd::FALLBACK_GETVARARGS: case IrCmd::FALLBACK_GETVARARGS:
defRange(inst.b.index, function.intOp(inst.c)); defRange(vmRegOp(inst.b), function.intOp(inst.c));
break; break;
case IrCmd::FALLBACK_NEWCLOSURE: case IrCmd::FALLBACK_NEWCLOSURE:
def(inst.b); def(inst.b);
@ -408,10 +434,10 @@ static RegisterSet computeBlockLiveInRegSet(IrFunction& function, const IrBlock&
case IrCmd::FALLBACK_FORGPREP: case IrCmd::FALLBACK_FORGPREP:
use(inst.b); use(inst.b);
defRange(inst.b.index, 3); defRange(vmRegOp(inst.b), 3);
break; break;
case IrCmd::ADJUST_STACK_TO_REG: case IrCmd::ADJUST_STACK_TO_REG:
defRange(inst.a.index, -1); defRange(vmRegOp(inst.a), -1);
break; break;
case IrCmd::ADJUST_STACK_TO_TOP: case IrCmd::ADJUST_STACK_TO_TOP:
// While this can be considered to be a vararg consumer, it is already handled in fastcall instructions // While this can be considered to be a vararg consumer, it is already handled in fastcall instructions

View File

@ -364,16 +364,16 @@ void IrBuilder::translateInst(LuauOpcode op, const Instruction* pc, int i)
translateInstForGPrepInext(*this, pc, i); translateInstForGPrepInext(*this, pc, i);
break; break;
case LOP_AND: case LOP_AND:
inst(IrCmd::AND, vmReg(LUAU_INSN_A(*pc)), vmReg(LUAU_INSN_B(*pc)), vmReg(LUAU_INSN_C(*pc))); translateInstAndX(*this, pc, i, vmReg(LUAU_INSN_C(*pc)));
break; break;
case LOP_ANDK: case LOP_ANDK:
inst(IrCmd::ANDK, vmReg(LUAU_INSN_A(*pc)), vmReg(LUAU_INSN_B(*pc)), vmConst(LUAU_INSN_C(*pc))); translateInstAndX(*this, pc, i, vmConst(LUAU_INSN_C(*pc)));
break; break;
case LOP_OR: case LOP_OR:
inst(IrCmd::OR, vmReg(LUAU_INSN_A(*pc)), vmReg(LUAU_INSN_B(*pc)), vmReg(LUAU_INSN_C(*pc))); translateInstOrX(*this, pc, i, vmReg(LUAU_INSN_C(*pc)));
break; break;
case LOP_ORK: case LOP_ORK:
inst(IrCmd::ORK, vmReg(LUAU_INSN_A(*pc)), vmReg(LUAU_INSN_B(*pc)), vmConst(LUAU_INSN_C(*pc))); translateInstOrX(*this, pc, i, vmConst(LUAU_INSN_C(*pc)));
break; break;
case LOP_COVERAGE: case LOP_COVERAGE:
inst(IrCmd::COVERAGE, constUint(i)); inst(IrCmd::COVERAGE, constUint(i));

View File

@ -58,14 +58,17 @@ void IrCallWrapperX64::call(const OperandX64& func)
{ {
CallArgument& arg = args[i]; CallArgument& arg = args[i];
// If source is the last use of IrInst, clear the register
// Source registers are recorded separately in CallArgument
if (arg.sourceOp.kind != IrOpKind::None) if (arg.sourceOp.kind != IrOpKind::None)
{ {
if (IrInst* inst = regs.function.asInstOp(arg.sourceOp)) if (IrInst* inst = regs.function.asInstOp(arg.sourceOp))
{ {
// Source registers are recorded separately from source operands in CallArgument
// If source is the last use of IrInst, clear the register from the operand
if (regs.isLastUseReg(*inst, instIdx)) if (regs.isLastUseReg(*inst, instIdx))
inst->regX64 = noreg; inst->regX64 = noreg;
// If it's not the last use and register is volatile, register ownership is taken, which also spills the operand
else if (inst->regX64.size == SizeX64::xmmword || regs.shouldFreeGpr(inst->regX64))
regs.takeReg(inst->regX64, kInvalidInstIdx);
} }
} }
@ -83,7 +86,11 @@ void IrCallWrapperX64::call(const OperandX64& func)
freeSourceRegisters(arg); freeSourceRegisters(arg);
build.mov(tmp.reg, arg.source); if (arg.source.memSize == SizeX64::none)
build.lea(tmp.reg, arg.source);
else
build.mov(tmp.reg, arg.source);
build.mov(arg.target, tmp.reg); build.mov(arg.target, tmp.reg);
} }
else else
@ -102,7 +109,7 @@ void IrCallWrapperX64::call(const OperandX64& func)
// If target is not used as source in other arguments, prevent register allocator from giving it out // If target is not used as source in other arguments, prevent register allocator from giving it out
if (getRegisterUses(arg.target.base) == 0) if (getRegisterUses(arg.target.base) == 0)
regs.takeReg(arg.target.base); regs.takeReg(arg.target.base, kInvalidInstIdx);
else // Otherwise, make sure we won't free it when last source use is completed else // Otherwise, make sure we won't free it when last source use is completed
addRegisterUse(arg.target.base); addRegisterUse(arg.target.base);
@ -122,7 +129,7 @@ void IrCallWrapperX64::call(const OperandX64& func)
freeSourceRegisters(*candidate); freeSourceRegisters(*candidate);
LUAU_ASSERT(getRegisterUses(candidate->target.base) == 0); LUAU_ASSERT(getRegisterUses(candidate->target.base) == 0);
regs.takeReg(candidate->target.base); regs.takeReg(candidate->target.base, kInvalidInstIdx);
moveToTarget(*candidate); moveToTarget(*candidate);
@ -131,15 +138,7 @@ void IrCallWrapperX64::call(const OperandX64& func)
// If all registers cross-interfere (rcx <- rdx, rdx <- rcx), one has to be renamed // If all registers cross-interfere (rcx <- rdx, rdx <- rcx), one has to be renamed
else if (RegisterX64 conflict = findConflictingTarget(); conflict != noreg) else if (RegisterX64 conflict = findConflictingTarget(); conflict != noreg)
{ {
// Get a fresh register renameConflictingRegister(conflict);
RegisterX64 freshReg = conflict.size == SizeX64::xmmword ? regs.allocXmmReg() : regs.allocGprReg(conflict.size);
if (conflict.size == SizeX64::xmmword)
build.vmovsd(freshReg, conflict, conflict);
else
build.mov(freshReg, conflict);
renameSourceRegisters(conflict, freshReg);
} }
else else
{ {
@ -156,10 +155,18 @@ void IrCallWrapperX64::call(const OperandX64& func)
if (arg.source.cat == CategoryX64::imm) if (arg.source.cat == CategoryX64::imm)
{ {
// There could be a conflict with the function source register, make this argument a candidate to find it
arg.candidate = true;
if (RegisterX64 conflict = findConflictingTarget(); conflict != noreg)
renameConflictingRegister(conflict);
if (arg.target.cat == CategoryX64::reg) if (arg.target.cat == CategoryX64::reg)
regs.takeReg(arg.target.base); regs.takeReg(arg.target.base, kInvalidInstIdx);
moveToTarget(arg); moveToTarget(arg);
arg.candidate = false;
} }
} }
@ -176,6 +183,10 @@ void IrCallWrapperX64::call(const OperandX64& func)
regs.freeReg(arg.target.base); regs.freeReg(arg.target.base);
} }
regs.preserveAndFreeInstValues();
regs.assertAllFree();
build.call(funcOp); build.call(funcOp);
} }
@ -362,6 +373,19 @@ RegisterX64 IrCallWrapperX64::findConflictingTarget() const
return noreg; return noreg;
} }
void IrCallWrapperX64::renameConflictingRegister(RegisterX64 conflict)
{
// Get a fresh register
RegisterX64 freshReg = conflict.size == SizeX64::xmmword ? regs.allocXmmReg(kInvalidInstIdx) : regs.allocGprReg(conflict.size, kInvalidInstIdx);
if (conflict.size == SizeX64::xmmword)
build.vmovsd(freshReg, conflict, conflict);
else
build.mov(freshReg, conflict);
renameSourceRegisters(conflict, freshReg);
}
int IrCallWrapperX64::getRegisterUses(RegisterX64 reg) const int IrCallWrapperX64::getRegisterUses(RegisterX64 reg) const
{ {
return reg.size == SizeX64::xmmword ? xmmUses[reg.index] : (reg.size != SizeX64::none ? gprUses[reg.index] : 0); return reg.size == SizeX64::xmmword ? xmmUses[reg.index] : (reg.size != SizeX64::none ? gprUses[reg.index] : 0);

View File

@ -100,6 +100,8 @@ const char* getCmdName(IrCmd cmd)
return "STORE_DOUBLE"; return "STORE_DOUBLE";
case IrCmd::STORE_INT: case IrCmd::STORE_INT:
return "STORE_INT"; return "STORE_INT";
case IrCmd::STORE_VECTOR:
return "STORE_VECTOR";
case IrCmd::STORE_TVALUE: case IrCmd::STORE_TVALUE:
return "STORE_TVALUE"; return "STORE_TVALUE";
case IrCmd::STORE_NODE_VALUE_TV: case IrCmd::STORE_NODE_VALUE_TV:
@ -238,14 +240,6 @@ const char* getCmdName(IrCmd cmd)
return "FORGLOOP_FALLBACK"; return "FORGLOOP_FALLBACK";
case IrCmd::FORGPREP_XNEXT_FALLBACK: case IrCmd::FORGPREP_XNEXT_FALLBACK:
return "FORGPREP_XNEXT_FALLBACK"; return "FORGPREP_XNEXT_FALLBACK";
case IrCmd::AND:
return "AND";
case IrCmd::ANDK:
return "ANDK";
case IrCmd::OR:
return "OR";
case IrCmd::ORK:
return "ORK";
case IrCmd::COVERAGE: case IrCmd::COVERAGE:
return "COVERAGE"; return "COVERAGE";
case IrCmd::FALLBACK_GETGLOBAL: case IrCmd::FALLBACK_GETGLOBAL:
@ -345,13 +339,13 @@ void toString(IrToStringContext& ctx, IrOp op)
append(ctx.result, "%s_%u", getBlockKindName(ctx.blocks[op.index].kind), op.index); append(ctx.result, "%s_%u", getBlockKindName(ctx.blocks[op.index].kind), op.index);
break; break;
case IrOpKind::VmReg: case IrOpKind::VmReg:
append(ctx.result, "R%u", op.index); append(ctx.result, "R%d", vmRegOp(op));
break; break;
case IrOpKind::VmConst: case IrOpKind::VmConst:
append(ctx.result, "K%u", op.index); append(ctx.result, "K%d", vmConstOp(op));
break; break;
case IrOpKind::VmUpvalue: case IrOpKind::VmUpvalue:
append(ctx.result, "U%u", op.index); append(ctx.result, "U%d", vmUpvalueOp(op));
break; break;
} }
} }

View File

@ -12,6 +12,7 @@
#include "NativeState.h" #include "NativeState.h"
#include "lstate.h" #include "lstate.h"
#include "lgc.h"
// TODO: Eventually this can go away // TODO: Eventually this can go away
// #define TRACE // #define TRACE
@ -32,7 +33,7 @@ struct LoweringStatsA64
~LoweringStatsA64() ~LoweringStatsA64()
{ {
if (total) if (total)
printf("A64 lowering succeded for %.1f%% functions (%d/%d)\n", double(can) / double(total) * 100, int(can), int(total)); printf("A64 lowering succeeded for %.1f%% functions (%d/%d)\n", double(can) / double(total) * 100, int(can), int(total));
} }
} gStatsA64; } gStatsA64;
#endif #endif
@ -77,6 +78,34 @@ inline ConditionA64 getConditionFP(IrCondition cond)
} }
} }
// TODO: instead of temp1/temp2 we can take a register that we will use for ra->value; that way callers to this function will be able to use it when
// calling luaC_barrier*
static void checkObjectBarrierConditions(AssemblyBuilderA64& build, RegisterA64 object, RegisterA64 temp1, RegisterA64 temp2, int ra, Label& skip)
{
RegisterA64 temp1w = castReg(KindA64::w, temp1);
RegisterA64 temp2w = castReg(KindA64::w, temp2);
// iscollectable(ra)
build.ldr(temp1w, mem(rBase, ra * sizeof(TValue) + offsetof(TValue, tt)));
build.cmp(temp1w, LUA_TSTRING);
build.b(ConditionA64::Less, skip);
// isblack(obj2gco(o))
// TODO: conditional bit test with BLACKBIT
build.ldrb(temp1w, mem(object, offsetof(GCheader, marked)));
build.mov(temp2w, bitmask(BLACKBIT));
build.and_(temp1w, temp1w, temp2w);
build.cbz(temp1w, skip);
// iswhite(gcvalue(ra))
// TODO: tst with bitmask(WHITE0BIT, WHITE1BIT)
build.ldr(temp1, mem(rBase, ra * sizeof(TValue) + offsetof(TValue, value)));
build.ldrb(temp1w, mem(temp1, offsetof(GCheader, marked)));
build.mov(temp2w, bit2mask(WHITE0BIT, WHITE1BIT));
build.and_(temp1w, temp1w, temp2w);
build.cbz(temp1w, skip);
}
IrLoweringA64::IrLoweringA64(AssemblyBuilderA64& build, ModuleHelpers& helpers, NativeState& data, Proto* proto, IrFunction& function) IrLoweringA64::IrLoweringA64(AssemblyBuilderA64& build, ModuleHelpers& helpers, NativeState& data, Proto* proto, IrFunction& function)
: build(build) : build(build)
, helpers(helpers) , helpers(helpers)
@ -108,37 +137,89 @@ bool IrLoweringA64::canLower(const IrFunction& function)
case IrCmd::LOAD_TVALUE: case IrCmd::LOAD_TVALUE:
case IrCmd::LOAD_NODE_VALUE_TV: case IrCmd::LOAD_NODE_VALUE_TV:
case IrCmd::LOAD_ENV: case IrCmd::LOAD_ENV:
case IrCmd::GET_ARR_ADDR:
case IrCmd::GET_SLOT_NODE_ADDR:
case IrCmd::GET_HASH_NODE_ADDR:
case IrCmd::STORE_TAG: case IrCmd::STORE_TAG:
case IrCmd::STORE_POINTER: case IrCmd::STORE_POINTER:
case IrCmd::STORE_DOUBLE: case IrCmd::STORE_DOUBLE:
case IrCmd::STORE_INT: case IrCmd::STORE_INT:
case IrCmd::STORE_TVALUE: case IrCmd::STORE_TVALUE:
case IrCmd::STORE_NODE_VALUE_TV: case IrCmd::STORE_NODE_VALUE_TV:
case IrCmd::ADD_INT:
case IrCmd::SUB_INT:
case IrCmd::ADD_NUM: case IrCmd::ADD_NUM:
case IrCmd::SUB_NUM: case IrCmd::SUB_NUM:
case IrCmd::MUL_NUM: case IrCmd::MUL_NUM:
case IrCmd::DIV_NUM: case IrCmd::DIV_NUM:
case IrCmd::MOD_NUM: case IrCmd::MOD_NUM:
case IrCmd::POW_NUM:
case IrCmd::MIN_NUM:
case IrCmd::MAX_NUM:
case IrCmd::UNM_NUM: case IrCmd::UNM_NUM:
case IrCmd::FLOOR_NUM:
case IrCmd::CEIL_NUM:
case IrCmd::ROUND_NUM:
case IrCmd::SQRT_NUM:
case IrCmd::ABS_NUM:
case IrCmd::JUMP: case IrCmd::JUMP:
case IrCmd::JUMP_IF_TRUTHY:
case IrCmd::JUMP_IF_FALSY:
case IrCmd::JUMP_EQ_TAG: case IrCmd::JUMP_EQ_TAG:
case IrCmd::JUMP_EQ_INT:
case IrCmd::JUMP_EQ_POINTER:
case IrCmd::JUMP_CMP_NUM: case IrCmd::JUMP_CMP_NUM:
case IrCmd::JUMP_CMP_ANY: case IrCmd::JUMP_CMP_ANY:
case IrCmd::TABLE_LEN:
case IrCmd::NEW_TABLE:
case IrCmd::DUP_TABLE:
case IrCmd::TRY_NUM_TO_INDEX:
case IrCmd::INT_TO_NUM:
case IrCmd::ADJUST_STACK_TO_REG:
case IrCmd::ADJUST_STACK_TO_TOP:
case IrCmd::INVOKE_FASTCALL:
case IrCmd::CHECK_FASTCALL_RES:
case IrCmd::DO_ARITH: case IrCmd::DO_ARITH:
case IrCmd::DO_LEN:
case IrCmd::GET_TABLE:
case IrCmd::SET_TABLE:
case IrCmd::GET_IMPORT: case IrCmd::GET_IMPORT:
case IrCmd::CONCAT:
case IrCmd::GET_UPVALUE: case IrCmd::GET_UPVALUE:
case IrCmd::SET_UPVALUE:
case IrCmd::PREPARE_FORN:
case IrCmd::CHECK_TAG: case IrCmd::CHECK_TAG:
case IrCmd::CHECK_READONLY: case IrCmd::CHECK_READONLY:
case IrCmd::CHECK_NO_METATABLE: case IrCmd::CHECK_NO_METATABLE:
case IrCmd::CHECK_SAFE_ENV: case IrCmd::CHECK_SAFE_ENV:
case IrCmd::CHECK_ARRAY_SIZE:
case IrCmd::CHECK_SLOT_MATCH:
case IrCmd::INTERRUPT: case IrCmd::INTERRUPT:
case IrCmd::CHECK_GC:
case IrCmd::BARRIER_OBJ:
case IrCmd::BARRIER_TABLE_BACK:
case IrCmd::BARRIER_TABLE_FORWARD:
case IrCmd::SET_SAVEDPC: case IrCmd::SET_SAVEDPC:
case IrCmd::CLOSE_UPVALS:
case IrCmd::CAPTURE:
case IrCmd::CALL: case IrCmd::CALL:
case IrCmd::RETURN: case IrCmd::RETURN:
case IrCmd::FALLBACK_GETGLOBAL:
case IrCmd::FALLBACK_SETGLOBAL:
case IrCmd::FALLBACK_GETTABLEKS:
case IrCmd::FALLBACK_SETTABLEKS:
case IrCmd::FALLBACK_NAMECALL:
case IrCmd::FALLBACK_PREPVARARGS:
case IrCmd::FALLBACK_GETVARARGS:
case IrCmd::FALLBACK_NEWCLOSURE:
case IrCmd::FALLBACK_DUPCLOSURE:
case IrCmd::SUBSTITUTE: case IrCmd::SUBSTITUTE:
continue; continue;
default: default:
#ifdef TRACE
printf("A64 lowering missing %s\n", getCmdName(inst.cmd));
#endif
return false; return false;
} }
} }
@ -199,6 +280,64 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
inst.regA64 = regs.allocReg(KindA64::x); inst.regA64 = regs.allocReg(KindA64::x);
build.ldr(inst.regA64, mem(rClosure, offsetof(Closure, env))); build.ldr(inst.regA64, mem(rClosure, offsetof(Closure, env)));
break; break;
case IrCmd::GET_ARR_ADDR:
{
inst.regA64 = regs.allocReg(KindA64::x);
build.ldr(inst.regA64, mem(regOp(inst.a), offsetof(Table, array)));
if (inst.b.kind == IrOpKind::Inst)
{
// TODO: This is a temporary hack that reads wN register as if it was xN. This should use unsigned extension shift once we support it.
build.add(inst.regA64, inst.regA64, castReg(KindA64::x, regOp(inst.b)), kTValueSizeLog2);
}
else if (inst.b.kind == IrOpKind::Constant)
{
LUAU_ASSERT(size_t(intOp(inst.b)) <= AssemblyBuilderA64::kMaxImmediate >> kTValueSizeLog2); // TODO: handle out of range values
build.add(inst.regA64, inst.regA64, uint16_t(intOp(inst.b) << kTValueSizeLog2));
}
else
LUAU_ASSERT(!"Unsupported instruction form");
break;
}
case IrCmd::GET_SLOT_NODE_ADDR:
{
inst.regA64 = regs.allocReuse(KindA64::x, index, {inst.a});
RegisterA64 temp1 = regs.allocTemp(KindA64::x);
RegisterA64 temp1w = castReg(KindA64::w, temp1);
RegisterA64 temp2 = regs.allocTemp(KindA64::w);
// TODO: this can use a slightly more efficient sequence with a 4b load + and-with-right-shift for pcpos<1024 but we don't support it yet.
build.mov(temp1, uintOp(inst.b) * sizeof(Instruction) + kOffsetOfInstructionC);
build.ldrb(temp1w, mem(rCode, temp1));
build.ldrb(temp2, mem(regOp(inst.a), offsetof(Table, nodemask8)));
build.and_(temp2, temp2, temp1w);
// note: this may clobber inst.a, so it's important that we don't use it after this
build.ldr(inst.regA64, mem(regOp(inst.a), offsetof(Table, node)));
// TODO: This is a temporary hack that reads wN register as if it was xN. This should use unsigned extension shift once we support it.
build.add(inst.regA64, inst.regA64, castReg(KindA64::x, temp2), kLuaNodeSizeLog2);
break;
}
case IrCmd::GET_HASH_NODE_ADDR:
{
inst.regA64 = regs.allocReuse(KindA64::x, index, {inst.a});
RegisterA64 temp1 = regs.allocTemp(KindA64::w);
RegisterA64 temp2 = regs.allocTemp(KindA64::w);
// TODO: this can use bic (andnot) to do hash & ~(-1 << lsizenode) instead but we don't support it yet
build.mov(temp1, 1);
build.ldrb(temp2, mem(regOp(inst.a), offsetof(Table, lsizenode)));
build.lsl(temp1, temp1, temp2);
build.sub(temp1, temp1, 1);
build.mov(temp2, uintOp(inst.b));
build.and_(temp2, temp2, temp1);
// note: this may clobber inst.a, so it's important that we don't use it after this
build.ldr(inst.regA64, mem(regOp(inst.a), offsetof(Table, node)));
// TODO: This is a temporary hack that reads wN register as if it was xN. This should use unsigned extension shift once we support it.
build.add(inst.regA64, inst.regA64, castReg(KindA64::x, temp2), kLuaNodeSizeLog2);
break;
}
case IrCmd::STORE_TAG: case IrCmd::STORE_TAG:
{ {
RegisterA64 temp = regs.allocTemp(KindA64::w); RegisterA64 temp = regs.allocTemp(KindA64::w);
@ -236,6 +375,16 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
case IrCmd::STORE_NODE_VALUE_TV: case IrCmd::STORE_NODE_VALUE_TV:
build.str(regOp(inst.b), mem(regOp(inst.a), offsetof(LuaNode, val))); build.str(regOp(inst.b), mem(regOp(inst.a), offsetof(LuaNode, val)));
break; break;
case IrCmd::ADD_INT:
LUAU_ASSERT(unsigned(intOp(inst.b)) <= AssemblyBuilderA64::kMaxImmediate); // TODO: handle out of range values
inst.regA64 = regs.allocReuse(KindA64::w, index, {inst.a});
build.add(inst.regA64, regOp(inst.a), uint16_t(intOp(inst.b)));
break;
case IrCmd::SUB_INT:
LUAU_ASSERT(unsigned(intOp(inst.b)) <= AssemblyBuilderA64::kMaxImmediate); // TODO: handle out of range values
inst.regA64 = regs.allocReuse(KindA64::w, index, {inst.a});
build.sub(inst.regA64, regOp(inst.a), uint16_t(intOp(inst.b)));
break;
case IrCmd::ADD_NUM: case IrCmd::ADD_NUM:
{ {
inst.regA64 = regs.allocReuse(KindA64::d, index, {inst.a, inst.b}); inst.regA64 = regs.allocReuse(KindA64::d, index, {inst.a, inst.b});
@ -270,7 +419,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
} }
case IrCmd::MOD_NUM: case IrCmd::MOD_NUM:
{ {
inst.regA64 = regs.allocReg(KindA64::d); inst.regA64 = regs.allocReg(KindA64::d); // can't allocReuse because both A and B are used twice
RegisterA64 temp1 = tempDouble(inst.a); RegisterA64 temp1 = tempDouble(inst.a);
RegisterA64 temp2 = tempDouble(inst.b); RegisterA64 temp2 = tempDouble(inst.b);
build.fdiv(inst.regA64, temp1, temp2); build.fdiv(inst.regA64, temp1, temp2);
@ -279,6 +428,37 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
build.fsub(inst.regA64, temp1, inst.regA64); build.fsub(inst.regA64, temp1, inst.regA64);
break; break;
} }
case IrCmd::POW_NUM:
{
// TODO: this instruction clobbers all registers because of a call but it's unclear how to assert that cleanly atm
inst.regA64 = regs.allocReg(KindA64::d);
RegisterA64 temp1 = tempDouble(inst.a);
RegisterA64 temp2 = tempDouble(inst.b);
build.fmov(d0, temp1); // TODO: aliasing hazard
build.fmov(d1, temp2); // TODO: aliasing hazard
build.ldr(x0, mem(rNativeContext, offsetof(NativeContext, libm_pow)));
build.blr(x0);
build.fmov(inst.regA64, d0);
break;
}
case IrCmd::MIN_NUM:
{
inst.regA64 = regs.allocReuse(KindA64::d, index, {inst.a, inst.b});
RegisterA64 temp1 = tempDouble(inst.a);
RegisterA64 temp2 = tempDouble(inst.b);
build.fcmp(temp1, temp2);
build.fcsel(inst.regA64, temp1, temp2, getConditionFP(IrCondition::Less));
break;
}
case IrCmd::MAX_NUM:
{
inst.regA64 = regs.allocReuse(KindA64::d, index, {inst.a, inst.b});
RegisterA64 temp1 = tempDouble(inst.a);
RegisterA64 temp2 = tempDouble(inst.b);
build.fcmp(temp1, temp2);
build.fcsel(inst.regA64, temp1, temp2, getConditionFP(IrCondition::Greater));
break;
}
case IrCmd::UNM_NUM: case IrCmd::UNM_NUM:
{ {
inst.regA64 = regs.allocReuse(KindA64::d, index, {inst.a}); inst.regA64 = regs.allocReuse(KindA64::d, index, {inst.a});
@ -286,9 +466,76 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
build.fneg(inst.regA64, temp); build.fneg(inst.regA64, temp);
break; break;
} }
case IrCmd::FLOOR_NUM:
{
inst.regA64 = regs.allocReuse(KindA64::d, index, {inst.a});
RegisterA64 temp = tempDouble(inst.a);
build.frintm(inst.regA64, temp);
break;
}
case IrCmd::CEIL_NUM:
{
inst.regA64 = regs.allocReuse(KindA64::d, index, {inst.a});
RegisterA64 temp = tempDouble(inst.a);
build.frintp(inst.regA64, temp);
break;
}
case IrCmd::ROUND_NUM:
{
inst.regA64 = regs.allocReuse(KindA64::d, index, {inst.a});
RegisterA64 temp = tempDouble(inst.a);
build.frinta(inst.regA64, temp);
break;
}
case IrCmd::SQRT_NUM:
{
inst.regA64 = regs.allocReuse(KindA64::d, index, {inst.a});
RegisterA64 temp = tempDouble(inst.a);
build.fsqrt(inst.regA64, temp);
break;
}
case IrCmd::ABS_NUM:
{
inst.regA64 = regs.allocReuse(KindA64::d, index, {inst.a});
RegisterA64 temp = tempDouble(inst.a);
build.fabs(inst.regA64, temp);
break;
}
case IrCmd::JUMP: case IrCmd::JUMP:
jumpOrFallthrough(blockOp(inst.a), next); jumpOrFallthrough(blockOp(inst.a), next);
break; break;
case IrCmd::JUMP_IF_TRUTHY:
{
RegisterA64 temp = regs.allocTemp(KindA64::w);
build.ldr(temp, mem(rBase, vmRegOp(inst.a) * sizeof(TValue) + offsetof(TValue, tt)));
// nil => falsy
LUAU_ASSERT(LUA_TNIL == 0);
build.cbz(temp, labelOp(inst.c));
// not boolean => truthy
build.cmp(temp, LUA_TBOOLEAN);
build.b(ConditionA64::NotEqual, labelOp(inst.b));
// compare boolean value
build.ldr(temp, mem(rBase, vmRegOp(inst.a) * sizeof(TValue) + offsetof(TValue, value)));
build.cbnz(temp, labelOp(inst.b));
jumpOrFallthrough(blockOp(inst.c), next);
break;
}
case IrCmd::JUMP_IF_FALSY:
{
RegisterA64 temp = regs.allocTemp(KindA64::w);
build.ldr(temp, mem(rBase, vmRegOp(inst.a) * sizeof(TValue) + offsetof(TValue, tt)));
// nil => falsy
LUAU_ASSERT(LUA_TNIL == 0);
build.cbz(temp, labelOp(inst.b));
// not boolean => truthy
build.cmp(temp, LUA_TBOOLEAN);
build.b(ConditionA64::NotEqual, labelOp(inst.c));
// compare boolean value
build.ldr(temp, mem(rBase, vmRegOp(inst.a) * sizeof(TValue) + offsetof(TValue, value)));
build.cbz(temp, labelOp(inst.b));
jumpOrFallthrough(blockOp(inst.c), next);
break;
}
case IrCmd::JUMP_EQ_TAG: case IrCmd::JUMP_EQ_TAG:
if (inst.b.kind == IrOpKind::Constant) if (inst.b.kind == IrOpKind::Constant)
build.cmp(regOp(inst.a), tagOp(inst.b)); build.cmp(regOp(inst.a), tagOp(inst.b));
@ -308,6 +555,17 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
jumpOrFallthrough(blockOp(inst.c), next); jumpOrFallthrough(blockOp(inst.c), next);
} }
break; break;
case IrCmd::JUMP_EQ_INT:
LUAU_ASSERT(unsigned(intOp(inst.b)) <= AssemblyBuilderA64::kMaxImmediate);
build.cmp(regOp(inst.a), uint16_t(intOp(inst.b)));
build.b(ConditionA64::Equal, labelOp(inst.c));
jumpOrFallthrough(blockOp(inst.d), next);
break;
case IrCmd::JUMP_EQ_POINTER:
build.cmp(regOp(inst.a), regOp(inst.b));
build.b(ConditionA64::Equal, labelOp(inst.c));
jumpOrFallthrough(blockOp(inst.d), next);
break;
case IrCmd::JUMP_CMP_NUM: case IrCmd::JUMP_CMP_NUM:
{ {
IrCondition cond = conditionOp(inst.c); IrCondition cond = conditionOp(inst.c);
@ -349,6 +607,150 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
jumpOrFallthrough(blockOp(inst.e), next); jumpOrFallthrough(blockOp(inst.e), next);
break; break;
} }
case IrCmd::TABLE_LEN:
{
regs.assertAllFreeExcept(regOp(inst.a));
build.mov(x0, regOp(inst.a)); // TODO: minor aliasing hazard
build.ldr(x1, mem(rNativeContext, offsetof(NativeContext, luaH_getn)));
build.blr(x1);
inst.regA64 = regs.allocReg(KindA64::d);
build.scvtf(inst.regA64, x0);
break;
}
case IrCmd::NEW_TABLE:
{
regs.assertAllFree();
build.mov(x0, rState);
build.mov(x1, uintOp(inst.a));
build.mov(x2, uintOp(inst.b));
build.ldr(x3, mem(rNativeContext, offsetof(NativeContext, luaH_new)));
build.blr(x3);
// TODO: we could takeReg x0 but it's unclear if we will be able to keep x0 allocatable due to aliasing concerns
inst.regA64 = regs.allocReg(KindA64::x);
build.mov(inst.regA64, x0);
break;
}
case IrCmd::DUP_TABLE:
{
regs.assertAllFreeExcept(regOp(inst.a));
build.mov(x0, rState);
build.mov(x1, regOp(inst.a)); // TODO: aliasing hazard
build.ldr(x2, mem(rNativeContext, offsetof(NativeContext, luaH_clone)));
build.blr(x2);
// TODO: we could takeReg x0 but it's unclear if we will be able to keep x0 allocatable due to aliasing concerns
inst.regA64 = regs.allocReg(KindA64::x);
build.mov(inst.regA64, x0);
break;
}
case IrCmd::TRY_NUM_TO_INDEX:
{
inst.regA64 = regs.allocReg(KindA64::w);
RegisterA64 temp1 = tempDouble(inst.a);
if (build.features & Feature_JSCVT)
{
build.fjcvtzs(inst.regA64, temp1); // fjcvtzs sets PSTATE.Z (equal) iff conversion is exact
build.b(ConditionA64::NotEqual, labelOp(inst.b));
}
else
{
RegisterA64 temp2 = regs.allocTemp(KindA64::d);
build.fcvtzs(inst.regA64, temp1);
build.scvtf(temp2, inst.regA64);
build.fcmp(temp1, temp2);
build.b(ConditionA64::NotEqual, labelOp(inst.b));
}
break;
}
case IrCmd::INT_TO_NUM:
{
inst.regA64 = regs.allocReg(KindA64::d);
RegisterA64 temp = tempInt(inst.a);
build.scvtf(inst.regA64, temp);
break;
}
case IrCmd::ADJUST_STACK_TO_REG:
{
RegisterA64 temp = regs.allocTemp(KindA64::x);
if (inst.b.kind == IrOpKind::Constant)
{
build.add(temp, rBase, uint16_t((vmRegOp(inst.a) + intOp(inst.b)) * sizeof(TValue)));
build.str(temp, mem(rState, offsetof(lua_State, top)));
}
else if (inst.b.kind == IrOpKind::Inst)
{
build.add(temp, rBase, uint16_t(vmRegOp(inst.a) * sizeof(TValue)));
// TODO: This is a temporary hack that reads wN register as if it was xN. This should use unsigned extension shift once we support it.
build.add(temp, temp, castReg(KindA64::x, regOp(inst.b)), kTValueSizeLog2);
build.str(temp, mem(rState, offsetof(lua_State, top)));
}
else
LUAU_ASSERT(!"Unsupported instruction form");
break;
}
case IrCmd::ADJUST_STACK_TO_TOP:
{
RegisterA64 temp = regs.allocTemp(KindA64::x);
build.ldr(temp, mem(rState, offsetof(lua_State, ci)));
build.ldr(temp, mem(temp, offsetof(CallInfo, top)));
build.str(temp, mem(rState, offsetof(lua_State, top)));
break;
}
case IrCmd::INVOKE_FASTCALL:
{
regs.assertAllFree();
build.mov(x0, rState);
build.add(x1, rBase, uint16_t(vmRegOp(inst.b) * sizeof(TValue)));
build.add(x2, rBase, uint16_t(vmRegOp(inst.c) * sizeof(TValue)));
build.mov(w3, intOp(inst.f)); // nresults
if (inst.d.kind == IrOpKind::VmReg)
build.add(x4, rBase, uint16_t(vmRegOp(inst.d) * sizeof(TValue)));
else if (inst.d.kind == IrOpKind::VmConst)
{
// TODO: refactor into a common helper
if (vmConstOp(inst.d) * sizeof(TValue) <= AssemblyBuilderA64::kMaxImmediate)
{
build.add(x4, rConstants, uint16_t(vmConstOp(inst.d) * sizeof(TValue)));
}
else
{
build.mov(x4, vmConstOp(inst.d) * sizeof(TValue));
build.add(x4, rConstants, x4);
}
}
else
LUAU_ASSERT(boolOp(inst.d) == false);
// nparams
if (intOp(inst.e) == LUA_MULTRET)
{
// L->top - (ra + 1)
build.ldr(x5, mem(rState, offsetof(lua_State, top)));
build.sub(x5, x5, rBase);
build.sub(x5, x5, uint16_t((vmRegOp(inst.b) + 1) * sizeof(TValue)));
// TODO: this can use immediate shift right or maybe add/sub with shift right but we don't implement them yet
build.mov(x6, kTValueSizeLog2);
build.lsr(x5, x5, x6);
}
else
build.mov(w5, intOp(inst.e));
build.ldr(x6, mem(rNativeContext, offsetof(NativeContext, luauF_table) + uintOp(inst.a) * sizeof(luau_FastFunction)));
build.blr(x6);
// TODO: we could takeReg w0 but it's unclear if we will be able to keep x0 allocatable due to aliasing concerns
inst.regA64 = regs.allocReg(KindA64::w);
build.mov(inst.regA64, w0);
break;
}
case IrCmd::CHECK_FASTCALL_RES:
build.cmp(regOp(inst.a), 0);
build.b(ConditionA64::Less, labelOp(inst.b));
break;
case IrCmd::DO_ARITH: case IrCmd::DO_ARITH:
regs.assertAllFree(); regs.assertAllFree();
build.mov(x0, rState); build.mov(x0, rState);
@ -375,12 +777,76 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
build.ldr(x5, mem(rNativeContext, offsetof(NativeContext, luaV_doarith))); build.ldr(x5, mem(rNativeContext, offsetof(NativeContext, luaV_doarith)));
build.blr(x5); build.blr(x5);
emitUpdateBase(build);
break;
case IrCmd::DO_LEN:
regs.assertAllFree();
build.mov(x0, rState);
build.add(x1, rBase, uint16_t(vmRegOp(inst.a) * sizeof(TValue)));
build.add(x2, rBase, uint16_t(vmRegOp(inst.b) * sizeof(TValue)));
build.ldr(x3, mem(rNativeContext, offsetof(NativeContext, luaV_dolen)));
build.blr(x3);
emitUpdateBase(build);
break;
case IrCmd::GET_TABLE:
regs.assertAllFree();
build.mov(x0, rState);
build.add(x1, rBase, uint16_t(vmRegOp(inst.b) * sizeof(TValue)));
if (inst.c.kind == IrOpKind::VmReg)
build.add(x2, rBase, uint16_t(vmRegOp(inst.c) * sizeof(TValue)));
else if (inst.c.kind == IrOpKind::Constant)
{
TValue n;
setnvalue(&n, uintOp(inst.c));
build.adr(x2, &n, sizeof(n));
}
else
LUAU_ASSERT(!"Unsupported instruction form");
build.add(x3, rBase, uint16_t(vmRegOp(inst.a) * sizeof(TValue)));
build.ldr(x4, mem(rNativeContext, offsetof(NativeContext, luaV_gettable)));
build.blr(x4);
emitUpdateBase(build);
break;
case IrCmd::SET_TABLE:
regs.assertAllFree();
build.mov(x0, rState);
build.add(x1, rBase, uint16_t(vmRegOp(inst.b) * sizeof(TValue)));
if (inst.c.kind == IrOpKind::VmReg)
build.add(x2, rBase, uint16_t(vmRegOp(inst.c) * sizeof(TValue)));
else if (inst.c.kind == IrOpKind::Constant)
{
TValue n;
setnvalue(&n, uintOp(inst.c));
build.adr(x2, &n, sizeof(n));
}
else
LUAU_ASSERT(!"Unsupported instruction form");
build.add(x3, rBase, uint16_t(vmRegOp(inst.a) * sizeof(TValue)));
build.ldr(x4, mem(rNativeContext, offsetof(NativeContext, luaV_settable)));
build.blr(x4);
emitUpdateBase(build); emitUpdateBase(build);
break; break;
case IrCmd::GET_IMPORT: case IrCmd::GET_IMPORT:
regs.assertAllFree(); regs.assertAllFree();
emitInstGetImport(build, vmRegOp(inst.a), uintOp(inst.b)); emitInstGetImport(build, vmRegOp(inst.a), uintOp(inst.b));
break; break;
case IrCmd::CONCAT:
regs.assertAllFree();
build.mov(x0, rState);
build.mov(x1, uintOp(inst.b));
build.mov(x2, vmRegOp(inst.a) + uintOp(inst.b) - 1);
build.ldr(x3, mem(rNativeContext, offsetof(NativeContext, luaV_concat)));
build.blr(x3);
emitUpdateBase(build);
break;
case IrCmd::GET_UPVALUE: case IrCmd::GET_UPVALUE:
{ {
RegisterA64 temp1 = regs.allocTemp(KindA64::x); RegisterA64 temp1 = regs.allocTemp(KindA64::x);
@ -405,6 +871,44 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
build.str(temp2, mem(rBase, vmRegOp(inst.a) * sizeof(TValue))); build.str(temp2, mem(rBase, vmRegOp(inst.a) * sizeof(TValue)));
break; break;
} }
case IrCmd::SET_UPVALUE:
{
regs.assertAllFree();
RegisterA64 temp1 = regs.allocTemp(KindA64::x);
RegisterA64 temp2 = regs.allocTemp(KindA64::x);
RegisterA64 temp3 = regs.allocTemp(KindA64::q);
RegisterA64 temp4 = regs.allocTemp(KindA64::x);
// UpVal*
build.ldr(temp1, mem(rClosure, offsetof(Closure, l.uprefs) + sizeof(TValue) * vmUpvalueOp(inst.a) + offsetof(TValue, value.gc)));
build.ldr(temp2, mem(temp1, offsetof(UpVal, v)));
build.ldr(temp3, mem(rBase, vmRegOp(inst.b) * sizeof(TValue)));
build.str(temp3, temp2);
Label skip;
checkObjectBarrierConditions(build, temp1, temp2, temp4, vmRegOp(inst.b), skip);
build.mov(x0, rState);
build.mov(x1, temp1); // TODO: aliasing hazard
build.ldr(x2, mem(rBase, vmRegOp(inst.b) * sizeof(TValue) + offsetof(TValue, value)));
build.ldr(x3, mem(rNativeContext, offsetof(NativeContext, luaC_barrierf)));
build.blr(x3);
// note: no emitUpdateBase necessary because luaC_ barriers do not reallocate stack
build.setLabel(skip);
break;
}
case IrCmd::PREPARE_FORN:
regs.assertAllFree();
build.mov(x0, rState);
build.add(x1, rBase, uint16_t(vmRegOp(inst.a) * sizeof(TValue)));
build.add(x2, rBase, uint16_t(vmRegOp(inst.b) * sizeof(TValue)));
build.add(x3, rBase, uint16_t(vmRegOp(inst.c) * sizeof(TValue)));
build.ldr(x4, mem(rNativeContext, offsetof(NativeContext, luaV_prepareFORN)));
build.blr(x4);
// note: no emitUpdateBase necessary because prepareFORN does not reallocate stack
break;
case IrCmd::CHECK_TAG: case IrCmd::CHECK_TAG:
build.cmp(regOp(inst.a), tagOp(inst.b)); build.cmp(regOp(inst.a), tagOp(inst.b));
build.b(ConditionA64::NotEqual, labelOp(inst.c)); build.b(ConditionA64::NotEqual, labelOp(inst.c));
@ -426,12 +930,55 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
case IrCmd::CHECK_SAFE_ENV: case IrCmd::CHECK_SAFE_ENV:
{ {
RegisterA64 temp = regs.allocTemp(KindA64::x); RegisterA64 temp = regs.allocTemp(KindA64::x);
RegisterA64 tempw{KindA64::w, temp.index}; RegisterA64 tempw = castReg(KindA64::w, temp);
build.ldr(temp, mem(rClosure, offsetof(Closure, env))); build.ldr(temp, mem(rClosure, offsetof(Closure, env)));
build.ldrb(tempw, mem(temp, offsetof(Table, safeenv))); build.ldrb(tempw, mem(temp, offsetof(Table, safeenv)));
build.cbz(tempw, labelOp(inst.a)); build.cbz(tempw, labelOp(inst.a));
break; break;
} }
case IrCmd::CHECK_ARRAY_SIZE:
{
RegisterA64 temp = regs.allocTemp(KindA64::w);
build.ldr(temp, mem(regOp(inst.a), offsetof(Table, sizearray)));
if (inst.b.kind == IrOpKind::Inst)
build.cmp(temp, regOp(inst.b));
else if (inst.b.kind == IrOpKind::Constant)
{
LUAU_ASSERT(size_t(intOp(inst.b)) <= AssemblyBuilderA64::kMaxImmediate); // TODO: handle out of range values
build.cmp(temp, uint16_t(intOp(inst.b)));
}
else
LUAU_ASSERT(!"Unsupported instruction form");
build.b(ConditionA64::UnsignedLessEqual, labelOp(inst.c));
break;
}
case IrCmd::CHECK_SLOT_MATCH:
{
RegisterA64 temp1 = regs.allocTemp(KindA64::x);
RegisterA64 temp1w = castReg(KindA64::w, temp1);
RegisterA64 temp2 = regs.allocTemp(KindA64::x);
RegisterA64 temp2w = castReg(KindA64::w, temp2);
build.ldr(temp1w, mem(regOp(inst.a), kOffsetOfLuaNodeTag));
// TODO: this needs bitfield extraction, or and-immediate
build.mov(temp2w, kLuaNodeTagMask);
build.and_(temp1w, temp1w, temp2w);
build.cmp(temp1w, LUA_TSTRING);
build.b(ConditionA64::NotEqual, labelOp(inst.c));
AddressA64 addr = tempAddr(inst.b, offsetof(TValue, value));
build.ldr(temp1, mem(regOp(inst.a), offsetof(LuaNode, key.value)));
build.ldr(temp2, addr);
build.cmp(temp1, temp2);
build.b(ConditionA64::NotEqual, labelOp(inst.c));
build.ldr(temp1w, mem(regOp(inst.a), offsetof(LuaNode, val.tt)));
LUAU_ASSERT(LUA_TNIL == 0);
build.cbz(temp1w, labelOp(inst.c));
break;
}
case IrCmd::INTERRUPT: case IrCmd::INTERRUPT:
{ {
unsigned int pcpos = uintOp(inst.a); unsigned int pcpos = uintOp(inst.a);
@ -450,6 +997,93 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
build.setLabel(skip); build.setLabel(skip);
break; break;
} }
case IrCmd::CHECK_GC:
{
regs.assertAllFree();
RegisterA64 temp1 = regs.allocTemp(KindA64::x);
RegisterA64 temp2 = regs.allocTemp(KindA64::x);
Label skip;
build.ldr(temp1, mem(rState, offsetof(lua_State, global)));
build.ldr(temp2, mem(temp1, offsetof(global_State, totalbytes)));
build.ldr(temp1, mem(temp1, offsetof(global_State, GCthreshold)));
build.cmp(temp1, temp2);
build.b(ConditionA64::UnsignedGreater, skip);
build.mov(x0, rState);
build.mov(w1, 1);
build.ldr(x1, mem(rNativeContext, offsetof(NativeContext, luaC_step)));
build.blr(x1);
emitUpdateBase(build);
build.setLabel(skip);
break;
}
case IrCmd::BARRIER_OBJ:
{
regs.assertAllFreeExcept(regOp(inst.a));
Label skip;
RegisterA64 temp1 = regs.allocTemp(KindA64::x);
RegisterA64 temp2 = regs.allocTemp(KindA64::x);
checkObjectBarrierConditions(build, regOp(inst.a), temp1, temp2, vmRegOp(inst.b), skip);
build.mov(x0, rState);
build.mov(x1, regOp(inst.a)); // TODO: aliasing hazard
build.ldr(x2, mem(rBase, vmRegOp(inst.b) * sizeof(TValue) + offsetof(TValue, value)));
build.ldr(x3, mem(rNativeContext, offsetof(NativeContext, luaC_barrierf)));
build.blr(x3);
// note: no emitUpdateBase necessary because luaC_ barriers do not reallocate stack
build.setLabel(skip);
break;
}
case IrCmd::BARRIER_TABLE_BACK:
{
regs.assertAllFreeExcept(regOp(inst.a));
Label skip;
RegisterA64 temp1 = regs.allocTemp(KindA64::w);
RegisterA64 temp2 = regs.allocTemp(KindA64::w);
// isblack(obj2gco(t))
build.ldrb(temp1, mem(regOp(inst.a), offsetof(GCheader, marked)));
// TODO: conditional bit test with BLACKBIT
build.mov(temp2, bitmask(BLACKBIT));
build.and_(temp1, temp1, temp2);
build.cbz(temp1, skip);
build.mov(x0, rState);
build.mov(x1, regOp(inst.a)); // TODO: aliasing hazard here and below
build.add(x2, regOp(inst.a), uint16_t(offsetof(Table, gclist)));
build.ldr(x3, mem(rNativeContext, offsetof(NativeContext, luaC_barrierback)));
build.blr(x3);
// note: no emitUpdateBase necessary because luaC_ barriers do not reallocate stack
build.setLabel(skip);
break;
}
case IrCmd::BARRIER_TABLE_FORWARD:
{
regs.assertAllFreeExcept(regOp(inst.a));
Label skip;
RegisterA64 temp1 = regs.allocTemp(KindA64::x);
RegisterA64 temp2 = regs.allocTemp(KindA64::x);
checkObjectBarrierConditions(build, regOp(inst.a), temp1, temp2, vmRegOp(inst.b), skip);
build.mov(x0, rState);
build.mov(x1, regOp(inst.a)); // TODO: aliasing hazard
build.ldr(x2, mem(rBase, vmRegOp(inst.b) * sizeof(TValue) + offsetof(TValue, value)));
build.ldr(x3, mem(rNativeContext, offsetof(NativeContext, luaC_barriertable)));
build.blr(x3);
// note: no emitUpdateBase necessary because luaC_ barriers do not reallocate stack
build.setLabel(skip);
break;
}
case IrCmd::SET_SAVEDPC: case IrCmd::SET_SAVEDPC:
{ {
unsigned int pcpos = uintOp(inst.a); unsigned int pcpos = uintOp(inst.a);
@ -471,6 +1105,34 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
build.str(temp1, mem(temp2, offsetof(CallInfo, savedpc))); build.str(temp1, mem(temp2, offsetof(CallInfo, savedpc)));
break; break;
} }
case IrCmd::CLOSE_UPVALS:
{
regs.assertAllFree();
Label skip;
RegisterA64 temp1 = regs.allocTemp(KindA64::x);
RegisterA64 temp2 = regs.allocTemp(KindA64::x);
// L->openupval != 0
build.ldr(temp1, mem(rState, offsetof(lua_State, openupval)));
build.cbz(temp1, skip);
// ra <= L->openuval->v
build.ldr(temp1, mem(temp1, offsetof(UpVal, v)));
build.add(temp2, rBase, uint16_t(vmRegOp(inst.a) * sizeof(TValue)));
build.cmp(temp2, temp1);
build.b(ConditionA64::UnsignedGreater, skip);
build.mov(x0, rState);
build.mov(x1, temp2); // TODO: aliasing hazard
build.ldr(x2, mem(rNativeContext, offsetof(NativeContext, luaF_close)));
build.blr(x2);
build.setLabel(skip);
break;
}
case IrCmd::CAPTURE:
// no-op
break;
case IrCmd::CALL: case IrCmd::CALL:
regs.assertAllFree(); regs.assertAllFree();
emitInstCall(build, helpers, vmRegOp(inst.a), intOp(inst.b), intOp(inst.c)); emitInstCall(build, helpers, vmRegOp(inst.a), intOp(inst.b), intOp(inst.c));
@ -479,6 +1141,74 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
regs.assertAllFree(); regs.assertAllFree();
emitInstReturn(build, helpers, vmRegOp(inst.a), intOp(inst.b)); emitInstReturn(build, helpers, vmRegOp(inst.a), intOp(inst.b));
break; break;
// Full instruction fallbacks
case IrCmd::FALLBACK_GETGLOBAL:
LUAU_ASSERT(inst.b.kind == IrOpKind::VmReg);
LUAU_ASSERT(inst.c.kind == IrOpKind::VmConst);
regs.assertAllFree();
emitFallback(build, LOP_GETGLOBAL, uintOp(inst.a));
break;
case IrCmd::FALLBACK_SETGLOBAL:
LUAU_ASSERT(inst.b.kind == IrOpKind::VmReg);
LUAU_ASSERT(inst.c.kind == IrOpKind::VmConst);
regs.assertAllFree();
emitFallback(build, LOP_SETGLOBAL, uintOp(inst.a));
break;
case IrCmd::FALLBACK_GETTABLEKS:
LUAU_ASSERT(inst.b.kind == IrOpKind::VmReg);
LUAU_ASSERT(inst.c.kind == IrOpKind::VmReg);
LUAU_ASSERT(inst.d.kind == IrOpKind::VmConst);
regs.assertAllFree();
emitFallback(build, LOP_GETTABLEKS, uintOp(inst.a));
break;
case IrCmd::FALLBACK_SETTABLEKS:
LUAU_ASSERT(inst.b.kind == IrOpKind::VmReg);
LUAU_ASSERT(inst.c.kind == IrOpKind::VmReg);
LUAU_ASSERT(inst.d.kind == IrOpKind::VmConst);
regs.assertAllFree();
emitFallback(build, LOP_SETTABLEKS, uintOp(inst.a));
break;
case IrCmd::FALLBACK_NAMECALL:
LUAU_ASSERT(inst.b.kind == IrOpKind::VmReg);
LUAU_ASSERT(inst.c.kind == IrOpKind::VmReg);
LUAU_ASSERT(inst.d.kind == IrOpKind::VmConst);
regs.assertAllFree();
emitFallback(build, LOP_NAMECALL, uintOp(inst.a));
break;
case IrCmd::FALLBACK_PREPVARARGS:
LUAU_ASSERT(inst.b.kind == IrOpKind::Constant);
regs.assertAllFree();
emitFallback(build, LOP_PREPVARARGS, uintOp(inst.a));
break;
case IrCmd::FALLBACK_GETVARARGS:
LUAU_ASSERT(inst.b.kind == IrOpKind::VmReg);
LUAU_ASSERT(inst.c.kind == IrOpKind::Constant);
regs.assertAllFree();
emitFallback(build, LOP_GETVARARGS, uintOp(inst.a));
break;
case IrCmd::FALLBACK_NEWCLOSURE:
LUAU_ASSERT(inst.b.kind == IrOpKind::VmReg);
LUAU_ASSERT(inst.c.kind == IrOpKind::Constant);
regs.assertAllFree();
emitFallback(build, LOP_NEWCLOSURE, uintOp(inst.a));
break;
case IrCmd::FALLBACK_DUPCLOSURE:
LUAU_ASSERT(inst.b.kind == IrOpKind::VmReg);
LUAU_ASSERT(inst.c.kind == IrOpKind::VmConst);
regs.assertAllFree();
emitFallback(build, LOP_DUPCLOSURE, uintOp(inst.a));
break;
default: default:
LUAU_ASSERT(!"Not supported yet"); LUAU_ASSERT(!"Not supported yet");
break; break;
@ -488,6 +1218,11 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
regs.freeTempRegs(); regs.freeTempRegs();
} }
bool IrLoweringA64::hasError() const
{
return false;
}
bool IrLoweringA64::isFallthroughBlock(IrBlock target, IrBlock next) bool IrLoweringA64::isFallthroughBlock(IrBlock target, IrBlock next)
{ {
return target.start == next.start; return target.start == next.start;

View File

@ -30,6 +30,8 @@ struct IrLoweringA64
void lowerInst(IrInst& inst, uint32_t index, IrBlock& next); void lowerInst(IrInst& inst, uint32_t index, IrBlock& next);
bool hasError() const;
bool isFallthroughBlock(IrBlock target, IrBlock next); bool isFallthroughBlock(IrBlock target, IrBlock next);
void jumpOrFallthrough(IrBlock& target, IrBlock& next); void jumpOrFallthrough(IrBlock& target, IrBlock& next);

View File

@ -27,18 +27,39 @@ IrLoweringX64::IrLoweringX64(AssemblyBuilderX64& build, ModuleHelpers& helpers,
, helpers(helpers) , helpers(helpers)
, data(data) , data(data)
, function(function) , function(function)
, regs(function) , regs(build, function)
{ {
// In order to allocate registers during lowering, we need to know where instruction results are last used // In order to allocate registers during lowering, we need to know where instruction results are last used
updateLastUseLocations(function); updateLastUseLocations(function);
} }
void IrLoweringX64::storeDoubleAsFloat(OperandX64 dst, IrOp src)
{
ScopedRegX64 tmp{regs, SizeX64::xmmword};
if (src.kind == IrOpKind::Constant)
{
build.vmovss(tmp.reg, build.f32(float(doubleOp(src))));
}
else if (src.kind == IrOpKind::Inst)
{
build.vcvtsd2ss(tmp.reg, regOp(src), regOp(src));
}
else
{
LUAU_ASSERT(!"Unsupported instruction form");
}
build.vmovss(dst, tmp.reg);
}
void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
{ {
regs.currInstIdx = index;
switch (inst.cmd) switch (inst.cmd)
{ {
case IrCmd::LOAD_TAG: case IrCmd::LOAD_TAG:
inst.regX64 = regs.allocGprReg(SizeX64::dword); inst.regX64 = regs.allocGprReg(SizeX64::dword, index);
if (inst.a.kind == IrOpKind::VmReg) if (inst.a.kind == IrOpKind::VmReg)
build.mov(inst.regX64, luauRegTag(vmRegOp(inst.a))); build.mov(inst.regX64, luauRegTag(vmRegOp(inst.a)));
@ -52,7 +73,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
LUAU_ASSERT(!"Unsupported instruction form"); LUAU_ASSERT(!"Unsupported instruction form");
break; break;
case IrCmd::LOAD_POINTER: case IrCmd::LOAD_POINTER:
inst.regX64 = regs.allocGprReg(SizeX64::qword); inst.regX64 = regs.allocGprReg(SizeX64::qword, index);
if (inst.a.kind == IrOpKind::VmReg) if (inst.a.kind == IrOpKind::VmReg)
build.mov(inst.regX64, luauRegValue(vmRegOp(inst.a))); build.mov(inst.regX64, luauRegValue(vmRegOp(inst.a)));
@ -66,7 +87,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
LUAU_ASSERT(!"Unsupported instruction form"); LUAU_ASSERT(!"Unsupported instruction form");
break; break;
case IrCmd::LOAD_DOUBLE: case IrCmd::LOAD_DOUBLE:
inst.regX64 = regs.allocXmmReg(); inst.regX64 = regs.allocXmmReg(index);
if (inst.a.kind == IrOpKind::VmReg) if (inst.a.kind == IrOpKind::VmReg)
build.vmovsd(inst.regX64, luauRegValue(vmRegOp(inst.a))); build.vmovsd(inst.regX64, luauRegValue(vmRegOp(inst.a)));
@ -76,12 +97,12 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
LUAU_ASSERT(!"Unsupported instruction form"); LUAU_ASSERT(!"Unsupported instruction form");
break; break;
case IrCmd::LOAD_INT: case IrCmd::LOAD_INT:
inst.regX64 = regs.allocGprReg(SizeX64::dword); inst.regX64 = regs.allocGprReg(SizeX64::dword, index);
build.mov(inst.regX64, luauRegValueInt(vmRegOp(inst.a))); build.mov(inst.regX64, luauRegValueInt(vmRegOp(inst.a)));
break; break;
case IrCmd::LOAD_TVALUE: case IrCmd::LOAD_TVALUE:
inst.regX64 = regs.allocXmmReg(); inst.regX64 = regs.allocXmmReg(index);
if (inst.a.kind == IrOpKind::VmReg) if (inst.a.kind == IrOpKind::VmReg)
build.vmovups(inst.regX64, luauReg(vmRegOp(inst.a))); build.vmovups(inst.regX64, luauReg(vmRegOp(inst.a)));
@ -93,12 +114,12 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
LUAU_ASSERT(!"Unsupported instruction form"); LUAU_ASSERT(!"Unsupported instruction form");
break; break;
case IrCmd::LOAD_NODE_VALUE_TV: case IrCmd::LOAD_NODE_VALUE_TV:
inst.regX64 = regs.allocXmmReg(); inst.regX64 = regs.allocXmmReg(index);
build.vmovups(inst.regX64, luauNodeValue(regOp(inst.a))); build.vmovups(inst.regX64, luauNodeValue(regOp(inst.a)));
break; break;
case IrCmd::LOAD_ENV: case IrCmd::LOAD_ENV:
inst.regX64 = regs.allocGprReg(SizeX64::qword); inst.regX64 = regs.allocGprReg(SizeX64::qword, index);
build.mov(inst.regX64, sClosure); build.mov(inst.regX64, sClosure);
build.mov(inst.regX64, qword[inst.regX64 + offsetof(Closure, env)]); build.mov(inst.regX64, qword[inst.regX64 + offsetof(Closure, env)]);
@ -130,7 +151,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
break; break;
case IrCmd::GET_SLOT_NODE_ADDR: case IrCmd::GET_SLOT_NODE_ADDR:
{ {
inst.regX64 = regs.allocGprReg(SizeX64::qword); inst.regX64 = regs.allocGprReg(SizeX64::qword, index);
ScopedRegX64 tmp{regs, SizeX64::qword}; ScopedRegX64 tmp{regs, SizeX64::qword};
@ -139,10 +160,10 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
} }
case IrCmd::GET_HASH_NODE_ADDR: case IrCmd::GET_HASH_NODE_ADDR:
{ {
inst.regX64 = regs.allocGprReg(SizeX64::qword); inst.regX64 = regs.allocGprReg(SizeX64::qword, index);
// Custom bit shift value can only be placed in cl // Custom bit shift value can only be placed in cl
ScopedRegX64 shiftTmp{regs, regs.takeReg(rcx)}; ScopedRegX64 shiftTmp{regs, regs.takeReg(rcx, kInvalidInstIdx)};
ScopedRegX64 tmp{regs, SizeX64::qword}; ScopedRegX64 tmp{regs, SizeX64::qword};
@ -192,6 +213,13 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
LUAU_ASSERT(!"Unsupported instruction form"); LUAU_ASSERT(!"Unsupported instruction form");
break; break;
} }
case IrCmd::STORE_VECTOR:
{
storeDoubleAsFloat(luauRegValueVector(vmRegOp(inst.a), 0), inst.b);
storeDoubleAsFloat(luauRegValueVector(vmRegOp(inst.a), 1), inst.c);
storeDoubleAsFloat(luauRegValueVector(vmRegOp(inst.a), 2), inst.d);
break;
}
case IrCmd::STORE_TVALUE: case IrCmd::STORE_TVALUE:
if (inst.a.kind == IrOpKind::VmReg) if (inst.a.kind == IrOpKind::VmReg)
build.vmovups(luauReg(vmRegOp(inst.a)), regOp(inst.b)); build.vmovups(luauReg(vmRegOp(inst.a)), regOp(inst.b));
@ -330,7 +358,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
callWrap.addArgument(SizeX64::xmmword, memRegDoubleOp(inst.a), inst.a); callWrap.addArgument(SizeX64::xmmword, memRegDoubleOp(inst.a), inst.a);
callWrap.addArgument(SizeX64::xmmword, memRegDoubleOp(inst.b), inst.b); callWrap.addArgument(SizeX64::xmmword, memRegDoubleOp(inst.b), inst.b);
callWrap.call(qword[rNativeContext + offsetof(NativeContext, libm_pow)]); callWrap.call(qword[rNativeContext + offsetof(NativeContext, libm_pow)]);
inst.regX64 = regs.takeReg(xmm0); inst.regX64 = regs.takeReg(xmm0, index);
break; break;
} }
case IrCmd::MIN_NUM: case IrCmd::MIN_NUM:
@ -398,8 +426,10 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
ScopedRegX64 tmp1{regs, SizeX64::xmmword}; ScopedRegX64 tmp1{regs, SizeX64::xmmword};
ScopedRegX64 tmp2{regs, SizeX64::xmmword}; ScopedRegX64 tmp2{regs, SizeX64::xmmword};
if (inst.a.kind != IrOpKind::Inst || regOp(inst.a) != inst.regX64) if (inst.a.kind != IrOpKind::Inst)
build.vmovsd(inst.regX64, memRegDoubleOp(inst.a)); build.vmovsd(inst.regX64, memRegDoubleOp(inst.a));
else if (regOp(inst.a) != inst.regX64)
build.vmovsd(inst.regX64, inst.regX64, regOp(inst.a));
build.vandpd(tmp1.reg, inst.regX64, build.f64x2(-0.0, -0.0)); build.vandpd(tmp1.reg, inst.regX64, build.f64x2(-0.0, -0.0));
build.vmovsd(tmp2.reg, build.i64(0x3fdfffffffffffff)); // 0.49999999999999994 build.vmovsd(tmp2.reg, build.i64(0x3fdfffffffffffff)); // 0.49999999999999994
@ -416,8 +446,10 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
case IrCmd::ABS_NUM: case IrCmd::ABS_NUM:
inst.regX64 = regs.allocXmmRegOrReuse(index, {inst.a}); inst.regX64 = regs.allocXmmRegOrReuse(index, {inst.a});
if (inst.a.kind != IrOpKind::Inst || regOp(inst.a) != inst.regX64) if (inst.a.kind != IrOpKind::Inst)
build.vmovsd(inst.regX64, memRegDoubleOp(inst.a)); build.vmovsd(inst.regX64, memRegDoubleOp(inst.a));
else if (regOp(inst.a) != inst.regX64)
build.vmovsd(inst.regX64, inst.regX64, regOp(inst.a));
build.vandpd(inst.regX64, inst.regX64, build.i64(~(1LL << 63))); build.vandpd(inst.regX64, inst.regX64, build.i64(~(1LL << 63)));
break; break;
@ -526,7 +558,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
callWrap.addArgument(SizeX64::qword, regOp(inst.a), inst.a); callWrap.addArgument(SizeX64::qword, regOp(inst.a), inst.a);
callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaH_getn)]); callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaH_getn)]);
inst.regX64 = regs.allocXmmReg(); inst.regX64 = regs.allocXmmReg(index);
build.vcvtsi2sd(inst.regX64, inst.regX64, eax); build.vcvtsi2sd(inst.regX64, inst.regX64, eax);
break; break;
} }
@ -537,7 +569,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
callWrap.addArgument(SizeX64::dword, int32_t(uintOp(inst.a)), inst.a); callWrap.addArgument(SizeX64::dword, int32_t(uintOp(inst.a)), inst.a);
callWrap.addArgument(SizeX64::dword, int32_t(uintOp(inst.b)), inst.b); callWrap.addArgument(SizeX64::dword, int32_t(uintOp(inst.b)), inst.b);
callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaH_new)]); callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaH_new)]);
inst.regX64 = regs.takeReg(rax); inst.regX64 = regs.takeReg(rax, index);
break; break;
} }
case IrCmd::DUP_TABLE: case IrCmd::DUP_TABLE:
@ -546,12 +578,12 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
callWrap.addArgument(SizeX64::qword, rState); callWrap.addArgument(SizeX64::qword, rState);
callWrap.addArgument(SizeX64::qword, regOp(inst.a), inst.a); callWrap.addArgument(SizeX64::qword, regOp(inst.a), inst.a);
callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaH_clone)]); callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaH_clone)]);
inst.regX64 = regs.takeReg(rax); inst.regX64 = regs.takeReg(rax, index);
break; break;
} }
case IrCmd::TRY_NUM_TO_INDEX: case IrCmd::TRY_NUM_TO_INDEX:
{ {
inst.regX64 = regs.allocGprReg(SizeX64::dword); inst.regX64 = regs.allocGprReg(SizeX64::dword, index);
ScopedRegX64 tmp{regs, SizeX64::xmmword}; ScopedRegX64 tmp{regs, SizeX64::xmmword};
@ -574,35 +606,39 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
ScopedRegX64 tmp2{regs, SizeX64::qword}; ScopedRegX64 tmp2{regs, SizeX64::qword};
build.mov(tmp2.reg, qword[rState + offsetof(lua_State, global)]); build.mov(tmp2.reg, qword[rState + offsetof(lua_State, global)]);
IrCallWrapperX64 callWrap(regs, build, index); {
callWrap.addArgument(SizeX64::qword, tmp); ScopedSpills spillGuard(regs);
callWrap.addArgument(SizeX64::qword, intOp(inst.b));
callWrap.addArgument(SizeX64::qword, qword[tmp2.release() + offsetof(global_State, tmname) + intOp(inst.b) * sizeof(TString*)]); IrCallWrapperX64 callWrap(regs, build, index);
callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaT_gettm)]); callWrap.addArgument(SizeX64::qword, tmp);
inst.regX64 = regs.takeReg(rax); callWrap.addArgument(SizeX64::qword, intOp(inst.b));
callWrap.addArgument(SizeX64::qword, qword[tmp2.release() + offsetof(global_State, tmname) + intOp(inst.b) * sizeof(TString*)]);
callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaT_gettm)]);
}
inst.regX64 = regs.takeReg(rax, index);
break; break;
} }
case IrCmd::INT_TO_NUM: case IrCmd::INT_TO_NUM:
inst.regX64 = regs.allocXmmReg(); inst.regX64 = regs.allocXmmReg(index);
build.vcvtsi2sd(inst.regX64, inst.regX64, regOp(inst.a)); build.vcvtsi2sd(inst.regX64, inst.regX64, regOp(inst.a));
break; break;
case IrCmd::ADJUST_STACK_TO_REG: case IrCmd::ADJUST_STACK_TO_REG:
{ {
ScopedRegX64 tmp{regs, SizeX64::qword};
if (inst.b.kind == IrOpKind::Constant) if (inst.b.kind == IrOpKind::Constant)
{ {
ScopedRegX64 tmp{regs, SizeX64::qword};
build.lea(tmp.reg, addr[rBase + (vmRegOp(inst.a) + 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); build.mov(qword[rState + offsetof(lua_State, top)], tmp.reg);
} }
else if (inst.b.kind == IrOpKind::Inst) else if (inst.b.kind == IrOpKind::Inst)
{ {
ScopedRegX64 tmp(regs, regs.allocGprRegOrReuse(SizeX64::dword, index, {inst.b})); build.mov(dwordReg(tmp.reg), regOp(inst.b));
build.shl(tmp.reg, kTValueSizeLog2);
build.shl(qwordReg(tmp.reg), kTValueSizeLog2); build.lea(tmp.reg, addr[rBase + tmp.reg + vmRegOp(inst.a) * 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)], tmp.reg);
build.mov(qword[rState + offsetof(lua_State, top)], qwordReg(tmp.reg));
} }
else else
{ {
@ -640,52 +676,37 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
int nparams = intOp(inst.e); int nparams = intOp(inst.e);
int nresults = intOp(inst.f); int nresults = intOp(inst.f);
regs.assertAllFree(); ScopedRegX64 func{regs, SizeX64::qword};
build.mov(func.reg, qword[rNativeContext + offsetof(NativeContext, luauF_table) + bfid * sizeof(luau_FastFunction)]);
build.mov(rax, qword[rNativeContext + offsetof(NativeContext, luauF_table) + bfid * sizeof(luau_FastFunction)]); IrCallWrapperX64 callWrap(regs, build, index);
callWrap.addArgument(SizeX64::qword, rState);
// 5th parameter (args) is left unset for LOP_FASTCALL1 callWrap.addArgument(SizeX64::qword, luauRegAddress(ra));
if (args.cat == CategoryX64::mem) callWrap.addArgument(SizeX64::qword, luauRegAddress(arg));
{ callWrap.addArgument(SizeX64::dword, nresults);
if (build.abi == ABIX64::Windows) callWrap.addArgument(SizeX64::qword, args);
{
build.lea(rcx, args);
build.mov(sArg5, rcx);
}
else
{
build.lea(rArg5, args);
}
}
if (nparams == LUA_MULTRET) if (nparams == LUA_MULTRET)
{ {
// L->top - (ra + 1) // Compute 'L->top - (ra + 1)', on SystemV, take r9 register to compute directly into the argument
RegisterX64 reg = (build.abi == ABIX64::Windows) ? rcx : rArg6; // TODO: IrCallWrapperX64 should provide a way to 'guess' target argument register correctly
RegisterX64 reg = build.abi == ABIX64::Windows ? regs.allocGprReg(SizeX64::qword, kInvalidInstIdx) : regs.takeReg(rArg6, kInvalidInstIdx);
ScopedRegX64 tmp{regs, SizeX64::qword};
build.mov(reg, qword[rState + offsetof(lua_State, top)]); build.mov(reg, qword[rState + offsetof(lua_State, top)]);
build.lea(rdx, addr[rBase + (ra + 1) * sizeof(TValue)]); build.lea(tmp.reg, addr[rBase + (ra + 1) * sizeof(TValue)]);
build.sub(reg, rdx); build.sub(reg, tmp.reg);
build.shr(reg, kTValueSizeLog2); build.shr(reg, kTValueSizeLog2);
if (build.abi == ABIX64::Windows) callWrap.addArgument(SizeX64::dword, dwordReg(reg));
build.mov(sArg6, reg);
} }
else else
{ {
if (build.abi == ABIX64::Windows) callWrap.addArgument(SizeX64::dword, nparams);
build.mov(sArg6, nparams);
else
build.mov(rArg6, nparams);
} }
build.mov(rArg1, rState); callWrap.call(func.release());
build.lea(rArg2, luauRegAddress(ra)); inst.regX64 = regs.takeReg(eax, index); // Result of a builtin call is returned in eax
build.lea(rArg3, luauRegAddress(arg));
build.mov(dwordReg(rArg4), nresults);
build.call(rax);
inst.regX64 = regs.takeReg(eax); // Result of a builtin call is returned in eax
break; break;
} }
case IrCmd::CHECK_FASTCALL_RES: case IrCmd::CHECK_FASTCALL_RES:
@ -738,6 +759,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
} }
break; break;
case IrCmd::GET_IMPORT: case IrCmd::GET_IMPORT:
regs.assertAllFree();
emitInstGetImportFallback(build, vmRegOp(inst.a), uintOp(inst.b)); emitInstGetImportFallback(build, vmRegOp(inst.a), uintOp(inst.b));
break; break;
case IrCmd::CONCAT: case IrCmd::CONCAT:
@ -777,7 +799,6 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
} }
case IrCmd::SET_UPVALUE: case IrCmd::SET_UPVALUE:
{ {
Label next;
ScopedRegX64 tmp1{regs, SizeX64::qword}; ScopedRegX64 tmp1{regs, SizeX64::qword};
ScopedRegX64 tmp2{regs, SizeX64::qword}; ScopedRegX64 tmp2{regs, SizeX64::qword};
@ -794,8 +815,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
tmp1.free(); tmp1.free();
callBarrierObject(regs, build, tmp2.release(), {}, vmRegOp(inst.b), next); callBarrierObject(regs, build, tmp2.release(), {}, vmRegOp(inst.b));
build.setLabel(next);
break; break;
} }
case IrCmd::PREPARE_FORN: case IrCmd::PREPARE_FORN:
@ -859,26 +879,14 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
emitInterrupt(build, uintOp(inst.a)); emitInterrupt(build, uintOp(inst.a));
break; break;
case IrCmd::CHECK_GC: case IrCmd::CHECK_GC:
{ callStepGc(regs, build);
Label skip;
callCheckGc(regs, build, skip);
build.setLabel(skip);
break; break;
}
case IrCmd::BARRIER_OBJ: case IrCmd::BARRIER_OBJ:
{ callBarrierObject(regs, build, regOp(inst.a), inst.a, vmRegOp(inst.b));
Label skip;
callBarrierObject(regs, build, regOp(inst.a), inst.a, vmRegOp(inst.b), skip);
build.setLabel(skip);
break; break;
}
case IrCmd::BARRIER_TABLE_BACK: case IrCmd::BARRIER_TABLE_BACK:
{ callBarrierTableFast(regs, build, regOp(inst.a), inst.a);
Label skip;
callBarrierTableFast(regs, build, regOp(inst.a), inst.a, skip);
build.setLabel(skip);
break; break;
}
case IrCmd::BARRIER_TABLE_FORWARD: case IrCmd::BARRIER_TABLE_FORWARD:
{ {
Label skip; Label skip;
@ -886,11 +894,15 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
ScopedRegX64 tmp{regs, SizeX64::qword}; ScopedRegX64 tmp{regs, SizeX64::qword};
checkObjectBarrierConditions(build, tmp.reg, regOp(inst.a), vmRegOp(inst.b), skip); checkObjectBarrierConditions(build, tmp.reg, regOp(inst.a), vmRegOp(inst.b), skip);
IrCallWrapperX64 callWrap(regs, build, index); {
callWrap.addArgument(SizeX64::qword, rState); ScopedSpills spillGuard(regs);
callWrap.addArgument(SizeX64::qword, regOp(inst.a), inst.a);
callWrap.addArgument(SizeX64::qword, tmp); IrCallWrapperX64 callWrap(regs, build, index);
callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaC_barriertable)]); callWrap.addArgument(SizeX64::qword, rState);
callWrap.addArgument(SizeX64::qword, regOp(inst.a), inst.a);
callWrap.addArgument(SizeX64::qword, tmp);
callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaC_barriertable)]);
}
build.setLabel(skip); build.setLabel(skip);
break; break;
@ -925,10 +937,14 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
tmp1.free(); tmp1.free();
IrCallWrapperX64 callWrap(regs, build, index); {
callWrap.addArgument(SizeX64::qword, rState); ScopedSpills spillGuard(regs);
callWrap.addArgument(SizeX64::qword, tmp2);
callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaF_close)]); IrCallWrapperX64 callWrap(regs, build, index);
callWrap.addArgument(SizeX64::qword, rState);
callWrap.addArgument(SizeX64::qword, tmp2);
callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaF_close)]);
}
build.setLabel(next); build.setLabel(next);
break; break;
@ -939,19 +955,17 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
// Fallbacks to non-IR instruction implementations // Fallbacks to non-IR instruction implementations
case IrCmd::SETLIST: case IrCmd::SETLIST:
{
Label next;
regs.assertAllFree(); regs.assertAllFree();
emitInstSetList(regs, build, next, vmRegOp(inst.b), vmRegOp(inst.c), intOp(inst.d), uintOp(inst.e)); emitInstSetList(regs, build, vmRegOp(inst.b), vmRegOp(inst.c), intOp(inst.d), uintOp(inst.e));
build.setLabel(next);
break; break;
}
case IrCmd::CALL: case IrCmd::CALL:
regs.assertAllFree(); regs.assertAllFree();
regs.assertNoSpills();
emitInstCall(build, helpers, vmRegOp(inst.a), intOp(inst.b), intOp(inst.c)); emitInstCall(build, helpers, vmRegOp(inst.a), intOp(inst.b), intOp(inst.c));
break; break;
case IrCmd::RETURN: case IrCmd::RETURN:
regs.assertAllFree(); regs.assertAllFree();
regs.assertNoSpills();
emitInstReturn(build, helpers, vmRegOp(inst.a), intOp(inst.b)); emitInstReturn(build, helpers, vmRegOp(inst.a), intOp(inst.b));
break; break;
case IrCmd::FORGLOOP: case IrCmd::FORGLOOP:
@ -967,22 +981,6 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
regs.assertAllFree(); regs.assertAllFree();
emitInstForGPrepXnextFallback(build, uintOp(inst.a), vmRegOp(inst.b), labelOp(inst.c)); emitInstForGPrepXnextFallback(build, uintOp(inst.a), vmRegOp(inst.b), labelOp(inst.c));
break; break;
case IrCmd::AND:
regs.assertAllFree();
emitInstAnd(build, vmRegOp(inst.a), vmRegOp(inst.b), vmRegOp(inst.c));
break;
case IrCmd::ANDK:
regs.assertAllFree();
emitInstAndK(build, vmRegOp(inst.a), vmRegOp(inst.b), vmConstOp(inst.c));
break;
case IrCmd::OR:
regs.assertAllFree();
emitInstOr(build, vmRegOp(inst.a), vmRegOp(inst.b), vmRegOp(inst.c));
break;
case IrCmd::ORK:
regs.assertAllFree();
emitInstOrK(build, vmRegOp(inst.a), vmRegOp(inst.b), vmConstOp(inst.c));
break;
case IrCmd::COVERAGE: case IrCmd::COVERAGE:
regs.assertAllFree(); regs.assertAllFree();
emitInstCoverage(build, uintOp(inst.a)); emitInstCoverage(build, uintOp(inst.a));
@ -1066,6 +1064,15 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
regs.freeLastUseRegs(inst, index); regs.freeLastUseRegs(inst, index);
} }
bool IrLoweringX64::hasError() const
{
// If register allocator had to use more stack slots than we have available, this function can't run natively
if (regs.maxUsedSlot > kSpillSlots)
return true;
return false;
}
bool IrLoweringX64::isFallthroughBlock(IrBlock target, IrBlock next) bool IrLoweringX64::isFallthroughBlock(IrBlock target, IrBlock next)
{ {
return target.start == next.start; return target.start == next.start;
@ -1077,7 +1084,7 @@ void IrLoweringX64::jumpOrFallthrough(IrBlock& target, IrBlock& next)
build.jmp(target.label); build.jmp(target.label);
} }
OperandX64 IrLoweringX64::memRegDoubleOp(IrOp op) const OperandX64 IrLoweringX64::memRegDoubleOp(IrOp op)
{ {
switch (op.kind) switch (op.kind)
{ {
@ -1096,7 +1103,7 @@ OperandX64 IrLoweringX64::memRegDoubleOp(IrOp op) const
return noreg; return noreg;
} }
OperandX64 IrLoweringX64::memRegTagOp(IrOp op) const OperandX64 IrLoweringX64::memRegTagOp(IrOp op)
{ {
switch (op.kind) switch (op.kind)
{ {
@ -1113,9 +1120,13 @@ OperandX64 IrLoweringX64::memRegTagOp(IrOp op) const
return noreg; return noreg;
} }
RegisterX64 IrLoweringX64::regOp(IrOp op) const RegisterX64 IrLoweringX64::regOp(IrOp op)
{ {
IrInst& inst = function.instOp(op); IrInst& inst = function.instOp(op);
if (inst.spilled)
regs.restore(inst, false);
LUAU_ASSERT(inst.regX64 != noreg); LUAU_ASSERT(inst.regX64 != noreg);
return inst.regX64; return inst.regX64;
} }

View File

@ -27,13 +27,17 @@ struct IrLoweringX64
void lowerInst(IrInst& inst, uint32_t index, IrBlock& next); void lowerInst(IrInst& inst, uint32_t index, IrBlock& next);
bool hasError() const;
bool isFallthroughBlock(IrBlock target, IrBlock next); bool isFallthroughBlock(IrBlock target, IrBlock next);
void jumpOrFallthrough(IrBlock& target, IrBlock& next); void jumpOrFallthrough(IrBlock& target, IrBlock& next);
void storeDoubleAsFloat(OperandX64 dst, IrOp src);
// Operand data lookup helpers // Operand data lookup helpers
OperandX64 memRegDoubleOp(IrOp op) const; OperandX64 memRegDoubleOp(IrOp op);
OperandX64 memRegTagOp(IrOp op) const; OperandX64 memRegTagOp(IrOp op);
RegisterX64 regOp(IrOp op) const; RegisterX64 regOp(IrOp op);
IrConst constOp(IrOp op) const; IrConst constOp(IrOp op) const;
uint8_t tagOp(IrOp op) const; uint8_t tagOp(IrOp op) const;

View File

@ -151,6 +151,15 @@ void IrRegAllocA64::assertAllFree() const
LUAU_ASSERT(simd.free == simd.base); LUAU_ASSERT(simd.free == simd.base);
} }
void IrRegAllocA64::assertAllFreeExcept(RegisterA64 reg) const
{
const Set& set = const_cast<IrRegAllocA64*>(this)->getSet(reg.kind);
const Set& other = &set == &gpr ? simd : gpr;
LUAU_ASSERT(set.free == (set.base & ~(1u << reg.index)));
LUAU_ASSERT(other.free == other.base);
}
IrRegAllocA64::Set& IrRegAllocA64::getSet(KindA64 kind) IrRegAllocA64::Set& IrRegAllocA64::getSet(KindA64 kind)
{ {
switch (kind) switch (kind)

View File

@ -30,6 +30,7 @@ struct IrRegAllocA64
void freeTempRegs(); void freeTempRegs();
void assertAllFree() const; void assertAllFree() const;
void assertAllFreeExcept(RegisterA64 reg) const;
IrFunction& function; IrFunction& function;

View File

@ -1,6 +1,8 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/IrRegAllocX64.h" #include "Luau/IrRegAllocX64.h"
#include "EmitCommonX64.h"
namespace Luau namespace Luau
{ {
namespace CodeGen namespace CodeGen
@ -10,14 +12,22 @@ namespace X64
static const RegisterX64 kGprAllocOrder[] = {rax, rdx, rcx, rbx, rsi, rdi, r8, r9, r10, r11}; static const RegisterX64 kGprAllocOrder[] = {rax, rdx, rcx, rbx, rsi, rdi, r8, r9, r10, r11};
IrRegAllocX64::IrRegAllocX64(IrFunction& function) static bool isFullTvalueOperand(IrCmd cmd)
: function(function)
{ {
freeGprMap.fill(true); return cmd == IrCmd::LOAD_TVALUE || cmd == IrCmd::LOAD_NODE_VALUE_TV;
freeXmmMap.fill(true);
} }
RegisterX64 IrRegAllocX64::allocGprReg(SizeX64 preferredSize) IrRegAllocX64::IrRegAllocX64(AssemblyBuilderX64& build, IrFunction& function)
: build(build)
, function(function)
{
freeGprMap.fill(true);
gprInstUsers.fill(kInvalidInstIdx);
freeXmmMap.fill(true);
xmmInstUsers.fill(kInvalidInstIdx);
}
RegisterX64 IrRegAllocX64::allocGprReg(SizeX64 preferredSize, uint32_t instIdx)
{ {
LUAU_ASSERT( LUAU_ASSERT(
preferredSize == SizeX64::byte || preferredSize == SizeX64::word || preferredSize == SizeX64::dword || preferredSize == SizeX64::qword); preferredSize == SizeX64::byte || preferredSize == SizeX64::word || preferredSize == SizeX64::dword || preferredSize == SizeX64::qword);
@ -27,30 +37,40 @@ RegisterX64 IrRegAllocX64::allocGprReg(SizeX64 preferredSize)
if (freeGprMap[reg.index]) if (freeGprMap[reg.index])
{ {
freeGprMap[reg.index] = false; freeGprMap[reg.index] = false;
gprInstUsers[reg.index] = instIdx;
return RegisterX64{preferredSize, reg.index}; return RegisterX64{preferredSize, reg.index};
} }
} }
// If possible, spill the value with the furthest next use
if (uint32_t furthestUseTarget = findInstructionWithFurthestNextUse(gprInstUsers); furthestUseTarget != kInvalidInstIdx)
return takeReg(function.instructions[furthestUseTarget].regX64, instIdx);
LUAU_ASSERT(!"Out of GPR registers to allocate"); LUAU_ASSERT(!"Out of GPR registers to allocate");
return noreg; return noreg;
} }
RegisterX64 IrRegAllocX64::allocXmmReg() RegisterX64 IrRegAllocX64::allocXmmReg(uint32_t instIdx)
{ {
for (size_t i = 0; i < freeXmmMap.size(); ++i) for (size_t i = 0; i < freeXmmMap.size(); ++i)
{ {
if (freeXmmMap[i]) if (freeXmmMap[i])
{ {
freeXmmMap[i] = false; freeXmmMap[i] = false;
xmmInstUsers[i] = instIdx;
return RegisterX64{SizeX64::xmmword, uint8_t(i)}; return RegisterX64{SizeX64::xmmword, uint8_t(i)};
} }
} }
// Out of registers, spill the value with the furthest next use
if (uint32_t furthestUseTarget = findInstructionWithFurthestNextUse(xmmInstUsers); furthestUseTarget != kInvalidInstIdx)
return takeReg(function.instructions[furthestUseTarget].regX64, instIdx);
LUAU_ASSERT(!"Out of XMM registers to allocate"); LUAU_ASSERT(!"Out of XMM registers to allocate");
return noreg; return noreg;
} }
RegisterX64 IrRegAllocX64::allocGprRegOrReuse(SizeX64 preferredSize, uint32_t index, std::initializer_list<IrOp> oprefs) RegisterX64 IrRegAllocX64::allocGprRegOrReuse(SizeX64 preferredSize, uint32_t instIdx, std::initializer_list<IrOp> oprefs)
{ {
for (IrOp op : oprefs) for (IrOp op : oprefs)
{ {
@ -59,20 +79,21 @@ RegisterX64 IrRegAllocX64::allocGprRegOrReuse(SizeX64 preferredSize, uint32_t in
IrInst& source = function.instructions[op.index]; IrInst& source = function.instructions[op.index];
if (source.lastUse == index && !source.reusedReg) if (source.lastUse == instIdx && !source.reusedReg && !source.spilled)
{ {
LUAU_ASSERT(source.regX64.size != SizeX64::xmmword); LUAU_ASSERT(source.regX64.size != SizeX64::xmmword);
LUAU_ASSERT(source.regX64 != noreg); LUAU_ASSERT(source.regX64 != noreg);
source.reusedReg = true; source.reusedReg = true;
gprInstUsers[source.regX64.index] = instIdx;
return RegisterX64{preferredSize, source.regX64.index}; return RegisterX64{preferredSize, source.regX64.index};
} }
} }
return allocGprReg(preferredSize); return allocGprReg(preferredSize, instIdx);
} }
RegisterX64 IrRegAllocX64::allocXmmRegOrReuse(uint32_t index, std::initializer_list<IrOp> oprefs) RegisterX64 IrRegAllocX64::allocXmmRegOrReuse(uint32_t instIdx, std::initializer_list<IrOp> oprefs)
{ {
for (IrOp op : oprefs) for (IrOp op : oprefs)
{ {
@ -81,32 +102,45 @@ RegisterX64 IrRegAllocX64::allocXmmRegOrReuse(uint32_t index, std::initializer_l
IrInst& source = function.instructions[op.index]; IrInst& source = function.instructions[op.index];
if (source.lastUse == index && !source.reusedReg) if (source.lastUse == instIdx && !source.reusedReg && !source.spilled)
{ {
LUAU_ASSERT(source.regX64.size == SizeX64::xmmword); LUAU_ASSERT(source.regX64.size == SizeX64::xmmword);
LUAU_ASSERT(source.regX64 != noreg); LUAU_ASSERT(source.regX64 != noreg);
source.reusedReg = true; source.reusedReg = true;
xmmInstUsers[source.regX64.index] = instIdx;
return source.regX64; return source.regX64;
} }
} }
return allocXmmReg(); return allocXmmReg(instIdx);
} }
RegisterX64 IrRegAllocX64::takeReg(RegisterX64 reg) RegisterX64 IrRegAllocX64::takeReg(RegisterX64 reg, uint32_t instIdx)
{ {
// In a more advanced register allocator, this would require a spill for the current register user
// But at the current stage we don't have register live ranges intersecting forced register uses
if (reg.size == SizeX64::xmmword) if (reg.size == SizeX64::xmmword)
{ {
if (!freeXmmMap[reg.index])
{
LUAU_ASSERT(xmmInstUsers[reg.index] != kInvalidInstIdx);
preserve(function.instructions[xmmInstUsers[reg.index]]);
}
LUAU_ASSERT(freeXmmMap[reg.index]); LUAU_ASSERT(freeXmmMap[reg.index]);
freeXmmMap[reg.index] = false; freeXmmMap[reg.index] = false;
xmmInstUsers[reg.index] = instIdx;
} }
else else
{ {
if (!freeGprMap[reg.index])
{
LUAU_ASSERT(gprInstUsers[reg.index] != kInvalidInstIdx);
preserve(function.instructions[gprInstUsers[reg.index]]);
}
LUAU_ASSERT(freeGprMap[reg.index]); LUAU_ASSERT(freeGprMap[reg.index]);
freeGprMap[reg.index] = false; freeGprMap[reg.index] = false;
gprInstUsers[reg.index] = instIdx;
} }
return reg; return reg;
@ -118,17 +152,19 @@ void IrRegAllocX64::freeReg(RegisterX64 reg)
{ {
LUAU_ASSERT(!freeXmmMap[reg.index]); LUAU_ASSERT(!freeXmmMap[reg.index]);
freeXmmMap[reg.index] = true; freeXmmMap[reg.index] = true;
xmmInstUsers[reg.index] = kInvalidInstIdx;
} }
else else
{ {
LUAU_ASSERT(!freeGprMap[reg.index]); LUAU_ASSERT(!freeGprMap[reg.index]);
freeGprMap[reg.index] = true; freeGprMap[reg.index] = true;
gprInstUsers[reg.index] = kInvalidInstIdx;
} }
} }
void IrRegAllocX64::freeLastUseReg(IrInst& target, uint32_t index) void IrRegAllocX64::freeLastUseReg(IrInst& target, uint32_t instIdx)
{ {
if (isLastUseReg(target, index)) if (isLastUseReg(target, instIdx))
{ {
// Register might have already been freed if it had multiple uses inside a single instruction // Register might have already been freed if it had multiple uses inside a single instruction
if (target.regX64 == noreg) if (target.regX64 == noreg)
@ -139,11 +175,11 @@ void IrRegAllocX64::freeLastUseReg(IrInst& target, uint32_t index)
} }
} }
void IrRegAllocX64::freeLastUseRegs(const IrInst& inst, uint32_t index) void IrRegAllocX64::freeLastUseRegs(const IrInst& inst, uint32_t instIdx)
{ {
auto checkOp = [this, index](IrOp op) { auto checkOp = [this, instIdx](IrOp op) {
if (op.kind == IrOpKind::Inst) if (op.kind == IrOpKind::Inst)
freeLastUseReg(function.instructions[op.index], index); freeLastUseReg(function.instructions[op.index], instIdx);
}; };
checkOp(inst.a); checkOp(inst.a);
@ -154,9 +190,132 @@ void IrRegAllocX64::freeLastUseRegs(const IrInst& inst, uint32_t index)
checkOp(inst.f); checkOp(inst.f);
} }
bool IrRegAllocX64::isLastUseReg(const IrInst& target, uint32_t index) const bool IrRegAllocX64::isLastUseReg(const IrInst& target, uint32_t instIdx) const
{ {
return target.lastUse == index && !target.reusedReg; return target.lastUse == instIdx && !target.reusedReg;
}
void IrRegAllocX64::preserve(IrInst& inst)
{
bool doubleSlot = isFullTvalueOperand(inst.cmd);
// Find a free stack slot. Two consecutive slots might be required for 16 byte TValues, so '- 1' is used
for (unsigned i = 0; i < unsigned(usedSpillSlots.size() - 1); ++i)
{
if (usedSpillSlots.test(i))
continue;
if (doubleSlot && usedSpillSlots.test(i + 1))
{
++i; // No need to retest this double position
continue;
}
if (inst.regX64.size == SizeX64::xmmword && doubleSlot)
{
build.vmovups(xmmword[sSpillArea + i * 8], inst.regX64);
}
else if (inst.regX64.size == SizeX64::xmmword)
{
build.vmovsd(qword[sSpillArea + i * 8], inst.regX64);
}
else
{
OperandX64 location = addr[sSpillArea + i * 8];
location.memSize = inst.regX64.size; // Override memory access size
build.mov(location, inst.regX64);
}
usedSpillSlots.set(i);
if (i + 1 > maxUsedSlot)
maxUsedSlot = i + 1;
if (doubleSlot)
{
usedSpillSlots.set(i + 1);
if (i + 2 > maxUsedSlot)
maxUsedSlot = i + 2;
}
IrSpillX64 spill;
spill.instIdx = function.getInstIndex(inst);
spill.useDoubleSlot = doubleSlot;
spill.stackSlot = uint8_t(i);
spill.originalLoc = inst.regX64;
spills.push_back(spill);
freeReg(inst.regX64);
inst.regX64 = noreg;
inst.spilled = true;
return;
}
LUAU_ASSERT(!"nowhere to spill");
}
void IrRegAllocX64::restore(IrInst& inst, bool intoOriginalLocation)
{
uint32_t instIdx = function.getInstIndex(inst);
for (size_t i = 0; i < spills.size(); i++)
{
const IrSpillX64& spill = spills[i];
if (spill.instIdx == instIdx)
{
LUAU_ASSERT(spill.stackSlot != kNoStackSlot);
RegisterX64 reg;
if (spill.originalLoc.size == SizeX64::xmmword)
{
reg = intoOriginalLocation ? takeReg(spill.originalLoc, instIdx) : allocXmmReg(instIdx);
if (spill.useDoubleSlot)
build.vmovups(reg, xmmword[sSpillArea + spill.stackSlot * 8]);
else
build.vmovsd(reg, qword[sSpillArea + spill.stackSlot * 8]);
}
else
{
reg = intoOriginalLocation ? takeReg(spill.originalLoc, instIdx) : allocGprReg(spill.originalLoc.size, instIdx);
OperandX64 location = addr[sSpillArea + spill.stackSlot * 8];
location.memSize = reg.size; // Override memory access size
build.mov(reg, location);
}
inst.regX64 = reg;
inst.spilled = false;
usedSpillSlots.set(spill.stackSlot, false);
if (spill.useDoubleSlot)
usedSpillSlots.set(spill.stackSlot + 1, false);
spills[i] = spills.back();
spills.pop_back();
return;
}
}
}
void IrRegAllocX64::preserveAndFreeInstValues()
{
for (uint32_t instIdx : gprInstUsers)
{
if (instIdx != kInvalidInstIdx)
preserve(function.instructions[instIdx]);
}
for (uint32_t instIdx : xmmInstUsers)
{
if (instIdx != kInvalidInstIdx)
preserve(function.instructions[instIdx]);
}
} }
bool IrRegAllocX64::shouldFreeGpr(RegisterX64 reg) const bool IrRegAllocX64::shouldFreeGpr(RegisterX64 reg) const
@ -175,6 +334,33 @@ bool IrRegAllocX64::shouldFreeGpr(RegisterX64 reg) const
return false; return false;
} }
uint32_t IrRegAllocX64::findInstructionWithFurthestNextUse(const std::array<uint32_t, 16>& regInstUsers) const
{
uint32_t furthestUseTarget = kInvalidInstIdx;
uint32_t furthestUseLocation = 0;
for (uint32_t regInstUser : regInstUsers)
{
// Cannot spill temporary registers or the register of the value that's defined in the current instruction
if (regInstUser == kInvalidInstIdx || regInstUser == currInstIdx)
continue;
uint32_t nextUse = getNextInstUse(function, regInstUser, currInstIdx);
// Cannot spill value that is about to be used in the current instruction
if (nextUse == currInstIdx)
continue;
if (furthestUseTarget == kInvalidInstIdx || nextUse > furthestUseLocation)
{
furthestUseLocation = nextUse;
furthestUseTarget = regInstUser;
}
}
return furthestUseTarget;
}
void IrRegAllocX64::assertFree(RegisterX64 reg) const void IrRegAllocX64::assertFree(RegisterX64 reg) const
{ {
if (reg.size == SizeX64::xmmword) if (reg.size == SizeX64::xmmword)
@ -192,6 +378,11 @@ void IrRegAllocX64::assertAllFree() const
LUAU_ASSERT(free); LUAU_ASSERT(free);
} }
void IrRegAllocX64::assertNoSpills() const
{
LUAU_ASSERT(spills.empty());
}
ScopedRegX64::ScopedRegX64(IrRegAllocX64& owner) ScopedRegX64::ScopedRegX64(IrRegAllocX64& owner)
: owner(owner) : owner(owner)
, reg(noreg) , reg(noreg)
@ -222,9 +413,9 @@ void ScopedRegX64::alloc(SizeX64 size)
LUAU_ASSERT(reg == noreg); LUAU_ASSERT(reg == noreg);
if (size == SizeX64::xmmword) if (size == SizeX64::xmmword)
reg = owner.allocXmmReg(); reg = owner.allocXmmReg(kInvalidInstIdx);
else else
reg = owner.allocGprReg(size); reg = owner.allocGprReg(size, kInvalidInstIdx);
} }
void ScopedRegX64::free() void ScopedRegX64::free()
@ -241,6 +432,41 @@ RegisterX64 ScopedRegX64::release()
return tmp; return tmp;
} }
ScopedSpills::ScopedSpills(IrRegAllocX64& owner)
: owner(owner)
{
snapshot = owner.spills;
}
ScopedSpills::~ScopedSpills()
{
// Taking a copy of current spills because we are going to potentially restore them
std::vector<IrSpillX64> current = owner.spills;
// Restore registers that were spilled inside scope protected by this object
for (IrSpillX64& curr : current)
{
// If spill existed before current scope, it can be restored outside of it
if (!wasSpilledBefore(curr))
{
IrInst& inst = owner.function.instructions[curr.instIdx];
owner.restore(inst, /*intoOriginalLocation*/ true);
}
}
}
bool ScopedSpills::wasSpilledBefore(const IrSpillX64& spill) const
{
for (const IrSpillX64& preexisting : snapshot)
{
if (spill.instIdx == preexisting.instIdx)
return true;
}
return false;
}
} // namespace X64 } // namespace X64
} // namespace CodeGen } // namespace CodeGen
} // namespace Luau } // namespace Luau

View File

@ -61,7 +61,8 @@ BuiltinImplResult translateBuiltinNumberTo2Number(
if (ra != arg) if (ra != arg)
build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TNUMBER)); build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TNUMBER));
build.inst(IrCmd::STORE_TAG, build.vmReg(ra + 1), build.constTag(LUA_TNUMBER)); if (nresults > 1)
build.inst(IrCmd::STORE_TAG, build.vmReg(ra + 1), build.constTag(LUA_TNUMBER));
return {BuiltinImplType::UsesFallback, 2}; return {BuiltinImplType::UsesFallback, 2};
} }
@ -190,10 +191,10 @@ BuiltinImplResult translateBuiltinMathClamp(IrBuilder& build, int nparams, int r
build.loadAndCheckTag(build.vmReg(arg), LUA_TNUMBER, fallback); build.loadAndCheckTag(build.vmReg(arg), LUA_TNUMBER, fallback);
build.loadAndCheckTag(args, LUA_TNUMBER, fallback); build.loadAndCheckTag(args, LUA_TNUMBER, fallback);
build.loadAndCheckTag(build.vmReg(args.index + 1), LUA_TNUMBER, fallback); build.loadAndCheckTag(build.vmReg(vmRegOp(args) + 1), LUA_TNUMBER, fallback);
IrOp min = build.inst(IrCmd::LOAD_DOUBLE, args); IrOp min = build.inst(IrCmd::LOAD_DOUBLE, args);
IrOp max = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(args.index + 1)); IrOp max = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(vmRegOp(args) + 1));
build.inst(IrCmd::JUMP_CMP_NUM, min, max, build.cond(IrCondition::NotLessEqual), fallback, block); build.inst(IrCmd::JUMP_CMP_NUM, min, max, build.cond(IrCondition::NotLessEqual), fallback, block);
build.beginBlock(block); build.beginBlock(block);
@ -274,6 +275,27 @@ BuiltinImplResult translateBuiltinTypeof(IrBuilder& build, int nparams, int ra,
return {BuiltinImplType::UsesFallback, 1}; return {BuiltinImplType::UsesFallback, 1};
} }
BuiltinImplResult translateBuiltinVector(IrBuilder& build, int nparams, int ra, int arg, IrOp args, int nresults, IrOp fallback)
{
if (nparams < 3 || nresults > 1)
return {BuiltinImplType::None, -1};
LUAU_ASSERT(LUA_VECTOR_SIZE == 3);
build.loadAndCheckTag(build.vmReg(arg), LUA_TNUMBER, fallback);
build.loadAndCheckTag(args, LUA_TNUMBER, fallback);
build.loadAndCheckTag(build.vmReg(vmRegOp(args) + 1), LUA_TNUMBER, fallback);
IrOp x = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(arg));
IrOp y = build.inst(IrCmd::LOAD_DOUBLE, args);
IrOp z = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(vmRegOp(args) + 1));
build.inst(IrCmd::STORE_VECTOR, build.vmReg(ra), x, y, z);
build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TVECTOR));
return {BuiltinImplType::UsesFallback, 1};
}
BuiltinImplResult translateBuiltin(IrBuilder& build, int bfid, int ra, int arg, IrOp args, int nparams, int nresults, IrOp fallback) 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 // Builtins are not allowed to handle variadic arguments
@ -332,6 +354,8 @@ BuiltinImplResult translateBuiltin(IrBuilder& build, int bfid, int ra, int arg,
return translateBuiltinType(build, nparams, ra, arg, args, nresults, fallback); return translateBuiltinType(build, nparams, ra, arg, args, nresults, fallback);
case LBF_TYPEOF: case LBF_TYPEOF:
return translateBuiltinTypeof(build, nparams, ra, arg, args, nresults, fallback); return translateBuiltinTypeof(build, nparams, ra, arg, args, nresults, fallback);
case LBF_VECTOR:
return translateBuiltinVector(build, nparams, ra, arg, args, nresults, fallback);
default: default:
return {BuiltinImplType::None, -1}; return {BuiltinImplType::None, -1};
} }

View File

@ -301,7 +301,7 @@ static void translateInstBinaryNumeric(IrBuilder& build, int ra, int rb, int rc,
if (opc.kind == IrOpKind::VmConst) if (opc.kind == IrOpKind::VmConst)
{ {
LUAU_ASSERT(build.function.proto); LUAU_ASSERT(build.function.proto);
TValue protok = build.function.proto->k[opc.index]; TValue protok = build.function.proto->k[vmConstOp(opc)];
LUAU_ASSERT(protok.tt == LUA_TNUMBER); LUAU_ASSERT(protok.tt == LUA_TNUMBER);
@ -1108,5 +1108,71 @@ void translateInstNamecall(IrBuilder& build, const Instruction* pc, int pcpos)
build.beginBlock(next); build.beginBlock(next);
} }
void translateInstAndX(IrBuilder& build, const Instruction* pc, int pcpos, IrOp c)
{
int ra = LUAU_INSN_A(*pc);
int rb = LUAU_INSN_B(*pc);
IrOp fallthrough = build.block(IrBlockKind::Internal);
IrOp next = build.blockAtInst(pcpos + 1);
IrOp target = (ra == rb) ? next : build.block(IrBlockKind::Internal);
build.inst(IrCmd::JUMP_IF_FALSY, build.vmReg(rb), target, fallthrough);
build.beginBlock(fallthrough);
IrOp load = build.inst(IrCmd::LOAD_TVALUE, c);
build.inst(IrCmd::STORE_TVALUE, build.vmReg(ra), load);
build.inst(IrCmd::JUMP, next);
if (ra == rb)
{
build.beginBlock(next);
}
else
{
build.beginBlock(target);
IrOp load1 = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(rb));
build.inst(IrCmd::STORE_TVALUE, build.vmReg(ra), load1);
build.inst(IrCmd::JUMP, next);
build.beginBlock(next);
}
}
void translateInstOrX(IrBuilder& build, const Instruction* pc, int pcpos, IrOp c)
{
int ra = LUAU_INSN_A(*pc);
int rb = LUAU_INSN_B(*pc);
IrOp fallthrough = build.block(IrBlockKind::Internal);
IrOp next = build.blockAtInst(pcpos + 1);
IrOp target = (ra == rb) ? next : build.block(IrBlockKind::Internal);
build.inst(IrCmd::JUMP_IF_TRUTHY, build.vmReg(rb), target, fallthrough);
build.beginBlock(fallthrough);
IrOp load = build.inst(IrCmd::LOAD_TVALUE, c);
build.inst(IrCmd::STORE_TVALUE, build.vmReg(ra), load);
build.inst(IrCmd::JUMP, next);
if (ra == rb)
{
build.beginBlock(next);
}
else
{
build.beginBlock(target);
IrOp load1 = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(rb));
build.inst(IrCmd::STORE_TVALUE, build.vmReg(ra), load1);
build.inst(IrCmd::JUMP, next);
build.beginBlock(next);
}
}
} // namespace CodeGen } // namespace CodeGen
} // namespace Luau } // namespace Luau

View File

@ -61,6 +61,8 @@ void translateInstSetGlobal(IrBuilder& build, const Instruction* pc, int pcpos);
void translateInstConcat(IrBuilder& build, const Instruction* pc, int pcpos); void translateInstConcat(IrBuilder& build, const Instruction* pc, int pcpos);
void translateInstCapture(IrBuilder& build, const Instruction* pc, int pcpos); void translateInstCapture(IrBuilder& build, const Instruction* pc, int pcpos);
void translateInstNamecall(IrBuilder& build, const Instruction* pc, int pcpos); void translateInstNamecall(IrBuilder& build, const Instruction* pc, int pcpos);
void translateInstAndX(IrBuilder& build, const Instruction* pc, int pcpos, IrOp c);
void translateInstOrX(IrBuilder& build, const Instruction* pc, int pcpos, IrOp c);
} // namespace CodeGen } // namespace CodeGen
} // namespace Luau } // namespace Luau

View File

@ -14,6 +14,134 @@ namespace Luau
namespace CodeGen namespace CodeGen
{ {
IrValueKind getCmdValueKind(IrCmd cmd)
{
switch (cmd)
{
case IrCmd::NOP:
return IrValueKind::None;
case IrCmd::LOAD_TAG:
return IrValueKind::Tag;
case IrCmd::LOAD_POINTER:
return IrValueKind::Pointer;
case IrCmd::LOAD_DOUBLE:
return IrValueKind::Double;
case IrCmd::LOAD_INT:
return IrValueKind::Int;
case IrCmd::LOAD_TVALUE:
case IrCmd::LOAD_NODE_VALUE_TV:
return IrValueKind::Tvalue;
case IrCmd::LOAD_ENV:
case IrCmd::GET_ARR_ADDR:
case IrCmd::GET_SLOT_NODE_ADDR:
case IrCmd::GET_HASH_NODE_ADDR:
return IrValueKind::Pointer;
case IrCmd::STORE_TAG:
case IrCmd::STORE_POINTER:
case IrCmd::STORE_DOUBLE:
case IrCmd::STORE_INT:
case IrCmd::STORE_VECTOR:
case IrCmd::STORE_TVALUE:
case IrCmd::STORE_NODE_VALUE_TV:
return IrValueKind::None;
case IrCmd::ADD_INT:
case IrCmd::SUB_INT:
return IrValueKind::Int;
case IrCmd::ADD_NUM:
case IrCmd::SUB_NUM:
case IrCmd::MUL_NUM:
case IrCmd::DIV_NUM:
case IrCmd::MOD_NUM:
case IrCmd::POW_NUM:
case IrCmd::MIN_NUM:
case IrCmd::MAX_NUM:
case IrCmd::UNM_NUM:
case IrCmd::FLOOR_NUM:
case IrCmd::CEIL_NUM:
case IrCmd::ROUND_NUM:
case IrCmd::SQRT_NUM:
case IrCmd::ABS_NUM:
return IrValueKind::Double;
case IrCmd::NOT_ANY:
return IrValueKind::Int;
case IrCmd::JUMP:
case IrCmd::JUMP_IF_TRUTHY:
case IrCmd::JUMP_IF_FALSY:
case IrCmd::JUMP_EQ_TAG:
case IrCmd::JUMP_EQ_INT:
case IrCmd::JUMP_EQ_POINTER:
case IrCmd::JUMP_CMP_NUM:
case IrCmd::JUMP_CMP_ANY:
case IrCmd::JUMP_SLOT_MATCH:
return IrValueKind::None;
case IrCmd::TABLE_LEN:
return IrValueKind::Double;
case IrCmd::NEW_TABLE:
case IrCmd::DUP_TABLE:
return IrValueKind::Pointer;
case IrCmd::TRY_NUM_TO_INDEX:
return IrValueKind::Int;
case IrCmd::TRY_CALL_FASTGETTM:
return IrValueKind::Pointer;
case IrCmd::INT_TO_NUM:
return IrValueKind::Double;
case IrCmd::ADJUST_STACK_TO_REG:
case IrCmd::ADJUST_STACK_TO_TOP:
return IrValueKind::None;
case IrCmd::FASTCALL:
return IrValueKind::None;
case IrCmd::INVOKE_FASTCALL:
return IrValueKind::Int;
case IrCmd::CHECK_FASTCALL_RES:
case IrCmd::DO_ARITH:
case IrCmd::DO_LEN:
case IrCmd::GET_TABLE:
case IrCmd::SET_TABLE:
case IrCmd::GET_IMPORT:
case IrCmd::CONCAT:
case IrCmd::GET_UPVALUE:
case IrCmd::SET_UPVALUE:
case IrCmd::PREPARE_FORN:
case IrCmd::CHECK_TAG:
case IrCmd::CHECK_READONLY:
case IrCmd::CHECK_NO_METATABLE:
case IrCmd::CHECK_SAFE_ENV:
case IrCmd::CHECK_ARRAY_SIZE:
case IrCmd::CHECK_SLOT_MATCH:
case IrCmd::CHECK_NODE_NO_NEXT:
case IrCmd::INTERRUPT:
case IrCmd::CHECK_GC:
case IrCmd::BARRIER_OBJ:
case IrCmd::BARRIER_TABLE_BACK:
case IrCmd::BARRIER_TABLE_FORWARD:
case IrCmd::SET_SAVEDPC:
case IrCmd::CLOSE_UPVALS:
case IrCmd::CAPTURE:
case IrCmd::SETLIST:
case IrCmd::CALL:
case IrCmd::RETURN:
case IrCmd::FORGLOOP:
case IrCmd::FORGLOOP_FALLBACK:
case IrCmd::FORGPREP_XNEXT_FALLBACK:
case IrCmd::COVERAGE:
case IrCmd::FALLBACK_GETGLOBAL:
case IrCmd::FALLBACK_SETGLOBAL:
case IrCmd::FALLBACK_GETTABLEKS:
case IrCmd::FALLBACK_SETTABLEKS:
case IrCmd::FALLBACK_NAMECALL:
case IrCmd::FALLBACK_PREPVARARGS:
case IrCmd::FALLBACK_GETVARARGS:
case IrCmd::FALLBACK_NEWCLOSURE:
case IrCmd::FALLBACK_DUPCLOSURE:
case IrCmd::FALLBACK_FORGPREP:
return IrValueKind::None;
case IrCmd::SUBSTITUTE:
return IrValueKind::Unknown;
}
LUAU_UNREACHABLE();
}
static void removeInstUse(IrFunction& function, uint32_t instIdx) static void removeInstUse(IrFunction& function, uint32_t instIdx)
{ {
IrInst& inst = function.instructions[instIdx]; IrInst& inst = function.instructions[instIdx];

View File

@ -45,6 +45,7 @@ void initFallbackTable(NativeState& data)
CODEGEN_SET_FALLBACK(LOP_BREAK, 0); CODEGEN_SET_FALLBACK(LOP_BREAK, 0);
// Fallbacks that are called from partial implementation of an instruction // Fallbacks that are called from partial implementation of an instruction
// TODO: these fallbacks should be replaced with special functions that exclude the (redundantly executed) fast path from the fallback
CODEGEN_SET_FALLBACK(LOP_GETGLOBAL, 0); CODEGEN_SET_FALLBACK(LOP_GETGLOBAL, 0);
CODEGEN_SET_FALLBACK(LOP_SETGLOBAL, 0); CODEGEN_SET_FALLBACK(LOP_SETGLOBAL, 0);
CODEGEN_SET_FALLBACK(LOP_GETTABLEKS, 0); CODEGEN_SET_FALLBACK(LOP_GETTABLEKS, 0);

View File

@ -96,20 +96,17 @@ struct ConstPropState
void invalidateTag(IrOp regOp) void invalidateTag(IrOp regOp)
{ {
LUAU_ASSERT(regOp.kind == IrOpKind::VmReg); invalidate(regs[vmRegOp(regOp)], /* invalidateTag */ true, /* invalidateValue */ false);
invalidate(regs[regOp.index], /* invalidateTag */ true, /* invalidateValue */ false);
} }
void invalidateValue(IrOp regOp) void invalidateValue(IrOp regOp)
{ {
LUAU_ASSERT(regOp.kind == IrOpKind::VmReg); invalidate(regs[vmRegOp(regOp)], /* invalidateTag */ false, /* invalidateValue */ true);
invalidate(regs[regOp.index], /* invalidateTag */ false, /* invalidateValue */ true);
} }
void invalidate(IrOp regOp) void invalidate(IrOp regOp)
{ {
LUAU_ASSERT(regOp.kind == IrOpKind::VmReg); invalidate(regs[vmRegOp(regOp)], /* invalidateTag */ true, /* invalidateValue */ true);
invalidate(regs[regOp.index], /* invalidateTag */ true, /* invalidateValue */ true);
} }
void invalidateRegistersFrom(int firstReg) void invalidateRegistersFrom(int firstReg)
@ -156,17 +153,16 @@ struct ConstPropState
void createRegLink(uint32_t instIdx, IrOp regOp) void createRegLink(uint32_t instIdx, IrOp regOp)
{ {
LUAU_ASSERT(regOp.kind == IrOpKind::VmReg);
LUAU_ASSERT(!instLink.contains(instIdx)); LUAU_ASSERT(!instLink.contains(instIdx));
instLink[instIdx] = RegisterLink{uint8_t(regOp.index), regs[regOp.index].version}; instLink[instIdx] = RegisterLink{uint8_t(vmRegOp(regOp)), regs[vmRegOp(regOp)].version};
} }
RegisterInfo* tryGetRegisterInfo(IrOp op) RegisterInfo* tryGetRegisterInfo(IrOp op)
{ {
if (op.kind == IrOpKind::VmReg) if (op.kind == IrOpKind::VmReg)
{ {
maxReg = int(op.index) > maxReg ? int(op.index) : maxReg; maxReg = vmRegOp(op) > maxReg ? vmRegOp(op) : maxReg;
return &regs[op.index]; return &regs[vmRegOp(op)];
} }
if (RegisterLink* link = tryGetRegLink(op)) if (RegisterLink* link = tryGetRegLink(op))
@ -368,6 +364,9 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
} }
} }
break; break;
case IrCmd::STORE_VECTOR:
state.invalidateValue(inst.a);
break;
case IrCmd::STORE_TVALUE: case IrCmd::STORE_TVALUE:
if (inst.a.kind == IrOpKind::VmReg) if (inst.a.kind == IrOpKind::VmReg)
{ {
@ -503,15 +502,9 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
} }
} }
break; break;
case IrCmd::AND:
case IrCmd::ANDK:
case IrCmd::OR:
case IrCmd::ORK:
state.invalidate(inst.a);
break;
case IrCmd::FASTCALL: case IrCmd::FASTCALL:
case IrCmd::INVOKE_FASTCALL: case IrCmd::INVOKE_FASTCALL:
handleBuiltinEffects(state, LuauBuiltinFunction(function.uintOp(inst.a)), inst.b.index, function.intOp(inst.f)); handleBuiltinEffects(state, LuauBuiltinFunction(function.uintOp(inst.a)), vmRegOp(inst.b), function.intOp(inst.f));
break; break;
// These instructions don't have an effect on register/memory state we are tracking // These instructions don't have an effect on register/memory state we are tracking
@ -590,7 +583,7 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
state.invalidateUserCall(); state.invalidateUserCall();
break; break;
case IrCmd::CONCAT: case IrCmd::CONCAT:
state.invalidateRegisterRange(inst.a.index, function.uintOp(inst.b)); state.invalidateRegisterRange(vmRegOp(inst.a), function.uintOp(inst.b));
state.invalidateUserCall(); // TODO: if only strings and numbers are concatenated, there will be no user calls state.invalidateUserCall(); // TODO: if only strings and numbers are concatenated, there will be no user calls
break; break;
case IrCmd::PREPARE_FORN: case IrCmd::PREPARE_FORN:
@ -605,14 +598,14 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
state.invalidateUserCall(); state.invalidateUserCall();
break; break;
case IrCmd::CALL: case IrCmd::CALL:
state.invalidateRegistersFrom(inst.a.index); state.invalidateRegistersFrom(vmRegOp(inst.a));
state.invalidateUserCall(); state.invalidateUserCall();
break; break;
case IrCmd::FORGLOOP: case IrCmd::FORGLOOP:
state.invalidateRegistersFrom(inst.a.index + 2); // Rn and Rn+1 are not modified state.invalidateRegistersFrom(vmRegOp(inst.a) + 2); // Rn and Rn+1 are not modified
break; break;
case IrCmd::FORGLOOP_FALLBACK: case IrCmd::FORGLOOP_FALLBACK:
state.invalidateRegistersFrom(inst.a.index + 2); // Rn and Rn+1 are not modified state.invalidateRegistersFrom(vmRegOp(inst.a) + 2); // Rn and Rn+1 are not modified
state.invalidateUserCall(); state.invalidateUserCall();
break; break;
case IrCmd::FORGPREP_XNEXT_FALLBACK: case IrCmd::FORGPREP_XNEXT_FALLBACK:
@ -633,14 +626,14 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
state.invalidateUserCall(); state.invalidateUserCall();
break; break;
case IrCmd::FALLBACK_NAMECALL: case IrCmd::FALLBACK_NAMECALL:
state.invalidate(IrOp{inst.b.kind, inst.b.index + 0u}); state.invalidate(IrOp{inst.b.kind, vmRegOp(inst.b) + 0u});
state.invalidate(IrOp{inst.b.kind, inst.b.index + 1u}); state.invalidate(IrOp{inst.b.kind, vmRegOp(inst.b) + 1u});
state.invalidateUserCall(); state.invalidateUserCall();
break; break;
case IrCmd::FALLBACK_PREPVARARGS: case IrCmd::FALLBACK_PREPVARARGS:
break; break;
case IrCmd::FALLBACK_GETVARARGS: case IrCmd::FALLBACK_GETVARARGS:
state.invalidateRegistersFrom(inst.b.index); state.invalidateRegistersFrom(vmRegOp(inst.b));
break; break;
case IrCmd::FALLBACK_NEWCLOSURE: case IrCmd::FALLBACK_NEWCLOSURE:
state.invalidate(inst.b); state.invalidate(inst.b);
@ -649,9 +642,9 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
state.invalidate(inst.b); state.invalidate(inst.b);
break; break;
case IrCmd::FALLBACK_FORGPREP: case IrCmd::FALLBACK_FORGPREP:
state.invalidate(IrOp{inst.b.kind, inst.b.index + 0u}); state.invalidate(IrOp{inst.b.kind, vmRegOp(inst.b) + 0u});
state.invalidate(IrOp{inst.b.kind, inst.b.index + 1u}); state.invalidate(IrOp{inst.b.kind, vmRegOp(inst.b) + 1u});
state.invalidate(IrOp{inst.b.kind, inst.b.index + 2u}); state.invalidate(IrOp{inst.b.kind, vmRegOp(inst.b) + 2u});
break; break;
} }
} }

View File

@ -29,7 +29,7 @@ enum lua_Status
LUA_OK = 0, LUA_OK = 0,
LUA_YIELD, LUA_YIELD,
LUA_ERRRUN, LUA_ERRRUN,
LUA_ERRSYNTAX, LUA_ERRSYNTAX, // legacy error code, preserved for compatibility
LUA_ERRMEM, LUA_ERRMEM,
LUA_ERRERR, LUA_ERRERR,
LUA_BREAK, // yielded for a debug breakpoint LUA_BREAK, // yielded for a debug breakpoint

View File

@ -17,6 +17,8 @@
#include <string.h> #include <string.h>
LUAU_FASTFLAGVARIABLE(LuauBetterOOMHandling, false)
/* /*
** {====================================================== ** {======================================================
** Error-recovery functions ** Error-recovery functions
@ -79,22 +81,17 @@ public:
const char* what() const throw() override const char* what() const throw() override
{ {
// LUA_ERRRUN/LUA_ERRSYNTAX pass an object on the stack which is intended to describe the error. // LUA_ERRRUN passes error object on the stack
if (status == LUA_ERRRUN || status == LUA_ERRSYNTAX) if (status == LUA_ERRRUN || (status == LUA_ERRSYNTAX && !FFlag::LuauBetterOOMHandling))
{
// Conversion to a string could still fail. For example if a user passes a non-string/non-number argument to `error()`.
if (const char* str = lua_tostring(L, -1)) if (const char* str = lua_tostring(L, -1))
{
return str; return str;
}
}
switch (status) switch (status)
{ {
case LUA_ERRRUN: case LUA_ERRRUN:
return "lua_exception: LUA_ERRRUN (no string/number provided as description)"; return "lua_exception: runtime error";
case LUA_ERRSYNTAX: case LUA_ERRSYNTAX:
return "lua_exception: LUA_ERRSYNTAX (no string/number provided as description)"; return "lua_exception: syntax error";
case LUA_ERRMEM: case LUA_ERRMEM:
return "lua_exception: " LUA_MEMERRMSG; return "lua_exception: " LUA_MEMERRMSG;
case LUA_ERRERR: case LUA_ERRERR:
@ -550,19 +547,42 @@ int luaD_pcall(lua_State* L, Pfunc func, void* u, ptrdiff_t old_top, ptrdiff_t e
int status = luaD_rawrunprotected(L, func, u); int status = luaD_rawrunprotected(L, func, u);
if (status != 0) if (status != 0)
{ {
int errstatus = status;
// call user-defined error function (used in xpcall) // call user-defined error function (used in xpcall)
if (ef) if (ef)
{ {
// if errfunc fails, we fail with "error in error handling" if (FFlag::LuauBetterOOMHandling)
if (luaD_rawrunprotected(L, callerrfunc, restorestack(L, ef)) != 0) {
status = LUA_ERRERR; // push error object to stack top if it's not already there
if (status != LUA_ERRRUN)
seterrorobj(L, status, L->top);
// if errfunc fails, we fail with "error in error handling" or "not enough memory"
int err = luaD_rawrunprotected(L, callerrfunc, restorestack(L, ef));
// in general we preserve the status, except for cases when the error handler fails
// out of memory is treated specially because it's common for it to be cascading, in which case we preserve the code
if (err == 0)
errstatus = LUA_ERRRUN;
else if (status == LUA_ERRMEM && err == LUA_ERRMEM)
errstatus = LUA_ERRMEM;
else
errstatus = status = LUA_ERRERR;
}
else
{
// if errfunc fails, we fail with "error in error handling"
if (luaD_rawrunprotected(L, callerrfunc, restorestack(L, ef)) != 0)
status = LUA_ERRERR;
}
} }
// since the call failed with an error, we might have to reset the 'active' thread state // since the call failed with an error, we might have to reset the 'active' thread state
if (!oldactive) if (!oldactive)
L->isactive = false; L->isactive = false;
// Restore nCcalls before calling the debugprotectederror callback which may rely on the proper value to have been restored. // restore nCcalls before calling the debugprotectederror callback which may rely on the proper value to have been restored.
L->nCcalls = oldnCcalls; L->nCcalls = oldnCcalls;
// an error occurred, check if we have a protected error callback // an error occurred, check if we have a protected error callback
@ -577,7 +597,7 @@ int luaD_pcall(lua_State* L, Pfunc func, void* u, ptrdiff_t old_top, ptrdiff_t e
StkId oldtop = restorestack(L, old_top); StkId oldtop = restorestack(L, old_top);
luaF_close(L, oldtop); // close eventual pending closures luaF_close(L, oldtop); // close eventual pending closures
seterrorobj(L, status, oldtop); seterrorobj(L, FFlag::LuauBetterOOMHandling ? errstatus : status, oldtop);
L->ci = restoreci(L, old_ci); L->ci = restoreci(L, old_ci);
L->base = L->ci->base; L->base = L->ci->base;
restore_stack_limit(L); restore_stack_limit(L);

View File

@ -10,7 +10,7 @@
#include "ldebug.h" #include "ldebug.h"
#include "lvm.h" #include "lvm.h"
LUAU_FASTFLAGVARIABLE(LuauOptimizedSort, false) LUAU_FASTFLAGVARIABLE(LuauIntrosort, false)
static int foreachi(lua_State* L) static int foreachi(lua_State* L)
{ {
@ -298,120 +298,6 @@ static int tunpack(lua_State* L)
return (int)n; return (int)n;
} }
/*
** {======================================================
** Quicksort
** (based on `Algorithms in MODULA-3', Robert Sedgewick;
** Addison-Wesley, 1993.)
*/
static void set2(lua_State* L, int i, int j)
{
LUAU_ASSERT(!FFlag::LuauOptimizedSort);
lua_rawseti(L, 1, i);
lua_rawseti(L, 1, j);
}
static int sort_comp(lua_State* L, int a, int b)
{
LUAU_ASSERT(!FFlag::LuauOptimizedSort);
if (!lua_isnil(L, 2))
{ // function?
int res;
lua_pushvalue(L, 2);
lua_pushvalue(L, a - 1); // -1 to compensate function
lua_pushvalue(L, b - 2); // -2 to compensate function and `a'
lua_call(L, 2, 1);
res = lua_toboolean(L, -1);
lua_pop(L, 1);
return res;
}
else // a < b?
return lua_lessthan(L, a, b);
}
static void auxsort(lua_State* L, int l, int u)
{
LUAU_ASSERT(!FFlag::LuauOptimizedSort);
while (l < u)
{ // for tail recursion
int i, j;
// sort elements a[l], a[(l+u)/2] and a[u]
lua_rawgeti(L, 1, l);
lua_rawgeti(L, 1, u);
if (sort_comp(L, -1, -2)) // a[u] < a[l]?
set2(L, l, u); // swap a[l] - a[u]
else
lua_pop(L, 2);
if (u - l == 1)
break; // only 2 elements
i = (l + u) / 2;
lua_rawgeti(L, 1, i);
lua_rawgeti(L, 1, l);
if (sort_comp(L, -2, -1)) // a[i]<a[l]?
set2(L, i, l);
else
{
lua_pop(L, 1); // remove a[l]
lua_rawgeti(L, 1, u);
if (sort_comp(L, -1, -2)) // a[u]<a[i]?
set2(L, i, u);
else
lua_pop(L, 2);
}
if (u - l == 2)
break; // only 3 elements
lua_rawgeti(L, 1, i); // Pivot
lua_pushvalue(L, -1);
lua_rawgeti(L, 1, u - 1);
set2(L, i, u - 1);
// a[l] <= P == a[u-1] <= a[u], only need to sort from l+1 to u-2
i = l;
j = u - 1;
for (;;)
{ // invariant: a[l..i] <= P <= a[j..u]
// repeat ++i until a[i] >= P
while (lua_rawgeti(L, 1, ++i), sort_comp(L, -1, -2))
{
if (i >= u)
luaL_error(L, "invalid order function for sorting");
lua_pop(L, 1); // remove a[i]
}
// repeat --j until a[j] <= P
while (lua_rawgeti(L, 1, --j), sort_comp(L, -3, -1))
{
if (j <= l)
luaL_error(L, "invalid order function for sorting");
lua_pop(L, 1); // remove a[j]
}
if (j < i)
{
lua_pop(L, 3); // pop pivot, a[i], a[j]
break;
}
set2(L, i, j);
}
lua_rawgeti(L, 1, u - 1);
lua_rawgeti(L, 1, i);
set2(L, u - 1, i); // swap pivot (a[u-1]) with a[i]
// a[l..i-1] <= a[i] == P <= a[i+1..u]
// adjust so that smaller half is in [j..i] and larger one in [l..u]
if (i - l < u - i)
{
j = l;
i = i - 1;
l = i + 2;
}
else
{
j = i + 1;
i = u;
u = j - 2;
}
auxsort(L, j, i); // call recursively the smaller one
} // repeat the routine for the larger one
}
typedef int (*SortPredicate)(lua_State* L, const TValue* l, const TValue* r); typedef int (*SortPredicate)(lua_State* L, const TValue* l, const TValue* r);
static int sort_func(lua_State* L, const TValue* l, const TValue* r) static int sort_func(lua_State* L, const TValue* l, const TValue* r)
@ -456,30 +342,77 @@ inline int sort_less(lua_State* L, Table* t, int i, int j, SortPredicate pred)
return res; return res;
} }
static void sort_rec(lua_State* L, Table* t, int l, int u, SortPredicate pred) static void sort_siftheap(lua_State* L, Table* t, int l, int u, SortPredicate pred, int root)
{
LUAU_ASSERT(l <= u);
int count = u - l + 1;
// process all elements with two children
while (root * 2 + 2 < count)
{
int left = root * 2 + 1, right = root * 2 + 2;
int next = root;
next = sort_less(L, t, l + next, l + left, pred) ? left : next;
next = sort_less(L, t, l + next, l + right, pred) ? right : next;
if (next == root)
break;
sort_swap(L, t, l + root, l + next);
root = next;
}
// process last element if it has just one child
int lastleft = root * 2 + 1;
if (lastleft == count - 1 && sort_less(L, t, l + root, l + lastleft, pred))
sort_swap(L, t, l + root, l + lastleft);
}
static void sort_heap(lua_State* L, Table* t, int l, int u, SortPredicate pred)
{
LUAU_ASSERT(l <= u);
int count = u - l + 1;
for (int i = count / 2 - 1; i >= 0; --i)
sort_siftheap(L, t, l, u, pred, i);
for (int i = count - 1; i > 0; --i)
{
sort_swap(L, t, l, l + i);
sort_siftheap(L, t, l, l + i - 1, pred, 0);
}
}
static void sort_rec(lua_State* L, Table* t, int l, int u, int limit, SortPredicate pred)
{ {
// sort range [l..u] (inclusive, 0-based) // sort range [l..u] (inclusive, 0-based)
while (l < u) while (l < u)
{ {
int i, j; // if the limit has been reached, quick sort is going over the permitted nlogn complexity, so we fall back to heap sort
if (FFlag::LuauIntrosort && limit == 0)
return sort_heap(L, t, l, u, pred);
// sort elements a[l], a[(l+u)/2] and a[u] // sort elements a[l], a[(l+u)/2] and a[u]
// note: this simultaneously acts as a small sort and a median selector
if (sort_less(L, t, u, l, pred)) // a[u] < a[l]? if (sort_less(L, t, u, l, pred)) // a[u] < a[l]?
sort_swap(L, t, u, l); // swap a[l] - a[u] sort_swap(L, t, u, l); // swap a[l] - a[u]
if (u - l == 1) if (u - l == 1)
break; // only 2 elements break; // only 2 elements
i = l + ((u - l) >> 1); // midpoint int m = l + ((u - l) >> 1); // midpoint
if (sort_less(L, t, i, l, pred)) // a[i]<a[l]? if (sort_less(L, t, m, l, pred)) // a[m]<a[l]?
sort_swap(L, t, i, l); sort_swap(L, t, m, l);
else if (sort_less(L, t, u, i, pred)) // a[u]<a[i]? else if (sort_less(L, t, u, m, pred)) // a[u]<a[m]?
sort_swap(L, t, i, u); sort_swap(L, t, m, u);
if (u - l == 2) if (u - l == 2)
break; // only 3 elements break; // only 3 elements
// here l, i, u are ordered; i will become the new pivot
// here l, m, u are ordered; m will become the new pivot
int p = u - 1; int p = u - 1;
sort_swap(L, t, i, u - 1); // pivot is now (and always) at u-1 sort_swap(L, t, m, u - 1); // pivot is now (and always) at u-1
// a[l] <= P == a[u-1] <= a[u], only need to sort from l+1 to u-2 // a[l] <= P == a[u-1] <= a[u], only need to sort from l+1 to u-2
i = l; int i = l;
j = u - 1; int j = u - 1;
for (;;) for (;;)
{ // invariant: a[l..i] <= P <= a[j..u] { // invariant: a[l..i] <= P <= a[j..u]
// repeat ++i until a[i] >= P // repeat ++i until a[i] >= P
@ -498,63 +431,72 @@ static void sort_rec(lua_State* L, Table* t, int l, int u, SortPredicate pred)
break; break;
sort_swap(L, t, i, j); sort_swap(L, t, i, j);
} }
// swap pivot (a[u-1]) with a[i], which is the new midpoint
sort_swap(L, t, u - 1, i); // swap pivot a[p] with a[i], which is the new midpoint
// a[l..i-1] <= a[i] == P <= a[i+1..u] sort_swap(L, t, p, i);
// adjust so that smaller half is in [j..i] and larger one in [l..u]
if (i - l < u - i) if (FFlag::LuauIntrosort)
{ {
j = l; // adjust limit to allow 1.5 log2N recursive steps
i = i - 1; limit = (limit >> 1) + (limit >> 2);
l = i + 2;
// a[l..i-1] <= a[i] == P <= a[i+1..u]
// sort smaller half recursively; the larger half is sorted in the next loop iteration
if (i - l < u - i)
{
sort_rec(L, t, l, i - 1, limit, pred);
l = i + 1;
}
else
{
sort_rec(L, t, i + 1, u, limit, pred);
u = i - 1;
}
} }
else else
{ {
j = i + 1; // a[l..i-1] <= a[i] == P <= a[i+1..u]
i = u; // adjust so that smaller half is in [j..i] and larger one in [l..u]
u = j - 2; if (i - l < u - i)
{
j = l;
i = i - 1;
l = i + 2;
}
else
{
j = i + 1;
i = u;
u = j - 2;
}
// sort smaller half recursively; the larger half is sorted in the next loop iteration
sort_rec(L, t, j, i, limit, pred);
} }
sort_rec(L, t, j, i, pred); // call recursively the smaller one }
} // repeat the routine for the larger one
} }
static int tsort(lua_State* L) static int tsort(lua_State* L)
{ {
if (FFlag::LuauOptimizedSort) luaL_checktype(L, 1, LUA_TTABLE);
{ Table* t = hvalue(L->base);
luaL_checktype(L, 1, LUA_TTABLE); int n = luaH_getn(t);
Table* t = hvalue(L->base); if (t->readonly)
int n = luaH_getn(t); luaG_readonlyerror(L);
if (t->readonly)
luaG_readonlyerror(L);
SortPredicate pred = luaV_lessthan; SortPredicate pred = luaV_lessthan;
if (!lua_isnoneornil(L, 2)) // is there a 2nd argument? if (!lua_isnoneornil(L, 2)) // is there a 2nd argument?
{
luaL_checktype(L, 2, LUA_TFUNCTION);
pred = sort_func;
}
lua_settop(L, 2); // make sure there are two arguments
if (n > 0)
sort_rec(L, t, 0, n - 1, pred);
return 0;
}
else
{ {
luaL_checktype(L, 1, LUA_TTABLE); luaL_checktype(L, 2, LUA_TFUNCTION);
int n = lua_objlen(L, 1); pred = sort_func;
luaL_checkstack(L, 40, ""); // assume array is smaller than 2^40
if (!lua_isnoneornil(L, 2)) // is there a 2nd argument?
luaL_checktype(L, 2, LUA_TFUNCTION);
lua_settop(L, 2); // make sure there is two arguments
auxsort(L, 1, n);
return 0;
} }
lua_settop(L, 2); // make sure there are two arguments
if (n > 0)
sort_rec(L, t, 0, n - 1, n, pred);
return 0;
} }
// }======================================================
static int tcreate(lua_State* L) static int tcreate(lua_State* L)
{ {
int size = luaL_checkinteger(L, 1); int size = luaL_checkinteger(L, 1);

View File

@ -507,6 +507,8 @@ TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "AVXConversionInstructionForms")
SINGLE_COMPARE(vcvtsi2sd(xmm6, xmm11, dword[rcx + rdx]), 0xc4, 0xe1, 0x23, 0x2a, 0x34, 0x11); SINGLE_COMPARE(vcvtsi2sd(xmm6, xmm11, dword[rcx + rdx]), 0xc4, 0xe1, 0x23, 0x2a, 0x34, 0x11);
SINGLE_COMPARE(vcvtsi2sd(xmm5, xmm10, r13), 0xc4, 0xc1, 0xab, 0x2a, 0xed); SINGLE_COMPARE(vcvtsi2sd(xmm5, xmm10, r13), 0xc4, 0xc1, 0xab, 0x2a, 0xed);
SINGLE_COMPARE(vcvtsi2sd(xmm6, xmm11, qword[rcx + rdx]), 0xc4, 0xe1, 0xa3, 0x2a, 0x34, 0x11); SINGLE_COMPARE(vcvtsi2sd(xmm6, xmm11, qword[rcx + rdx]), 0xc4, 0xe1, 0xa3, 0x2a, 0x34, 0x11);
SINGLE_COMPARE(vcvtsd2ss(xmm5, xmm10, xmm11), 0xc4, 0xc1, 0x2b, 0x5a, 0xeb);
SINGLE_COMPARE(vcvtsd2ss(xmm6, xmm11, qword[rcx + rdx]), 0xc4, 0xe1, 0xa3, 0x5a, 0x34, 0x11);
} }
TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "AVXTernaryInstructionForms") TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "AVXTernaryInstructionForms")

View File

@ -85,8 +85,8 @@ struct ACFixtureImpl : BaseType
{ {
GlobalTypes& globals = this->frontend.globalsForAutocomplete; GlobalTypes& globals = this->frontend.globalsForAutocomplete;
unfreeze(globals.globalTypes); unfreeze(globals.globalTypes);
LoadDefinitionFileResult result = LoadDefinitionFileResult result = this->frontend.loadDefinitionFile(
loadDefinitionFile(this->frontend.typeChecker, globals, globals.globalScope, source, "@test", /* captureComments */ false); globals, globals.globalScope, source, "@test", /* captureComments */ false, /* typeCheckForAutocomplete */ true);
freeze(globals.globalTypes); freeze(globals.globalTypes);
REQUIRE_MESSAGE(result.success, "loadDefinition: unable to load definition file"); REQUIRE_MESSAGE(result.success, "loadDefinition: unable to load definition file");
@ -3448,8 +3448,6 @@ TEST_CASE_FIXTURE(ACFixture, "string_contents_is_available_to_callback")
TEST_CASE_FIXTURE(ACFixture, "autocomplete_response_perf1" * doctest::timeout(0.5)) TEST_CASE_FIXTURE(ACFixture, "autocomplete_response_perf1" * doctest::timeout(0.5))
{ {
ScopedFastFlag luauAutocompleteSkipNormalization{"LuauAutocompleteSkipNormalization", true};
// Build a function type with a large overload set // Build a function type with a large overload set
const int parts = 100; const int parts = 100;
std::string source; std::string source;

View File

@ -9,6 +9,7 @@
#include "Luau/StringUtils.h" #include "Luau/StringUtils.h"
#include "Luau/BytecodeBuilder.h" #include "Luau/BytecodeBuilder.h"
#include "Luau/CodeGen.h" #include "Luau/CodeGen.h"
#include "Luau/Frontend.h"
#include "doctest.h" #include "doctest.h"
#include "ScopedFlags.h" #include "ScopedFlags.h"
@ -243,6 +244,24 @@ static StateRef runConformance(const char* name, void (*setup)(lua_State* L) = n
return globalState; return globalState;
} }
static void* limitedRealloc(void* ud, void* ptr, size_t osize, size_t nsize)
{
if (nsize == 0)
{
free(ptr);
return nullptr;
}
else if (nsize > 8 * 1024 * 1024)
{
// For testing purposes return null for large allocations so we can generate errors related to memory allocation failures
return nullptr;
}
else
{
return realloc(ptr, nsize);
}
}
TEST_SUITE_BEGIN("Conformance"); TEST_SUITE_BEGIN("Conformance");
TEST_CASE("Assert") TEST_CASE("Assert")
@ -381,6 +400,8 @@ static int cxxthrow(lua_State* L)
TEST_CASE("PCall") TEST_CASE("PCall")
{ {
ScopedFastFlag sff("LuauBetterOOMHandling", true);
runConformance("pcall.lua", [](lua_State* L) { runConformance("pcall.lua", [](lua_State* L) {
lua_pushcfunction(L, cxxthrow, "cxxthrow"); lua_pushcfunction(L, cxxthrow, "cxxthrow");
lua_setglobal(L, "cxxthrow"); lua_setglobal(L, "cxxthrow");
@ -395,7 +416,7 @@ TEST_CASE("PCall")
}, },
"resumeerror"); "resumeerror");
lua_setglobal(L, "resumeerror"); lua_setglobal(L, "resumeerror");
}); }, nullptr, lua_newstate(limitedRealloc, nullptr));
} }
TEST_CASE("Pack") TEST_CASE("Pack")
@ -501,17 +522,15 @@ TEST_CASE("Types")
{ {
runConformance("types.lua", [](lua_State* L) { runConformance("types.lua", [](lua_State* L) {
Luau::NullModuleResolver moduleResolver; Luau::NullModuleResolver moduleResolver;
Luau::InternalErrorReporter iceHandler; Luau::NullFileResolver fileResolver;
Luau::BuiltinTypes builtinTypes; Luau::NullConfigResolver configResolver;
Luau::GlobalTypes globals{Luau::NotNull{&builtinTypes}}; Luau::Frontend frontend{&fileResolver, &configResolver};
Luau::TypeChecker env(globals.globalScope, &moduleResolver, Luau::NotNull{&builtinTypes}, &iceHandler); Luau::registerBuiltinGlobals(frontend, frontend.globals);
Luau::freeze(frontend.globals.globalTypes);
Luau::registerBuiltinGlobals(env, globals);
Luau::freeze(globals.globalTypes);
lua_newtable(L); lua_newtable(L);
for (const auto& [name, binding] : globals.globalScope->bindings) for (const auto& [name, binding] : frontend.globals.globalScope->bindings)
{ {
populateRTTI(L, binding.typeId); populateRTTI(L, binding.typeId);
lua_setfield(L, -2, toString(name).c_str()); lua_setfield(L, -2, toString(name).c_str());
@ -882,7 +901,7 @@ TEST_CASE("ApiIter")
TEST_CASE("ApiCalls") TEST_CASE("ApiCalls")
{ {
StateRef globalState = runConformance("apicalls.lua"); StateRef globalState = runConformance("apicalls.lua", nullptr, nullptr, lua_newstate(limitedRealloc, nullptr));
lua_State* L = globalState.get(); lua_State* L = globalState.get();
// lua_call // lua_call
@ -981,6 +1000,55 @@ TEST_CASE("ApiCalls")
CHECK(lua_tonumber(L, -1) == 4); CHECK(lua_tonumber(L, -1) == 4);
lua_pop(L, 1); lua_pop(L, 1);
} }
ScopedFastFlag sff("LuauBetterOOMHandling", true);
// lua_pcall on OOM
{
lua_getfield(L, LUA_GLOBALSINDEX, "largealloc");
int res = lua_pcall(L, 0, 0, 0);
CHECK(res == LUA_ERRMEM);
}
// lua_pcall on OOM with an error handler
{
lua_getfield(L, LUA_GLOBALSINDEX, "oops");
lua_getfield(L, LUA_GLOBALSINDEX, "largealloc");
int res = lua_pcall(L, 0, 1, -2);
CHECK(res == LUA_ERRMEM);
CHECK((lua_isstring(L, -1) && strcmp(lua_tostring(L, -1), "oops") == 0));
lua_pop(L, 1);
}
// lua_pcall on OOM with an error handler that errors
{
lua_getfield(L, LUA_GLOBALSINDEX, "error");
lua_getfield(L, LUA_GLOBALSINDEX, "largealloc");
int res = lua_pcall(L, 0, 1, -2);
CHECK(res == LUA_ERRERR);
CHECK((lua_isstring(L, -1) && strcmp(lua_tostring(L, -1), "error in error handling") == 0));
lua_pop(L, 1);
}
// lua_pcall on OOM with an error handler that OOMs
{
lua_getfield(L, LUA_GLOBALSINDEX, "largealloc");
lua_getfield(L, LUA_GLOBALSINDEX, "largealloc");
int res = lua_pcall(L, 0, 1, -2);
CHECK(res == LUA_ERRMEM);
CHECK((lua_isstring(L, -1) && strcmp(lua_tostring(L, -1), "not enough memory") == 0));
lua_pop(L, 1);
}
// lua_pcall on error with an error handler that OOMs
{
lua_getfield(L, LUA_GLOBALSINDEX, "largealloc");
lua_getfield(L, LUA_GLOBALSINDEX, "error");
int res = lua_pcall(L, 0, 1, -2);
CHECK(res == LUA_ERRERR);
CHECK((lua_isstring(L, -1) && strcmp(lua_tostring(L, -1), "error in error handling") == 0));
lua_pop(L, 1);
}
} }
TEST_CASE("ApiAtoms") TEST_CASE("ApiAtoms")
@ -1051,26 +1119,7 @@ TEST_CASE("ExceptionObject")
return ExceptionResult{false, ""}; return ExceptionResult{false, ""};
}; };
auto reallocFunc = [](void* /*ud*/, void* ptr, size_t /*osize*/, size_t nsize) -> void* { StateRef globalState = runConformance("exceptions.lua", nullptr, nullptr, lua_newstate(limitedRealloc, nullptr));
if (nsize == 0)
{
free(ptr);
return nullptr;
}
else if (nsize > 512 * 1024)
{
// For testing purposes return null for large allocations
// so we can generate exceptions related to memory allocation
// failures.
return nullptr;
}
else
{
return realloc(ptr, nsize);
}
};
StateRef globalState = runConformance("exceptions.lua", nullptr, nullptr, lua_newstate(reallocFunc, nullptr));
lua_State* L = globalState.get(); lua_State* L = globalState.get();
{ {

View File

@ -506,7 +506,8 @@ void Fixture::validateErrors(const std::vector<Luau::TypeError>& errors)
LoadDefinitionFileResult Fixture::loadDefinition(const std::string& source) LoadDefinitionFileResult Fixture::loadDefinition(const std::string& source)
{ {
unfreeze(frontend.globals.globalTypes); unfreeze(frontend.globals.globalTypes);
LoadDefinitionFileResult result = frontend.loadDefinitionFile(source, "@test", /* captureComments */ false); LoadDefinitionFileResult result =
frontend.loadDefinitionFile(frontend.globals, frontend.globals.globalScope, source, "@test", /* captureComments */ false);
freeze(frontend.globals.globalTypes); freeze(frontend.globals.globalTypes);
if (result.module) if (result.module)
@ -521,9 +522,9 @@ BuiltinsFixture::BuiltinsFixture(bool freeze, bool prepareAutocomplete)
Luau::unfreeze(frontend.globals.globalTypes); Luau::unfreeze(frontend.globals.globalTypes);
Luau::unfreeze(frontend.globalsForAutocomplete.globalTypes); Luau::unfreeze(frontend.globalsForAutocomplete.globalTypes);
registerBuiltinGlobals(frontend); registerBuiltinGlobals(frontend, frontend.globals);
if (prepareAutocomplete) if (prepareAutocomplete)
registerBuiltinGlobals(frontend.typeCheckerForAutocomplete, frontend.globalsForAutocomplete); registerBuiltinGlobals(frontend, frontend.globalsForAutocomplete, /*typeCheckForAutocomplete*/ true);
registerTestTypes(); registerTestTypes();
Luau::freeze(frontend.globals.globalTypes); Luau::freeze(frontend.globals.globalTypes);
@ -594,8 +595,12 @@ void registerHiddenTypes(Frontend* frontend)
TypeId t = globals.globalTypes.addType(GenericType{"T"}); TypeId t = globals.globalTypes.addType(GenericType{"T"});
GenericTypeDefinition genericT{t}; GenericTypeDefinition genericT{t};
TypeId u = globals.globalTypes.addType(GenericType{"U"});
GenericTypeDefinition genericU{u};
ScopePtr globalScope = globals.globalScope; ScopePtr globalScope = globals.globalScope;
globalScope->exportedTypeBindings["Not"] = TypeFun{{genericT}, globals.globalTypes.addType(NegationType{t})}; globalScope->exportedTypeBindings["Not"] = TypeFun{{genericT}, globals.globalTypes.addType(NegationType{t})};
globalScope->exportedTypeBindings["Mt"] = TypeFun{{genericT, genericU}, globals.globalTypes.addType(MetatableType{t, u})};
globalScope->exportedTypeBindings["fun"] = TypeFun{{}, frontend->builtinTypes->functionType}; globalScope->exportedTypeBindings["fun"] = TypeFun{{}, frontend->builtinTypes->functionType};
globalScope->exportedTypeBindings["cls"] = TypeFun{{}, frontend->builtinTypes->classType}; globalScope->exportedTypeBindings["cls"] = TypeFun{{}, frontend->builtinTypes->classType};
globalScope->exportedTypeBindings["err"] = TypeFun{{}, frontend->builtinTypes->errorType}; globalScope->exportedTypeBindings["err"] = TypeFun{{}, frontend->builtinTypes->errorType};

View File

@ -94,7 +94,6 @@ struct Fixture
TypeId requireTypeAlias(const std::string& name); TypeId requireTypeAlias(const std::string& name);
ScopedFastFlag sff_DebugLuauFreezeArena; ScopedFastFlag sff_DebugLuauFreezeArena;
ScopedFastFlag luauLintInTypecheck{"LuauLintInTypecheck", true};
TestFileResolver fileResolver; TestFileResolver fileResolver;
TestConfigResolver configResolver; TestConfigResolver configResolver;

View File

@ -877,7 +877,7 @@ TEST_CASE_FIXTURE(FrontendFixture, "environments")
ScopePtr testScope = frontend.addEnvironment("test"); ScopePtr testScope = frontend.addEnvironment("test");
unfreeze(frontend.globals.globalTypes); unfreeze(frontend.globals.globalTypes);
loadDefinitionFile(frontend.typeChecker, frontend.globals, testScope, R"( frontend.loadDefinitionFile(frontend.globals, testScope, R"(
export type Foo = number | string export type Foo = number | string
)", )",
"@test", /* captureComments */ false); "@test", /* captureComments */ false);

View File

@ -12,7 +12,7 @@ class IrCallWrapperX64Fixture
public: public:
IrCallWrapperX64Fixture() IrCallWrapperX64Fixture()
: build(/* logText */ true, ABIX64::Windows) : build(/* logText */ true, ABIX64::Windows)
, regs(function) , regs(build, function)
, callWrap(regs, build, ~0u) , callWrap(regs, build, ~0u)
{ {
} }
@ -46,8 +46,8 @@ TEST_SUITE_BEGIN("IrCallWrapperX64");
TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "SimpleRegs") TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "SimpleRegs")
{ {
ScopedRegX64 tmp1{regs, regs.takeReg(rax)}; ScopedRegX64 tmp1{regs, regs.takeReg(rax, kInvalidInstIdx)};
ScopedRegX64 tmp2{regs, regs.takeReg(rArg2)}; ScopedRegX64 tmp2{regs, regs.takeReg(rArg2, kInvalidInstIdx)};
callWrap.addArgument(SizeX64::qword, tmp1); callWrap.addArgument(SizeX64::qword, tmp1);
callWrap.addArgument(SizeX64::qword, tmp2); // Already in its place callWrap.addArgument(SizeX64::qword, tmp2); // Already in its place
callWrap.call(qword[r12]); callWrap.call(qword[r12]);
@ -60,7 +60,7 @@ TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "SimpleRegs")
TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "TrickyUse1") TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "TrickyUse1")
{ {
ScopedRegX64 tmp1{regs, regs.takeReg(rArg1)}; ScopedRegX64 tmp1{regs, regs.takeReg(rArg1, kInvalidInstIdx)};
callWrap.addArgument(SizeX64::qword, tmp1.reg); // Already in its place callWrap.addArgument(SizeX64::qword, tmp1.reg); // Already in its place
callWrap.addArgument(SizeX64::qword, tmp1.release()); callWrap.addArgument(SizeX64::qword, tmp1.release());
callWrap.call(qword[r12]); callWrap.call(qword[r12]);
@ -73,7 +73,7 @@ TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "TrickyUse1")
TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "TrickyUse2") TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "TrickyUse2")
{ {
ScopedRegX64 tmp1{regs, regs.takeReg(rArg1)}; ScopedRegX64 tmp1{regs, regs.takeReg(rArg1, kInvalidInstIdx)};
callWrap.addArgument(SizeX64::qword, qword[tmp1.reg]); callWrap.addArgument(SizeX64::qword, qword[tmp1.reg]);
callWrap.addArgument(SizeX64::qword, tmp1.release()); callWrap.addArgument(SizeX64::qword, tmp1.release());
callWrap.call(qword[r12]); callWrap.call(qword[r12]);
@ -87,8 +87,8 @@ TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "TrickyUse2")
TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "SimpleMemImm") TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "SimpleMemImm")
{ {
ScopedRegX64 tmp1{regs, regs.takeReg(rax)}; ScopedRegX64 tmp1{regs, regs.takeReg(rax, kInvalidInstIdx)};
ScopedRegX64 tmp2{regs, regs.takeReg(rsi)}; ScopedRegX64 tmp2{regs, regs.takeReg(rsi, kInvalidInstIdx)};
callWrap.addArgument(SizeX64::dword, 32); callWrap.addArgument(SizeX64::dword, 32);
callWrap.addArgument(SizeX64::dword, -1); callWrap.addArgument(SizeX64::dword, -1);
callWrap.addArgument(SizeX64::qword, qword[r14 + 32]); callWrap.addArgument(SizeX64::qword, qword[r14 + 32]);
@ -106,7 +106,7 @@ TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "SimpleMemImm")
TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "SimpleStackArgs") TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "SimpleStackArgs")
{ {
ScopedRegX64 tmp{regs, regs.takeReg(rax)}; ScopedRegX64 tmp{regs, regs.takeReg(rax, kInvalidInstIdx)};
callWrap.addArgument(SizeX64::qword, tmp); callWrap.addArgument(SizeX64::qword, tmp);
callWrap.addArgument(SizeX64::qword, qword[r14 + 16]); callWrap.addArgument(SizeX64::qword, qword[r14 + 16]);
callWrap.addArgument(SizeX64::qword, qword[r14 + 32]); callWrap.addArgument(SizeX64::qword, qword[r14 + 32]);
@ -148,10 +148,10 @@ TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "FixedRegisters")
TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "EasyInterference") TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "EasyInterference")
{ {
ScopedRegX64 tmp1{regs, regs.takeReg(rdi)}; ScopedRegX64 tmp1{regs, regs.takeReg(rdi, kInvalidInstIdx)};
ScopedRegX64 tmp2{regs, regs.takeReg(rsi)}; ScopedRegX64 tmp2{regs, regs.takeReg(rsi, kInvalidInstIdx)};
ScopedRegX64 tmp3{regs, regs.takeReg(rArg2)}; ScopedRegX64 tmp3{regs, regs.takeReg(rArg2, kInvalidInstIdx)};
ScopedRegX64 tmp4{regs, regs.takeReg(rArg1)}; ScopedRegX64 tmp4{regs, regs.takeReg(rArg1, kInvalidInstIdx)};
callWrap.addArgument(SizeX64::qword, tmp1); callWrap.addArgument(SizeX64::qword, tmp1);
callWrap.addArgument(SizeX64::qword, tmp2); callWrap.addArgument(SizeX64::qword, tmp2);
callWrap.addArgument(SizeX64::qword, tmp3); callWrap.addArgument(SizeX64::qword, tmp3);
@ -169,8 +169,8 @@ TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "EasyInterference")
TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "FakeInterference") TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "FakeInterference")
{ {
ScopedRegX64 tmp1{regs, regs.takeReg(rArg1)}; ScopedRegX64 tmp1{regs, regs.takeReg(rArg1, kInvalidInstIdx)};
ScopedRegX64 tmp2{regs, regs.takeReg(rArg2)}; ScopedRegX64 tmp2{regs, regs.takeReg(rArg2, kInvalidInstIdx)};
callWrap.addArgument(SizeX64::qword, qword[tmp1.release() + 8]); callWrap.addArgument(SizeX64::qword, qword[tmp1.release() + 8]);
callWrap.addArgument(SizeX64::qword, qword[tmp2.release() + 8]); callWrap.addArgument(SizeX64::qword, qword[tmp2.release() + 8]);
callWrap.call(qword[r12]); callWrap.call(qword[r12]);
@ -184,10 +184,10 @@ TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "FakeInterference")
TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "HardInterferenceInt") TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "HardInterferenceInt")
{ {
ScopedRegX64 tmp1{regs, regs.takeReg(rArg4)}; ScopedRegX64 tmp1{regs, regs.takeReg(rArg4, kInvalidInstIdx)};
ScopedRegX64 tmp2{regs, regs.takeReg(rArg3)}; ScopedRegX64 tmp2{regs, regs.takeReg(rArg3, kInvalidInstIdx)};
ScopedRegX64 tmp3{regs, regs.takeReg(rArg2)}; ScopedRegX64 tmp3{regs, regs.takeReg(rArg2, kInvalidInstIdx)};
ScopedRegX64 tmp4{regs, regs.takeReg(rArg1)}; ScopedRegX64 tmp4{regs, regs.takeReg(rArg1, kInvalidInstIdx)};
callWrap.addArgument(SizeX64::qword, tmp1); callWrap.addArgument(SizeX64::qword, tmp1);
callWrap.addArgument(SizeX64::qword, tmp2); callWrap.addArgument(SizeX64::qword, tmp2);
callWrap.addArgument(SizeX64::qword, tmp3); callWrap.addArgument(SizeX64::qword, tmp3);
@ -207,10 +207,10 @@ TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "HardInterferenceInt")
TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "HardInterferenceInt2") TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "HardInterferenceInt2")
{ {
ScopedRegX64 tmp1{regs, regs.takeReg(rArg4d)}; ScopedRegX64 tmp1{regs, regs.takeReg(rArg4d, kInvalidInstIdx)};
ScopedRegX64 tmp2{regs, regs.takeReg(rArg3d)}; ScopedRegX64 tmp2{regs, regs.takeReg(rArg3d, kInvalidInstIdx)};
ScopedRegX64 tmp3{regs, regs.takeReg(rArg2d)}; ScopedRegX64 tmp3{regs, regs.takeReg(rArg2d, kInvalidInstIdx)};
ScopedRegX64 tmp4{regs, regs.takeReg(rArg1d)}; ScopedRegX64 tmp4{regs, regs.takeReg(rArg1d, kInvalidInstIdx)};
callWrap.addArgument(SizeX64::dword, tmp1); callWrap.addArgument(SizeX64::dword, tmp1);
callWrap.addArgument(SizeX64::dword, tmp2); callWrap.addArgument(SizeX64::dword, tmp2);
callWrap.addArgument(SizeX64::dword, tmp3); callWrap.addArgument(SizeX64::dword, tmp3);
@ -230,8 +230,8 @@ TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "HardInterferenceInt2")
TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "HardInterferenceFp") TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "HardInterferenceFp")
{ {
ScopedRegX64 tmp1{regs, regs.takeReg(xmm1)}; ScopedRegX64 tmp1{regs, regs.takeReg(xmm1, kInvalidInstIdx)};
ScopedRegX64 tmp2{regs, regs.takeReg(xmm0)}; ScopedRegX64 tmp2{regs, regs.takeReg(xmm0, kInvalidInstIdx)};
callWrap.addArgument(SizeX64::xmmword, tmp1); callWrap.addArgument(SizeX64::xmmword, tmp1);
callWrap.addArgument(SizeX64::xmmword, tmp2); callWrap.addArgument(SizeX64::xmmword, tmp2);
callWrap.call(qword[r12]); callWrap.call(qword[r12]);
@ -246,10 +246,10 @@ TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "HardInterferenceFp")
TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "HardInterferenceBoth") TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "HardInterferenceBoth")
{ {
ScopedRegX64 int1{regs, regs.takeReg(rArg2)}; ScopedRegX64 int1{regs, regs.takeReg(rArg2, kInvalidInstIdx)};
ScopedRegX64 int2{regs, regs.takeReg(rArg1)}; ScopedRegX64 int2{regs, regs.takeReg(rArg1, kInvalidInstIdx)};
ScopedRegX64 fp1{regs, regs.takeReg(xmm3)}; ScopedRegX64 fp1{regs, regs.takeReg(xmm3, kInvalidInstIdx)};
ScopedRegX64 fp2{regs, regs.takeReg(xmm2)}; ScopedRegX64 fp2{regs, regs.takeReg(xmm2, kInvalidInstIdx)};
callWrap.addArgument(SizeX64::qword, int1); callWrap.addArgument(SizeX64::qword, int1);
callWrap.addArgument(SizeX64::qword, int2); callWrap.addArgument(SizeX64::qword, int2);
callWrap.addArgument(SizeX64::xmmword, fp1); callWrap.addArgument(SizeX64::xmmword, fp1);
@ -269,8 +269,8 @@ TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "HardInterferenceBoth")
TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "FakeMultiuseInterferenceMem") TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "FakeMultiuseInterferenceMem")
{ {
ScopedRegX64 tmp1{regs, regs.takeReg(rArg1)}; ScopedRegX64 tmp1{regs, regs.takeReg(rArg1, kInvalidInstIdx)};
ScopedRegX64 tmp2{regs, regs.takeReg(rArg2)}; ScopedRegX64 tmp2{regs, regs.takeReg(rArg2, kInvalidInstIdx)};
callWrap.addArgument(SizeX64::qword, qword[tmp1.reg + tmp2.reg + 8]); callWrap.addArgument(SizeX64::qword, qword[tmp1.reg + tmp2.reg + 8]);
callWrap.addArgument(SizeX64::qword, qword[tmp2.reg + 16]); callWrap.addArgument(SizeX64::qword, qword[tmp2.reg + 16]);
tmp1.release(); tmp1.release();
@ -286,8 +286,8 @@ TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "FakeMultiuseInterferenceMem")
TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "HardMultiuseInterferenceMem1") TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "HardMultiuseInterferenceMem1")
{ {
ScopedRegX64 tmp1{regs, regs.takeReg(rArg1)}; ScopedRegX64 tmp1{regs, regs.takeReg(rArg1, kInvalidInstIdx)};
ScopedRegX64 tmp2{regs, regs.takeReg(rArg2)}; ScopedRegX64 tmp2{regs, regs.takeReg(rArg2, kInvalidInstIdx)};
callWrap.addArgument(SizeX64::qword, qword[tmp1.reg + tmp2.reg + 8]); callWrap.addArgument(SizeX64::qword, qword[tmp1.reg + tmp2.reg + 8]);
callWrap.addArgument(SizeX64::qword, qword[tmp1.reg + 16]); callWrap.addArgument(SizeX64::qword, qword[tmp1.reg + 16]);
tmp1.release(); tmp1.release();
@ -304,8 +304,8 @@ TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "HardMultiuseInterferenceMem1")
TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "HardMultiuseInterferenceMem2") TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "HardMultiuseInterferenceMem2")
{ {
ScopedRegX64 tmp1{regs, regs.takeReg(rArg1)}; ScopedRegX64 tmp1{regs, regs.takeReg(rArg1, kInvalidInstIdx)};
ScopedRegX64 tmp2{regs, regs.takeReg(rArg2)}; ScopedRegX64 tmp2{regs, regs.takeReg(rArg2, kInvalidInstIdx)};
callWrap.addArgument(SizeX64::qword, qword[tmp1.reg + tmp2.reg + 8]); callWrap.addArgument(SizeX64::qword, qword[tmp1.reg + tmp2.reg + 8]);
callWrap.addArgument(SizeX64::qword, qword[tmp1.reg + tmp2.reg + 16]); callWrap.addArgument(SizeX64::qword, qword[tmp1.reg + tmp2.reg + 16]);
tmp1.release(); tmp1.release();
@ -322,9 +322,9 @@ TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "HardMultiuseInterferenceMem2")
TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "HardMultiuseInterferenceMem3") TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "HardMultiuseInterferenceMem3")
{ {
ScopedRegX64 tmp1{regs, regs.takeReg(rArg3)}; ScopedRegX64 tmp1{regs, regs.takeReg(rArg3, kInvalidInstIdx)};
ScopedRegX64 tmp2{regs, regs.takeReg(rArg2)}; ScopedRegX64 tmp2{regs, regs.takeReg(rArg2, kInvalidInstIdx)};
ScopedRegX64 tmp3{regs, regs.takeReg(rArg1)}; ScopedRegX64 tmp3{regs, regs.takeReg(rArg1, kInvalidInstIdx)};
callWrap.addArgument(SizeX64::qword, qword[tmp1.reg + tmp2.reg + 8]); callWrap.addArgument(SizeX64::qword, qword[tmp1.reg + tmp2.reg + 8]);
callWrap.addArgument(SizeX64::qword, qword[tmp2.reg + tmp3.reg + 16]); callWrap.addArgument(SizeX64::qword, qword[tmp2.reg + tmp3.reg + 16]);
callWrap.addArgument(SizeX64::qword, qword[tmp3.reg + tmp1.reg + 16]); callWrap.addArgument(SizeX64::qword, qword[tmp3.reg + tmp1.reg + 16]);
@ -345,7 +345,7 @@ TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "HardMultiuseInterferenceMem3")
TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "InterferenceWithCallArg1") TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "InterferenceWithCallArg1")
{ {
ScopedRegX64 tmp1{regs, regs.takeReg(rArg1)}; ScopedRegX64 tmp1{regs, regs.takeReg(rArg1, kInvalidInstIdx)};
callWrap.addArgument(SizeX64::qword, qword[tmp1.reg + 8]); callWrap.addArgument(SizeX64::qword, qword[tmp1.reg + 8]);
callWrap.call(qword[tmp1.release() + 16]); callWrap.call(qword[tmp1.release() + 16]);
@ -358,8 +358,8 @@ TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "InterferenceWithCallArg1")
TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "InterferenceWithCallArg2") TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "InterferenceWithCallArg2")
{ {
ScopedRegX64 tmp1{regs, regs.takeReg(rArg1)}; ScopedRegX64 tmp1{regs, regs.takeReg(rArg1, kInvalidInstIdx)};
ScopedRegX64 tmp2{regs, regs.takeReg(rArg2)}; ScopedRegX64 tmp2{regs, regs.takeReg(rArg2, kInvalidInstIdx)};
callWrap.addArgument(SizeX64::qword, tmp2); callWrap.addArgument(SizeX64::qword, tmp2);
callWrap.call(qword[tmp1.release() + 16]); callWrap.call(qword[tmp1.release() + 16]);
@ -372,7 +372,7 @@ TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "InterferenceWithCallArg2")
TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "InterferenceWithCallArg3") TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "InterferenceWithCallArg3")
{ {
ScopedRegX64 tmp1{regs, regs.takeReg(rArg1)}; ScopedRegX64 tmp1{regs, regs.takeReg(rArg1, kInvalidInstIdx)};
callWrap.addArgument(SizeX64::qword, tmp1.reg); callWrap.addArgument(SizeX64::qword, tmp1.reg);
callWrap.call(qword[tmp1.release() + 16]); callWrap.call(qword[tmp1.release() + 16]);
@ -385,7 +385,7 @@ TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "WithLastIrInstUse1")
{ {
IrInst irInst1; IrInst irInst1;
IrOp irOp1 = {IrOpKind::Inst, 0}; IrOp irOp1 = {IrOpKind::Inst, 0};
irInst1.regX64 = regs.takeReg(xmm0); irInst1.regX64 = regs.takeReg(xmm0, irOp1.index);
irInst1.lastUse = 1; irInst1.lastUse = 1;
function.instructions.push_back(irInst1); function.instructions.push_back(irInst1);
callWrap.instIdx = irInst1.lastUse; callWrap.instIdx = irInst1.lastUse;
@ -404,7 +404,7 @@ TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "WithLastIrInstUse2")
{ {
IrInst irInst1; IrInst irInst1;
IrOp irOp1 = {IrOpKind::Inst, 0}; IrOp irOp1 = {IrOpKind::Inst, 0};
irInst1.regX64 = regs.takeReg(xmm0); irInst1.regX64 = regs.takeReg(xmm0, irOp1.index);
irInst1.lastUse = 1; irInst1.lastUse = 1;
function.instructions.push_back(irInst1); function.instructions.push_back(irInst1);
callWrap.instIdx = irInst1.lastUse; callWrap.instIdx = irInst1.lastUse;
@ -424,7 +424,7 @@ TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "WithLastIrInstUse3")
{ {
IrInst irInst1; IrInst irInst1;
IrOp irOp1 = {IrOpKind::Inst, 0}; IrOp irOp1 = {IrOpKind::Inst, 0};
irInst1.regX64 = regs.takeReg(xmm0); irInst1.regX64 = regs.takeReg(xmm0, irOp1.index);
irInst1.lastUse = 1; irInst1.lastUse = 1;
function.instructions.push_back(irInst1); function.instructions.push_back(irInst1);
callWrap.instIdx = irInst1.lastUse; callWrap.instIdx = irInst1.lastUse;
@ -443,12 +443,12 @@ TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "WithLastIrInstUse4")
{ {
IrInst irInst1; IrInst irInst1;
IrOp irOp1 = {IrOpKind::Inst, 0}; IrOp irOp1 = {IrOpKind::Inst, 0};
irInst1.regX64 = regs.takeReg(rax); irInst1.regX64 = regs.takeReg(rax, irOp1.index);
irInst1.lastUse = 1; irInst1.lastUse = 1;
function.instructions.push_back(irInst1); function.instructions.push_back(irInst1);
callWrap.instIdx = irInst1.lastUse; callWrap.instIdx = irInst1.lastUse;
ScopedRegX64 tmp{regs, regs.takeReg(rdx)}; ScopedRegX64 tmp{regs, regs.takeReg(rdx, kInvalidInstIdx)};
callWrap.addArgument(SizeX64::qword, r15); callWrap.addArgument(SizeX64::qword, r15);
callWrap.addArgument(SizeX64::qword, irInst1.regX64, irOp1); callWrap.addArgument(SizeX64::qword, irInst1.regX64, irOp1);
callWrap.addArgument(SizeX64::qword, tmp); callWrap.addArgument(SizeX64::qword, tmp);
@ -464,8 +464,8 @@ TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "WithLastIrInstUse4")
TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "ExtraCoverage") TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "ExtraCoverage")
{ {
ScopedRegX64 tmp1{regs, regs.takeReg(rArg1)}; ScopedRegX64 tmp1{regs, regs.takeReg(rArg1, kInvalidInstIdx)};
ScopedRegX64 tmp2{regs, regs.takeReg(rArg2)}; ScopedRegX64 tmp2{regs, regs.takeReg(rArg2, kInvalidInstIdx)};
callWrap.addArgument(SizeX64::qword, addr[r12 + 8]); callWrap.addArgument(SizeX64::qword, addr[r12 + 8]);
callWrap.addArgument(SizeX64::qword, addr[r12 + 16]); callWrap.addArgument(SizeX64::qword, addr[r12 + 16]);
callWrap.addArgument(SizeX64::xmmword, xmmword[r13]); callWrap.addArgument(SizeX64::xmmword, xmmword[r13]);
@ -481,4 +481,42 @@ TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "ExtraCoverage")
)"); )");
} }
TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "AddressInStackArguments")
{
callWrap.addArgument(SizeX64::dword, 1);
callWrap.addArgument(SizeX64::dword, 2);
callWrap.addArgument(SizeX64::dword, 3);
callWrap.addArgument(SizeX64::dword, 4);
callWrap.addArgument(SizeX64::qword, addr[r12 + 16]);
callWrap.call(qword[r14]);
checkMatch(R"(
lea rax,none ptr [r12+010h]
mov qword ptr [rsp+020h],rax
mov ecx,1
mov edx,2
mov r8d,3
mov r9d,4
call qword ptr [r14]
)");
}
TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "ImmediateConflictWithFunction")
{
ScopedRegX64 tmp1{regs, regs.takeReg(rArg1, kInvalidInstIdx)};
ScopedRegX64 tmp2{regs, regs.takeReg(rArg2, kInvalidInstIdx)};
callWrap.addArgument(SizeX64::dword, 1);
callWrap.addArgument(SizeX64::dword, 2);
callWrap.call(qword[tmp1.release() + tmp2.release()]);
checkMatch(R"(
mov rax,rcx
mov ecx,1
mov rbx,rdx
mov edx,2
call qword ptr [rax+rbx]
)");
}
TEST_SUITE_END(); TEST_SUITE_END();

View File

@ -1273,7 +1273,7 @@ TEST_CASE_FIXTURE(Fixture, "use_all_parent_scopes_for_globals")
{ {
ScopePtr testScope = frontend.addEnvironment("Test"); ScopePtr testScope = frontend.addEnvironment("Test");
unfreeze(frontend.globals.globalTypes); unfreeze(frontend.globals.globalTypes);
loadDefinitionFile(frontend.typeChecker, frontend.globals, testScope, R"( frontend.loadDefinitionFile(frontend.globals, testScope, R"(
declare Foo: number declare Foo: number
)", )",
"@test", /* captureComments */ false); "@test", /* captureComments */ false);

View File

@ -748,6 +748,20 @@ TEST_CASE_FIXTURE(NormalizeFixture, "narrow_union_of_classes_with_intersection")
CHECK("Child" == toString(normal("(Child | Unrelated) & Child"))); CHECK("Child" == toString(normal("(Child | Unrelated) & Child")));
} }
TEST_CASE_FIXTURE(NormalizeFixture, "intersection_of_metatables_where_the_metatable_is_top_or_bottom")
{
ScopedFastFlag sff{"LuauNormalizeMetatableFixes", true};
CHECK("{ @metatable *error-type*, {| |} }" == toString(normal("Mt<{}, any> & Mt<{}, err>")));
}
TEST_CASE_FIXTURE(NormalizeFixture, "crazy_metatable")
{
ScopedFastFlag sff{"LuauNormalizeMetatableFixes", true};
CHECK("never" == toString(normal("Mt<{}, number> & Mt<{}, string>")));
}
TEST_CASE_FIXTURE(NormalizeFixture, "negations_of_classes") TEST_CASE_FIXTURE(NormalizeFixture, "negations_of_classes")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag sffs[] = {

View File

@ -78,7 +78,7 @@ TEST_CASE_FIXTURE(Fixture, "definition_file_loading")
TEST_CASE_FIXTURE(Fixture, "load_definition_file_errors_do_not_pollute_global_scope") TEST_CASE_FIXTURE(Fixture, "load_definition_file_errors_do_not_pollute_global_scope")
{ {
unfreeze(frontend.globals.globalTypes); unfreeze(frontend.globals.globalTypes);
LoadDefinitionFileResult parseFailResult = loadDefinitionFile(frontend.typeChecker, frontend.globals, frontend.globals.globalScope, R"( LoadDefinitionFileResult parseFailResult = frontend.loadDefinitionFile(frontend.globals, frontend.globals.globalScope, R"(
declare foo declare foo
)", )",
"@test", /* captureComments */ false); "@test", /* captureComments */ false);
@ -88,7 +88,7 @@ TEST_CASE_FIXTURE(Fixture, "load_definition_file_errors_do_not_pollute_global_sc
std::optional<Binding> fooTy = tryGetGlobalBinding(frontend.globals, "foo"); std::optional<Binding> fooTy = tryGetGlobalBinding(frontend.globals, "foo");
CHECK(!fooTy.has_value()); CHECK(!fooTy.has_value());
LoadDefinitionFileResult checkFailResult = loadDefinitionFile(frontend.typeChecker, frontend.globals, frontend.globals.globalScope, R"( LoadDefinitionFileResult checkFailResult = frontend.loadDefinitionFile(frontend.globals, frontend.globals.globalScope, R"(
local foo: string = 123 local foo: string = 123
declare bar: typeof(foo) declare bar: typeof(foo)
)", )",
@ -140,7 +140,7 @@ TEST_CASE_FIXTURE(Fixture, "definition_file_classes")
TEST_CASE_FIXTURE(Fixture, "class_definitions_cannot_overload_non_function") TEST_CASE_FIXTURE(Fixture, "class_definitions_cannot_overload_non_function")
{ {
unfreeze(frontend.globals.globalTypes); unfreeze(frontend.globals.globalTypes);
LoadDefinitionFileResult result = loadDefinitionFile(frontend.typeChecker, frontend.globals, frontend.globals.globalScope, R"( LoadDefinitionFileResult result = frontend.loadDefinitionFile(frontend.globals, frontend.globals.globalScope, R"(
declare class A declare class A
X: number X: number
X: string X: string
@ -161,7 +161,7 @@ TEST_CASE_FIXTURE(Fixture, "class_definitions_cannot_overload_non_function")
TEST_CASE_FIXTURE(Fixture, "class_definitions_cannot_extend_non_class") TEST_CASE_FIXTURE(Fixture, "class_definitions_cannot_extend_non_class")
{ {
unfreeze(frontend.globals.globalTypes); unfreeze(frontend.globals.globalTypes);
LoadDefinitionFileResult result = loadDefinitionFile(frontend.typeChecker, frontend.globals, frontend.globals.globalScope, R"( LoadDefinitionFileResult result = frontend.loadDefinitionFile(frontend.globals, frontend.globals.globalScope, R"(
type NotAClass = {} type NotAClass = {}
declare class Foo extends NotAClass declare class Foo extends NotAClass
@ -182,7 +182,7 @@ TEST_CASE_FIXTURE(Fixture, "class_definitions_cannot_extend_non_class")
TEST_CASE_FIXTURE(Fixture, "no_cyclic_defined_classes") TEST_CASE_FIXTURE(Fixture, "no_cyclic_defined_classes")
{ {
unfreeze(frontend.globals.globalTypes); unfreeze(frontend.globals.globalTypes);
LoadDefinitionFileResult result = loadDefinitionFile(frontend.typeChecker, frontend.globals, frontend.globals.globalScope, R"( LoadDefinitionFileResult result = frontend.loadDefinitionFile(frontend.globals, frontend.globals.globalScope, R"(
declare class Foo extends Bar declare class Foo extends Bar
end end
@ -397,7 +397,7 @@ TEST_CASE_FIXTURE(Fixture, "class_definition_string_props")
TEST_CASE_FIXTURE(Fixture, "class_definitions_reference_other_classes") TEST_CASE_FIXTURE(Fixture, "class_definitions_reference_other_classes")
{ {
unfreeze(frontend.globals.globalTypes); unfreeze(frontend.globals.globalTypes);
LoadDefinitionFileResult result = loadDefinitionFile(frontend.typeChecker, frontend.globals, frontend.globals.globalScope, R"( LoadDefinitionFileResult result = frontend.loadDefinitionFile(frontend.globals, frontend.globals.globalScope, R"(
declare class Channel declare class Channel
Messages: { Message } Messages: { Message }
OnMessage: (message: Message) -> () OnMessage: (message: Message) -> ()

View File

@ -874,7 +874,7 @@ TEST_CASE_FIXTURE(Fixture, "generic_table_method")
std::vector<TypeId> args = flatten(ftv->argTypes).first; std::vector<TypeId> args = flatten(ftv->argTypes).first;
TypeId argType = args.at(1); TypeId argType = args.at(1);
CHECK_MESSAGE(get<Unifiable::Generic>(argType), "Should be generic: " << *barType); CHECK_MESSAGE(get<GenericType>(argType), "Should be generic: " << *barType);
} }
TEST_CASE_FIXTURE(Fixture, "correctly_instantiate_polymorphic_member_functions") TEST_CASE_FIXTURE(Fixture, "correctly_instantiate_polymorphic_member_functions")

View File

@ -477,10 +477,10 @@ TEST_CASE_FIXTURE(Fixture, "free_options_cannot_be_unified_together")
std::unique_ptr scope = std::make_unique<Scope>(builtinTypes->anyTypePack); std::unique_ptr scope = std::make_unique<Scope>(builtinTypes->anyTypePack);
TypeId free1 = arena.addType(FreeTypePack{scope.get()}); TypeId free1 = arena.addType(FreeType{scope.get()});
TypeId option1 = arena.addType(UnionType{{nilType, free1}}); TypeId option1 = arena.addType(UnionType{{nilType, free1}});
TypeId free2 = arena.addType(FreeTypePack{scope.get()}); TypeId free2 = arena.addType(FreeType{scope.get()});
TypeId option2 = arena.addType(UnionType{{nilType, free2}}); TypeId option2 = arena.addType(UnionType{{nilType, free2}});
InternalErrorReporter iceHandler; InternalErrorReporter iceHandler;

View File

@ -490,8 +490,13 @@ struct FindFreeTypes
return !foundOne; return !foundOne;
} }
template<typename ID> bool operator()(TypeId, FreeType)
bool operator()(ID, Unifiable::Free) {
foundOne = true;
return false;
}
bool operator()(TypePackId, FreeTypePack)
{ {
foundOne = true; foundOne = true;
return false; return false;

View File

@ -25,7 +25,7 @@ struct TypePackFixture
TypePackId freshTypePack() TypePackId freshTypePack()
{ {
typePacks.emplace_back(new TypePackVar{Unifiable::Free{TypeLevel{}}}); typePacks.emplace_back(new TypePackVar{FreeTypePack{TypeLevel{}}});
return typePacks.back().get(); return typePacks.back().get();
} }

View File

@ -74,7 +74,7 @@ TEST_CASE_FIXTURE(Fixture, "return_type_of_function_is_parenthesized_if_not_just
TEST_CASE_FIXTURE(Fixture, "return_type_of_function_is_parenthesized_if_tail_is_free") TEST_CASE_FIXTURE(Fixture, "return_type_of_function_is_parenthesized_if_tail_is_free")
{ {
auto emptyArgumentPack = TypePackVar{TypePack{}}; auto emptyArgumentPack = TypePackVar{TypePack{}};
auto free = Unifiable::Free(TypeLevel()); auto free = FreeTypePack(TypeLevel());
auto freePack = TypePackVar{TypePackVariant{free}}; auto freePack = TypePackVar{TypePackVariant{free}};
auto returnPack = TypePackVar{TypePack{{builtinTypes->numberType}, &freePack}}; auto returnPack = TypePackVar{TypePack{{builtinTypes->numberType}, &freePack}};
auto returnsTwo = Type(FunctionType(frontend.globals.globalScope->level, &emptyArgumentPack, &returnPack)); auto returnsTwo = Type(FunctionType(frontend.globals.globalScope->level, &emptyArgumentPack, &returnPack));

View File

@ -22,4 +22,12 @@ function getpi()
return pi return pi
end end
function largealloc()
table.create(1000000)
end
function oops()
return "oops"
end
return('OK') return('OK')

View File

@ -161,4 +161,11 @@ checkresults({ false, "ok" }, xpcall(recurse, function() return string.reverse("
-- however, if xpcall handler itself runs out of extra stack space, we get "error in error handling" -- however, if xpcall handler itself runs out of extra stack space, we get "error in error handling"
checkresults({ false, "error in error handling" }, xpcall(recurse, function() return recurse(calllimit) end, calllimit - 2)) checkresults({ false, "error in error handling" }, xpcall(recurse, function() return recurse(calllimit) end, calllimit - 2))
-- simulate OOM and make sure we can catch it with pcall or xpcall
checkresults({ false, "not enough memory" }, pcall(function() table.create(1e6) end))
checkresults({ false, "not enough memory" }, xpcall(function() table.create(1e6) end, function(e) return e end))
checkresults({ false, "oops" }, xpcall(function() table.create(1e6) end, function(e) return "oops" end))
checkresults({ false, "error in error handling" }, xpcall(function() error("oops") end, function(e) table.create(1e6) end))
checkresults({ false, "not enough memory" }, xpcall(function() table.create(1e6) end, function(e) table.create(1e6) end))
return 'OK' return 'OK'

View File

@ -99,12 +99,12 @@ a = {"
table.sort(a) table.sort(a)
check(a) check(a)
-- TODO: assert that pcall returns false for new sort implementation (table is modified during sorting) local ok = pcall(table.sort, a, function (x, y)
pcall(table.sort, a, function (x, y)
loadstring(string.format("a[%q] = ''", x))() loadstring(string.format("a[%q] = ''", x))()
collectgarbage() collectgarbage()
return x<y return x<y
end) end)
assert(not ok)
tt = {__lt = function (a,b) return a.val < b.val end} tt = {__lt = function (a,b) return a.val < b.val end}
a = {} a = {}
@ -113,4 +113,48 @@ table.sort(a)
check(a, tt.__lt) check(a, tt.__lt)
check(a) check(a)
-- force quicksort to degrade to heap sort
do
-- discover quick sort killer (this triggers heap sort which is what we want more or less; note that the "internal" heap sort iterations will result in a different order that wouldn't fully defeat a vanilla quicksort)
-- see https://igoro.com/archive/quicksort-killer/
local keys = {}
local candidate = 0
local next = 0
local t = table.create(100, 0)
for k in t do
t[k] = k
end
table.sort(t, function (x, y)
if keys[x] == nil and keys[y] == nil then
if x == candidate then keys[x] = next else keys[y] = next end
next += 1
end
if keys[x] == nil then
candidate = x
return true
end
if keys[y] == nil then
candidate = y
return false
end
return keys[x] < keys[y]
end)
-- repeat the sort for the generated sequence; it should produce an integer sequence and trigger heap sort, although we can't confirm the latter
local arr = table.create(#t)
for k,v in t do
arr[v] = k
end
table.sort(arr)
for k in arr do
assert(arr[k] == k)
end
end
return"OK" return"OK"

View File

@ -219,6 +219,13 @@ do
assert(eq(string.split("abc", "c"), {'ab', ''})) assert(eq(string.split("abc", "c"), {'ab', ''}))
end end
-- validate that variadic string fast calls get correct number of arguments
local function chr1(c)
return string.char(tonumber(c));
end
assert(chr1("0") == "\0")
--[[ --[[
local locales = { "ptb", "ISO-8859-1", "pt_BR" } local locales = { "ptb", "ISO-8859-1", "pt_BR" }
local function trylocale (w) local function trylocale (w)

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
<Type Name="Luau::detail::DenseHashTable&lt;*,*,*,*,*,*&gt;">
<Expand>
<Item Name="[size]">count</Item>
<Item Name="[capacity]">capacity</Item>
<ArrayItems>
<Size>capacity</Size>
<ValuePointer>data</ValuePointer>
</ArrayItems>
</Expand>
</Type>
<Type Name="Luau::DenseHashSet&lt;*,*,*&gt;">
<Expand>
<ExpandedItem>impl</ExpandedItem>
</Expand>
</Type>
<Type Name="Luau::DenseHashMap&lt;*,*,*,*&gt;">
<Expand>
<ExpandedItem>impl</ExpandedItem>
</Expand>
</Type>
</AutoVisualizer>

View File

@ -107,6 +107,12 @@ def main():
action="store_true", action="store_true",
help="Write a new faillist.txt after running tests.", help="Write a new faillist.txt after running tests.",
) )
parser.add_argument(
"--lti",
dest="lti",
action="store_true",
help="Run the tests with local type inference enabled.",
)
parser.add_argument("--randomize", action="store_true", help="Pick a random seed") parser.add_argument("--randomize", action="store_true", help="Pick a random seed")
@ -120,13 +126,19 @@ def main():
args = parser.parse_args() args = parser.parse_args()
if args.write and args.lti:
print_stderr(
"Cannot run test_dcr.py with --write *and* --lti. You don't want to commit local type inference faillist.txt yet."
)
sys.exit(1)
failList = loadFailList() failList = loadFailList()
commandLine = [ flags = ["true", "DebugLuauDeferredConstraintResolution"]
args.path, if args.lti:
"--reporters=xml", flags.append("DebugLuauLocalTypeInference")
"--fflags=true,DebugLuauDeferredConstraintResolution=true",
] commandLine = [args.path, "--reporters=xml", "--fflags=" + ",".join(flags)]
if args.random_seed: if args.random_seed:
commandLine.append("--random-seed=" + str(args.random_seed)) commandLine.append("--random-seed=" + str(args.random_seed))