mirror of
https://github.com/luau-lang/luau.git
synced 2024-11-15 06:15:44 +08:00
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:
parent
d148d7d574
commit
ba67fb275e
@ -16,9 +16,7 @@ struct TypeArena;
|
||||
|
||||
void registerBuiltinTypes(GlobalTypes& globals);
|
||||
|
||||
void registerBuiltinGlobals(TypeChecker& typeChecker, GlobalTypes& globals);
|
||||
void registerBuiltinGlobals(Frontend& frontend);
|
||||
|
||||
void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeCheckForAutocomplete = false);
|
||||
TypeId makeUnion(TypeArena& arena, std::vector<TypeId>&& types);
|
||||
TypeId makeIntersection(TypeArena& arena, std::vector<TypeId>&& types);
|
||||
|
||||
|
@ -8,7 +8,6 @@
|
||||
#include "Luau/Scope.h"
|
||||
#include "Luau/TypeInfer.h"
|
||||
#include "Luau/Variant.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
@ -36,9 +35,6 @@ struct LoadDefinitionFileResult
|
||||
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::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.
|
||||
*/
|
||||
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
|
||||
{
|
||||
bool hasDirtySourceModule() const
|
||||
@ -140,10 +138,6 @@ struct Frontend
|
||||
|
||||
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;
|
||||
void markDirty(const ModuleName& name, std::vector<ModuleName>* markedDirty = nullptr);
|
||||
|
||||
@ -164,10 +158,11 @@ struct Frontend
|
||||
ScopePtr addEnvironment(const std::string& environmentName);
|
||||
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);
|
||||
|
||||
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:
|
||||
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;
|
||||
|
||||
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_;
|
||||
|
||||
|
@ -75,14 +75,45 @@ using TypeId = const Type*;
|
||||
using Name = std::string;
|
||||
|
||||
// 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"
|
||||
// to that type var, indicating that the two types are actually the same type.
|
||||
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;
|
||||
};
|
||||
|
||||
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 GenericType = Unifiable::Generic;
|
||||
|
||||
using Tags = std::vector<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.
|
||||
struct MetatableType
|
||||
{
|
||||
// Always points to a TableType.
|
||||
// Should always be a TableType.
|
||||
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;
|
||||
|
||||
std::optional<std::string> syntheticName;
|
||||
@ -536,8 +569,8 @@ struct NegationType
|
||||
|
||||
using ErrorType = Unifiable::Error;
|
||||
|
||||
using TypeVariant = Unifiable::Variant<TypeId, PrimitiveType, BlockedType, PendingExpansionType, SingletonType, FunctionType, TableType,
|
||||
MetatableType, ClassType, AnyType, UnionType, IntersectionType, LazyType, UnknownType, NeverType, NegationType>;
|
||||
using TypeVariant = Unifiable::Variant<TypeId, FreeType, GenericType, PrimitiveType, BlockedType, PendingExpansionType, SingletonType, FunctionType,
|
||||
TableType, MetatableType, ClassType, AnyType, UnionType, IntersectionType, LazyType, UnknownType, NeverType, NegationType>;
|
||||
|
||||
struct Type final
|
||||
{
|
||||
|
@ -12,20 +12,48 @@ namespace Luau
|
||||
{
|
||||
|
||||
struct TypeArena;
|
||||
struct TxnLog;
|
||||
|
||||
struct TypePack;
|
||||
struct VariadicTypePack;
|
||||
struct BlockedTypePack;
|
||||
|
||||
struct TypePackVar;
|
||||
|
||||
struct TxnLog;
|
||||
|
||||
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 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
|
||||
* notions like packs of unknown length and packs of any length, as well as more
|
||||
|
@ -83,24 +83,6 @@ using Name = std::string;
|
||||
|
||||
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>
|
||||
struct Bound
|
||||
{
|
||||
@ -112,26 +94,6 @@ struct Bound
|
||||
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
|
||||
{
|
||||
// This constructor has to be public, since it's used in Type and TypePack,
|
||||
@ -145,6 +107,6 @@ private:
|
||||
};
|
||||
|
||||
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
|
||||
|
@ -341,10 +341,10 @@ struct GenericTypeVisitor
|
||||
traverse(btv->boundTo);
|
||||
}
|
||||
|
||||
else if (auto ftv = get<Unifiable::Free>(tp))
|
||||
else if (auto ftv = get<FreeTypePack>(tp))
|
||||
visit(tp, *ftv);
|
||||
|
||||
else if (auto gtv = get<Unifiable::Generic>(tp))
|
||||
else if (auto gtv = get<GenericTypePack>(tp))
|
||||
visit(tp, *gtv);
|
||||
|
||||
else if (auto etv = get<Unifiable::Error>(tp))
|
||||
|
@ -13,8 +13,6 @@
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauAutocompleteSkipNormalization, false);
|
||||
|
||||
static const std::unordered_set<std::string> kStatementStartingKeywords = {
|
||||
"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}};
|
||||
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;
|
||||
unifier.checkInhabited = false;
|
||||
}
|
||||
// Cost of normalization can be too high for autocomplete response time requirements
|
||||
unifier.normalize = false;
|
||||
unifier.checkInhabited = false;
|
||||
|
||||
return unifier.canUnify(subTy, superTy).empty();
|
||||
}
|
||||
|
@ -212,7 +212,7 @@ void registerBuiltinTypes(GlobalTypes& globals)
|
||||
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.typePacks.isFrozen());
|
||||
@ -220,8 +220,8 @@ void registerBuiltinGlobals(TypeChecker& typeChecker, GlobalTypes& globals)
|
||||
TypeArena& arena = globals.globalTypes;
|
||||
NotNull<BuiltinTypes> builtinTypes = globals.builtinTypes;
|
||||
|
||||
LoadDefinitionFileResult loadResult =
|
||||
Luau::loadDefinitionFile(typeChecker, globals, globals.globalScope, getBuiltinDefinitionSource(), "@luau", /* captureComments */ false);
|
||||
LoadDefinitionFileResult loadResult = frontend.loadDefinitionFile(
|
||||
globals, globals.globalScope, getBuiltinDefinitionSource(), "@luau", /* captureComments */ false, typeCheckForAutocomplete);
|
||||
LUAU_ASSERT(loadResult.success);
|
||||
|
||||
TypeId genericK = arena.addType(GenericType{"K"});
|
||||
@ -309,106 +309,6 @@ void registerBuiltinGlobals(TypeChecker& typeChecker, GlobalTypes& globals)
|
||||
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(
|
||||
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate)
|
||||
{
|
||||
|
@ -44,10 +44,10 @@ struct TypeCloner
|
||||
template<typename T>
|
||||
void defaultClone(const T& t);
|
||||
|
||||
void operator()(const Unifiable::Free& t);
|
||||
void operator()(const Unifiable::Generic& t);
|
||||
void operator()(const Unifiable::Bound<TypeId>& t);
|
||||
void operator()(const Unifiable::Error& t);
|
||||
void operator()(const FreeType& t);
|
||||
void operator()(const GenericType& t);
|
||||
void operator()(const BoundType& t);
|
||||
void operator()(const ErrorType& t);
|
||||
void operator()(const BlockedType& t);
|
||||
void operator()(const PendingExpansionType& t);
|
||||
void operator()(const PrimitiveType& t);
|
||||
@ -89,15 +89,15 @@ struct TypePackCloner
|
||||
seenTypePacks[typePackId] = cloned;
|
||||
}
|
||||
|
||||
void operator()(const Unifiable::Free& t)
|
||||
void operator()(const FreeTypePack& t)
|
||||
{
|
||||
defaultClone(t);
|
||||
}
|
||||
void operator()(const Unifiable::Generic& t)
|
||||
void operator()(const GenericTypePack& t)
|
||||
{
|
||||
defaultClone(t);
|
||||
}
|
||||
void operator()(const Unifiable::Error& t)
|
||||
void operator()(const ErrorTypePack& t)
|
||||
{
|
||||
defaultClone(t);
|
||||
}
|
||||
@ -145,12 +145,12 @@ void TypeCloner::defaultClone(const T& t)
|
||||
seenTypes[typeId] = cloned;
|
||||
}
|
||||
|
||||
void TypeCloner::operator()(const Unifiable::Free& t)
|
||||
void TypeCloner::operator()(const FreeType& t)
|
||||
{
|
||||
defaultClone(t);
|
||||
}
|
||||
|
||||
void TypeCloner::operator()(const Unifiable::Generic& t)
|
||||
void TypeCloner::operator()(const GenericType& t)
|
||||
{
|
||||
defaultClone(t);
|
||||
}
|
||||
|
@ -29,7 +29,6 @@ LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||
LUAU_FASTINT(LuauTarjanChildLimit)
|
||||
LUAU_FASTFLAG(LuauInferInNoCheckMode)
|
||||
LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauLintInTypecheck, false)
|
||||
LUAU_FASTINTVARIABLE(LuauAutocompleteCheckTimeoutMs, 100)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauDeferredConstraintResolution, 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;
|
||||
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 = 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};
|
||||
return parseResult;
|
||||
}
|
||||
|
||||
LoadDefinitionFileResult loadDefinitionFile(TypeChecker& typeChecker, GlobalTypes& globals, ScopePtr targetScope, std::string_view source,
|
||||
const std::string& packageName, bool captureComments)
|
||||
static void persistCheckedTypes(ModulePtr checkedModule, GlobalTypes& globals, ScopePtr targetScope, const std::string& packageName)
|
||||
{
|
||||
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;
|
||||
|
||||
std::vector<TypeId> typesToPersist;
|
||||
@ -196,6 +126,49 @@ LoadDefinitionFileResult loadDefinitionFile(TypeChecker& typeChecker, GlobalType
|
||||
{
|
||||
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};
|
||||
}
|
||||
@ -316,8 +289,6 @@ static ErrorVec accumulateErrors(
|
||||
|
||||
static void filterLintOptions(LintOptions& lintOptions, const std::vector<HotComment>& hotcomments, Mode mode)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauLintInTypecheck);
|
||||
|
||||
uint64_t ignoreLints = LintWarning::parseMask(hotcomments);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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
|
||||
if (auto it = modules.find(name); it != modules.end())
|
||||
checkResult.lintResult = it->second->lintResult;
|
||||
// Get lint result only for top checked module
|
||||
if (auto it = modules.find(name); it != modules.end())
|
||||
checkResult.lintResult = it->second->lintResult;
|
||||
|
||||
return checkResult;
|
||||
}
|
||||
else
|
||||
{
|
||||
return CheckResult{accumulateErrors(
|
||||
sourceNodes, frontendOptions.forAutocomplete ? moduleResolverForAutocomplete.modules : moduleResolver.modules, name)};
|
||||
}
|
||||
return checkResult;
|
||||
}
|
||||
|
||||
std::vector<ModuleName> buildQueue;
|
||||
@ -553,9 +516,10 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
|
||||
else
|
||||
typeCheckerForAutocomplete.unifierIterationLimit = std::nullopt;
|
||||
|
||||
ModulePtr moduleForAutocomplete = (FFlag::DebugLuauDeferredConstraintResolution && mode == Mode::Strict)
|
||||
? check(sourceModule, mode, requireCycles, /*forAutocomplete*/ true, /*recordJsonLog*/ false)
|
||||
: typeCheckerForAutocomplete.check(sourceModule, Mode::Strict, environmentScope);
|
||||
ModulePtr moduleForAutocomplete =
|
||||
FFlag::DebugLuauDeferredConstraintResolution
|
||||
? check(sourceModule, Mode::Strict, requireCycles, /*forAutocomplete*/ true, /*recordJsonLog*/ false)
|
||||
: typeCheckerForAutocomplete.check(sourceModule, Mode::Strict, environmentScope);
|
||||
|
||||
moduleResolverForAutocomplete.modules[moduleName] = moduleForAutocomplete;
|
||||
|
||||
@ -601,8 +565,6 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
|
||||
{
|
||||
LUAU_TIMETRACE_SCOPE("lint", "Frontend");
|
||||
|
||||
LUAU_ASSERT(FFlag::LuauLintInTypecheck);
|
||||
|
||||
LintOptions lintOptions = frontendOptions.enabledLintWarnings.value_or(config.enabledLint);
|
||||
filterLintOptions(lintOptions, sourceModule.hotcomments, mode);
|
||||
|
||||
@ -662,15 +624,12 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
|
||||
sourceNode.dirtyModule = false;
|
||||
}
|
||||
|
||||
if (FFlag::LuauLintInTypecheck)
|
||||
{
|
||||
// Get lint result only for top checked module
|
||||
std::unordered_map<ModuleName, ModulePtr>& modules =
|
||||
frontendOptions.forAutocomplete ? moduleResolverForAutocomplete.modules : moduleResolver.modules;
|
||||
// Get lint result only for top checked module
|
||||
std::unordered_map<ModuleName, ModulePtr>& modules =
|
||||
frontendOptions.forAutocomplete ? moduleResolverForAutocomplete.modules : moduleResolver.modules;
|
||||
|
||||
if (auto it = modules.find(name); it != modules.end())
|
||||
checkResult.lintResult = it->second->lintResult;
|
||||
}
|
||||
if (auto it = modules.find(name); it != modules.end())
|
||||
checkResult.lintResult = it->second->lintResult;
|
||||
|
||||
return checkResult;
|
||||
}
|
||||
@ -800,59 +759,6 @@ ScopePtr Frontend::getModuleEnvironment(const SourceModule& module, const Config
|
||||
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
|
||||
{
|
||||
auto it = sourceNodes.find(name);
|
||||
@ -1195,7 +1101,7 @@ ScopePtr Frontend::getEnvironmentScope(const std::string& environmentName) const
|
||||
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);
|
||||
|
||||
@ -1208,7 +1114,7 @@ void Frontend::applyBuiltinDefinitionToEnvironment(const std::string& environmen
|
||||
LUAU_ASSERT(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)
|
||||
|
@ -20,6 +20,7 @@ LUAU_FASTINTVARIABLE(LuauNormalizeCacheLimit, 100000);
|
||||
LUAU_FASTFLAGVARIABLE(LuauNegatedClassTypes, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauNegatedTableTypes, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauNormalizeBlockedTypes, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauNormalizeMetatableFixes, false);
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
LUAU_FASTFLAG(LuauUninhabitedSubAnything2)
|
||||
LUAU_FASTFLAG(LuauTransitiveSubtyping)
|
||||
@ -2062,6 +2063,18 @@ std::optional<TypeId> Normalizer::intersectionOfTables(TypeId here, TypeId there
|
||||
else if (isPrim(there, PrimitiveType::Table))
|
||||
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 hmtable = nullptr;
|
||||
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);
|
||||
LUAU_ASSERT(httv);
|
||||
if (FFlag::LuauNormalizeMetatableFixes)
|
||||
{
|
||||
if (!httv)
|
||||
return std::nullopt;
|
||||
}
|
||||
else
|
||||
LUAU_ASSERT(httv);
|
||||
|
||||
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)
|
||||
return std::nullopt;
|
||||
|
@ -14,7 +14,6 @@
|
||||
#include <stdexcept>
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFunctionReturnStringificationFixup, false)
|
||||
|
||||
/*
|
||||
* Prefix generic typenames with gen-
|
||||
@ -369,7 +368,7 @@ struct TypeStringifier
|
||||
state.emit(">");
|
||||
}
|
||||
|
||||
void operator()(TypeId ty, const Unifiable::Free& ftv)
|
||||
void operator()(TypeId ty, const FreeType& ftv)
|
||||
{
|
||||
state.result.invalid = true;
|
||||
if (FFlag::DebugLuauVerboseTypeNames)
|
||||
|
@ -430,6 +430,69 @@ bool hasLength(TypeId ty, DenseHashSet<TypeId>& seen, int* recursionCount)
|
||||
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()
|
||||
: index(FFlag::LuauNormalizeBlockedTypes ? Unifiable::freshIndex() : ++DEPRECATED_nextIndex)
|
||||
{
|
||||
@ -971,7 +1034,7 @@ const TypeLevel* getLevel(TypeId ty)
|
||||
{
|
||||
ty = follow(ty);
|
||||
|
||||
if (auto ftv = get<Unifiable::Free>(ty))
|
||||
if (auto ftv = get<FreeType>(ty))
|
||||
return &ftv->level;
|
||||
else if (auto ttv = get<TableType>(ty))
|
||||
return &ttv->level;
|
||||
@ -990,7 +1053,7 @@ std::optional<TypeLevel> getLevel(TypePackId tp)
|
||||
{
|
||||
tp = follow(tp);
|
||||
|
||||
if (auto ftv = get<Unifiable::Free>(tp))
|
||||
if (auto ftv = get<FreeTypePack>(tp))
|
||||
return ftv->level;
|
||||
else
|
||||
return std::nullopt;
|
||||
|
@ -35,7 +35,21 @@ using SyntheticNames = std::unordered_map<const void*, char*>;
|
||||
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();
|
||||
char*& n = (*syntheticNames)[&gen];
|
||||
@ -237,7 +251,7 @@ public:
|
||||
size_t numGenericPacks = 0;
|
||||
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};
|
||||
}
|
||||
|
||||
|
@ -1020,7 +1020,7 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatAssign& assig
|
||||
right = errorRecoveryType(scope);
|
||||
else if (auto vtp = get<VariadicTypePack>(tailPack))
|
||||
right = vtp->ty;
|
||||
else if (get<Unifiable::Free>(tailPack))
|
||||
else if (get<FreeTypePack>(tailPack))
|
||||
{
|
||||
*asMutable(tailPack) = TypePack{{left}};
|
||||
growingPack = getMutable<TypePack>(tailPack);
|
||||
@ -1281,7 +1281,7 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatForIn& forin)
|
||||
callRetPack = checkExprPack(scope, *exprCall).type;
|
||||
callRetPack = follow(callRetPack);
|
||||
|
||||
if (get<Unifiable::Free>(callRetPack))
|
||||
if (get<FreeTypePack>(callRetPack))
|
||||
{
|
||||
iterTy = freshType(scope);
|
||||
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)};
|
||||
else if (auto vtp = get<VariadicTypePack>(varargPack))
|
||||
return WithPredicate{vtp->ty};
|
||||
else if (get<Unifiable::Generic>(varargPack))
|
||||
else if (get<GenericTypePack>(varargPack))
|
||||
{
|
||||
// TODO: Better error?
|
||||
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)};
|
||||
}
|
||||
else if (const FreeTypePack* ftp = get<Unifiable::Free>(retPack))
|
||||
else if (const FreeTypePack* ftp = get<FreeTypePack>(retPack))
|
||||
{
|
||||
TypeId head = freshType(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)};
|
||||
else if (auto vtp = get<VariadicTypePack>(retPack))
|
||||
return {vtp->ty, std::move(result.predicates)};
|
||||
else if (get<Unifiable::Generic>(retPack))
|
||||
else if (get<GenericTypePack>(retPack))
|
||||
{
|
||||
if (FFlag::LuauReturnAnyInsteadOfICE)
|
||||
return {anyType, std::move(result.predicates)};
|
||||
@ -3838,7 +3838,7 @@ void TypeChecker::checkArgumentList(const ScopePtr& scope, const AstExpr& funNam
|
||||
|
||||
if (argTail)
|
||||
{
|
||||
if (state.log.getMutable<Unifiable::Free>(state.log.follow(*argTail)))
|
||||
if (state.log.getMutable<FreeTypePack>(state.log.follow(*argTail)))
|
||||
{
|
||||
if (paramTail)
|
||||
state.tryUnify(*paramTail, *argTail);
|
||||
@ -3853,7 +3853,7 @@ void TypeChecker::checkArgumentList(const ScopePtr& scope, const AstExpr& funNam
|
||||
else if (paramTail)
|
||||
{
|
||||
// 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{{}}));
|
||||
}
|
||||
|
||||
@ -5570,7 +5570,7 @@ GenericTypeDefinitions TypeChecker::createGenericTypes(const ScopePtr& scope, st
|
||||
}
|
||||
else
|
||||
{
|
||||
g = addType(Unifiable::Generic{level, n});
|
||||
g = addType(GenericType{level, n});
|
||||
}
|
||||
|
||||
generics.push_back({g, defaultValue});
|
||||
@ -5598,7 +5598,7 @@ GenericTypeDefinitions TypeChecker::createGenericTypes(const ScopePtr& scope, st
|
||||
|
||||
TypePackId& cached = scope->parent->typeAliasTypePackParameters[n];
|
||||
if (!cached)
|
||||
cached = addTypePack(TypePackVar{Unifiable::Generic{level, n}});
|
||||
cached = addTypePack(TypePackVar{GenericTypePack{level, n}});
|
||||
|
||||
genericPacks.push_back({cached, defaultValue});
|
||||
scope->privateTypePackBindings[n] = cached;
|
||||
|
@ -9,6 +9,69 @@
|
||||
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()
|
||||
: index(++nextIndex)
|
||||
{
|
||||
@ -160,8 +223,8 @@ bool areEqual(SeenSet& seen, const TypePackVar& lhs, const TypePackVar& rhs)
|
||||
TypePackId rhsTail = *rhsIter.tail();
|
||||
|
||||
{
|
||||
const Unifiable::Free* lf = get_if<Unifiable::Free>(&lhsTail->ty);
|
||||
const Unifiable::Free* rf = get_if<Unifiable::Free>(&rhsTail->ty);
|
||||
const FreeTypePack* lf = get_if<FreeTypePack>(&lhsTail->ty);
|
||||
const FreeTypePack* rf = get_if<FreeTypePack>(&rhsTail->ty);
|
||||
if (lf && rf)
|
||||
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 Unifiable::Generic* rg = get_if<Unifiable::Generic>(&rhsTail->ty);
|
||||
const GenericTypePack* lg = get_if<GenericTypePack>(&lhsTail->ty);
|
||||
const GenericTypePack* rg = get_if<GenericTypePack>(&rhsTail->ty);
|
||||
if (lg && rg)
|
||||
return lg->index == rg->index;
|
||||
}
|
||||
|
@ -13,71 +13,6 @@ int freshIndex()
|
||||
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()
|
||||
: index(++nextIndex)
|
||||
{
|
||||
|
@ -1489,7 +1489,7 @@ struct WeirdIter
|
||||
|
||||
bool canGrow() const
|
||||
{
|
||||
return nullptr != log.getMutable<Unifiable::Free>(packId);
|
||||
return nullptr != log.getMutable<FreeTypePack>(packId);
|
||||
}
|
||||
|
||||
void grow(TypePackId newTail)
|
||||
@ -1497,7 +1497,7 @@ struct WeirdIter
|
||||
LUAU_ASSERT(canGrow());
|
||||
LUAU_ASSERT(log.getMutable<TypePack>(newTail));
|
||||
|
||||
auto freePack = log.getMutable<Unifiable::Free>(packId);
|
||||
auto freePack = log.getMutable<FreeTypePack>(packId);
|
||||
|
||||
level = freePack->level;
|
||||
if (FFlag::LuauMaintainScopesInUnifier && freePack->scope != nullptr)
|
||||
@ -1591,7 +1591,7 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
|
||||
if (log.haveSeen(superTp, subTp))
|
||||
return;
|
||||
|
||||
if (log.getMutable<Unifiable::Free>(superTp))
|
||||
if (log.getMutable<FreeTypePack>(superTp))
|
||||
{
|
||||
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)));
|
||||
}
|
||||
}
|
||||
else if (log.getMutable<Unifiable::Free>(subTp))
|
||||
else if (log.getMutable<FreeTypePack>(subTp))
|
||||
{
|
||||
if (!occursCheck(subTp, superTp))
|
||||
{
|
||||
@ -2567,9 +2567,9 @@ static void queueTypePack(std::vector<TypeId>& queue, DenseHashSet<TypePackId>&
|
||||
break;
|
||||
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))
|
||||
{
|
||||
@ -2617,7 +2617,7 @@ void Unifier::tryUnifyVariadics(TypePackId subTp, TypePackId superTp, bool rever
|
||||
{
|
||||
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"});
|
||||
}
|
||||
@ -2777,10 +2777,10 @@ bool Unifier::occursCheck(DenseHashSet<TypeId>& seen, TypeId needle, TypeId hays
|
||||
|
||||
seen.insert(haystack);
|
||||
|
||||
if (log.getMutable<Unifiable::Error>(needle))
|
||||
if (log.getMutable<ErrorType>(needle))
|
||||
return false;
|
||||
|
||||
if (!log.getMutable<Unifiable::Free>(needle))
|
||||
if (!log.getMutable<FreeType>(needle))
|
||||
ice("Expected needle to be free");
|
||||
|
||||
if (needle == haystack)
|
||||
@ -2824,10 +2824,10 @@ bool Unifier::occursCheck(DenseHashSet<TypePackId>& seen, TypePackId needle, Typ
|
||||
|
||||
seen.insert(haystack);
|
||||
|
||||
if (log.getMutable<Unifiable::Error>(needle))
|
||||
if (log.getMutable<ErrorTypePack>(needle))
|
||||
return false;
|
||||
|
||||
if (!log.getMutable<Unifiable::Free>(needle))
|
||||
if (!log.getMutable<FreeTypePack>(needle))
|
||||
ice("Expected needle pack to be free");
|
||||
|
||||
RecursionLimiter _ra(&sharedState.counters.recursionCount, sharedState.counters.recursionLimit);
|
||||
|
@ -14,7 +14,6 @@
|
||||
#endif
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauTimeTracing)
|
||||
LUAU_FASTFLAG(LuauLintInTypecheck)
|
||||
|
||||
enum class ReportFormat
|
||||
{
|
||||
@ -81,12 +80,10 @@ static bool analyzeFile(Luau::Frontend& frontend, const char* name, ReportFormat
|
||||
for (auto& error : cr.errors)
|
||||
reportError(frontend, format, error);
|
||||
|
||||
Luau::LintResult lr = FFlag::LuauLintInTypecheck ? cr.lintResult : frontend.lint_DEPRECATED(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);
|
||||
for (auto& warning : lr.warnings)
|
||||
for (auto& warning : cr.lintResult.warnings)
|
||||
reportWarning(format, humanReadableName.c_str(), warning);
|
||||
|
||||
if (annotate)
|
||||
@ -101,7 +98,7 @@ static bool analyzeFile(Luau::Frontend& frontend, const char* name, ReportFormat
|
||||
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)
|
||||
@ -264,13 +261,13 @@ int main(int argc, char** argv)
|
||||
|
||||
Luau::FrontendOptions frontendOptions;
|
||||
frontendOptions.retainFullTypeGraphs = annotate;
|
||||
frontendOptions.runLintChecks = FFlag::LuauLintInTypecheck;
|
||||
frontendOptions.runLintChecks = true;
|
||||
|
||||
CliFileResolver fileResolver;
|
||||
CliConfigResolver configResolver(mode);
|
||||
Luau::Frontend frontend(&fileResolver, &configResolver, frontendOptions);
|
||||
|
||||
Luau::registerBuiltinGlobals(frontend.typeChecker, frontend.globals);
|
||||
Luau::registerBuiltinGlobals(frontend, frontend.globals);
|
||||
Luau::freeze(frontend.globals.globalTypes);
|
||||
|
||||
#ifdef CALLGRIND
|
||||
|
@ -37,6 +37,7 @@ public:
|
||||
void movk(RegisterA64 dst, uint16_t src, int shift = 0);
|
||||
|
||||
// Arithmetics
|
||||
// TODO: support various kinds of shifts
|
||||
void add(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2, int shift = 0);
|
||||
void add(RegisterA64 dst, RegisterA64 src1, uint16_t src2);
|
||||
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);
|
||||
|
||||
// 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 bic (andnot)
|
||||
// TODO: support shifts
|
||||
// TODO: support bitfield ops
|
||||
void and_(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2);
|
||||
void orr(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);
|
||||
|
||||
// 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(ConditionA64 cond, Label& label);
|
||||
void cbz(RegisterA64 src, Label& label);
|
||||
|
@ -121,6 +121,7 @@ public:
|
||||
|
||||
void vcvttsd2si(OperandX64 dst, OperandX64 src);
|
||||
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
|
||||
|
||||
|
@ -19,6 +19,8 @@ void updateUseCounts(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)
|
||||
std::pair<uint32_t, uint32_t> getLiveInOutValueCount(IrFunction& function, IrBlock& block);
|
||||
uint32_t getLiveInValueCount(IrFunction& function, IrBlock& block);
|
||||
|
@ -17,10 +17,6 @@ namespace CodeGen
|
||||
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 ScopedRegX64;
|
||||
|
||||
@ -61,6 +57,7 @@ private:
|
||||
void renameRegister(RegisterX64& target, RegisterX64 reg, RegisterX64 replacement);
|
||||
void renameSourceRegisters(RegisterX64 reg, RegisterX64 replacement);
|
||||
RegisterX64 findConflictingTarget() const;
|
||||
void renameConflictingRegister(RegisterX64 conflict);
|
||||
|
||||
int getRegisterUses(RegisterX64 reg) const;
|
||||
void addRegisterUse(RegisterX64 reg);
|
||||
|
@ -62,11 +62,12 @@ enum class IrCmd : uint8_t
|
||||
|
||||
// Get pointer (LuaNode) to table node element at the active cached slot index
|
||||
// A: pointer (Table)
|
||||
// B: unsigned int (pcpos)
|
||||
GET_SLOT_NODE_ADDR,
|
||||
|
||||
// Get pointer (LuaNode) to table node element at the main position of the specified key hash
|
||||
// A: pointer (Table)
|
||||
// B: unsigned int
|
||||
// B: unsigned int (hash)
|
||||
GET_HASH_NODE_ADDR,
|
||||
|
||||
// Store a tag into TValue
|
||||
@ -89,6 +90,13 @@ enum class IrCmd : uint8_t
|
||||
// B: 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
|
||||
// A: Rn or pointer (TValue)
|
||||
// B: TValue
|
||||
@ -438,15 +446,6 @@ enum class IrCmd : uint8_t
|
||||
// C: block (forgloop location)
|
||||
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)
|
||||
// A: unsigned int (bytecode instruction index)
|
||||
COVERAGE,
|
||||
@ -622,6 +621,17 @@ struct IrOp
|
||||
|
||||
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
|
||||
{
|
||||
IrCmd cmd;
|
||||
@ -641,8 +651,12 @@ struct IrInst
|
||||
X64::RegisterX64 regX64 = X64::noreg;
|
||||
A64::RegisterA64 regA64 = A64::noreg;
|
||||
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
|
||||
{
|
||||
Bytecode,
|
||||
@ -821,6 +835,13 @@ struct IrFunction
|
||||
LUAU_ASSERT(&block >= blocks.data() && &block <= blocks.data() + blocks.size());
|
||||
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)
|
||||
|
@ -1,6 +1,7 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/AssemblyBuilderX64.h"
|
||||
#include "Luau/IrData.h"
|
||||
#include "Luau/RegisterX64.h"
|
||||
|
||||
@ -14,33 +15,66 @@ namespace CodeGen
|
||||
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
|
||||
{
|
||||
IrRegAllocX64(IrFunction& function);
|
||||
IrRegAllocX64(AssemblyBuilderX64& build, IrFunction& function);
|
||||
|
||||
RegisterX64 allocGprReg(SizeX64 preferredSize);
|
||||
RegisterX64 allocXmmReg();
|
||||
RegisterX64 allocGprReg(SizeX64 preferredSize, uint32_t instIdx);
|
||||
RegisterX64 allocXmmReg(uint32_t instIdx);
|
||||
|
||||
RegisterX64 allocGprRegOrReuse(SizeX64 preferredSize, uint32_t index, std::initializer_list<IrOp> oprefs);
|
||||
RegisterX64 allocXmmRegOrReuse(uint32_t index, std::initializer_list<IrOp> oprefs);
|
||||
RegisterX64 allocGprRegOrReuse(SizeX64 preferredSize, uint32_t instIdx, 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 freeLastUseReg(IrInst& target, uint32_t index);
|
||||
void freeLastUseRegs(const IrInst& inst, uint32_t index);
|
||||
void freeLastUseReg(IrInst& target, uint32_t instIdx);
|
||||
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;
|
||||
|
||||
// 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 assertAllFree() const;
|
||||
void assertNoSpills() const;
|
||||
|
||||
AssemblyBuilderX64& build;
|
||||
IrFunction& function;
|
||||
|
||||
uint32_t currInstIdx = ~0u;
|
||||
|
||||
std::array<bool, 16> freeGprMap;
|
||||
std::array<uint32_t, 16> gprInstUsers;
|
||||
std::array<bool, 16> freeXmmMap;
|
||||
std::array<uint32_t, 16> xmmInstUsers;
|
||||
|
||||
std::bitset<256> usedSpillSlots;
|
||||
unsigned maxUsedSlot = 0;
|
||||
std::vector<IrSpillX64> spills;
|
||||
};
|
||||
|
||||
struct ScopedRegX64
|
||||
@ -62,6 +96,23 @@ struct ScopedRegX64
|
||||
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 CodeGen
|
||||
} // namespace Luau
|
||||
|
@ -175,6 +175,8 @@ inline bool isPseudo(IrCmd cmd)
|
||||
return cmd == IrCmd::NOP || cmd == IrCmd::SUBSTITUTE;
|
||||
}
|
||||
|
||||
IrValueKind getCmdValueKind(IrCmd cmd);
|
||||
|
||||
bool isGCO(uint8_t tag);
|
||||
|
||||
// Manually add or remove use of an operand
|
||||
|
@ -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 w0{KindA64::w, 0};
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
placeAvx("vroundsd", dst, src1, src2, uint8_t(roundingMode) | kRoundingPrecisionInexact, 0x0b, false, AVX_0F3A, AVX_66);
|
||||
|
@ -74,7 +74,7 @@ static NativeProto* createNativeProto(Proto* proto, const IrBuilder& ir)
|
||||
}
|
||||
|
||||
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
|
||||
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;
|
||||
|
||||
lowering.lowerInst(inst, index, next);
|
||||
|
||||
if (lowering.hasError())
|
||||
return false;
|
||||
}
|
||||
|
||||
if (options.includeIr)
|
||||
@ -213,6 +216,8 @@ static void lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction&
|
||||
if (irLocation != ~0u)
|
||||
asmLocation = bcLocations[irLocation];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[[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);
|
||||
|
||||
lowerImpl(build, lowering, ir.function, proto->bytecodeid, options);
|
||||
|
||||
return true;
|
||||
return lowerImpl(build, lowering, ir.function, proto->bytecodeid, options);
|
||||
}
|
||||
|
||||
[[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);
|
||||
|
||||
lowerImpl(build, lowering, ir.function, proto->bytecodeid, options);
|
||||
|
||||
return true;
|
||||
return lowerImpl(build, lowering, ir.function, proto->bytecodeid, options);
|
||||
}
|
||||
|
||||
template<typename AssemblyBuilder>
|
||||
|
@ -11,8 +11,6 @@
|
||||
|
||||
#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 CodeGen
|
||||
@ -176,8 +174,11 @@ void emitBuiltinMathFrexp(IrRegAllocX64& regs, AssemblyBuilderX64& build, int np
|
||||
|
||||
build.vmovsd(luauRegValue(ra), xmm0);
|
||||
|
||||
build.vcvtsi2sd(xmm0, xmm0, dword[sTemporarySlot + 0]);
|
||||
build.vmovsd(luauRegValue(ra + 1), xmm0);
|
||||
if (nresults > 1)
|
||||
{
|
||||
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)
|
||||
@ -190,7 +191,8 @@ void emitBuiltinMathModf(IrRegAllocX64& regs, AssemblyBuilderX64& build, int npa
|
||||
build.vmovsd(xmm1, qword[sTemporarySlot + 0]);
|
||||
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)
|
||||
@ -248,9 +250,9 @@ void emitBuiltin(IrRegAllocX64& regs, AssemblyBuilderX64& build, int bfid, int r
|
||||
OperandX64 argsOp = 0;
|
||||
|
||||
if (args.kind == IrOpKind::VmReg)
|
||||
argsOp = luauRegAddress(args.index);
|
||||
argsOp = luauRegAddress(vmRegOp(args));
|
||||
else if (args.kind == IrOpKind::VmConst)
|
||||
argsOp = luauConstantAddress(args.index);
|
||||
argsOp = luauConstantAddress(vmConstOp(args));
|
||||
|
||||
switch (bfid)
|
||||
{
|
||||
|
@ -101,6 +101,30 @@ void emitReentry(AssemblyBuilderA64& build, ModuleHelpers& helpers)
|
||||
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 CodeGen
|
||||
} // namespace Luau
|
||||
|
@ -46,6 +46,7 @@ void emitUpdateBase(AssemblyBuilderA64& build);
|
||||
void emitExit(AssemblyBuilderA64& build, bool continueInVm);
|
||||
void emitInterrupt(AssemblyBuilderA64& build);
|
||||
void emitReentry(AssemblyBuilderA64& build, ModuleHelpers& helpers);
|
||||
void emitFallback(AssemblyBuilderA64& build, int op, int pcpos);
|
||||
|
||||
} // namespace A64
|
||||
} // namespace CodeGen
|
||||
|
@ -196,33 +196,51 @@ void checkObjectBarrierConditions(AssemblyBuilderX64& build, RegisterX64 tmp, Re
|
||||
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};
|
||||
checkObjectBarrierConditions(build, tmp.reg, object, ra, skip);
|
||||
|
||||
IrCallWrapperX64 callWrap(regs, build);
|
||||
callWrap.addArgument(SizeX64::qword, rState);
|
||||
callWrap.addArgument(SizeX64::qword, object, objectOp);
|
||||
callWrap.addArgument(SizeX64::qword, tmp);
|
||||
callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaC_barrierf)]);
|
||||
{
|
||||
ScopedSpills spillGuard(regs);
|
||||
|
||||
IrCallWrapperX64 callWrap(regs, build);
|
||||
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))
|
||||
build.test(byte[table + offsetof(GCheader, marked)], bitmask(BLACKBIT));
|
||||
build.jcc(ConditionX64::Zero, skip);
|
||||
|
||||
IrCallWrapperX64 callWrap(regs, build);
|
||||
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)]);
|
||||
{
|
||||
ScopedSpills spillGuard(regs);
|
||||
|
||||
IrCallWrapperX64 callWrap(regs, build);
|
||||
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 tmp2{regs, SizeX64::qword};
|
||||
@ -233,11 +251,17 @@ void callCheckGc(IrRegAllocX64& regs, AssemblyBuilderX64& build, Label& skip)
|
||||
build.jcc(ConditionX64::Below, skip);
|
||||
}
|
||||
|
||||
IrCallWrapperX64 callWrap(regs, build);
|
||||
callWrap.addArgument(SizeX64::qword, rState);
|
||||
callWrap.addArgument(SizeX64::dword, 1);
|
||||
callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaC_step)]);
|
||||
emitUpdateBase(build);
|
||||
{
|
||||
ScopedSpills spillGuard(regs);
|
||||
|
||||
IrCallWrapperX64 callWrap(regs, 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)
|
||||
@ -256,7 +280,7 @@ void emitUpdateBase(AssemblyBuilderX64& build)
|
||||
}
|
||||
|
||||
// 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.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)
|
||||
{
|
||||
if (op == LOP_CAPTURE)
|
||||
return;
|
||||
|
||||
NativeFallback& opinfo = data.context.fallback[op];
|
||||
LUAU_ASSERT(opinfo.fallback);
|
||||
|
||||
|
@ -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
|
||||
// See CodeGenX64.cpp for layout
|
||||
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 kStackSize = 32 + 16; // 4 home locations for registers, 16 bytes for additional function call arguments
|
||||
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 sCode = qword[rsp + kStackSize + 8]; // Instruction* code
|
||||
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
|
||||
#if defined(_WIN32)
|
||||
@ -99,6 +101,11 @@ inline OperandX64 luauRegValueInt(int ri)
|
||||
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)
|
||||
{
|
||||
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 callSetTable(IrRegAllocX64& regs, AssemblyBuilderX64& build, int rb, OperandX64 c, int ra);
|
||||
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 callBarrierTableFast(IrRegAllocX64& regs, AssemblyBuilderX64& build, RegisterX64 table, IrOp tableOp, Label& skip);
|
||||
void callCheckGc(IrRegAllocX64& regs, AssemblyBuilderX64& build, Label& skip);
|
||||
void callBarrierObject(IrRegAllocX64& regs, AssemblyBuilderX64& build, RegisterX64 object, IrOp objectOp, int ra);
|
||||
void callBarrierTableFast(IrRegAllocX64& regs, AssemblyBuilderX64& build, RegisterX64 table, IrOp tableOp);
|
||||
void callStepGc(IrRegAllocX64& regs, AssemblyBuilderX64& build);
|
||||
|
||||
void emitExit(AssemblyBuilderX64& build, bool continueInVm);
|
||||
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 emitFallback(AssemblyBuilderX64& build, NativeState& data, int op, int pcpos);
|
||||
|
||||
|
@ -316,7 +316,7 @@ void emitInstReturn(AssemblyBuilderX64& build, ModuleHelpers& helpers, int ra, i
|
||||
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;
|
||||
|
||||
@ -347,7 +347,7 @@ void emitInstSetList(IrRegAllocX64& regs, AssemblyBuilderX64& build, Label& next
|
||||
|
||||
Label skipResize;
|
||||
|
||||
RegisterX64 table = regs.takeReg(rax);
|
||||
RegisterX64 table = regs.takeReg(rax, kInvalidInstIdx);
|
||||
|
||||
build.mov(table, luauRegValue(ra));
|
||||
|
||||
@ -412,7 +412,7 @@ void emitInstSetList(IrRegAllocX64& regs, AssemblyBuilderX64& build, Label& next
|
||||
build.setLabel(endLoop);
|
||||
}
|
||||
|
||||
callBarrierTableFast(regs, build, table, {}, next);
|
||||
callBarrierTableFast(regs, build, table, {});
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
build.mov(rax, sClosure);
|
||||
|
@ -19,14 +19,10 @@ struct IrRegAllocX64;
|
||||
|
||||
void emitInstCall(AssemblyBuilderX64& build, ModuleHelpers& helpers, int ra, int nparams, int nresults);
|
||||
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 emitinstForGLoopFallback(AssemblyBuilderX64& build, int ra, int aux, Label& loopRepeat);
|
||||
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 emitInstCoverage(AssemblyBuilderX64& build, int pcpos);
|
||||
|
||||
|
@ -69,6 +69,9 @@ void updateLastUseLocations(IrFunction& function)
|
||||
instructions[op.index].lastUse = uint32_t(instIdx);
|
||||
};
|
||||
|
||||
if (isPseudo(inst.cmd))
|
||||
continue;
|
||||
|
||||
checkOp(inst.a);
|
||||
checkOp(inst.b);
|
||||
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)
|
||||
{
|
||||
uint32_t liveIns = 0;
|
||||
@ -97,6 +136,9 @@ std::pair<uint32_t, uint32_t> getLiveInOutValueCount(IrFunction& function, IrBlo
|
||||
{
|
||||
IrInst& inst = function.instructions[instIdx];
|
||||
|
||||
if (isPseudo(inst.cmd))
|
||||
continue;
|
||||
|
||||
liveOuts += inst.useCount;
|
||||
|
||||
checkOp(inst.a);
|
||||
@ -149,26 +191,24 @@ static RegisterSet computeBlockLiveInRegSet(IrFunction& function, const IrBlock&
|
||||
RegisterSet inRs;
|
||||
|
||||
auto def = [&](IrOp op, int offset = 0) {
|
||||
LUAU_ASSERT(op.kind == IrOpKind::VmReg);
|
||||
defRs.regs.set(op.index + offset, true);
|
||||
defRs.regs.set(vmRegOp(op) + offset, true);
|
||||
};
|
||||
|
||||
auto use = [&](IrOp op, int offset = 0) {
|
||||
LUAU_ASSERT(op.kind == IrOpKind::VmReg);
|
||||
if (!defRs.regs.test(op.index + offset))
|
||||
inRs.regs.set(op.index + offset, true);
|
||||
if (!defRs.regs.test(vmRegOp(op) + offset))
|
||||
inRs.regs.set(vmRegOp(op) + offset, true);
|
||||
};
|
||||
|
||||
auto maybeDef = [&](IrOp op) {
|
||||
if (op.kind == IrOpKind::VmReg)
|
||||
defRs.regs.set(op.index, true);
|
||||
defRs.regs.set(vmRegOp(op), true);
|
||||
};
|
||||
|
||||
auto maybeUse = [&](IrOp op) {
|
||||
if (op.kind == IrOpKind::VmReg)
|
||||
{
|
||||
if (!defRs.regs.test(op.index))
|
||||
inRs.regs.set(op.index, true);
|
||||
if (!defRs.regs.test(vmRegOp(op)))
|
||||
inRs.regs.set(vmRegOp(op), true);
|
||||
}
|
||||
};
|
||||
|
||||
@ -230,6 +270,7 @@ static RegisterSet computeBlockLiveInRegSet(IrFunction& function, const IrBlock&
|
||||
case IrCmd::STORE_POINTER:
|
||||
case IrCmd::STORE_DOUBLE:
|
||||
case IrCmd::STORE_INT:
|
||||
case IrCmd::STORE_VECTOR:
|
||||
case IrCmd::STORE_TVALUE:
|
||||
maybeDef(inst.a); // Argument can also be a pointer value
|
||||
break;
|
||||
@ -264,9 +305,9 @@ static RegisterSet computeBlockLiveInRegSet(IrFunction& function, const IrBlock&
|
||||
def(inst.a);
|
||||
break;
|
||||
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;
|
||||
case IrCmd::GET_UPVALUE:
|
||||
def(inst.a);
|
||||
@ -298,20 +339,20 @@ static RegisterSet computeBlockLiveInRegSet(IrFunction& function, const IrBlock&
|
||||
maybeUse(inst.a);
|
||||
|
||||
if (function.boolOp(inst.b))
|
||||
capturedRegs.set(inst.a.index, true);
|
||||
capturedRegs.set(vmRegOp(inst.a), true);
|
||||
break;
|
||||
case IrCmd::SETLIST:
|
||||
use(inst.b);
|
||||
useRange(inst.c.index, function.intOp(inst.d));
|
||||
useRange(vmRegOp(inst.c), function.intOp(inst.d));
|
||||
break;
|
||||
case IrCmd::CALL:
|
||||
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;
|
||||
case IrCmd::RETURN:
|
||||
useRange(inst.a.index, function.intOp(inst.b));
|
||||
useRange(vmRegOp(inst.a), function.intOp(inst.b));
|
||||
break;
|
||||
case IrCmd::FASTCALL:
|
||||
case IrCmd::INVOKE_FASTCALL:
|
||||
@ -319,9 +360,9 @@ static RegisterSet computeBlockLiveInRegSet(IrFunction& function, const IrBlock&
|
||||
{
|
||||
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
|
||||
{
|
||||
@ -334,12 +375,12 @@ static RegisterSet computeBlockLiveInRegSet(IrFunction& function, const IrBlock&
|
||||
}
|
||||
else
|
||||
{
|
||||
useVarargs(inst.c.index);
|
||||
useVarargs(vmRegOp(inst.c));
|
||||
}
|
||||
|
||||
// Multiple return sequences (count == -1) are defined by ADJUST_STACK_TO_REG
|
||||
if (int count = function.intOp(inst.f); count != -1)
|
||||
defRange(inst.b.index, count);
|
||||
defRange(vmRegOp(inst.b), count);
|
||||
break;
|
||||
case IrCmd::FORGLOOP:
|
||||
// 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);
|
||||
|
||||
def(inst.a, 2);
|
||||
defRange(inst.a.index + 3, function.intOp(inst.b));
|
||||
defRange(vmRegOp(inst.a) + 3, function.intOp(inst.b));
|
||||
break;
|
||||
case IrCmd::FORGLOOP_FALLBACK:
|
||||
useRange(inst.a.index, 3);
|
||||
useRange(vmRegOp(inst.a), 3);
|
||||
|
||||
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;
|
||||
case IrCmd::FORGPREP_XNEXT_FALLBACK:
|
||||
use(inst.b);
|
||||
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:
|
||||
def(inst.b);
|
||||
break;
|
||||
@ -391,13 +417,13 @@ static RegisterSet computeBlockLiveInRegSet(IrFunction& function, const IrBlock&
|
||||
case IrCmd::FALLBACK_NAMECALL:
|
||||
use(inst.c);
|
||||
|
||||
defRange(inst.b.index, 2);
|
||||
defRange(vmRegOp(inst.b), 2);
|
||||
break;
|
||||
case IrCmd::FALLBACK_PREPVARARGS:
|
||||
// No effect on explicitly referenced registers
|
||||
break;
|
||||
case IrCmd::FALLBACK_GETVARARGS:
|
||||
defRange(inst.b.index, function.intOp(inst.c));
|
||||
defRange(vmRegOp(inst.b), function.intOp(inst.c));
|
||||
break;
|
||||
case IrCmd::FALLBACK_NEWCLOSURE:
|
||||
def(inst.b);
|
||||
@ -408,10 +434,10 @@ static RegisterSet computeBlockLiveInRegSet(IrFunction& function, const IrBlock&
|
||||
case IrCmd::FALLBACK_FORGPREP:
|
||||
use(inst.b);
|
||||
|
||||
defRange(inst.b.index, 3);
|
||||
defRange(vmRegOp(inst.b), 3);
|
||||
break;
|
||||
case IrCmd::ADJUST_STACK_TO_REG:
|
||||
defRange(inst.a.index, -1);
|
||||
defRange(vmRegOp(inst.a), -1);
|
||||
break;
|
||||
case IrCmd::ADJUST_STACK_TO_TOP:
|
||||
// While this can be considered to be a vararg consumer, it is already handled in fastcall instructions
|
||||
|
@ -364,16 +364,16 @@ void IrBuilder::translateInst(LuauOpcode op, const Instruction* pc, int i)
|
||||
translateInstForGPrepInext(*this, pc, i);
|
||||
break;
|
||||
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;
|
||||
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;
|
||||
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;
|
||||
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;
|
||||
case LOP_COVERAGE:
|
||||
inst(IrCmd::COVERAGE, constUint(i));
|
||||
|
@ -58,14 +58,17 @@ void IrCallWrapperX64::call(const OperandX64& func)
|
||||
{
|
||||
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 (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))
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
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 (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
|
||||
addRegisterUse(arg.target.base);
|
||||
|
||||
@ -122,7 +129,7 @@ void IrCallWrapperX64::call(const OperandX64& func)
|
||||
freeSourceRegisters(*candidate);
|
||||
|
||||
LUAU_ASSERT(getRegisterUses(candidate->target.base) == 0);
|
||||
regs.takeReg(candidate->target.base);
|
||||
regs.takeReg(candidate->target.base, kInvalidInstIdx);
|
||||
|
||||
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
|
||||
else if (RegisterX64 conflict = findConflictingTarget(); conflict != noreg)
|
||||
{
|
||||
// Get a fresh register
|
||||
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);
|
||||
renameConflictingRegister(conflict);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -156,10 +155,18 @@ void IrCallWrapperX64::call(const OperandX64& func)
|
||||
|
||||
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)
|
||||
regs.takeReg(arg.target.base);
|
||||
regs.takeReg(arg.target.base, kInvalidInstIdx);
|
||||
|
||||
moveToTarget(arg);
|
||||
|
||||
arg.candidate = false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -176,6 +183,10 @@ void IrCallWrapperX64::call(const OperandX64& func)
|
||||
regs.freeReg(arg.target.base);
|
||||
}
|
||||
|
||||
regs.preserveAndFreeInstValues();
|
||||
|
||||
regs.assertAllFree();
|
||||
|
||||
build.call(funcOp);
|
||||
}
|
||||
|
||||
@ -362,6 +373,19 @@ RegisterX64 IrCallWrapperX64::findConflictingTarget() const
|
||||
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
|
||||
{
|
||||
return reg.size == SizeX64::xmmword ? xmmUses[reg.index] : (reg.size != SizeX64::none ? gprUses[reg.index] : 0);
|
||||
|
@ -100,6 +100,8 @@ const char* getCmdName(IrCmd cmd)
|
||||
return "STORE_DOUBLE";
|
||||
case IrCmd::STORE_INT:
|
||||
return "STORE_INT";
|
||||
case IrCmd::STORE_VECTOR:
|
||||
return "STORE_VECTOR";
|
||||
case IrCmd::STORE_TVALUE:
|
||||
return "STORE_TVALUE";
|
||||
case IrCmd::STORE_NODE_VALUE_TV:
|
||||
@ -238,14 +240,6 @@ const char* getCmdName(IrCmd cmd)
|
||||
return "FORGLOOP_FALLBACK";
|
||||
case IrCmd::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:
|
||||
return "COVERAGE";
|
||||
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);
|
||||
break;
|
||||
case IrOpKind::VmReg:
|
||||
append(ctx.result, "R%u", op.index);
|
||||
append(ctx.result, "R%d", vmRegOp(op));
|
||||
break;
|
||||
case IrOpKind::VmConst:
|
||||
append(ctx.result, "K%u", op.index);
|
||||
append(ctx.result, "K%d", vmConstOp(op));
|
||||
break;
|
||||
case IrOpKind::VmUpvalue:
|
||||
append(ctx.result, "U%u", op.index);
|
||||
append(ctx.result, "U%d", vmUpvalueOp(op));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include "NativeState.h"
|
||||
|
||||
#include "lstate.h"
|
||||
#include "lgc.h"
|
||||
|
||||
// TODO: Eventually this can go away
|
||||
// #define TRACE
|
||||
@ -32,7 +33,7 @@ struct LoweringStatsA64
|
||||
~LoweringStatsA64()
|
||||
{
|
||||
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;
|
||||
#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)
|
||||
: build(build)
|
||||
, helpers(helpers)
|
||||
@ -108,37 +137,89 @@ bool IrLoweringA64::canLower(const IrFunction& function)
|
||||
case IrCmd::LOAD_TVALUE:
|
||||
case IrCmd::LOAD_NODE_VALUE_TV:
|
||||
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_POINTER:
|
||||
case IrCmd::STORE_DOUBLE:
|
||||
case IrCmd::STORE_INT:
|
||||
case IrCmd::STORE_TVALUE:
|
||||
case IrCmd::STORE_NODE_VALUE_TV:
|
||||
case IrCmd::ADD_INT:
|
||||
case IrCmd::SUB_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:
|
||||
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::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_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::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::CALL:
|
||||
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:
|
||||
continue;
|
||||
|
||||
default:
|
||||
#ifdef TRACE
|
||||
printf("A64 lowering missing %s\n", getCmdName(inst.cmd));
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -199,6 +280,64 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
inst.regA64 = regs.allocReg(KindA64::x);
|
||||
build.ldr(inst.regA64, mem(rClosure, offsetof(Closure, env)));
|
||||
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:
|
||||
{
|
||||
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:
|
||||
build.str(regOp(inst.b), mem(regOp(inst.a), offsetof(LuaNode, val)));
|
||||
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:
|
||||
{
|
||||
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:
|
||||
{
|
||||
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 temp2 = tempDouble(inst.b);
|
||||
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);
|
||||
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:
|
||||
{
|
||||
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);
|
||||
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:
|
||||
jumpOrFallthrough(blockOp(inst.a), next);
|
||||
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:
|
||||
if (inst.b.kind == IrOpKind::Constant)
|
||||
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);
|
||||
}
|
||||
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:
|
||||
{
|
||||
IrCondition cond = conditionOp(inst.c);
|
||||
@ -349,6 +607,150 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
jumpOrFallthrough(blockOp(inst.e), next);
|
||||
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:
|
||||
regs.assertAllFree();
|
||||
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.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);
|
||||
break;
|
||||
case IrCmd::GET_IMPORT:
|
||||
regs.assertAllFree();
|
||||
emitInstGetImport(build, vmRegOp(inst.a), uintOp(inst.b));
|
||||
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:
|
||||
{
|
||||
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)));
|
||||
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:
|
||||
build.cmp(regOp(inst.a), tagOp(inst.b));
|
||||
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:
|
||||
{
|
||||
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.ldrb(tempw, mem(temp, offsetof(Table, safeenv)));
|
||||
build.cbz(tempw, labelOp(inst.a));
|
||||
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:
|
||||
{
|
||||
unsigned int pcpos = uintOp(inst.a);
|
||||
@ -450,6 +997,93 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
build.setLabel(skip);
|
||||
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:
|
||||
{
|
||||
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)));
|
||||
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:
|
||||
regs.assertAllFree();
|
||||
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();
|
||||
emitInstReturn(build, helpers, vmRegOp(inst.a), intOp(inst.b));
|
||||
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:
|
||||
LUAU_ASSERT(!"Not supported yet");
|
||||
break;
|
||||
@ -488,6 +1218,11 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
regs.freeTempRegs();
|
||||
}
|
||||
|
||||
bool IrLoweringA64::hasError() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IrLoweringA64::isFallthroughBlock(IrBlock target, IrBlock next)
|
||||
{
|
||||
return target.start == next.start;
|
||||
|
@ -30,6 +30,8 @@ struct IrLoweringA64
|
||||
|
||||
void lowerInst(IrInst& inst, uint32_t index, IrBlock& next);
|
||||
|
||||
bool hasError() const;
|
||||
|
||||
bool isFallthroughBlock(IrBlock target, IrBlock next);
|
||||
void jumpOrFallthrough(IrBlock& target, IrBlock& next);
|
||||
|
||||
|
@ -27,18 +27,39 @@ IrLoweringX64::IrLoweringX64(AssemblyBuilderX64& build, ModuleHelpers& helpers,
|
||||
, helpers(helpers)
|
||||
, data(data)
|
||||
, function(function)
|
||||
, regs(function)
|
||||
, regs(build, function)
|
||||
{
|
||||
// In order to allocate registers during lowering, we need to know where instruction results are last used
|
||||
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)
|
||||
{
|
||||
regs.currInstIdx = index;
|
||||
|
||||
switch (inst.cmd)
|
||||
{
|
||||
case IrCmd::LOAD_TAG:
|
||||
inst.regX64 = regs.allocGprReg(SizeX64::dword);
|
||||
inst.regX64 = regs.allocGprReg(SizeX64::dword, index);
|
||||
|
||||
if (inst.a.kind == IrOpKind::VmReg)
|
||||
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");
|
||||
break;
|
||||
case IrCmd::LOAD_POINTER:
|
||||
inst.regX64 = regs.allocGprReg(SizeX64::qword);
|
||||
inst.regX64 = regs.allocGprReg(SizeX64::qword, index);
|
||||
|
||||
if (inst.a.kind == IrOpKind::VmReg)
|
||||
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");
|
||||
break;
|
||||
case IrCmd::LOAD_DOUBLE:
|
||||
inst.regX64 = regs.allocXmmReg();
|
||||
inst.regX64 = regs.allocXmmReg(index);
|
||||
|
||||
if (inst.a.kind == IrOpKind::VmReg)
|
||||
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");
|
||||
break;
|
||||
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)));
|
||||
break;
|
||||
case IrCmd::LOAD_TVALUE:
|
||||
inst.regX64 = regs.allocXmmReg();
|
||||
inst.regX64 = regs.allocXmmReg(index);
|
||||
|
||||
if (inst.a.kind == IrOpKind::VmReg)
|
||||
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");
|
||||
break;
|
||||
case IrCmd::LOAD_NODE_VALUE_TV:
|
||||
inst.regX64 = regs.allocXmmReg();
|
||||
inst.regX64 = regs.allocXmmReg(index);
|
||||
|
||||
build.vmovups(inst.regX64, luauNodeValue(regOp(inst.a)));
|
||||
break;
|
||||
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, qword[inst.regX64 + offsetof(Closure, env)]);
|
||||
@ -130,7 +151,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
break;
|
||||
case IrCmd::GET_SLOT_NODE_ADDR:
|
||||
{
|
||||
inst.regX64 = regs.allocGprReg(SizeX64::qword);
|
||||
inst.regX64 = regs.allocGprReg(SizeX64::qword, index);
|
||||
|
||||
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:
|
||||
{
|
||||
inst.regX64 = regs.allocGprReg(SizeX64::qword);
|
||||
inst.regX64 = regs.allocGprReg(SizeX64::qword, index);
|
||||
|
||||
// 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};
|
||||
|
||||
@ -192,6 +213,13 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
LUAU_ASSERT(!"Unsupported instruction form");
|
||||
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:
|
||||
if (inst.a.kind == IrOpKind::VmReg)
|
||||
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.b), inst.b);
|
||||
callWrap.call(qword[rNativeContext + offsetof(NativeContext, libm_pow)]);
|
||||
inst.regX64 = regs.takeReg(xmm0);
|
||||
inst.regX64 = regs.takeReg(xmm0, index);
|
||||
break;
|
||||
}
|
||||
case IrCmd::MIN_NUM:
|
||||
@ -398,8 +426,10 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
ScopedRegX64 tmp1{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));
|
||||
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.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:
|
||||
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));
|
||||
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)));
|
||||
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.call(qword[rNativeContext + offsetof(NativeContext, luaH_getn)]);
|
||||
|
||||
inst.regX64 = regs.allocXmmReg();
|
||||
inst.regX64 = regs.allocXmmReg(index);
|
||||
build.vcvtsi2sd(inst.regX64, inst.regX64, eax);
|
||||
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.b)), inst.b);
|
||||
callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaH_new)]);
|
||||
inst.regX64 = regs.takeReg(rax);
|
||||
inst.regX64 = regs.takeReg(rax, index);
|
||||
break;
|
||||
}
|
||||
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, regOp(inst.a), inst.a);
|
||||
callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaH_clone)]);
|
||||
inst.regX64 = regs.takeReg(rax);
|
||||
inst.regX64 = regs.takeReg(rax, index);
|
||||
break;
|
||||
}
|
||||
case IrCmd::TRY_NUM_TO_INDEX:
|
||||
{
|
||||
inst.regX64 = regs.allocGprReg(SizeX64::dword);
|
||||
inst.regX64 = regs.allocGprReg(SizeX64::dword, index);
|
||||
|
||||
ScopedRegX64 tmp{regs, SizeX64::xmmword};
|
||||
|
||||
@ -574,35 +606,39 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
ScopedRegX64 tmp2{regs, SizeX64::qword};
|
||||
build.mov(tmp2.reg, qword[rState + offsetof(lua_State, global)]);
|
||||
|
||||
IrCallWrapperX64 callWrap(regs, build, index);
|
||||
callWrap.addArgument(SizeX64::qword, tmp);
|
||||
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);
|
||||
{
|
||||
ScopedSpills spillGuard(regs);
|
||||
|
||||
IrCallWrapperX64 callWrap(regs, build, index);
|
||||
callWrap.addArgument(SizeX64::qword, tmp);
|
||||
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;
|
||||
}
|
||||
case IrCmd::INT_TO_NUM:
|
||||
inst.regX64 = regs.allocXmmReg();
|
||||
inst.regX64 = regs.allocXmmReg(index);
|
||||
|
||||
build.vcvtsi2sd(inst.regX64, inst.regX64, regOp(inst.a));
|
||||
break;
|
||||
case IrCmd::ADJUST_STACK_TO_REG:
|
||||
{
|
||||
ScopedRegX64 tmp{regs, SizeX64::qword};
|
||||
|
||||
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.mov(qword[rState + offsetof(lua_State, top)], tmp.reg);
|
||||
}
|
||||
else if (inst.b.kind == IrOpKind::Inst)
|
||||
{
|
||||
ScopedRegX64 tmp(regs, regs.allocGprRegOrReuse(SizeX64::dword, index, {inst.b}));
|
||||
|
||||
build.shl(qwordReg(tmp.reg), kTValueSizeLog2);
|
||||
build.lea(qwordReg(tmp.reg), addr[rBase + qwordReg(tmp.reg) + vmRegOp(inst.a) * sizeof(TValue)]);
|
||||
build.mov(qword[rState + offsetof(lua_State, top)], qwordReg(tmp.reg));
|
||||
build.mov(dwordReg(tmp.reg), regOp(inst.b));
|
||||
build.shl(tmp.reg, kTValueSizeLog2);
|
||||
build.lea(tmp.reg, addr[rBase + tmp.reg + vmRegOp(inst.a) * sizeof(TValue)]);
|
||||
build.mov(qword[rState + offsetof(lua_State, top)], tmp.reg);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -640,52 +676,37 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
int nparams = intOp(inst.e);
|
||||
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)]);
|
||||
|
||||
// 5th parameter (args) is left unset for LOP_FASTCALL1
|
||||
if (args.cat == CategoryX64::mem)
|
||||
{
|
||||
if (build.abi == ABIX64::Windows)
|
||||
{
|
||||
build.lea(rcx, args);
|
||||
build.mov(sArg5, rcx);
|
||||
}
|
||||
else
|
||||
{
|
||||
build.lea(rArg5, args);
|
||||
}
|
||||
}
|
||||
IrCallWrapperX64 callWrap(regs, build, index);
|
||||
callWrap.addArgument(SizeX64::qword, rState);
|
||||
callWrap.addArgument(SizeX64::qword, luauRegAddress(ra));
|
||||
callWrap.addArgument(SizeX64::qword, luauRegAddress(arg));
|
||||
callWrap.addArgument(SizeX64::dword, nresults);
|
||||
callWrap.addArgument(SizeX64::qword, args);
|
||||
|
||||
if (nparams == LUA_MULTRET)
|
||||
{
|
||||
// L->top - (ra + 1)
|
||||
RegisterX64 reg = (build.abi == ABIX64::Windows) ? rcx : rArg6;
|
||||
// Compute 'L->top - (ra + 1)', on SystemV, take r9 register to compute directly into the argument
|
||||
// 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.lea(rdx, addr[rBase + (ra + 1) * sizeof(TValue)]);
|
||||
build.sub(reg, rdx);
|
||||
build.lea(tmp.reg, addr[rBase + (ra + 1) * sizeof(TValue)]);
|
||||
build.sub(reg, tmp.reg);
|
||||
build.shr(reg, kTValueSizeLog2);
|
||||
|
||||
if (build.abi == ABIX64::Windows)
|
||||
build.mov(sArg6, reg);
|
||||
callWrap.addArgument(SizeX64::dword, dwordReg(reg));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (build.abi == ABIX64::Windows)
|
||||
build.mov(sArg6, nparams);
|
||||
else
|
||||
build.mov(rArg6, nparams);
|
||||
callWrap.addArgument(SizeX64::dword, nparams);
|
||||
}
|
||||
|
||||
build.mov(rArg1, rState);
|
||||
build.lea(rArg2, luauRegAddress(ra));
|
||||
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
|
||||
callWrap.call(func.release());
|
||||
inst.regX64 = regs.takeReg(eax, index); // Result of a builtin call is returned in eax
|
||||
break;
|
||||
}
|
||||
case IrCmd::CHECK_FASTCALL_RES:
|
||||
@ -738,6 +759,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
}
|
||||
break;
|
||||
case IrCmd::GET_IMPORT:
|
||||
regs.assertAllFree();
|
||||
emitInstGetImportFallback(build, vmRegOp(inst.a), uintOp(inst.b));
|
||||
break;
|
||||
case IrCmd::CONCAT:
|
||||
@ -777,7 +799,6 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
}
|
||||
case IrCmd::SET_UPVALUE:
|
||||
{
|
||||
Label next;
|
||||
ScopedRegX64 tmp1{regs, SizeX64::qword};
|
||||
ScopedRegX64 tmp2{regs, SizeX64::qword};
|
||||
|
||||
@ -794,8 +815,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
|
||||
tmp1.free();
|
||||
|
||||
callBarrierObject(regs, build, tmp2.release(), {}, vmRegOp(inst.b), next);
|
||||
build.setLabel(next);
|
||||
callBarrierObject(regs, build, tmp2.release(), {}, vmRegOp(inst.b));
|
||||
break;
|
||||
}
|
||||
case IrCmd::PREPARE_FORN:
|
||||
@ -859,26 +879,14 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
emitInterrupt(build, uintOp(inst.a));
|
||||
break;
|
||||
case IrCmd::CHECK_GC:
|
||||
{
|
||||
Label skip;
|
||||
callCheckGc(regs, build, skip);
|
||||
build.setLabel(skip);
|
||||
callStepGc(regs, build);
|
||||
break;
|
||||
}
|
||||
case IrCmd::BARRIER_OBJ:
|
||||
{
|
||||
Label skip;
|
||||
callBarrierObject(regs, build, regOp(inst.a), inst.a, vmRegOp(inst.b), skip);
|
||||
build.setLabel(skip);
|
||||
callBarrierObject(regs, build, regOp(inst.a), inst.a, vmRegOp(inst.b));
|
||||
break;
|
||||
}
|
||||
case IrCmd::BARRIER_TABLE_BACK:
|
||||
{
|
||||
Label skip;
|
||||
callBarrierTableFast(regs, build, regOp(inst.a), inst.a, skip);
|
||||
build.setLabel(skip);
|
||||
callBarrierTableFast(regs, build, regOp(inst.a), inst.a);
|
||||
break;
|
||||
}
|
||||
case IrCmd::BARRIER_TABLE_FORWARD:
|
||||
{
|
||||
Label skip;
|
||||
@ -886,11 +894,15 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
ScopedRegX64 tmp{regs, SizeX64::qword};
|
||||
checkObjectBarrierConditions(build, tmp.reg, regOp(inst.a), vmRegOp(inst.b), skip);
|
||||
|
||||
IrCallWrapperX64 callWrap(regs, build, index);
|
||||
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)]);
|
||||
{
|
||||
ScopedSpills spillGuard(regs);
|
||||
|
||||
IrCallWrapperX64 callWrap(regs, build, index);
|
||||
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);
|
||||
break;
|
||||
@ -925,10 +937,14 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
|
||||
tmp1.free();
|
||||
|
||||
IrCallWrapperX64 callWrap(regs, build, index);
|
||||
callWrap.addArgument(SizeX64::qword, rState);
|
||||
callWrap.addArgument(SizeX64::qword, tmp2);
|
||||
callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaF_close)]);
|
||||
{
|
||||
ScopedSpills spillGuard(regs);
|
||||
|
||||
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);
|
||||
break;
|
||||
@ -939,19 +955,17 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
|
||||
// Fallbacks to non-IR instruction implementations
|
||||
case IrCmd::SETLIST:
|
||||
{
|
||||
Label next;
|
||||
regs.assertAllFree();
|
||||
emitInstSetList(regs, build, next, vmRegOp(inst.b), vmRegOp(inst.c), intOp(inst.d), uintOp(inst.e));
|
||||
build.setLabel(next);
|
||||
emitInstSetList(regs, build, vmRegOp(inst.b), vmRegOp(inst.c), intOp(inst.d), uintOp(inst.e));
|
||||
break;
|
||||
}
|
||||
case IrCmd::CALL:
|
||||
regs.assertAllFree();
|
||||
regs.assertNoSpills();
|
||||
emitInstCall(build, helpers, vmRegOp(inst.a), intOp(inst.b), intOp(inst.c));
|
||||
break;
|
||||
case IrCmd::RETURN:
|
||||
regs.assertAllFree();
|
||||
regs.assertNoSpills();
|
||||
emitInstReturn(build, helpers, vmRegOp(inst.a), intOp(inst.b));
|
||||
break;
|
||||
case IrCmd::FORGLOOP:
|
||||
@ -967,22 +981,6 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
regs.assertAllFree();
|
||||
emitInstForGPrepXnextFallback(build, uintOp(inst.a), vmRegOp(inst.b), labelOp(inst.c));
|
||||
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:
|
||||
regs.assertAllFree();
|
||||
emitInstCoverage(build, uintOp(inst.a));
|
||||
@ -1066,6 +1064,15 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
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)
|
||||
{
|
||||
return target.start == next.start;
|
||||
@ -1077,7 +1084,7 @@ void IrLoweringX64::jumpOrFallthrough(IrBlock& target, IrBlock& next)
|
||||
build.jmp(target.label);
|
||||
}
|
||||
|
||||
OperandX64 IrLoweringX64::memRegDoubleOp(IrOp op) const
|
||||
OperandX64 IrLoweringX64::memRegDoubleOp(IrOp op)
|
||||
{
|
||||
switch (op.kind)
|
||||
{
|
||||
@ -1096,7 +1103,7 @@ OperandX64 IrLoweringX64::memRegDoubleOp(IrOp op) const
|
||||
return noreg;
|
||||
}
|
||||
|
||||
OperandX64 IrLoweringX64::memRegTagOp(IrOp op) const
|
||||
OperandX64 IrLoweringX64::memRegTagOp(IrOp op)
|
||||
{
|
||||
switch (op.kind)
|
||||
{
|
||||
@ -1113,9 +1120,13 @@ OperandX64 IrLoweringX64::memRegTagOp(IrOp op) const
|
||||
return noreg;
|
||||
}
|
||||
|
||||
RegisterX64 IrLoweringX64::regOp(IrOp op) const
|
||||
RegisterX64 IrLoweringX64::regOp(IrOp op)
|
||||
{
|
||||
IrInst& inst = function.instOp(op);
|
||||
|
||||
if (inst.spilled)
|
||||
regs.restore(inst, false);
|
||||
|
||||
LUAU_ASSERT(inst.regX64 != noreg);
|
||||
return inst.regX64;
|
||||
}
|
||||
|
@ -27,13 +27,17 @@ struct IrLoweringX64
|
||||
|
||||
void lowerInst(IrInst& inst, uint32_t index, IrBlock& next);
|
||||
|
||||
bool hasError() const;
|
||||
|
||||
bool isFallthroughBlock(IrBlock target, IrBlock next);
|
||||
void jumpOrFallthrough(IrBlock& target, IrBlock& next);
|
||||
|
||||
void storeDoubleAsFloat(OperandX64 dst, IrOp src);
|
||||
|
||||
// Operand data lookup helpers
|
||||
OperandX64 memRegDoubleOp(IrOp op) const;
|
||||
OperandX64 memRegTagOp(IrOp op) const;
|
||||
RegisterX64 regOp(IrOp op) const;
|
||||
OperandX64 memRegDoubleOp(IrOp op);
|
||||
OperandX64 memRegTagOp(IrOp op);
|
||||
RegisterX64 regOp(IrOp op);
|
||||
|
||||
IrConst constOp(IrOp op) const;
|
||||
uint8_t tagOp(IrOp op) const;
|
||||
|
@ -151,6 +151,15 @@ void IrRegAllocA64::assertAllFree() const
|
||||
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)
|
||||
{
|
||||
switch (kind)
|
||||
|
@ -30,6 +30,7 @@ struct IrRegAllocA64
|
||||
void freeTempRegs();
|
||||
|
||||
void assertAllFree() const;
|
||||
void assertAllFreeExcept(RegisterA64 reg) const;
|
||||
|
||||
IrFunction& function;
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
// 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 "EmitCommonX64.h"
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
namespace CodeGen
|
||||
@ -10,14 +12,22 @@ namespace X64
|
||||
|
||||
static const RegisterX64 kGprAllocOrder[] = {rax, rdx, rcx, rbx, rsi, rdi, r8, r9, r10, r11};
|
||||
|
||||
IrRegAllocX64::IrRegAllocX64(IrFunction& function)
|
||||
: function(function)
|
||||
static bool isFullTvalueOperand(IrCmd cmd)
|
||||
{
|
||||
freeGprMap.fill(true);
|
||||
freeXmmMap.fill(true);
|
||||
return cmd == IrCmd::LOAD_TVALUE || cmd == IrCmd::LOAD_NODE_VALUE_TV;
|
||||
}
|
||||
|
||||
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(
|
||||
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])
|
||||
{
|
||||
freeGprMap[reg.index] = false;
|
||||
gprInstUsers[reg.index] = instIdx;
|
||||
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");
|
||||
return noreg;
|
||||
}
|
||||
|
||||
RegisterX64 IrRegAllocX64::allocXmmReg()
|
||||
RegisterX64 IrRegAllocX64::allocXmmReg(uint32_t instIdx)
|
||||
{
|
||||
for (size_t i = 0; i < freeXmmMap.size(); ++i)
|
||||
{
|
||||
if (freeXmmMap[i])
|
||||
{
|
||||
freeXmmMap[i] = false;
|
||||
xmmInstUsers[i] = instIdx;
|
||||
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");
|
||||
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)
|
||||
{
|
||||
@ -59,20 +79,21 @@ RegisterX64 IrRegAllocX64::allocGprRegOrReuse(SizeX64 preferredSize, uint32_t in
|
||||
|
||||
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 != noreg);
|
||||
|
||||
source.reusedReg = true;
|
||||
gprInstUsers[source.regX64.index] = instIdx;
|
||||
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)
|
||||
{
|
||||
@ -81,32 +102,45 @@ RegisterX64 IrRegAllocX64::allocXmmRegOrReuse(uint32_t index, std::initializer_l
|
||||
|
||||
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 != noreg);
|
||||
|
||||
source.reusedReg = true;
|
||||
xmmInstUsers[source.regX64.index] = instIdx;
|
||||
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 (!freeXmmMap[reg.index])
|
||||
{
|
||||
LUAU_ASSERT(xmmInstUsers[reg.index] != kInvalidInstIdx);
|
||||
preserve(function.instructions[xmmInstUsers[reg.index]]);
|
||||
}
|
||||
|
||||
LUAU_ASSERT(freeXmmMap[reg.index]);
|
||||
freeXmmMap[reg.index] = false;
|
||||
xmmInstUsers[reg.index] = instIdx;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!freeGprMap[reg.index])
|
||||
{
|
||||
LUAU_ASSERT(gprInstUsers[reg.index] != kInvalidInstIdx);
|
||||
preserve(function.instructions[gprInstUsers[reg.index]]);
|
||||
}
|
||||
|
||||
LUAU_ASSERT(freeGprMap[reg.index]);
|
||||
freeGprMap[reg.index] = false;
|
||||
gprInstUsers[reg.index] = instIdx;
|
||||
}
|
||||
|
||||
return reg;
|
||||
@ -118,17 +152,19 @@ void IrRegAllocX64::freeReg(RegisterX64 reg)
|
||||
{
|
||||
LUAU_ASSERT(!freeXmmMap[reg.index]);
|
||||
freeXmmMap[reg.index] = true;
|
||||
xmmInstUsers[reg.index] = kInvalidInstIdx;
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_ASSERT(!freeGprMap[reg.index]);
|
||||
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
|
||||
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)
|
||||
freeLastUseReg(function.instructions[op.index], index);
|
||||
freeLastUseReg(function.instructions[op.index], instIdx);
|
||||
};
|
||||
|
||||
checkOp(inst.a);
|
||||
@ -154,9 +190,132 @@ void IrRegAllocX64::freeLastUseRegs(const IrInst& inst, uint32_t index)
|
||||
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
|
||||
@ -175,6 +334,33 @@ bool IrRegAllocX64::shouldFreeGpr(RegisterX64 reg) const
|
||||
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
|
||||
{
|
||||
if (reg.size == SizeX64::xmmword)
|
||||
@ -192,6 +378,11 @@ void IrRegAllocX64::assertAllFree() const
|
||||
LUAU_ASSERT(free);
|
||||
}
|
||||
|
||||
void IrRegAllocX64::assertNoSpills() const
|
||||
{
|
||||
LUAU_ASSERT(spills.empty());
|
||||
}
|
||||
|
||||
ScopedRegX64::ScopedRegX64(IrRegAllocX64& owner)
|
||||
: owner(owner)
|
||||
, reg(noreg)
|
||||
@ -222,9 +413,9 @@ void ScopedRegX64::alloc(SizeX64 size)
|
||||
LUAU_ASSERT(reg == noreg);
|
||||
|
||||
if (size == SizeX64::xmmword)
|
||||
reg = owner.allocXmmReg();
|
||||
reg = owner.allocXmmReg(kInvalidInstIdx);
|
||||
else
|
||||
reg = owner.allocGprReg(size);
|
||||
reg = owner.allocGprReg(size, kInvalidInstIdx);
|
||||
}
|
||||
|
||||
void ScopedRegX64::free()
|
||||
@ -241,6 +432,41 @@ RegisterX64 ScopedRegX64::release()
|
||||
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 CodeGen
|
||||
} // namespace Luau
|
||||
|
@ -61,7 +61,8 @@ BuiltinImplResult translateBuiltinNumberTo2Number(
|
||||
if (ra != arg)
|
||||
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};
|
||||
}
|
||||
@ -190,10 +191,10 @@ BuiltinImplResult translateBuiltinMathClamp(IrBuilder& build, int nparams, int r
|
||||
|
||||
build.loadAndCheckTag(build.vmReg(arg), 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 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.beginBlock(block);
|
||||
@ -274,6 +275,27 @@ BuiltinImplResult translateBuiltinTypeof(IrBuilder& build, int nparams, int ra,
|
||||
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)
|
||||
{
|
||||
// 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);
|
||||
case LBF_TYPEOF:
|
||||
return translateBuiltinTypeof(build, nparams, ra, arg, args, nresults, fallback);
|
||||
case LBF_VECTOR:
|
||||
return translateBuiltinVector(build, nparams, ra, arg, args, nresults, fallback);
|
||||
default:
|
||||
return {BuiltinImplType::None, -1};
|
||||
}
|
||||
|
@ -301,7 +301,7 @@ static void translateInstBinaryNumeric(IrBuilder& build, int ra, int rb, int rc,
|
||||
if (opc.kind == IrOpKind::VmConst)
|
||||
{
|
||||
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);
|
||||
|
||||
@ -1108,5 +1108,71 @@ void translateInstNamecall(IrBuilder& build, const Instruction* pc, int pcpos)
|
||||
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 Luau
|
||||
|
@ -61,6 +61,8 @@ void translateInstSetGlobal(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 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 Luau
|
||||
|
@ -14,6 +14,134 @@ namespace Luau
|
||||
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)
|
||||
{
|
||||
IrInst& inst = function.instructions[instIdx];
|
||||
|
@ -45,6 +45,7 @@ void initFallbackTable(NativeState& data)
|
||||
CODEGEN_SET_FALLBACK(LOP_BREAK, 0);
|
||||
|
||||
// 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_SETGLOBAL, 0);
|
||||
CODEGEN_SET_FALLBACK(LOP_GETTABLEKS, 0);
|
||||
|
@ -96,20 +96,17 @@ struct ConstPropState
|
||||
|
||||
void invalidateTag(IrOp regOp)
|
||||
{
|
||||
LUAU_ASSERT(regOp.kind == IrOpKind::VmReg);
|
||||
invalidate(regs[regOp.index], /* invalidateTag */ true, /* invalidateValue */ false);
|
||||
invalidate(regs[vmRegOp(regOp)], /* invalidateTag */ true, /* invalidateValue */ false);
|
||||
}
|
||||
|
||||
void invalidateValue(IrOp regOp)
|
||||
{
|
||||
LUAU_ASSERT(regOp.kind == IrOpKind::VmReg);
|
||||
invalidate(regs[regOp.index], /* invalidateTag */ false, /* invalidateValue */ true);
|
||||
invalidate(regs[vmRegOp(regOp)], /* invalidateTag */ false, /* invalidateValue */ true);
|
||||
}
|
||||
|
||||
void invalidate(IrOp regOp)
|
||||
{
|
||||
LUAU_ASSERT(regOp.kind == IrOpKind::VmReg);
|
||||
invalidate(regs[regOp.index], /* invalidateTag */ true, /* invalidateValue */ true);
|
||||
invalidate(regs[vmRegOp(regOp)], /* invalidateTag */ true, /* invalidateValue */ true);
|
||||
}
|
||||
|
||||
void invalidateRegistersFrom(int firstReg)
|
||||
@ -156,17 +153,16 @@ struct ConstPropState
|
||||
|
||||
void createRegLink(uint32_t instIdx, IrOp regOp)
|
||||
{
|
||||
LUAU_ASSERT(regOp.kind == IrOpKind::VmReg);
|
||||
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)
|
||||
{
|
||||
if (op.kind == IrOpKind::VmReg)
|
||||
{
|
||||
maxReg = int(op.index) > maxReg ? int(op.index) : maxReg;
|
||||
return ®s[op.index];
|
||||
maxReg = vmRegOp(op) > maxReg ? vmRegOp(op) : maxReg;
|
||||
return ®s[vmRegOp(op)];
|
||||
}
|
||||
|
||||
if (RegisterLink* link = tryGetRegLink(op))
|
||||
@ -368,6 +364,9 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
|
||||
}
|
||||
}
|
||||
break;
|
||||
case IrCmd::STORE_VECTOR:
|
||||
state.invalidateValue(inst.a);
|
||||
break;
|
||||
case IrCmd::STORE_TVALUE:
|
||||
if (inst.a.kind == IrOpKind::VmReg)
|
||||
{
|
||||
@ -503,15 +502,9 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
|
||||
}
|
||||
}
|
||||
break;
|
||||
case IrCmd::AND:
|
||||
case IrCmd::ANDK:
|
||||
case IrCmd::OR:
|
||||
case IrCmd::ORK:
|
||||
state.invalidate(inst.a);
|
||||
break;
|
||||
case IrCmd::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;
|
||||
|
||||
// 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();
|
||||
break;
|
||||
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
|
||||
break;
|
||||
case IrCmd::PREPARE_FORN:
|
||||
@ -605,14 +598,14 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
|
||||
state.invalidateUserCall();
|
||||
break;
|
||||
case IrCmd::CALL:
|
||||
state.invalidateRegistersFrom(inst.a.index);
|
||||
state.invalidateRegistersFrom(vmRegOp(inst.a));
|
||||
state.invalidateUserCall();
|
||||
break;
|
||||
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;
|
||||
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();
|
||||
break;
|
||||
case IrCmd::FORGPREP_XNEXT_FALLBACK:
|
||||
@ -633,14 +626,14 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
|
||||
state.invalidateUserCall();
|
||||
break;
|
||||
case IrCmd::FALLBACK_NAMECALL:
|
||||
state.invalidate(IrOp{inst.b.kind, inst.b.index + 0u});
|
||||
state.invalidate(IrOp{inst.b.kind, inst.b.index + 1u});
|
||||
state.invalidate(IrOp{inst.b.kind, vmRegOp(inst.b) + 0u});
|
||||
state.invalidate(IrOp{inst.b.kind, vmRegOp(inst.b) + 1u});
|
||||
state.invalidateUserCall();
|
||||
break;
|
||||
case IrCmd::FALLBACK_PREPVARARGS:
|
||||
break;
|
||||
case IrCmd::FALLBACK_GETVARARGS:
|
||||
state.invalidateRegistersFrom(inst.b.index);
|
||||
state.invalidateRegistersFrom(vmRegOp(inst.b));
|
||||
break;
|
||||
case IrCmd::FALLBACK_NEWCLOSURE:
|
||||
state.invalidate(inst.b);
|
||||
@ -649,9 +642,9 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
|
||||
state.invalidate(inst.b);
|
||||
break;
|
||||
case IrCmd::FALLBACK_FORGPREP:
|
||||
state.invalidate(IrOp{inst.b.kind, inst.b.index + 0u});
|
||||
state.invalidate(IrOp{inst.b.kind, inst.b.index + 1u});
|
||||
state.invalidate(IrOp{inst.b.kind, inst.b.index + 2u});
|
||||
state.invalidate(IrOp{inst.b.kind, vmRegOp(inst.b) + 0u});
|
||||
state.invalidate(IrOp{inst.b.kind, vmRegOp(inst.b) + 1u});
|
||||
state.invalidate(IrOp{inst.b.kind, vmRegOp(inst.b) + 2u});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ enum lua_Status
|
||||
LUA_OK = 0,
|
||||
LUA_YIELD,
|
||||
LUA_ERRRUN,
|
||||
LUA_ERRSYNTAX,
|
||||
LUA_ERRSYNTAX, // legacy error code, preserved for compatibility
|
||||
LUA_ERRMEM,
|
||||
LUA_ERRERR,
|
||||
LUA_BREAK, // yielded for a debug breakpoint
|
||||
|
@ -17,6 +17,8 @@
|
||||
|
||||
#include <string.h>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauBetterOOMHandling, false)
|
||||
|
||||
/*
|
||||
** {======================================================
|
||||
** Error-recovery functions
|
||||
@ -79,22 +81,17 @@ public:
|
||||
|
||||
const char* what() const throw() override
|
||||
{
|
||||
// LUA_ERRRUN/LUA_ERRSYNTAX pass an object on the stack which is intended to describe the error.
|
||||
if (status == LUA_ERRRUN || status == LUA_ERRSYNTAX)
|
||||
{
|
||||
// Conversion to a string could still fail. For example if a user passes a non-string/non-number argument to `error()`.
|
||||
// LUA_ERRRUN passes error object on the stack
|
||||
if (status == LUA_ERRRUN || (status == LUA_ERRSYNTAX && !FFlag::LuauBetterOOMHandling))
|
||||
if (const char* str = lua_tostring(L, -1))
|
||||
{
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
switch (status)
|
||||
{
|
||||
case LUA_ERRRUN:
|
||||
return "lua_exception: LUA_ERRRUN (no string/number provided as description)";
|
||||
return "lua_exception: runtime error";
|
||||
case LUA_ERRSYNTAX:
|
||||
return "lua_exception: LUA_ERRSYNTAX (no string/number provided as description)";
|
||||
return "lua_exception: syntax error";
|
||||
case LUA_ERRMEM:
|
||||
return "lua_exception: " LUA_MEMERRMSG;
|
||||
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);
|
||||
if (status != 0)
|
||||
{
|
||||
int errstatus = status;
|
||||
|
||||
// call user-defined error function (used in xpcall)
|
||||
if (ef)
|
||||
{
|
||||
// if errfunc fails, we fail with "error in error handling"
|
||||
if (luaD_rawrunprotected(L, callerrfunc, restorestack(L, ef)) != 0)
|
||||
status = LUA_ERRERR;
|
||||
if (FFlag::LuauBetterOOMHandling)
|
||||
{
|
||||
// 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
|
||||
if (!oldactive)
|
||||
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;
|
||||
|
||||
// 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);
|
||||
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->base = L->ci->base;
|
||||
restore_stack_limit(L);
|
||||
|
@ -10,7 +10,7 @@
|
||||
#include "ldebug.h"
|
||||
#include "lvm.h"
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauOptimizedSort, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauIntrosort, false)
|
||||
|
||||
static int foreachi(lua_State* L)
|
||||
{
|
||||
@ -298,120 +298,6 @@ static int tunpack(lua_State* L)
|
||||
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);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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)
|
||||
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]
|
||||
// note: this simultaneously acts as a small sort and a median selector
|
||||
if (sort_less(L, t, u, l, pred)) // a[u] < a[l]?
|
||||
sort_swap(L, t, u, l); // swap a[l] - a[u]
|
||||
if (u - l == 1)
|
||||
break; // only 2 elements
|
||||
i = l + ((u - l) >> 1); // midpoint
|
||||
if (sort_less(L, t, i, l, pred)) // a[i]<a[l]?
|
||||
sort_swap(L, t, i, l);
|
||||
else if (sort_less(L, t, u, i, pred)) // a[u]<a[i]?
|
||||
sort_swap(L, t, i, u);
|
||||
int m = l + ((u - l) >> 1); // midpoint
|
||||
if (sort_less(L, t, m, l, pred)) // a[m]<a[l]?
|
||||
sort_swap(L, t, m, l);
|
||||
else if (sort_less(L, t, u, m, pred)) // a[u]<a[m]?
|
||||
sort_swap(L, t, m, u);
|
||||
if (u - l == 2)
|
||||
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;
|
||||
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
|
||||
i = l;
|
||||
j = u - 1;
|
||||
int i = l;
|
||||
int j = u - 1;
|
||||
for (;;)
|
||||
{ // invariant: a[l..i] <= P <= a[j..u]
|
||||
// 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;
|
||||
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);
|
||||
// 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)
|
||||
|
||||
// swap pivot a[p] with a[i], which is the new midpoint
|
||||
sort_swap(L, t, p, i);
|
||||
|
||||
if (FFlag::LuauIntrosort)
|
||||
{
|
||||
j = l;
|
||||
i = i - 1;
|
||||
l = i + 2;
|
||||
// adjust limit to allow 1.5 log2N recursive steps
|
||||
limit = (limit >> 1) + (limit >> 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
|
||||
{
|
||||
j = i + 1;
|
||||
i = u;
|
||||
u = j - 2;
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
if (FFlag::LuauOptimizedSort)
|
||||
{
|
||||
luaL_checktype(L, 1, LUA_TTABLE);
|
||||
Table* t = hvalue(L->base);
|
||||
int n = luaH_getn(t);
|
||||
if (t->readonly)
|
||||
luaG_readonlyerror(L);
|
||||
luaL_checktype(L, 1, LUA_TTABLE);
|
||||
Table* t = hvalue(L->base);
|
||||
int n = luaH_getn(t);
|
||||
if (t->readonly)
|
||||
luaG_readonlyerror(L);
|
||||
|
||||
SortPredicate pred = luaV_lessthan;
|
||||
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
|
||||
SortPredicate pred = luaV_lessthan;
|
||||
if (!lua_isnoneornil(L, 2)) // is there a 2nd argument?
|
||||
{
|
||||
luaL_checktype(L, 1, LUA_TTABLE);
|
||||
int n = lua_objlen(L, 1);
|
||||
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;
|
||||
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, n, pred);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// }======================================================
|
||||
|
||||
static int tcreate(lua_State* L)
|
||||
{
|
||||
int size = luaL_checkinteger(L, 1);
|
||||
|
@ -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(xmm5, xmm10, r13), 0xc4, 0xc1, 0xab, 0x2a, 0xed);
|
||||
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")
|
||||
|
@ -85,8 +85,8 @@ struct ACFixtureImpl : BaseType
|
||||
{
|
||||
GlobalTypes& globals = this->frontend.globalsForAutocomplete;
|
||||
unfreeze(globals.globalTypes);
|
||||
LoadDefinitionFileResult result =
|
||||
loadDefinitionFile(this->frontend.typeChecker, globals, globals.globalScope, source, "@test", /* captureComments */ false);
|
||||
LoadDefinitionFileResult result = this->frontend.loadDefinitionFile(
|
||||
globals, globals.globalScope, source, "@test", /* captureComments */ false, /* typeCheckForAutocomplete */ true);
|
||||
freeze(globals.globalTypes);
|
||||
|
||||
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))
|
||||
{
|
||||
ScopedFastFlag luauAutocompleteSkipNormalization{"LuauAutocompleteSkipNormalization", true};
|
||||
|
||||
// Build a function type with a large overload set
|
||||
const int parts = 100;
|
||||
std::string source;
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include "Luau/StringUtils.h"
|
||||
#include "Luau/BytecodeBuilder.h"
|
||||
#include "Luau/CodeGen.h"
|
||||
#include "Luau/Frontend.h"
|
||||
|
||||
#include "doctest.h"
|
||||
#include "ScopedFlags.h"
|
||||
@ -243,6 +244,24 @@ static StateRef runConformance(const char* name, void (*setup)(lua_State* L) = n
|
||||
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_CASE("Assert")
|
||||
@ -381,6 +400,8 @@ static int cxxthrow(lua_State* L)
|
||||
|
||||
TEST_CASE("PCall")
|
||||
{
|
||||
ScopedFastFlag sff("LuauBetterOOMHandling", true);
|
||||
|
||||
runConformance("pcall.lua", [](lua_State* L) {
|
||||
lua_pushcfunction(L, cxxthrow, "cxxthrow");
|
||||
lua_setglobal(L, "cxxthrow");
|
||||
@ -395,7 +416,7 @@ TEST_CASE("PCall")
|
||||
},
|
||||
"resumeerror");
|
||||
lua_setglobal(L, "resumeerror");
|
||||
});
|
||||
}, nullptr, lua_newstate(limitedRealloc, nullptr));
|
||||
}
|
||||
|
||||
TEST_CASE("Pack")
|
||||
@ -501,17 +522,15 @@ TEST_CASE("Types")
|
||||
{
|
||||
runConformance("types.lua", [](lua_State* L) {
|
||||
Luau::NullModuleResolver moduleResolver;
|
||||
Luau::InternalErrorReporter iceHandler;
|
||||
Luau::BuiltinTypes builtinTypes;
|
||||
Luau::GlobalTypes globals{Luau::NotNull{&builtinTypes}};
|
||||
Luau::TypeChecker env(globals.globalScope, &moduleResolver, Luau::NotNull{&builtinTypes}, &iceHandler);
|
||||
|
||||
Luau::registerBuiltinGlobals(env, globals);
|
||||
Luau::freeze(globals.globalTypes);
|
||||
Luau::NullFileResolver fileResolver;
|
||||
Luau::NullConfigResolver configResolver;
|
||||
Luau::Frontend frontend{&fileResolver, &configResolver};
|
||||
Luau::registerBuiltinGlobals(frontend, frontend.globals);
|
||||
Luau::freeze(frontend.globals.globalTypes);
|
||||
|
||||
lua_newtable(L);
|
||||
|
||||
for (const auto& [name, binding] : globals.globalScope->bindings)
|
||||
for (const auto& [name, binding] : frontend.globals.globalScope->bindings)
|
||||
{
|
||||
populateRTTI(L, binding.typeId);
|
||||
lua_setfield(L, -2, toString(name).c_str());
|
||||
@ -882,7 +901,7 @@ TEST_CASE("ApiIter")
|
||||
|
||||
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_call
|
||||
@ -981,6 +1000,55 @@ TEST_CASE("ApiCalls")
|
||||
CHECK(lua_tonumber(L, -1) == 4);
|
||||
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")
|
||||
@ -1051,26 +1119,7 @@ TEST_CASE("ExceptionObject")
|
||||
return ExceptionResult{false, ""};
|
||||
};
|
||||
|
||||
auto reallocFunc = [](void* /*ud*/, void* ptr, size_t /*osize*/, size_t nsize) -> void* {
|
||||
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));
|
||||
StateRef globalState = runConformance("exceptions.lua", nullptr, nullptr, lua_newstate(limitedRealloc, nullptr));
|
||||
lua_State* L = globalState.get();
|
||||
|
||||
{
|
||||
|
@ -506,7 +506,8 @@ void Fixture::validateErrors(const std::vector<Luau::TypeError>& errors)
|
||||
LoadDefinitionFileResult Fixture::loadDefinition(const std::string& source)
|
||||
{
|
||||
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);
|
||||
|
||||
if (result.module)
|
||||
@ -521,9 +522,9 @@ BuiltinsFixture::BuiltinsFixture(bool freeze, bool prepareAutocomplete)
|
||||
Luau::unfreeze(frontend.globals.globalTypes);
|
||||
Luau::unfreeze(frontend.globalsForAutocomplete.globalTypes);
|
||||
|
||||
registerBuiltinGlobals(frontend);
|
||||
registerBuiltinGlobals(frontend, frontend.globals);
|
||||
if (prepareAutocomplete)
|
||||
registerBuiltinGlobals(frontend.typeCheckerForAutocomplete, frontend.globalsForAutocomplete);
|
||||
registerBuiltinGlobals(frontend, frontend.globalsForAutocomplete, /*typeCheckForAutocomplete*/ true);
|
||||
registerTestTypes();
|
||||
|
||||
Luau::freeze(frontend.globals.globalTypes);
|
||||
@ -594,8 +595,12 @@ void registerHiddenTypes(Frontend* frontend)
|
||||
TypeId t = globals.globalTypes.addType(GenericType{"T"});
|
||||
GenericTypeDefinition genericT{t};
|
||||
|
||||
TypeId u = globals.globalTypes.addType(GenericType{"U"});
|
||||
GenericTypeDefinition genericU{u};
|
||||
|
||||
ScopePtr globalScope = globals.globalScope;
|
||||
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["cls"] = TypeFun{{}, frontend->builtinTypes->classType};
|
||||
globalScope->exportedTypeBindings["err"] = TypeFun{{}, frontend->builtinTypes->errorType};
|
||||
|
@ -94,7 +94,6 @@ struct Fixture
|
||||
TypeId requireTypeAlias(const std::string& name);
|
||||
|
||||
ScopedFastFlag sff_DebugLuauFreezeArena;
|
||||
ScopedFastFlag luauLintInTypecheck{"LuauLintInTypecheck", true};
|
||||
|
||||
TestFileResolver fileResolver;
|
||||
TestConfigResolver configResolver;
|
||||
|
@ -877,7 +877,7 @@ TEST_CASE_FIXTURE(FrontendFixture, "environments")
|
||||
ScopePtr testScope = frontend.addEnvironment("test");
|
||||
|
||||
unfreeze(frontend.globals.globalTypes);
|
||||
loadDefinitionFile(frontend.typeChecker, frontend.globals, testScope, R"(
|
||||
frontend.loadDefinitionFile(frontend.globals, testScope, R"(
|
||||
export type Foo = number | string
|
||||
)",
|
||||
"@test", /* captureComments */ false);
|
||||
|
@ -12,7 +12,7 @@ class IrCallWrapperX64Fixture
|
||||
public:
|
||||
IrCallWrapperX64Fixture()
|
||||
: build(/* logText */ true, ABIX64::Windows)
|
||||
, regs(function)
|
||||
, regs(build, function)
|
||||
, callWrap(regs, build, ~0u)
|
||||
{
|
||||
}
|
||||
@ -46,8 +46,8 @@ TEST_SUITE_BEGIN("IrCallWrapperX64");
|
||||
|
||||
TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "SimpleRegs")
|
||||
{
|
||||
ScopedRegX64 tmp1{regs, regs.takeReg(rax)};
|
||||
ScopedRegX64 tmp2{regs, regs.takeReg(rArg2)};
|
||||
ScopedRegX64 tmp1{regs, regs.takeReg(rax, kInvalidInstIdx)};
|
||||
ScopedRegX64 tmp2{regs, regs.takeReg(rArg2, kInvalidInstIdx)};
|
||||
callWrap.addArgument(SizeX64::qword, tmp1);
|
||||
callWrap.addArgument(SizeX64::qword, tmp2); // Already in its place
|
||||
callWrap.call(qword[r12]);
|
||||
@ -60,7 +60,7 @@ TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "SimpleRegs")
|
||||
|
||||
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.release());
|
||||
callWrap.call(qword[r12]);
|
||||
@ -73,7 +73,7 @@ TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "TrickyUse1")
|
||||
|
||||
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, tmp1.release());
|
||||
callWrap.call(qword[r12]);
|
||||
@ -87,8 +87,8 @@ TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "TrickyUse2")
|
||||
|
||||
TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "SimpleMemImm")
|
||||
{
|
||||
ScopedRegX64 tmp1{regs, regs.takeReg(rax)};
|
||||
ScopedRegX64 tmp2{regs, regs.takeReg(rsi)};
|
||||
ScopedRegX64 tmp1{regs, regs.takeReg(rax, kInvalidInstIdx)};
|
||||
ScopedRegX64 tmp2{regs, regs.takeReg(rsi, kInvalidInstIdx)};
|
||||
callWrap.addArgument(SizeX64::dword, 32);
|
||||
callWrap.addArgument(SizeX64::dword, -1);
|
||||
callWrap.addArgument(SizeX64::qword, qword[r14 + 32]);
|
||||
@ -106,7 +106,7 @@ TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "SimpleMemImm")
|
||||
|
||||
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, qword[r14 + 16]);
|
||||
callWrap.addArgument(SizeX64::qword, qword[r14 + 32]);
|
||||
@ -148,10 +148,10 @@ TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "FixedRegisters")
|
||||
|
||||
TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "EasyInterference")
|
||||
{
|
||||
ScopedRegX64 tmp1{regs, regs.takeReg(rdi)};
|
||||
ScopedRegX64 tmp2{regs, regs.takeReg(rsi)};
|
||||
ScopedRegX64 tmp3{regs, regs.takeReg(rArg2)};
|
||||
ScopedRegX64 tmp4{regs, regs.takeReg(rArg1)};
|
||||
ScopedRegX64 tmp1{regs, regs.takeReg(rdi, kInvalidInstIdx)};
|
||||
ScopedRegX64 tmp2{regs, regs.takeReg(rsi, kInvalidInstIdx)};
|
||||
ScopedRegX64 tmp3{regs, regs.takeReg(rArg2, kInvalidInstIdx)};
|
||||
ScopedRegX64 tmp4{regs, regs.takeReg(rArg1, kInvalidInstIdx)};
|
||||
callWrap.addArgument(SizeX64::qword, tmp1);
|
||||
callWrap.addArgument(SizeX64::qword, tmp2);
|
||||
callWrap.addArgument(SizeX64::qword, tmp3);
|
||||
@ -169,8 +169,8 @@ TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "EasyInterference")
|
||||
|
||||
TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "FakeInterference")
|
||||
{
|
||||
ScopedRegX64 tmp1{regs, regs.takeReg(rArg1)};
|
||||
ScopedRegX64 tmp2{regs, regs.takeReg(rArg2)};
|
||||
ScopedRegX64 tmp1{regs, regs.takeReg(rArg1, kInvalidInstIdx)};
|
||||
ScopedRegX64 tmp2{regs, regs.takeReg(rArg2, kInvalidInstIdx)};
|
||||
callWrap.addArgument(SizeX64::qword, qword[tmp1.release() + 8]);
|
||||
callWrap.addArgument(SizeX64::qword, qword[tmp2.release() + 8]);
|
||||
callWrap.call(qword[r12]);
|
||||
@ -184,10 +184,10 @@ TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "FakeInterference")
|
||||
|
||||
TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "HardInterferenceInt")
|
||||
{
|
||||
ScopedRegX64 tmp1{regs, regs.takeReg(rArg4)};
|
||||
ScopedRegX64 tmp2{regs, regs.takeReg(rArg3)};
|
||||
ScopedRegX64 tmp3{regs, regs.takeReg(rArg2)};
|
||||
ScopedRegX64 tmp4{regs, regs.takeReg(rArg1)};
|
||||
ScopedRegX64 tmp1{regs, regs.takeReg(rArg4, kInvalidInstIdx)};
|
||||
ScopedRegX64 tmp2{regs, regs.takeReg(rArg3, kInvalidInstIdx)};
|
||||
ScopedRegX64 tmp3{regs, regs.takeReg(rArg2, kInvalidInstIdx)};
|
||||
ScopedRegX64 tmp4{regs, regs.takeReg(rArg1, kInvalidInstIdx)};
|
||||
callWrap.addArgument(SizeX64::qword, tmp1);
|
||||
callWrap.addArgument(SizeX64::qword, tmp2);
|
||||
callWrap.addArgument(SizeX64::qword, tmp3);
|
||||
@ -207,10 +207,10 @@ TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "HardInterferenceInt")
|
||||
|
||||
TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "HardInterferenceInt2")
|
||||
{
|
||||
ScopedRegX64 tmp1{regs, regs.takeReg(rArg4d)};
|
||||
ScopedRegX64 tmp2{regs, regs.takeReg(rArg3d)};
|
||||
ScopedRegX64 tmp3{regs, regs.takeReg(rArg2d)};
|
||||
ScopedRegX64 tmp4{regs, regs.takeReg(rArg1d)};
|
||||
ScopedRegX64 tmp1{regs, regs.takeReg(rArg4d, kInvalidInstIdx)};
|
||||
ScopedRegX64 tmp2{regs, regs.takeReg(rArg3d, kInvalidInstIdx)};
|
||||
ScopedRegX64 tmp3{regs, regs.takeReg(rArg2d, kInvalidInstIdx)};
|
||||
ScopedRegX64 tmp4{regs, regs.takeReg(rArg1d, kInvalidInstIdx)};
|
||||
callWrap.addArgument(SizeX64::dword, tmp1);
|
||||
callWrap.addArgument(SizeX64::dword, tmp2);
|
||||
callWrap.addArgument(SizeX64::dword, tmp3);
|
||||
@ -230,8 +230,8 @@ TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "HardInterferenceInt2")
|
||||
|
||||
TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "HardInterferenceFp")
|
||||
{
|
||||
ScopedRegX64 tmp1{regs, regs.takeReg(xmm1)};
|
||||
ScopedRegX64 tmp2{regs, regs.takeReg(xmm0)};
|
||||
ScopedRegX64 tmp1{regs, regs.takeReg(xmm1, kInvalidInstIdx)};
|
||||
ScopedRegX64 tmp2{regs, regs.takeReg(xmm0, kInvalidInstIdx)};
|
||||
callWrap.addArgument(SizeX64::xmmword, tmp1);
|
||||
callWrap.addArgument(SizeX64::xmmword, tmp2);
|
||||
callWrap.call(qword[r12]);
|
||||
@ -246,10 +246,10 @@ TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "HardInterferenceFp")
|
||||
|
||||
TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "HardInterferenceBoth")
|
||||
{
|
||||
ScopedRegX64 int1{regs, regs.takeReg(rArg2)};
|
||||
ScopedRegX64 int2{regs, regs.takeReg(rArg1)};
|
||||
ScopedRegX64 fp1{regs, regs.takeReg(xmm3)};
|
||||
ScopedRegX64 fp2{regs, regs.takeReg(xmm2)};
|
||||
ScopedRegX64 int1{regs, regs.takeReg(rArg2, kInvalidInstIdx)};
|
||||
ScopedRegX64 int2{regs, regs.takeReg(rArg1, kInvalidInstIdx)};
|
||||
ScopedRegX64 fp1{regs, regs.takeReg(xmm3, kInvalidInstIdx)};
|
||||
ScopedRegX64 fp2{regs, regs.takeReg(xmm2, kInvalidInstIdx)};
|
||||
callWrap.addArgument(SizeX64::qword, int1);
|
||||
callWrap.addArgument(SizeX64::qword, int2);
|
||||
callWrap.addArgument(SizeX64::xmmword, fp1);
|
||||
@ -269,8 +269,8 @@ TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "HardInterferenceBoth")
|
||||
|
||||
TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "FakeMultiuseInterferenceMem")
|
||||
{
|
||||
ScopedRegX64 tmp1{regs, regs.takeReg(rArg1)};
|
||||
ScopedRegX64 tmp2{regs, regs.takeReg(rArg2)};
|
||||
ScopedRegX64 tmp1{regs, regs.takeReg(rArg1, kInvalidInstIdx)};
|
||||
ScopedRegX64 tmp2{regs, regs.takeReg(rArg2, kInvalidInstIdx)};
|
||||
callWrap.addArgument(SizeX64::qword, qword[tmp1.reg + tmp2.reg + 8]);
|
||||
callWrap.addArgument(SizeX64::qword, qword[tmp2.reg + 16]);
|
||||
tmp1.release();
|
||||
@ -286,8 +286,8 @@ TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "FakeMultiuseInterferenceMem")
|
||||
|
||||
TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "HardMultiuseInterferenceMem1")
|
||||
{
|
||||
ScopedRegX64 tmp1{regs, regs.takeReg(rArg1)};
|
||||
ScopedRegX64 tmp2{regs, regs.takeReg(rArg2)};
|
||||
ScopedRegX64 tmp1{regs, regs.takeReg(rArg1, kInvalidInstIdx)};
|
||||
ScopedRegX64 tmp2{regs, regs.takeReg(rArg2, kInvalidInstIdx)};
|
||||
callWrap.addArgument(SizeX64::qword, qword[tmp1.reg + tmp2.reg + 8]);
|
||||
callWrap.addArgument(SizeX64::qword, qword[tmp1.reg + 16]);
|
||||
tmp1.release();
|
||||
@ -304,8 +304,8 @@ TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "HardMultiuseInterferenceMem1")
|
||||
|
||||
TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "HardMultiuseInterferenceMem2")
|
||||
{
|
||||
ScopedRegX64 tmp1{regs, regs.takeReg(rArg1)};
|
||||
ScopedRegX64 tmp2{regs, regs.takeReg(rArg2)};
|
||||
ScopedRegX64 tmp1{regs, regs.takeReg(rArg1, kInvalidInstIdx)};
|
||||
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 + 16]);
|
||||
tmp1.release();
|
||||
@ -322,9 +322,9 @@ TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "HardMultiuseInterferenceMem2")
|
||||
|
||||
TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "HardMultiuseInterferenceMem3")
|
||||
{
|
||||
ScopedRegX64 tmp1{regs, regs.takeReg(rArg3)};
|
||||
ScopedRegX64 tmp2{regs, regs.takeReg(rArg2)};
|
||||
ScopedRegX64 tmp3{regs, regs.takeReg(rArg1)};
|
||||
ScopedRegX64 tmp1{regs, regs.takeReg(rArg3, kInvalidInstIdx)};
|
||||
ScopedRegX64 tmp2{regs, regs.takeReg(rArg2, kInvalidInstIdx)};
|
||||
ScopedRegX64 tmp3{regs, regs.takeReg(rArg1, kInvalidInstIdx)};
|
||||
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[tmp3.reg + tmp1.reg + 16]);
|
||||
@ -345,7 +345,7 @@ TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "HardMultiuseInterferenceMem3")
|
||||
|
||||
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.call(qword[tmp1.release() + 16]);
|
||||
|
||||
@ -358,8 +358,8 @@ TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "InterferenceWithCallArg1")
|
||||
|
||||
TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "InterferenceWithCallArg2")
|
||||
{
|
||||
ScopedRegX64 tmp1{regs, regs.takeReg(rArg1)};
|
||||
ScopedRegX64 tmp2{regs, regs.takeReg(rArg2)};
|
||||
ScopedRegX64 tmp1{regs, regs.takeReg(rArg1, kInvalidInstIdx)};
|
||||
ScopedRegX64 tmp2{regs, regs.takeReg(rArg2, kInvalidInstIdx)};
|
||||
callWrap.addArgument(SizeX64::qword, tmp2);
|
||||
callWrap.call(qword[tmp1.release() + 16]);
|
||||
|
||||
@ -372,7 +372,7 @@ TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "InterferenceWithCallArg2")
|
||||
|
||||
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.call(qword[tmp1.release() + 16]);
|
||||
|
||||
@ -385,7 +385,7 @@ TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "WithLastIrInstUse1")
|
||||
{
|
||||
IrInst irInst1;
|
||||
IrOp irOp1 = {IrOpKind::Inst, 0};
|
||||
irInst1.regX64 = regs.takeReg(xmm0);
|
||||
irInst1.regX64 = regs.takeReg(xmm0, irOp1.index);
|
||||
irInst1.lastUse = 1;
|
||||
function.instructions.push_back(irInst1);
|
||||
callWrap.instIdx = irInst1.lastUse;
|
||||
@ -404,7 +404,7 @@ TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "WithLastIrInstUse2")
|
||||
{
|
||||
IrInst irInst1;
|
||||
IrOp irOp1 = {IrOpKind::Inst, 0};
|
||||
irInst1.regX64 = regs.takeReg(xmm0);
|
||||
irInst1.regX64 = regs.takeReg(xmm0, irOp1.index);
|
||||
irInst1.lastUse = 1;
|
||||
function.instructions.push_back(irInst1);
|
||||
callWrap.instIdx = irInst1.lastUse;
|
||||
@ -424,7 +424,7 @@ TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "WithLastIrInstUse3")
|
||||
{
|
||||
IrInst irInst1;
|
||||
IrOp irOp1 = {IrOpKind::Inst, 0};
|
||||
irInst1.regX64 = regs.takeReg(xmm0);
|
||||
irInst1.regX64 = regs.takeReg(xmm0, irOp1.index);
|
||||
irInst1.lastUse = 1;
|
||||
function.instructions.push_back(irInst1);
|
||||
callWrap.instIdx = irInst1.lastUse;
|
||||
@ -443,12 +443,12 @@ TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "WithLastIrInstUse4")
|
||||
{
|
||||
IrInst irInst1;
|
||||
IrOp irOp1 = {IrOpKind::Inst, 0};
|
||||
irInst1.regX64 = regs.takeReg(rax);
|
||||
irInst1.regX64 = regs.takeReg(rax, irOp1.index);
|
||||
irInst1.lastUse = 1;
|
||||
function.instructions.push_back(irInst1);
|
||||
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, irInst1.regX64, irOp1);
|
||||
callWrap.addArgument(SizeX64::qword, tmp);
|
||||
@ -464,8 +464,8 @@ TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "WithLastIrInstUse4")
|
||||
|
||||
TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "ExtraCoverage")
|
||||
{
|
||||
ScopedRegX64 tmp1{regs, regs.takeReg(rArg1)};
|
||||
ScopedRegX64 tmp2{regs, regs.takeReg(rArg2)};
|
||||
ScopedRegX64 tmp1{regs, regs.takeReg(rArg1, kInvalidInstIdx)};
|
||||
ScopedRegX64 tmp2{regs, regs.takeReg(rArg2, kInvalidInstIdx)};
|
||||
callWrap.addArgument(SizeX64::qword, addr[r12 + 8]);
|
||||
callWrap.addArgument(SizeX64::qword, addr[r12 + 16]);
|
||||
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();
|
||||
|
@ -1273,7 +1273,7 @@ TEST_CASE_FIXTURE(Fixture, "use_all_parent_scopes_for_globals")
|
||||
{
|
||||
ScopePtr testScope = frontend.addEnvironment("Test");
|
||||
unfreeze(frontend.globals.globalTypes);
|
||||
loadDefinitionFile(frontend.typeChecker, frontend.globals, testScope, R"(
|
||||
frontend.loadDefinitionFile(frontend.globals, testScope, R"(
|
||||
declare Foo: number
|
||||
)",
|
||||
"@test", /* captureComments */ false);
|
||||
|
@ -748,6 +748,20 @@ TEST_CASE_FIXTURE(NormalizeFixture, "narrow_union_of_classes_with_intersection")
|
||||
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")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
|
@ -78,7 +78,7 @@ TEST_CASE_FIXTURE(Fixture, "definition_file_loading")
|
||||
TEST_CASE_FIXTURE(Fixture, "load_definition_file_errors_do_not_pollute_global_scope")
|
||||
{
|
||||
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
|
||||
)",
|
||||
"@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");
|
||||
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
|
||||
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")
|
||||
{
|
||||
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
|
||||
X: number
|
||||
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")
|
||||
{
|
||||
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 = {}
|
||||
|
||||
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")
|
||||
{
|
||||
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
|
||||
end
|
||||
|
||||
@ -397,7 +397,7 @@ TEST_CASE_FIXTURE(Fixture, "class_definition_string_props")
|
||||
TEST_CASE_FIXTURE(Fixture, "class_definitions_reference_other_classes")
|
||||
{
|
||||
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
|
||||
Messages: { Message }
|
||||
OnMessage: (message: Message) -> ()
|
||||
|
@ -874,7 +874,7 @@ TEST_CASE_FIXTURE(Fixture, "generic_table_method")
|
||||
std::vector<TypeId> args = flatten(ftv->argTypes).first;
|
||||
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")
|
||||
|
@ -477,10 +477,10 @@ TEST_CASE_FIXTURE(Fixture, "free_options_cannot_be_unified_together")
|
||||
|
||||
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 free2 = arena.addType(FreeTypePack{scope.get()});
|
||||
TypeId free2 = arena.addType(FreeType{scope.get()});
|
||||
TypeId option2 = arena.addType(UnionType{{nilType, free2}});
|
||||
|
||||
InternalErrorReporter iceHandler;
|
||||
|
@ -490,8 +490,13 @@ struct FindFreeTypes
|
||||
return !foundOne;
|
||||
}
|
||||
|
||||
template<typename ID>
|
||||
bool operator()(ID, Unifiable::Free)
|
||||
bool operator()(TypeId, FreeType)
|
||||
{
|
||||
foundOne = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool operator()(TypePackId, FreeTypePack)
|
||||
{
|
||||
foundOne = true;
|
||||
return false;
|
||||
|
@ -25,7 +25,7 @@ struct TypePackFixture
|
||||
|
||||
TypePackId freshTypePack()
|
||||
{
|
||||
typePacks.emplace_back(new TypePackVar{Unifiable::Free{TypeLevel{}}});
|
||||
typePacks.emplace_back(new TypePackVar{FreeTypePack{TypeLevel{}}});
|
||||
return typePacks.back().get();
|
||||
}
|
||||
|
||||
|
@ -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")
|
||||
{
|
||||
auto emptyArgumentPack = TypePackVar{TypePack{}};
|
||||
auto free = Unifiable::Free(TypeLevel());
|
||||
auto free = FreeTypePack(TypeLevel());
|
||||
auto freePack = TypePackVar{TypePackVariant{free}};
|
||||
auto returnPack = TypePackVar{TypePack{{builtinTypes->numberType}, &freePack}};
|
||||
auto returnsTwo = Type(FunctionType(frontend.globals.globalScope->level, &emptyArgumentPack, &returnPack));
|
||||
|
@ -22,4 +22,12 @@ function getpi()
|
||||
return pi
|
||||
end
|
||||
|
||||
function largealloc()
|
||||
table.create(1000000)
|
||||
end
|
||||
|
||||
function oops()
|
||||
return "oops"
|
||||
end
|
||||
|
||||
return('OK')
|
||||
|
@ -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"
|
||||
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'
|
||||
|
@ -99,12 +99,12 @@ a = {"
|
||||
table.sort(a)
|
||||
check(a)
|
||||
|
||||
-- TODO: assert that pcall returns false for new sort implementation (table is modified during sorting)
|
||||
pcall(table.sort, a, function (x, y)
|
||||
local ok = pcall(table.sort, a, function (x, y)
|
||||
loadstring(string.format("a[%q] = ''", x))()
|
||||
collectgarbage()
|
||||
return x<y
|
||||
end)
|
||||
assert(not ok)
|
||||
|
||||
tt = {__lt = function (a,b) return a.val < b.val end}
|
||||
a = {}
|
||||
@ -113,4 +113,48 @@ table.sort(a)
|
||||
check(a, tt.__lt)
|
||||
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"
|
||||
|
@ -219,6 +219,13 @@ do
|
||||
assert(eq(string.split("abc", "c"), {'ab', ''}))
|
||||
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 function trylocale (w)
|
||||
|
27
tools/natvis/Common.natvis
Normal file
27
tools/natvis/Common.natvis
Normal 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<*,*,*,*,*,*>">
|
||||
<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<*,*,*>">
|
||||
<Expand>
|
||||
<ExpandedItem>impl</ExpandedItem>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<Type Name="Luau::DenseHashMap<*,*,*,*>">
|
||||
<Expand>
|
||||
<ExpandedItem>impl</ExpandedItem>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
</AutoVisualizer>
|
@ -107,6 +107,12 @@ def main():
|
||||
action="store_true",
|
||||
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")
|
||||
|
||||
@ -120,13 +126,19 @@ def main():
|
||||
|
||||
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()
|
||||
|
||||
commandLine = [
|
||||
args.path,
|
||||
"--reporters=xml",
|
||||
"--fflags=true,DebugLuauDeferredConstraintResolution=true",
|
||||
]
|
||||
flags = ["true", "DebugLuauDeferredConstraintResolution"]
|
||||
if args.lti:
|
||||
flags.append("DebugLuauLocalTypeInference")
|
||||
|
||||
commandLine = [args.path, "--reporters=xml", "--fflags=" + ",".join(flags)]
|
||||
|
||||
if args.random_seed:
|
||||
commandLine.append("--random-seed=" + str(args.random_seed))
|
||||
|
Loading…
Reference in New Issue
Block a user