mirror of
https://github.com/luau-lang/luau.git
synced 2024-11-15 14:25:44 +08:00
Sync to upstream/release/570 (#885)
Once again, all of our changes this week are for new type solver and the JIT. In the new type solver, we fixed cyclic type alias handling and multiple stability issues. In the JIT, our main progress was for arm64, where, after lowering 36% of instructions, we start seeing first Luau functions executing natively. For x64, we performed code cleanup and refactoring to allow for future optimizations.
This commit is contained in:
parent
b4ebad4862
commit
1212fdacbf
@ -53,7 +53,6 @@ struct ConstraintSolver
|
||||
NotNull<BuiltinTypes> builtinTypes;
|
||||
InternalErrorReporter iceReporter;
|
||||
NotNull<Normalizer> normalizer;
|
||||
NotNull<TypeReduction> reducer;
|
||||
// The entire set of constraints that the solver is trying to resolve.
|
||||
std::vector<NotNull<Constraint>> constraints;
|
||||
NotNull<Scope> rootScope;
|
||||
@ -85,8 +84,7 @@ struct ConstraintSolver
|
||||
DcrLogger* logger;
|
||||
|
||||
explicit ConstraintSolver(NotNull<Normalizer> normalizer, NotNull<Scope> rootScope, std::vector<NotNull<Constraint>> constraints,
|
||||
ModuleName moduleName, NotNull<TypeReduction> reducer, NotNull<ModuleResolver> moduleResolver, std::vector<RequireCycle> requireCycles,
|
||||
DcrLogger* logger);
|
||||
ModuleName moduleName, NotNull<ModuleResolver> moduleResolver, std::vector<RequireCycle> requireCycles, DcrLogger* logger);
|
||||
|
||||
// Randomize the order in which to dispatch constraints
|
||||
void randomize(unsigned seed);
|
||||
@ -219,6 +217,20 @@ struct ConstraintSolver
|
||||
void reportError(TypeError e);
|
||||
|
||||
private:
|
||||
|
||||
/** Helper used by tryDispatch(SubtypeConstraint) and
|
||||
* tryDispatch(PackSubtypeConstraint)
|
||||
*
|
||||
* Attempts to unify subTy with superTy. If doing so would require unifying
|
||||
* BlockedTypes, fail and block the constraint on those BlockedTypes.
|
||||
*
|
||||
* If unification fails, replace all free types with errorType.
|
||||
*
|
||||
* If unification succeeds, unblock every type changed by the unification.
|
||||
*/
|
||||
template <typename TID>
|
||||
bool tryUnify(NotNull<const Constraint> constraint, TID subTy, TID superTy);
|
||||
|
||||
/**
|
||||
* Marks a constraint as being blocked on a type or type pack. The constraint
|
||||
* solver will not attempt to dispatch blocked constraints until their
|
||||
|
@ -191,12 +191,8 @@ struct NormalizedClassType
|
||||
// this type may contain `error`.
|
||||
struct NormalizedFunctionType
|
||||
{
|
||||
NormalizedFunctionType();
|
||||
|
||||
bool isTop = false;
|
||||
// TODO: Remove this wrapping optional when clipping
|
||||
// FFlagLuauNegatedFunctionTypes.
|
||||
std::optional<TypeIds> parts;
|
||||
TypeIds parts;
|
||||
|
||||
void resetToNever();
|
||||
void resetToTop();
|
||||
|
@ -55,11 +55,11 @@ struct Scope
|
||||
std::optional<TypeId> lookup(DefId def) const;
|
||||
std::optional<std::pair<Binding*, Scope*>> lookupEx(Symbol sym);
|
||||
|
||||
std::optional<TypeFun> lookupType(const Name& name);
|
||||
std::optional<TypeFun> lookupImportedType(const Name& moduleAlias, const Name& name);
|
||||
std::optional<TypeFun> lookupType(const Name& name) const;
|
||||
std::optional<TypeFun> lookupImportedType(const Name& moduleAlias, const Name& name) const;
|
||||
|
||||
std::unordered_map<Name, TypePackId> privateTypePackBindings;
|
||||
std::optional<TypePackId> lookupPack(const Name& name);
|
||||
std::optional<TypePackId> lookupPack(const Name& name) const;
|
||||
|
||||
// WARNING: This function linearly scans for a string key of equal value! It is thus O(n**2)
|
||||
std::optional<Binding> linearSearchForBinding(const std::string& name, bool traverseScopeChain = true) const;
|
||||
|
@ -79,7 +79,8 @@ struct GlobalTypes
|
||||
// within a program are borrowed pointers into this set.
|
||||
struct TypeChecker
|
||||
{
|
||||
explicit TypeChecker(const GlobalTypes& globals, ModuleResolver* resolver, NotNull<BuiltinTypes> builtinTypes, InternalErrorReporter* iceHandler);
|
||||
explicit TypeChecker(
|
||||
const ScopePtr& globalScope, ModuleResolver* resolver, NotNull<BuiltinTypes> builtinTypes, InternalErrorReporter* iceHandler);
|
||||
TypeChecker(const TypeChecker&) = delete;
|
||||
TypeChecker& operator=(const TypeChecker&) = delete;
|
||||
|
||||
@ -367,8 +368,7 @@ public:
|
||||
*/
|
||||
std::vector<TypeId> unTypePack(const ScopePtr& scope, TypePackId pack, size_t expectedLength, const Location& location);
|
||||
|
||||
// TODO: only const version of global scope should be available to make sure nothing else is modified inside of from users of TypeChecker
|
||||
const GlobalTypes& globals;
|
||||
const ScopePtr& globalScope;
|
||||
|
||||
ModuleResolver* resolver;
|
||||
ModulePtr currentModule;
|
||||
|
@ -11,8 +11,6 @@
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
LUAU_FASTFLAG(LuauCompleteTableKeysBetter);
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
@ -30,8 +28,6 @@ struct AutocompleteNodeFinder : public AstVisitor
|
||||
}
|
||||
|
||||
bool visit(AstExpr* expr) override
|
||||
{
|
||||
if (FFlag::LuauCompleteTableKeysBetter)
|
||||
{
|
||||
if (expr->location.begin <= pos && pos <= expr->location.end)
|
||||
{
|
||||
@ -40,16 +36,6 @@ struct AutocompleteNodeFinder : public AstVisitor
|
||||
}
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (expr->location.begin < pos && pos <= expr->location.end)
|
||||
{
|
||||
ancestry.push_back(expr);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool visit(AstStat* stat) override
|
||||
{
|
||||
|
@ -13,7 +13,6 @@
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauCompleteTableKeysBetter, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauAutocompleteSkipNormalization, false);
|
||||
|
||||
static const std::unordered_set<std::string> kStatementStartingKeywords = {
|
||||
@ -981,8 +980,6 @@ T* extractStat(const std::vector<AstNode*>& ancestry)
|
||||
AstNode* grandParent = ancestry.size() >= 3 ? ancestry.rbegin()[2] : nullptr;
|
||||
AstNode* greatGrandParent = ancestry.size() >= 4 ? ancestry.rbegin()[3] : nullptr;
|
||||
|
||||
if (FFlag::LuauCompleteTableKeysBetter)
|
||||
{
|
||||
if (!grandParent)
|
||||
return nullptr;
|
||||
|
||||
@ -991,15 +988,6 @@ T* extractStat(const std::vector<AstNode*>& ancestry)
|
||||
|
||||
if (!greatGrandParent)
|
||||
return nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (T* t = parent->as<T>(); t && parent->is<AstStatBlock>())
|
||||
return t;
|
||||
|
||||
if (!grandParent || !greatGrandParent)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (T* t = greatGrandParent->as<T>(); t && grandParent->is<AstStatBlock>() && parent->is<AstStatError>() && isIdentifier(node))
|
||||
return t;
|
||||
@ -1533,8 +1521,6 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
|
||||
{
|
||||
auto result = autocompleteProps(*module, typeArena, builtinTypes, *it, PropIndexType::Key, ancestry);
|
||||
|
||||
if (FFlag::LuauCompleteTableKeysBetter)
|
||||
{
|
||||
if (auto nodeIt = module->astExpectedTypes.find(node->asExpr()))
|
||||
autocompleteStringSingleton(*nodeIt, !node->is<AstExprConstantString>(), result);
|
||||
|
||||
@ -1551,7 +1537,6 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
|
||||
autocompleteStringSingleton(ttv->indexer->indexType, false, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove keys that are already completed
|
||||
for (const auto& item : exprTable->items)
|
||||
|
@ -15,8 +15,6 @@
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauDeprecateTableGetnForeach, false)
|
||||
|
||||
/** FIXME: Many of these type definitions are not quite completely accurate.
|
||||
*
|
||||
* Some of them require richer generics than we have. For instance, we do not yet have a way to talk
|
||||
@ -298,13 +296,10 @@ void registerBuiltinGlobals(TypeChecker& typeChecker, GlobalTypes& globals)
|
||||
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");
|
||||
|
||||
if (FFlag::LuauDeprecateTableGetnForeach)
|
||||
{
|
||||
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);
|
||||
@ -401,15 +396,13 @@ void registerBuiltinGlobals(Frontend& frontend)
|
||||
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");
|
||||
|
||||
if (FFlag::LuauDeprecateTableGetnForeach)
|
||||
{
|
||||
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);
|
||||
|
@ -226,12 +226,10 @@ void dump(ConstraintSolver* cs, ToStringOptions& opts)
|
||||
}
|
||||
|
||||
ConstraintSolver::ConstraintSolver(NotNull<Normalizer> normalizer, NotNull<Scope> rootScope, std::vector<NotNull<Constraint>> constraints,
|
||||
ModuleName moduleName, NotNull<TypeReduction> reducer, NotNull<ModuleResolver> moduleResolver, std::vector<RequireCycle> requireCycles,
|
||||
DcrLogger* logger)
|
||||
ModuleName moduleName, NotNull<ModuleResolver> moduleResolver, std::vector<RequireCycle> requireCycles, DcrLogger* logger)
|
||||
: arena(normalizer->arena)
|
||||
, builtinTypes(normalizer->builtinTypes)
|
||||
, normalizer(normalizer)
|
||||
, reducer(reducer)
|
||||
, constraints(std::move(constraints))
|
||||
, rootScope(rootScope)
|
||||
, currentModuleName(std::move(moduleName))
|
||||
@ -458,40 +456,7 @@ bool ConstraintSolver::tryDispatch(const SubtypeConstraint& c, NotNull<const Con
|
||||
else if (isBlocked(c.superType))
|
||||
return block(c.superType, constraint);
|
||||
|
||||
Unifier u{normalizer, Mode::Strict, constraint->scope, Location{}, Covariant};
|
||||
u.useScopes = true;
|
||||
|
||||
u.tryUnify(c.subType, c.superType);
|
||||
|
||||
if (!u.blockedTypes.empty() || !u.blockedTypePacks.empty())
|
||||
{
|
||||
for (TypeId bt : u.blockedTypes)
|
||||
block(bt, constraint);
|
||||
for (TypePackId btp : u.blockedTypePacks)
|
||||
block(btp, constraint);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (const auto& e = hasUnificationTooComplex(u.errors))
|
||||
reportError(*e);
|
||||
|
||||
if (!u.errors.empty())
|
||||
{
|
||||
TypeId errorType = errorRecoveryType();
|
||||
u.tryUnify(c.subType, errorType);
|
||||
u.tryUnify(c.superType, errorType);
|
||||
}
|
||||
|
||||
const auto [changedTypes, changedPacks] = u.log.getChanges();
|
||||
|
||||
u.log.commit();
|
||||
|
||||
unblock(changedTypes);
|
||||
unblock(changedPacks);
|
||||
|
||||
// unify(c.subType, c.superType, constraint->scope);
|
||||
|
||||
return true;
|
||||
return tryUnify(constraint, c.subType, c.superType);
|
||||
}
|
||||
|
||||
bool ConstraintSolver::tryDispatch(const PackSubtypeConstraint& c, NotNull<const Constraint> constraint, bool force)
|
||||
@ -501,9 +466,7 @@ bool ConstraintSolver::tryDispatch(const PackSubtypeConstraint& c, NotNull<const
|
||||
else if (isBlocked(c.superPack))
|
||||
return block(c.superPack, constraint);
|
||||
|
||||
unify(c.subPack, c.superPack, constraint->scope);
|
||||
|
||||
return true;
|
||||
return tryUnify(constraint, c.subPack, c.superPack);
|
||||
}
|
||||
|
||||
bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<const Constraint> constraint, bool force)
|
||||
@ -1117,7 +1080,7 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
|
||||
InstantiationQueuer queuer{constraint->scope, constraint->location, this};
|
||||
queuer.traverse(target);
|
||||
|
||||
if (target->persistent)
|
||||
if (target->persistent || target->owningArena != arena)
|
||||
{
|
||||
bindResult(target);
|
||||
return true;
|
||||
@ -1335,8 +1298,6 @@ bool ConstraintSolver::tryDispatch(const HasPropConstraint& c, NotNull<const Con
|
||||
return true;
|
||||
}
|
||||
|
||||
subjectType = reducer->reduce(subjectType).value_or(subjectType);
|
||||
|
||||
auto [blocked, result] = lookupTableProp(subjectType, c.prop);
|
||||
if (!blocked.empty())
|
||||
{
|
||||
@ -1716,8 +1677,15 @@ bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const Iterabl
|
||||
|
||||
if (auto iteratorTable = get<TableType>(iteratorTy))
|
||||
{
|
||||
if (iteratorTable->state == TableState::Free)
|
||||
return block_(iteratorTy);
|
||||
/*
|
||||
* We try not to dispatch IterableConstraints over free tables because
|
||||
* it's possible that there are other constraints on the table that will
|
||||
* clarify what we should do.
|
||||
*
|
||||
* We should eventually introduce a type family to talk about iteration.
|
||||
*/
|
||||
if (iteratorTable->state == TableState::Free && !force)
|
||||
return block(iteratorTy, constraint);
|
||||
|
||||
if (iteratorTable->indexer)
|
||||
{
|
||||
@ -1957,14 +1925,14 @@ std::pair<std::vector<TypeId>, std::optional<TypeId>> ConstraintSolver::lookupTa
|
||||
else if (auto utv = get<UnionType>(subjectType))
|
||||
{
|
||||
std::vector<TypeId> blocked;
|
||||
std::vector<TypeId> options;
|
||||
std::set<TypeId> options;
|
||||
|
||||
for (TypeId ty : utv)
|
||||
{
|
||||
auto [innerBlocked, innerResult] = lookupTableProp(ty, propName, seen);
|
||||
blocked.insert(blocked.end(), innerBlocked.begin(), innerBlocked.end());
|
||||
if (innerResult)
|
||||
options.push_back(*innerResult);
|
||||
options.insert(*innerResult);
|
||||
}
|
||||
|
||||
if (!blocked.empty())
|
||||
@ -1973,21 +1941,21 @@ std::pair<std::vector<TypeId>, std::optional<TypeId>> ConstraintSolver::lookupTa
|
||||
if (options.empty())
|
||||
return {{}, std::nullopt};
|
||||
else if (options.size() == 1)
|
||||
return {{}, options[0]};
|
||||
return {{}, *begin(options)};
|
||||
else
|
||||
return {{}, arena->addType(UnionType{std::move(options)})};
|
||||
return {{}, arena->addType(UnionType{std::vector<TypeId>(begin(options), end(options))})};
|
||||
}
|
||||
else if (auto itv = get<IntersectionType>(subjectType))
|
||||
{
|
||||
std::vector<TypeId> blocked;
|
||||
std::vector<TypeId> options;
|
||||
std::set<TypeId> options;
|
||||
|
||||
for (TypeId ty : itv)
|
||||
{
|
||||
auto [innerBlocked, innerResult] = lookupTableProp(ty, propName, seen);
|
||||
blocked.insert(blocked.end(), innerBlocked.begin(), innerBlocked.end());
|
||||
if (innerResult)
|
||||
options.push_back(*innerResult);
|
||||
options.insert(*innerResult);
|
||||
}
|
||||
|
||||
if (!blocked.empty())
|
||||
@ -1996,14 +1964,61 @@ std::pair<std::vector<TypeId>, std::optional<TypeId>> ConstraintSolver::lookupTa
|
||||
if (options.empty())
|
||||
return {{}, std::nullopt};
|
||||
else if (options.size() == 1)
|
||||
return {{}, options[0]};
|
||||
return {{}, *begin(options)};
|
||||
else
|
||||
return {{}, arena->addType(IntersectionType{std::move(options)})};
|
||||
return {{}, arena->addType(IntersectionType{std::vector<TypeId>(begin(options), end(options))})};
|
||||
}
|
||||
|
||||
return {{}, std::nullopt};
|
||||
}
|
||||
|
||||
static TypeId getErrorType(NotNull<BuiltinTypes> builtinTypes, TypeId)
|
||||
{
|
||||
return builtinTypes->errorRecoveryType();
|
||||
}
|
||||
|
||||
static TypePackId getErrorType(NotNull<BuiltinTypes> builtinTypes, TypePackId)
|
||||
{
|
||||
return builtinTypes->errorRecoveryTypePack();
|
||||
}
|
||||
|
||||
template <typename TID>
|
||||
bool ConstraintSolver::tryUnify(NotNull<const Constraint> constraint, TID subTy, TID superTy)
|
||||
{
|
||||
Unifier u{normalizer, Mode::Strict, constraint->scope, Location{}, Covariant};
|
||||
u.useScopes = true;
|
||||
|
||||
u.tryUnify(subTy, superTy);
|
||||
|
||||
if (!u.blockedTypes.empty() || !u.blockedTypePacks.empty())
|
||||
{
|
||||
for (TypeId bt : u.blockedTypes)
|
||||
block(bt, constraint);
|
||||
for (TypePackId btp : u.blockedTypePacks)
|
||||
block(btp, constraint);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (const auto& e = hasUnificationTooComplex(u.errors))
|
||||
reportError(*e);
|
||||
|
||||
if (!u.errors.empty())
|
||||
{
|
||||
TID errorType = getErrorType(builtinTypes, TID{});
|
||||
u.tryUnify(subTy, errorType);
|
||||
u.tryUnify(superTy, errorType);
|
||||
}
|
||||
|
||||
const auto [changedTypes, changedPacks] = u.log.getChanges();
|
||||
|
||||
u.log.commit();
|
||||
|
||||
unblock(changedTypes);
|
||||
unblock(changedPacks);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ConstraintSolver::block_(BlockedConstraintId target, NotNull<const Constraint> constraint)
|
||||
{
|
||||
blocked[target].push_back(constraint);
|
||||
|
@ -435,8 +435,8 @@ Frontend::Frontend(FileResolver* fileResolver, ConfigResolver* configResolver, c
|
||||
, moduleResolverForAutocomplete(this)
|
||||
, globals(builtinTypes)
|
||||
, globalsForAutocomplete(builtinTypes)
|
||||
, typeChecker(globals, &moduleResolver, builtinTypes, &iceHandler)
|
||||
, typeCheckerForAutocomplete(globalsForAutocomplete, &moduleResolverForAutocomplete, builtinTypes, &iceHandler)
|
||||
, typeChecker(globals.globalScope, &moduleResolver, builtinTypes, &iceHandler)
|
||||
, typeCheckerForAutocomplete(globalsForAutocomplete.globalScope, &moduleResolverForAutocomplete, builtinTypes, &iceHandler)
|
||||
, configResolver(configResolver)
|
||||
, options(options)
|
||||
{
|
||||
@ -970,8 +970,8 @@ ModulePtr check(const SourceModule& sourceModule, const std::vector<RequireCycle
|
||||
cgb.visit(sourceModule.root);
|
||||
result->errors = std::move(cgb.errors);
|
||||
|
||||
ConstraintSolver cs{NotNull{&normalizer}, NotNull(cgb.rootScope), borrowConstraints(cgb.constraints), sourceModule.name,
|
||||
NotNull{result->reduction.get()}, moduleResolver, requireCycles, logger.get()};
|
||||
ConstraintSolver cs{NotNull{&normalizer}, NotNull(cgb.rootScope), borrowConstraints(cgb.constraints), sourceModule.name, moduleResolver,
|
||||
requireCycles, logger.get()};
|
||||
|
||||
if (options.randomizeConstraintResolutionSeed)
|
||||
cs.randomize(*options.randomizeConstraintResolutionSeed);
|
||||
|
@ -14,8 +14,6 @@
|
||||
|
||||
LUAU_FASTINTVARIABLE(LuauSuggestionDistance, 4)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauImproveDeprecatedApiLint, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
@ -2102,9 +2100,6 @@ class LintDeprecatedApi : AstVisitor
|
||||
public:
|
||||
LUAU_NOINLINE static void process(LintContext& context)
|
||||
{
|
||||
if (!FFlag::LuauImproveDeprecatedApiLint && !context.module)
|
||||
return;
|
||||
|
||||
LintDeprecatedApi pass{&context};
|
||||
context.root->visit(&pass);
|
||||
}
|
||||
@ -2122,7 +2117,6 @@ private:
|
||||
if (std::optional<TypeId> ty = context->getType(node->expr))
|
||||
check(node, follow(*ty));
|
||||
else if (AstExprGlobal* global = node->expr->as<AstExprGlobal>())
|
||||
if (FFlag::LuauImproveDeprecatedApiLint)
|
||||
check(node->location, global->name, node->index);
|
||||
|
||||
return true;
|
||||
@ -2144,7 +2138,7 @@ private:
|
||||
if (prop != tty->props.end() && prop->second.deprecated)
|
||||
{
|
||||
// strip synthetic typeof() for builtin tables
|
||||
if (FFlag::LuauImproveDeprecatedApiLint && tty->name && tty->name->compare(0, 7, "typeof(") == 0 && tty->name->back() == ')')
|
||||
if (tty->name && tty->name->compare(0, 7, "typeof(") == 0 && tty->name->back() == ')')
|
||||
report(node->location, prop->second, tty->name->substr(7, tty->name->length() - 8).c_str(), node->index.value);
|
||||
else
|
||||
report(node->location, prop->second, tty->name ? tty->name->c_str() : nullptr, node->index.value);
|
||||
|
@ -18,7 +18,6 @@ LUAU_FASTFLAGVARIABLE(DebugLuauCheckNormalizeInvariant, false)
|
||||
LUAU_FASTINTVARIABLE(LuauNormalizeIterationLimit, 1200);
|
||||
LUAU_FASTINTVARIABLE(LuauNormalizeCacheLimit, 100000);
|
||||
LUAU_FASTFLAGVARIABLE(LuauNegatedClassTypes, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauNegatedFunctionTypes, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauNegatedTableTypes, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauNormalizeBlockedTypes, false);
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
@ -202,26 +201,21 @@ bool NormalizedClassType::isNever() const
|
||||
return classes.empty();
|
||||
}
|
||||
|
||||
NormalizedFunctionType::NormalizedFunctionType()
|
||||
: parts(FFlag::LuauNegatedFunctionTypes ? std::optional<TypeIds>{TypeIds{}} : std::nullopt)
|
||||
{
|
||||
}
|
||||
|
||||
void NormalizedFunctionType::resetToTop()
|
||||
{
|
||||
isTop = true;
|
||||
parts.emplace();
|
||||
parts.clear();
|
||||
}
|
||||
|
||||
void NormalizedFunctionType::resetToNever()
|
||||
{
|
||||
isTop = false;
|
||||
parts.emplace();
|
||||
parts.clear();
|
||||
}
|
||||
|
||||
bool NormalizedFunctionType::isNever() const
|
||||
{
|
||||
return !isTop && (!parts || parts->empty());
|
||||
return !isTop && parts.empty();
|
||||
}
|
||||
|
||||
NormalizedType::NormalizedType(NotNull<BuiltinTypes> builtinTypes)
|
||||
@ -438,14 +432,11 @@ static bool isNormalizedThread(TypeId ty)
|
||||
|
||||
static bool areNormalizedFunctions(const NormalizedFunctionType& tys)
|
||||
{
|
||||
if (tys.parts)
|
||||
{
|
||||
for (TypeId ty : *tys.parts)
|
||||
for (TypeId ty : tys.parts)
|
||||
{
|
||||
if (!get<FunctionType>(ty) && !get<ErrorType>(ty))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1169,14 +1160,11 @@ std::optional<TypeId> Normalizer::unionOfFunctions(TypeId here, TypeId there)
|
||||
}
|
||||
|
||||
void Normalizer::unionFunctions(NormalizedFunctionType& heres, const NormalizedFunctionType& theres)
|
||||
{
|
||||
if (FFlag::LuauNegatedFunctionTypes)
|
||||
{
|
||||
if (heres.isTop)
|
||||
return;
|
||||
if (theres.isTop)
|
||||
heres.resetToTop();
|
||||
}
|
||||
|
||||
if (theres.isNever())
|
||||
return;
|
||||
@ -1185,13 +1173,13 @@ void Normalizer::unionFunctions(NormalizedFunctionType& heres, const NormalizedF
|
||||
|
||||
if (heres.isNever())
|
||||
{
|
||||
tmps.insert(theres.parts->begin(), theres.parts->end());
|
||||
tmps.insert(theres.parts.begin(), theres.parts.end());
|
||||
heres.parts = std::move(tmps);
|
||||
return;
|
||||
}
|
||||
|
||||
for (TypeId here : *heres.parts)
|
||||
for (TypeId there : *theres.parts)
|
||||
for (TypeId here : heres.parts)
|
||||
for (TypeId there : theres.parts)
|
||||
{
|
||||
if (std::optional<TypeId> fun = unionOfFunctions(here, there))
|
||||
tmps.insert(*fun);
|
||||
@ -1213,7 +1201,7 @@ void Normalizer::unionFunctionsWithFunction(NormalizedFunctionType& heres, TypeI
|
||||
}
|
||||
|
||||
TypeIds tmps;
|
||||
for (TypeId here : *heres.parts)
|
||||
for (TypeId here : heres.parts)
|
||||
{
|
||||
if (std::optional<TypeId> fun = unionOfFunctions(here, there))
|
||||
tmps.insert(*fun);
|
||||
@ -1420,7 +1408,6 @@ bool Normalizer::unionNormalWithTy(NormalizedType& here, TypeId there, int ignor
|
||||
here.threads = there;
|
||||
else if (ptv->type == PrimitiveType::Function)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauNegatedFunctionTypes);
|
||||
here.functions.resetToTop();
|
||||
}
|
||||
else if (ptv->type == PrimitiveType::Table && FFlag::LuauNegatedTableTypes)
|
||||
@ -1553,15 +1540,12 @@ std::optional<NormalizedType> Normalizer::negateNormal(const NormalizedType& her
|
||||
* arbitrary function types. Ordinary code can never form these kinds of
|
||||
* types, so we decline to negate them.
|
||||
*/
|
||||
if (FFlag::LuauNegatedFunctionTypes)
|
||||
{
|
||||
if (here.functions.isNever())
|
||||
result.functions.resetToTop();
|
||||
else if (here.functions.isTop)
|
||||
result.functions.resetToNever();
|
||||
else
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/*
|
||||
* It is not possible to negate an arbitrary table type, because function
|
||||
@ -2390,15 +2374,15 @@ void Normalizer::intersectFunctionsWithFunction(NormalizedFunctionType& heres, T
|
||||
|
||||
heres.isTop = false;
|
||||
|
||||
for (auto it = heres.parts->begin(); it != heres.parts->end();)
|
||||
for (auto it = heres.parts.begin(); it != heres.parts.end();)
|
||||
{
|
||||
TypeId here = *it;
|
||||
if (get<ErrorType>(here))
|
||||
it++;
|
||||
else if (std::optional<TypeId> tmp = intersectionOfFunctions(here, there))
|
||||
{
|
||||
heres.parts->erase(it);
|
||||
heres.parts->insert(*tmp);
|
||||
heres.parts.erase(it);
|
||||
heres.parts.insert(*tmp);
|
||||
return;
|
||||
}
|
||||
else
|
||||
@ -2406,13 +2390,13 @@ void Normalizer::intersectFunctionsWithFunction(NormalizedFunctionType& heres, T
|
||||
}
|
||||
|
||||
TypeIds tmps;
|
||||
for (TypeId here : *heres.parts)
|
||||
for (TypeId here : heres.parts)
|
||||
{
|
||||
if (std::optional<TypeId> tmp = unionSaturatedFunctions(here, there))
|
||||
tmps.insert(*tmp);
|
||||
}
|
||||
heres.parts->insert(there);
|
||||
heres.parts->insert(tmps.begin(), tmps.end());
|
||||
heres.parts.insert(there);
|
||||
heres.parts.insert(tmps.begin(), tmps.end());
|
||||
}
|
||||
|
||||
void Normalizer::intersectFunctions(NormalizedFunctionType& heres, const NormalizedFunctionType& theres)
|
||||
@ -2426,7 +2410,7 @@ void Normalizer::intersectFunctions(NormalizedFunctionType& heres, const Normali
|
||||
}
|
||||
else
|
||||
{
|
||||
for (TypeId there : *theres.parts)
|
||||
for (TypeId there : theres.parts)
|
||||
intersectFunctionsWithFunction(heres, there);
|
||||
}
|
||||
}
|
||||
@ -2621,10 +2605,7 @@ bool Normalizer::intersectNormalWithTy(NormalizedType& here, TypeId there)
|
||||
else if (ptv->type == PrimitiveType::Thread)
|
||||
here.threads = threads;
|
||||
else if (ptv->type == PrimitiveType::Function)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauNegatedFunctionTypes);
|
||||
here.functions = std::move(functions);
|
||||
}
|
||||
else if (ptv->type == PrimitiveType::Table)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauNegatedTableTypes);
|
||||
@ -2768,16 +2749,16 @@ TypeId Normalizer::typeFromNormal(const NormalizedType& norm)
|
||||
|
||||
if (!get<NeverType>(norm.errors))
|
||||
result.push_back(norm.errors);
|
||||
if (FFlag::LuauNegatedFunctionTypes && norm.functions.isTop)
|
||||
if (norm.functions.isTop)
|
||||
result.push_back(builtinTypes->functionType);
|
||||
else if (!norm.functions.isNever())
|
||||
{
|
||||
if (norm.functions.parts->size() == 1)
|
||||
result.push_back(*norm.functions.parts->begin());
|
||||
if (norm.functions.parts.size() == 1)
|
||||
result.push_back(*norm.functions.parts.begin());
|
||||
else
|
||||
{
|
||||
std::vector<TypeId> parts;
|
||||
parts.insert(parts.end(), norm.functions.parts->begin(), norm.functions.parts->end());
|
||||
parts.insert(parts.end(), norm.functions.parts.begin(), norm.functions.parts.end());
|
||||
result.push_back(arena->addType(IntersectionType{std::move(parts)}));
|
||||
}
|
||||
}
|
||||
|
@ -65,7 +65,7 @@ std::optional<TypeId> Scope::lookup(DefId def) const
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<TypeFun> Scope::lookupType(const Name& name)
|
||||
std::optional<TypeFun> Scope::lookupType(const Name& name) const
|
||||
{
|
||||
const Scope* scope = this;
|
||||
while (true)
|
||||
@ -85,7 +85,7 @@ std::optional<TypeFun> Scope::lookupType(const Name& name)
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<TypeFun> Scope::lookupImportedType(const Name& moduleAlias, const Name& name)
|
||||
std::optional<TypeFun> Scope::lookupImportedType(const Name& moduleAlias, const Name& name) const
|
||||
{
|
||||
const Scope* scope = this;
|
||||
while (scope)
|
||||
@ -110,7 +110,7 @@ std::optional<TypeFun> Scope::lookupImportedType(const Name& moduleAlias, const
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<TypePackId> Scope::lookupPack(const Name& name)
|
||||
std::optional<TypePackId> Scope::lookupPack(const Name& name) const
|
||||
{
|
||||
const Scope* scope = this;
|
||||
while (true)
|
||||
|
@ -2075,12 +2075,12 @@ struct TypeChecker2
|
||||
fetch(builtinTypes->functionType);
|
||||
else if (!norm.functions.isNever())
|
||||
{
|
||||
if (norm.functions.parts->size() == 1)
|
||||
fetch(norm.functions.parts->front());
|
||||
if (norm.functions.parts.size() == 1)
|
||||
fetch(norm.functions.parts.front());
|
||||
else
|
||||
{
|
||||
std::vector<TypeId> parts;
|
||||
parts.insert(parts.end(), norm.functions.parts->begin(), norm.functions.parts->end());
|
||||
parts.insert(parts.end(), norm.functions.parts.begin(), norm.functions.parts.end());
|
||||
fetch(testArena.addType(IntersectionType{std::move(parts)}));
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,6 @@
|
||||
#include <iterator>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauMagicTypes, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauDontExtendUnsealedRValueTables, false)
|
||||
LUAU_FASTINTVARIABLE(LuauTypeInferRecursionLimit, 165)
|
||||
LUAU_FASTINTVARIABLE(LuauTypeInferIterationLimit, 20000)
|
||||
LUAU_FASTINTVARIABLE(LuauTypeInferTypePackLoopLimit, 5000)
|
||||
@ -38,7 +37,6 @@ LUAU_FASTFLAGVARIABLE(LuauReturnAnyInsteadOfICE, false) // Eventually removed as
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauSharedSelf, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTryhardAnd, false)
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
LUAU_FASTFLAGVARIABLE(LuauIntersectionTestForEquality, false)
|
||||
LUAU_FASTFLAG(LuauNegatedClassTypes)
|
||||
LUAU_FASTFLAGVARIABLE(LuauAllowIndexClassParameters, false)
|
||||
LUAU_FASTFLAG(LuauUninhabitedSubAnything2)
|
||||
@ -228,8 +226,8 @@ GlobalTypes::GlobalTypes(NotNull<BuiltinTypes> builtinTypes)
|
||||
globalScope->addBuiltinTypeBinding("never", TypeFun{{}, builtinTypes->neverType});
|
||||
}
|
||||
|
||||
TypeChecker::TypeChecker(const GlobalTypes& globals, ModuleResolver* resolver, NotNull<BuiltinTypes> builtinTypes, InternalErrorReporter* iceHandler)
|
||||
: globals(globals)
|
||||
TypeChecker::TypeChecker(const ScopePtr& globalScope, ModuleResolver* resolver, NotNull<BuiltinTypes> builtinTypes, InternalErrorReporter* iceHandler)
|
||||
: globalScope(globalScope)
|
||||
, resolver(resolver)
|
||||
, builtinTypes(builtinTypes)
|
||||
, iceHandler(iceHandler)
|
||||
@ -280,7 +278,7 @@ ModulePtr TypeChecker::checkWithoutRecursionCheck(const SourceModule& module, Mo
|
||||
unifierState.counters.recursionLimit = FInt::LuauTypeInferRecursionLimit;
|
||||
unifierState.counters.iterationLimit = unifierIterationLimit ? *unifierIterationLimit : FInt::LuauTypeInferIterationLimit;
|
||||
|
||||
ScopePtr parentScope = environmentScope.value_or(globals.globalScope);
|
||||
ScopePtr parentScope = environmentScope.value_or(globalScope);
|
||||
ScopePtr moduleScope = std::make_shared<Scope>(parentScope);
|
||||
|
||||
if (module.cyclic)
|
||||
@ -1656,7 +1654,7 @@ void TypeChecker::prototype(const ScopePtr& scope, const AstStatTypeAlias& typea
|
||||
}
|
||||
else
|
||||
{
|
||||
if (globals.globalScope->builtinTypeNames.contains(name))
|
||||
if (globalScope->builtinTypeNames.contains(name))
|
||||
{
|
||||
reportError(typealias.location, DuplicateTypeDefinition{name});
|
||||
duplicateTypeAliases.insert({typealias.exported, name});
|
||||
@ -2690,7 +2688,7 @@ TypeId TypeChecker::checkRelationalOperation(
|
||||
if (get<NeverType>(lhsType) || get<NeverType>(rhsType))
|
||||
return booleanType;
|
||||
|
||||
if (FFlag::LuauIntersectionTestForEquality && isEquality)
|
||||
if (isEquality)
|
||||
{
|
||||
// Unless either type is free or any, an equality comparison is only
|
||||
// valid when the intersection of the two operands is non-empty.
|
||||
@ -3261,16 +3259,7 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex
|
||||
{
|
||||
return it->second.type;
|
||||
}
|
||||
else if (!FFlag::LuauDontExtendUnsealedRValueTables && (lhsTable->state == TableState::Unsealed || lhsTable->state == TableState::Free))
|
||||
{
|
||||
TypeId theType = freshType(scope);
|
||||
Property& property = lhsTable->props[name];
|
||||
property.type = theType;
|
||||
property.location = expr.indexLocation;
|
||||
return theType;
|
||||
}
|
||||
else if (FFlag::LuauDontExtendUnsealedRValueTables &&
|
||||
((ctx == ValueContext::LValue && lhsTable->state == TableState::Unsealed) || lhsTable->state == TableState::Free))
|
||||
else if ((ctx == ValueContext::LValue && lhsTable->state == TableState::Unsealed) || lhsTable->state == TableState::Free)
|
||||
{
|
||||
TypeId theType = freshType(scope);
|
||||
Property& property = lhsTable->props[name];
|
||||
@ -3391,16 +3380,7 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex
|
||||
{
|
||||
return it->second.type;
|
||||
}
|
||||
else if (!FFlag::LuauDontExtendUnsealedRValueTables && (exprTable->state == TableState::Unsealed || exprTable->state == TableState::Free))
|
||||
{
|
||||
TypeId resultType = freshType(scope);
|
||||
Property& property = exprTable->props[value->value.data];
|
||||
property.type = resultType;
|
||||
property.location = expr.index->location;
|
||||
return resultType;
|
||||
}
|
||||
else if (FFlag::LuauDontExtendUnsealedRValueTables &&
|
||||
((ctx == ValueContext::LValue && exprTable->state == TableState::Unsealed) || exprTable->state == TableState::Free))
|
||||
else if ((ctx == ValueContext::LValue && exprTable->state == TableState::Unsealed) || exprTable->state == TableState::Free)
|
||||
{
|
||||
TypeId resultType = freshType(scope);
|
||||
Property& property = exprTable->props[value->value.data];
|
||||
@ -3416,14 +3396,7 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex
|
||||
unify(indexType, indexer.indexType, scope, expr.index->location);
|
||||
return indexer.indexResultType;
|
||||
}
|
||||
else if (!FFlag::LuauDontExtendUnsealedRValueTables && (exprTable->state == TableState::Unsealed || exprTable->state == TableState::Free))
|
||||
{
|
||||
TypeId resultType = freshType(exprTable->level);
|
||||
exprTable->indexer = TableIndexer{anyIfNonstrict(indexType), anyIfNonstrict(resultType)};
|
||||
return resultType;
|
||||
}
|
||||
else if (FFlag::LuauDontExtendUnsealedRValueTables &&
|
||||
((ctx == ValueContext::LValue && exprTable->state == TableState::Unsealed) || exprTable->state == TableState::Free))
|
||||
else if ((ctx == ValueContext::LValue && exprTable->state == TableState::Unsealed) || exprTable->state == TableState::Free)
|
||||
{
|
||||
TypeId indexerType = freshType(exprTable->level);
|
||||
unify(indexType, indexerType, scope, expr.location);
|
||||
@ -3439,13 +3412,7 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex
|
||||
* has no indexer, we have no idea if it will work so we just return any
|
||||
* and hope for the best.
|
||||
*/
|
||||
if (FFlag::LuauDontExtendUnsealedRValueTables)
|
||||
return anyType;
|
||||
else
|
||||
{
|
||||
TypeId resultType = freshType(scope);
|
||||
return resultType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -5997,7 +5964,7 @@ void TypeChecker::resolve(const TypeGuardPredicate& typeguardP, RefinementMap& r
|
||||
if (!typeguardP.isTypeof)
|
||||
return addRefinement(refis, typeguardP.lvalue, errorRecoveryType(scope));
|
||||
|
||||
auto typeFun = globals.globalScope->lookupType(typeguardP.kind);
|
||||
auto typeFun = globalScope->lookupType(typeguardP.kind);
|
||||
if (!typeFun || !typeFun->typeParams.empty() || !typeFun->typePackParams.empty())
|
||||
return addRefinement(refis, typeguardP.lvalue, errorRecoveryType(scope));
|
||||
|
||||
|
@ -21,11 +21,9 @@ LUAU_FASTFLAGVARIABLE(LuauInstantiateInSubtyping, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUninhabitedSubAnything2, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauMaintainScopesInUnifier, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTransitiveSubtyping, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTinyUnifyNormalsFix, false)
|
||||
LUAU_FASTFLAG(LuauClassTypeVarsInSubstitution)
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
LUAU_FASTFLAG(LuauNormalizeBlockedTypes)
|
||||
LUAU_FASTFLAG(LuauNegatedFunctionTypes)
|
||||
LUAU_FASTFLAG(LuauNegatedClassTypes)
|
||||
LUAU_FASTFLAG(LuauNegatedTableTypes)
|
||||
|
||||
@ -615,8 +613,7 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
|
||||
else if ((log.getMutable<PrimitiveType>(superTy) || log.getMutable<SingletonType>(superTy)) && log.getMutable<SingletonType>(subTy))
|
||||
tryUnifySingletons(subTy, superTy);
|
||||
|
||||
else if (auto ptv = get<PrimitiveType>(superTy);
|
||||
FFlag::LuauNegatedFunctionTypes && ptv && ptv->type == PrimitiveType::Function && get<FunctionType>(subTy))
|
||||
else if (auto ptv = get<PrimitiveType>(superTy); ptv && ptv->type == PrimitiveType::Function && get<FunctionType>(subTy))
|
||||
{
|
||||
// Ok. Do nothing. forall functions F, F <: function
|
||||
}
|
||||
@ -1275,17 +1272,7 @@ void Unifier::tryUnifyNormalizedTypes(
|
||||
|
||||
Unifier innerState = makeChildUnifier();
|
||||
|
||||
if (FFlag::LuauTinyUnifyNormalsFix)
|
||||
innerState.tryUnify(subTable, superTable);
|
||||
else
|
||||
{
|
||||
if (get<MetatableType>(superTable))
|
||||
innerState.tryUnifyWithMetatable(subTable, superTable, /* reversed */ false);
|
||||
else if (get<MetatableType>(subTable))
|
||||
innerState.tryUnifyWithMetatable(superTable, subTable, /* reversed */ true);
|
||||
else
|
||||
innerState.tryUnifyTables(subTable, superTable);
|
||||
}
|
||||
|
||||
if (innerState.errors.empty())
|
||||
{
|
||||
@ -1304,7 +1291,7 @@ void Unifier::tryUnifyNormalizedTypes(
|
||||
{
|
||||
if (superNorm.functions.isNever())
|
||||
return reportError(location, TypeMismatch{superTy, subTy, reason, error, mismatchContext()});
|
||||
for (TypeId superFun : *superNorm.functions.parts)
|
||||
for (TypeId superFun : superNorm.functions.parts)
|
||||
{
|
||||
Unifier innerState = makeChildUnifier();
|
||||
const FunctionType* superFtv = get<FunctionType>(superFun);
|
||||
@ -1343,7 +1330,7 @@ TypePackId Unifier::tryApplyOverloadedFunction(TypeId function, const Normalized
|
||||
|
||||
std::optional<TypePackId> result;
|
||||
const FunctionType* firstFun = nullptr;
|
||||
for (TypeId overload : *overloads.parts)
|
||||
for (TypeId overload : overloads.parts)
|
||||
{
|
||||
if (const FunctionType* ftv = get<FunctionType>(overload))
|
||||
{
|
||||
|
@ -6,8 +6,6 @@
|
||||
|
||||
#include <limits.h>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauFixInterpStringMid, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
@ -642,9 +640,7 @@ Lexeme Lexer::readInterpolatedStringSection(Position start, Lexeme::Type formatT
|
||||
}
|
||||
|
||||
consume();
|
||||
Lexeme lexemeOutput(Location(start, position()), FFlag::LuauFixInterpStringMid ? formatType : Lexeme::InterpStringBegin,
|
||||
&buffer[startOffset], offset - startOffset - 1);
|
||||
return lexemeOutput;
|
||||
return Lexeme(Location(start, position()), formatType, &buffer[startOffset], offset - startOffset - 1);
|
||||
}
|
||||
|
||||
default:
|
||||
|
@ -3,6 +3,8 @@
|
||||
|
||||
#include "Luau/RegisterA64.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
namespace CodeGen
|
||||
@ -23,6 +25,10 @@ enum class AddressKindA64 : uint8_t
|
||||
|
||||
struct AddressA64
|
||||
{
|
||||
// This is a little misleading since AddressA64 can encode offsets up to 1023*size where size depends on the load/store size
|
||||
// For example, ldr x0, [reg+imm] is limited to 8 KB offsets assuming imm is divisible by 8, but loading into w0 reduces the range to 4 KB
|
||||
static constexpr size_t kMaxOffset = 1023;
|
||||
|
||||
AddressA64(RegisterA64 base, int off = 0)
|
||||
: kind(AddressKindA64::imm)
|
||||
, base(base)
|
||||
@ -30,7 +36,6 @@ struct AddressA64
|
||||
, data(off)
|
||||
{
|
||||
LUAU_ASSERT(base.kind == KindA64::x || base == sp);
|
||||
LUAU_ASSERT(off >= -256 && off < 4096);
|
||||
}
|
||||
|
||||
AddressA64(RegisterA64 base, RegisterA64 offset)
|
||||
|
@ -16,10 +16,15 @@ namespace CodeGen
|
||||
namespace A64
|
||||
{
|
||||
|
||||
enum FeaturesA64
|
||||
{
|
||||
Feature_JSCVT = 1 << 0,
|
||||
};
|
||||
|
||||
class AssemblyBuilderA64
|
||||
{
|
||||
public:
|
||||
explicit AssemblyBuilderA64(bool logText);
|
||||
explicit AssemblyBuilderA64(bool logText, unsigned int features = 0);
|
||||
~AssemblyBuilderA64();
|
||||
|
||||
// Moves
|
||||
@ -42,6 +47,7 @@ public:
|
||||
// Note: some arithmetic instructions also have versions that update flags (ADDS etc) but we aren't using them atm
|
||||
void cmp(RegisterA64 src1, RegisterA64 src2);
|
||||
void cmp(RegisterA64 src1, uint16_t src2);
|
||||
void csel(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2, ConditionA64 cond);
|
||||
|
||||
// Bitwise
|
||||
// Note: shifted-register support and bitfield operations are omitted for simplicity
|
||||
@ -93,6 +99,36 @@ public:
|
||||
// Address of code (label)
|
||||
void adr(RegisterA64 dst, Label& label);
|
||||
|
||||
// Floating-point scalar moves
|
||||
void fmov(RegisterA64 dst, RegisterA64 src);
|
||||
|
||||
// Floating-point scalar math
|
||||
void fabs(RegisterA64 dst, RegisterA64 src);
|
||||
void fadd(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2);
|
||||
void fdiv(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2);
|
||||
void fmul(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2);
|
||||
void fneg(RegisterA64 dst, RegisterA64 src);
|
||||
void fsqrt(RegisterA64 dst, RegisterA64 src);
|
||||
void fsub(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2);
|
||||
|
||||
// Floating-point rounding and conversions
|
||||
void frinta(RegisterA64 dst, RegisterA64 src);
|
||||
void frintm(RegisterA64 dst, RegisterA64 src);
|
||||
void frintp(RegisterA64 dst, RegisterA64 src);
|
||||
void fcvtzs(RegisterA64 dst, RegisterA64 src);
|
||||
void fcvtzu(RegisterA64 dst, RegisterA64 src);
|
||||
void scvtf(RegisterA64 dst, RegisterA64 src);
|
||||
void ucvtf(RegisterA64 dst, RegisterA64 src);
|
||||
|
||||
// Floating-point conversion to integer using JS rules (wrap around 2^32) and set Z flag
|
||||
// note: this is part of ARM8.3 (JSCVT feature); support of this instruction needs to be checked at runtime
|
||||
void fjcvtzs(RegisterA64 dst, RegisterA64 src);
|
||||
|
||||
// Floating-point comparisons
|
||||
void fcmp(RegisterA64 src1, RegisterA64 src2);
|
||||
void fcmpz(RegisterA64 src);
|
||||
void fcsel(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2, ConditionA64 cond);
|
||||
|
||||
// Run final checks
|
||||
bool finalize();
|
||||
|
||||
@ -121,6 +157,7 @@ public:
|
||||
std::string text;
|
||||
|
||||
const bool logText = false;
|
||||
const unsigned int features = 0;
|
||||
|
||||
// Maximum immediate argument to functions like add/sub/cmp
|
||||
static constexpr size_t kMaxImmediate = (1 << 12) - 1;
|
||||
@ -134,13 +171,15 @@ private:
|
||||
void placeR1(const char* name, RegisterA64 dst, RegisterA64 src, uint32_t op);
|
||||
void placeI12(const char* name, RegisterA64 dst, RegisterA64 src1, int src2, uint8_t op);
|
||||
void placeI16(const char* name, RegisterA64 dst, int src, uint8_t op, int shift = 0);
|
||||
void placeA(const char* name, RegisterA64 dst, AddressA64 src, uint8_t op, uint8_t size);
|
||||
void placeA(const char* name, RegisterA64 dst, AddressA64 src, uint8_t op, uint8_t size, int sizelog);
|
||||
void placeBC(const char* name, Label& label, uint8_t op, uint8_t cond);
|
||||
void placeBCR(const char* name, Label& label, uint8_t op, RegisterA64 cond);
|
||||
void placeBR(const char* name, RegisterA64 src, uint32_t op);
|
||||
void placeADR(const char* name, RegisterA64 src, uint8_t op);
|
||||
void placeADR(const char* name, RegisterA64 src, uint8_t op, Label& label);
|
||||
void placeP(const char* name, RegisterA64 dst1, RegisterA64 dst2, AddressA64 src, uint8_t op, uint8_t size);
|
||||
void placeP(const char* name, RegisterA64 dst1, RegisterA64 dst2, AddressA64 src, uint8_t op, uint8_t opc, int sizelog);
|
||||
void placeCS(const char* name, RegisterA64 dst, RegisterA64 src1, RegisterA64 src2, ConditionA64 cond, uint8_t op, uint8_t opc);
|
||||
void placeFCMP(const char* name, RegisterA64 src1, RegisterA64 src2, uint8_t op, uint8_t opc);
|
||||
|
||||
void place(uint32_t word);
|
||||
|
||||
@ -164,6 +203,7 @@ private:
|
||||
LUAU_NOINLINE void log(const char* opcode, RegisterA64 src, Label label);
|
||||
LUAU_NOINLINE void log(const char* opcode, RegisterA64 src);
|
||||
LUAU_NOINLINE void log(const char* opcode, Label label);
|
||||
LUAU_NOINLINE void log(const char* opcode, RegisterA64 dst, RegisterA64 src1, RegisterA64 src2, ConditionA64 cond);
|
||||
LUAU_NOINLINE void log(Label label);
|
||||
LUAU_NOINLINE void log(RegisterA64 reg);
|
||||
LUAU_NOINLINE void log(AddressA64 addr);
|
||||
|
@ -41,6 +41,7 @@ enum class ABIX64
|
||||
class AssemblyBuilderX64
|
||||
{
|
||||
public:
|
||||
explicit AssemblyBuilderX64(bool logText, ABIX64 abi);
|
||||
explicit AssemblyBuilderX64(bool logText);
|
||||
~AssemblyBuilderX64();
|
||||
|
||||
|
@ -8,28 +8,45 @@ namespace CodeGen
|
||||
namespace A64
|
||||
{
|
||||
|
||||
// See Table C1-1 on page C1-229 of Arm ARM for A-profile architecture
|
||||
enum class ConditionA64
|
||||
{
|
||||
// EQ: integer (equal), floating-point (equal)
|
||||
Equal,
|
||||
// NE: integer (not equal), floating-point (not equal or unordered)
|
||||
NotEqual,
|
||||
|
||||
// CS: integer (carry set), floating-point (greater than, equal or unordered)
|
||||
CarrySet,
|
||||
// CC: integer (carry clear), floating-point (less than)
|
||||
CarryClear,
|
||||
|
||||
// MI: integer (negative), floating-point (less than)
|
||||
Minus,
|
||||
// PL: integer (positive or zero), floating-point (greater than, equal or unordered)
|
||||
Plus,
|
||||
|
||||
// VS: integer (overflow), floating-point (unordered)
|
||||
Overflow,
|
||||
// VC: integer (no overflow), floating-point (ordered)
|
||||
NoOverflow,
|
||||
|
||||
// HI: integer (unsigned higher), floating-point (greater than, or unordered)
|
||||
UnsignedGreater,
|
||||
// LS: integer (unsigned lower or same), floating-point (less than or equal)
|
||||
UnsignedLessEqual,
|
||||
|
||||
// GE: integer (signed greater than or equal), floating-point (greater than or equal)
|
||||
GreaterEqual,
|
||||
// LT: integer (signed less than), floating-point (less than, or unordered)
|
||||
Less,
|
||||
|
||||
// GT: integer (signed greater than), floating-point (greater than)
|
||||
Greater,
|
||||
// LE: integer (signed less than or equal), floating-point (less than, equal or unordered)
|
||||
LessEqual,
|
||||
|
||||
// AL: always
|
||||
Always,
|
||||
|
||||
Count
|
||||
|
82
CodeGen/include/Luau/IrCallWrapperX64.h
Normal file
82
CodeGen/include/Luau/IrCallWrapperX64.h
Normal file
@ -0,0 +1,82 @@
|
||||
// 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/OperandX64.h"
|
||||
#include "Luau/RegisterX64.h"
|
||||
|
||||
#include <array>
|
||||
|
||||
// TODO: call wrapper can be used to suggest target registers for ScopedRegX64 to compute data into argument registers directly
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
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;
|
||||
|
||||
struct CallArgument
|
||||
{
|
||||
SizeX64 targetSize = SizeX64::none;
|
||||
|
||||
OperandX64 source = noreg;
|
||||
IrOp sourceOp;
|
||||
|
||||
OperandX64 target = noreg;
|
||||
bool candidate = true;
|
||||
};
|
||||
|
||||
class IrCallWrapperX64
|
||||
{
|
||||
public:
|
||||
IrCallWrapperX64(IrRegAllocX64& regs, AssemblyBuilderX64& build, uint32_t instIdx = kInvalidInstIdx);
|
||||
|
||||
void addArgument(SizeX64 targetSize, OperandX64 source, IrOp sourceOp = {});
|
||||
void addArgument(SizeX64 targetSize, ScopedRegX64& scopedReg);
|
||||
|
||||
void call(const OperandX64& func);
|
||||
|
||||
IrRegAllocX64& regs;
|
||||
AssemblyBuilderX64& build;
|
||||
uint32_t instIdx = ~0u;
|
||||
|
||||
private:
|
||||
void assignTargetRegisters();
|
||||
void countRegisterUses();
|
||||
CallArgument* findNonInterferingArgument();
|
||||
bool interferesWithOperand(const OperandX64& op, RegisterX64 reg) const;
|
||||
bool interferesWithActiveSources(const CallArgument& targetArg, int targetArgIndex) const;
|
||||
bool interferesWithActiveTarget(RegisterX64 sourceReg) const;
|
||||
void moveToTarget(CallArgument& arg);
|
||||
void freeSourceRegisters(CallArgument& arg);
|
||||
void renameRegister(RegisterX64& target, RegisterX64 reg, RegisterX64 replacement);
|
||||
void renameSourceRegisters(RegisterX64 reg, RegisterX64 replacement);
|
||||
RegisterX64 findConflictingTarget() const;
|
||||
|
||||
int getRegisterUses(RegisterX64 reg) const;
|
||||
void addRegisterUse(RegisterX64 reg);
|
||||
void removeRegisterUse(RegisterX64 reg);
|
||||
|
||||
static const int kMaxCallArguments = 6;
|
||||
std::array<CallArgument, kMaxCallArguments> args;
|
||||
int argCount = 0;
|
||||
|
||||
OperandX64 funcOp;
|
||||
|
||||
// Internal counters for remaining register use counts
|
||||
std::array<uint8_t, 16> gprUses;
|
||||
std::array<uint8_t, 16> xmmUses;
|
||||
};
|
||||
|
||||
} // namespace X64
|
||||
} // namespace CodeGen
|
||||
} // namespace Luau
|
@ -125,6 +125,26 @@ enum class IrCmd : uint8_t
|
||||
// A: double
|
||||
UNM_NUM,
|
||||
|
||||
// Round number to negative infinity (math.floor)
|
||||
// A: double
|
||||
FLOOR_NUM,
|
||||
|
||||
// Round number to positive infinity (math.ceil)
|
||||
// A: double
|
||||
CEIL_NUM,
|
||||
|
||||
// Round number to nearest integer number, rounding half-way cases away from zero (math.round)
|
||||
// A: double
|
||||
ROUND_NUM,
|
||||
|
||||
// Get square root of the argument (math.sqrt)
|
||||
// A: double
|
||||
SQRT_NUM,
|
||||
|
||||
// Get absolute value of the argument (math.abs)
|
||||
// A: double
|
||||
ABS_NUM,
|
||||
|
||||
// Compute Luau 'not' operation on destructured TValue
|
||||
// A: tag
|
||||
// B: double
|
||||
@ -252,6 +272,7 @@ enum class IrCmd : uint8_t
|
||||
// A: Rn (where to store the result)
|
||||
// B: Rn (lhs)
|
||||
// C: Rn or Kn (rhs)
|
||||
// D: int (TMS enum with arithmetic type)
|
||||
DO_ARITH,
|
||||
|
||||
// Get length of a TValue of any type
|
||||
@ -382,54 +403,53 @@ enum class IrCmd : uint8_t
|
||||
// C: Rn (source start)
|
||||
// D: int (count or -1 to assign values up to stack top)
|
||||
// E: unsigned int (table index to start from)
|
||||
LOP_SETLIST,
|
||||
SETLIST,
|
||||
|
||||
// Call specified function
|
||||
// A: Rn (function, followed by arguments)
|
||||
// B: int (argument count or -1 to use all arguments up to stack top)
|
||||
// C: int (result count or -1 to preserve all results and adjust stack top)
|
||||
// Note: return values are placed starting from Rn specified in 'A'
|
||||
LOP_CALL,
|
||||
CALL,
|
||||
|
||||
// Return specified values from the function
|
||||
// A: Rn (value start)
|
||||
// B: int (result count or -1 to return all values up to stack top)
|
||||
LOP_RETURN,
|
||||
RETURN,
|
||||
|
||||
// Adjust loop variables for one iteration of a generic for loop, jump back to the loop header if loop needs to continue
|
||||
// A: Rn (loop variable start, updates Rn+2 and 'B' number of registers starting from Rn+3)
|
||||
// B: int (loop variable count, if more than 2, registers starting from Rn+5 are set to nil)
|
||||
// C: block (repeat)
|
||||
// D: block (exit)
|
||||
LOP_FORGLOOP,
|
||||
FORGLOOP,
|
||||
|
||||
// Handle LOP_FORGLOOP fallback when variable being iterated is not a table
|
||||
// A: unsigned int (bytecode instruction index)
|
||||
// B: Rn (loop state start, updates Rn+2 and 'C' number of registers starting from Rn+3)
|
||||
// C: int (loop variable count and a MSB set when it's an ipairs-like iteration loop)
|
||||
// D: block (repeat)
|
||||
// E: block (exit)
|
||||
LOP_FORGLOOP_FALLBACK,
|
||||
// A: Rn (loop state start, updates Rn+2 and 'B' number of registers starting from Rn+3)
|
||||
// B: int (loop variable count and a MSB set when it's an ipairs-like iteration loop)
|
||||
// C: block (repeat)
|
||||
// D: block (exit)
|
||||
FORGLOOP_FALLBACK,
|
||||
|
||||
// Fallback for generic for loop preparation when iterating over builtin pairs/ipairs
|
||||
// It raises an error if 'B' register is not a function
|
||||
// A: unsigned int (bytecode instruction index)
|
||||
// B: Rn
|
||||
// C: block (forgloop location)
|
||||
LOP_FORGPREP_XNEXT_FALLBACK,
|
||||
FORGPREP_XNEXT_FALLBACK,
|
||||
|
||||
// Perform `and` or `or` operation (selecting lhs or rhs based on whether the lhs is truthy) and put the result into target register
|
||||
// A: Rn (target)
|
||||
// B: Rn (lhs)
|
||||
// C: Rn or Kn (rhs)
|
||||
LOP_AND,
|
||||
LOP_ANDK,
|
||||
LOP_OR,
|
||||
LOP_ORK,
|
||||
AND,
|
||||
ANDK,
|
||||
OR,
|
||||
ORK,
|
||||
|
||||
// Increment coverage data (saturating 24 bit add)
|
||||
// A: unsigned int (bytecode instruction index)
|
||||
LOP_COVERAGE,
|
||||
COVERAGE,
|
||||
|
||||
// Operations that have a translation, but use a full instruction fallback
|
||||
|
||||
@ -676,6 +696,14 @@ struct IrFunction
|
||||
return instructions[op.index];
|
||||
}
|
||||
|
||||
IrInst* asInstOp(IrOp op)
|
||||
{
|
||||
if (op.kind == IrOpKind::Inst)
|
||||
return &instructions[op.index];
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
IrConst& constOp(IrOp op)
|
||||
{
|
||||
LUAU_ASSERT(op.kind == IrOpKind::Constant);
|
||||
|
@ -24,12 +24,17 @@ struct IrRegAllocX64
|
||||
RegisterX64 allocGprRegOrReuse(SizeX64 preferredSize, uint32_t index, std::initializer_list<IrOp> oprefs);
|
||||
RegisterX64 allocXmmRegOrReuse(uint32_t index, std::initializer_list<IrOp> oprefs);
|
||||
|
||||
RegisterX64 takeGprReg(RegisterX64 reg);
|
||||
RegisterX64 takeReg(RegisterX64 reg);
|
||||
|
||||
void freeReg(RegisterX64 reg);
|
||||
void freeLastUseReg(IrInst& target, uint32_t index);
|
||||
void freeLastUseRegs(const IrInst& inst, uint32_t index);
|
||||
|
||||
bool isLastUseReg(const IrInst& target, uint32_t index) const;
|
||||
|
||||
bool shouldFreeGpr(RegisterX64 reg) const;
|
||||
|
||||
void assertFree(RegisterX64 reg) const;
|
||||
void assertAllFree() const;
|
||||
|
||||
IrFunction& function;
|
||||
@ -51,6 +56,8 @@ struct ScopedRegX64
|
||||
void alloc(SizeX64 size);
|
||||
void free();
|
||||
|
||||
RegisterX64 release();
|
||||
|
||||
IrRegAllocX64& owner;
|
||||
RegisterX64 reg;
|
||||
};
|
@ -99,10 +99,10 @@ inline bool isBlockTerminator(IrCmd cmd)
|
||||
case IrCmd::JUMP_CMP_NUM:
|
||||
case IrCmd::JUMP_CMP_ANY:
|
||||
case IrCmd::JUMP_SLOT_MATCH:
|
||||
case IrCmd::LOP_RETURN:
|
||||
case IrCmd::LOP_FORGLOOP:
|
||||
case IrCmd::LOP_FORGLOOP_FALLBACK:
|
||||
case IrCmd::LOP_FORGPREP_XNEXT_FALLBACK:
|
||||
case IrCmd::RETURN:
|
||||
case IrCmd::FORGLOOP:
|
||||
case IrCmd::FORGLOOP_FALLBACK:
|
||||
case IrCmd::FORGPREP_XNEXT_FALLBACK:
|
||||
case IrCmd::FALLBACK_FORGPREP:
|
||||
return true;
|
||||
default:
|
||||
@ -137,6 +137,11 @@ inline bool hasResult(IrCmd cmd)
|
||||
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::NOT_ANY:
|
||||
case IrCmd::TABLE_LEN:
|
||||
case IrCmd::NEW_TABLE:
|
||||
|
@ -17,6 +17,8 @@ enum class KindA64 : uint8_t
|
||||
none,
|
||||
w, // 32-bit GPR
|
||||
x, // 64-bit GPR
|
||||
d, // 64-bit SIMD&FP scalar
|
||||
q, // 128-bit SIMD&FP vector
|
||||
};
|
||||
|
||||
struct RegisterA64
|
||||
@ -105,6 +107,72 @@ constexpr RegisterA64 xzr{KindA64::x, 31};
|
||||
|
||||
constexpr RegisterA64 sp{KindA64::none, 31};
|
||||
|
||||
constexpr RegisterA64 d0{KindA64::d, 0};
|
||||
constexpr RegisterA64 d1{KindA64::d, 1};
|
||||
constexpr RegisterA64 d2{KindA64::d, 2};
|
||||
constexpr RegisterA64 d3{KindA64::d, 3};
|
||||
constexpr RegisterA64 d4{KindA64::d, 4};
|
||||
constexpr RegisterA64 d5{KindA64::d, 5};
|
||||
constexpr RegisterA64 d6{KindA64::d, 6};
|
||||
constexpr RegisterA64 d7{KindA64::d, 7};
|
||||
constexpr RegisterA64 d8{KindA64::d, 8};
|
||||
constexpr RegisterA64 d9{KindA64::d, 9};
|
||||
constexpr RegisterA64 d10{KindA64::d, 10};
|
||||
constexpr RegisterA64 d11{KindA64::d, 11};
|
||||
constexpr RegisterA64 d12{KindA64::d, 12};
|
||||
constexpr RegisterA64 d13{KindA64::d, 13};
|
||||
constexpr RegisterA64 d14{KindA64::d, 14};
|
||||
constexpr RegisterA64 d15{KindA64::d, 15};
|
||||
constexpr RegisterA64 d16{KindA64::d, 16};
|
||||
constexpr RegisterA64 d17{KindA64::d, 17};
|
||||
constexpr RegisterA64 d18{KindA64::d, 18};
|
||||
constexpr RegisterA64 d19{KindA64::d, 19};
|
||||
constexpr RegisterA64 d20{KindA64::d, 20};
|
||||
constexpr RegisterA64 d21{KindA64::d, 21};
|
||||
constexpr RegisterA64 d22{KindA64::d, 22};
|
||||
constexpr RegisterA64 d23{KindA64::d, 23};
|
||||
constexpr RegisterA64 d24{KindA64::d, 24};
|
||||
constexpr RegisterA64 d25{KindA64::d, 25};
|
||||
constexpr RegisterA64 d26{KindA64::d, 26};
|
||||
constexpr RegisterA64 d27{KindA64::d, 27};
|
||||
constexpr RegisterA64 d28{KindA64::d, 28};
|
||||
constexpr RegisterA64 d29{KindA64::d, 29};
|
||||
constexpr RegisterA64 d30{KindA64::d, 30};
|
||||
constexpr RegisterA64 d31{KindA64::d, 31};
|
||||
|
||||
constexpr RegisterA64 q0{KindA64::q, 0};
|
||||
constexpr RegisterA64 q1{KindA64::q, 1};
|
||||
constexpr RegisterA64 q2{KindA64::q, 2};
|
||||
constexpr RegisterA64 q3{KindA64::q, 3};
|
||||
constexpr RegisterA64 q4{KindA64::q, 4};
|
||||
constexpr RegisterA64 q5{KindA64::q, 5};
|
||||
constexpr RegisterA64 q6{KindA64::q, 6};
|
||||
constexpr RegisterA64 q7{KindA64::q, 7};
|
||||
constexpr RegisterA64 q8{KindA64::q, 8};
|
||||
constexpr RegisterA64 q9{KindA64::q, 9};
|
||||
constexpr RegisterA64 q10{KindA64::q, 10};
|
||||
constexpr RegisterA64 q11{KindA64::q, 11};
|
||||
constexpr RegisterA64 q12{KindA64::q, 12};
|
||||
constexpr RegisterA64 q13{KindA64::q, 13};
|
||||
constexpr RegisterA64 q14{KindA64::q, 14};
|
||||
constexpr RegisterA64 q15{KindA64::q, 15};
|
||||
constexpr RegisterA64 q16{KindA64::q, 16};
|
||||
constexpr RegisterA64 q17{KindA64::q, 17};
|
||||
constexpr RegisterA64 q18{KindA64::q, 18};
|
||||
constexpr RegisterA64 q19{KindA64::q, 19};
|
||||
constexpr RegisterA64 q20{KindA64::q, 20};
|
||||
constexpr RegisterA64 q21{KindA64::q, 21};
|
||||
constexpr RegisterA64 q22{KindA64::q, 22};
|
||||
constexpr RegisterA64 q23{KindA64::q, 23};
|
||||
constexpr RegisterA64 q24{KindA64::q, 24};
|
||||
constexpr RegisterA64 q25{KindA64::q, 25};
|
||||
constexpr RegisterA64 q26{KindA64::q, 26};
|
||||
constexpr RegisterA64 q27{KindA64::q, 27};
|
||||
constexpr RegisterA64 q28{KindA64::q, 28};
|
||||
constexpr RegisterA64 q29{KindA64::q, 29};
|
||||
constexpr RegisterA64 q30{KindA64::q, 30};
|
||||
constexpr RegisterA64 q31{KindA64::q, 31};
|
||||
|
||||
} // namespace A64
|
||||
} // namespace CodeGen
|
||||
} // namespace Luau
|
||||
|
@ -21,8 +21,9 @@ static_assert(sizeof(textForCondition) / sizeof(textForCondition[0]) == size_t(C
|
||||
|
||||
const unsigned kMaxAlign = 32;
|
||||
|
||||
AssemblyBuilderA64::AssemblyBuilderA64(bool logText)
|
||||
AssemblyBuilderA64::AssemblyBuilderA64(bool logText, unsigned int features)
|
||||
: logText(logText)
|
||||
, features(features)
|
||||
{
|
||||
data.resize(4096);
|
||||
dataPos = data.size(); // data is filled backwards
|
||||
@ -39,6 +40,9 @@ AssemblyBuilderA64::~AssemblyBuilderA64()
|
||||
|
||||
void AssemblyBuilderA64::mov(RegisterA64 dst, RegisterA64 src)
|
||||
{
|
||||
LUAU_ASSERT(dst.kind == KindA64::w || dst.kind == KindA64::x || dst == sp);
|
||||
LUAU_ASSERT(dst.kind == src.kind || (dst.kind == KindA64::x && src == sp) || (dst == sp && src.kind == KindA64::x));
|
||||
|
||||
if (dst == sp || src == sp)
|
||||
placeR1("mov", dst, src, 0b00'100010'0'000000000000);
|
||||
else
|
||||
@ -115,6 +119,13 @@ void AssemblyBuilderA64::cmp(RegisterA64 src1, uint16_t src2)
|
||||
placeI12("cmp", dst, src1, src2, 0b11'10001);
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::csel(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2, ConditionA64 cond)
|
||||
{
|
||||
LUAU_ASSERT(dst.kind == KindA64::x || dst.kind == KindA64::w);
|
||||
|
||||
placeCS("csel", dst, src1, src2, cond, 0b11010'10'0, 0b00);
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::and_(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2)
|
||||
{
|
||||
placeSR3("and", dst, src1, src2, 0b00'01010);
|
||||
@ -157,54 +168,76 @@ void AssemblyBuilderA64::ror(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2
|
||||
|
||||
void AssemblyBuilderA64::clz(RegisterA64 dst, RegisterA64 src)
|
||||
{
|
||||
LUAU_ASSERT(dst.kind == KindA64::w || dst.kind == KindA64::x);
|
||||
LUAU_ASSERT(dst.kind == src.kind);
|
||||
|
||||
placeR1("clz", dst, src, 0b10'11010110'00000'00010'0);
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::rbit(RegisterA64 dst, RegisterA64 src)
|
||||
{
|
||||
LUAU_ASSERT(dst.kind == KindA64::w || dst.kind == KindA64::x);
|
||||
LUAU_ASSERT(dst.kind == src.kind);
|
||||
|
||||
placeR1("rbit", dst, src, 0b10'11010110'00000'0000'00);
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::ldr(RegisterA64 dst, AddressA64 src)
|
||||
{
|
||||
LUAU_ASSERT(dst.kind == KindA64::x || dst.kind == KindA64::w);
|
||||
LUAU_ASSERT(dst.kind == KindA64::x || dst.kind == KindA64::w || dst.kind == KindA64::d || dst.kind == KindA64::q);
|
||||
|
||||
placeA("ldr", dst, src, 0b11100001, 0b10 | uint8_t(dst.kind == KindA64::x));
|
||||
switch (dst.kind)
|
||||
{
|
||||
case KindA64::w:
|
||||
placeA("ldr", dst, src, 0b11100001, 0b10, 2);
|
||||
break;
|
||||
case KindA64::x:
|
||||
placeA("ldr", dst, src, 0b11100001, 0b11, 3);
|
||||
break;
|
||||
case KindA64::d:
|
||||
placeA("ldr", dst, src, 0b11110001, 0b11, 3);
|
||||
break;
|
||||
case KindA64::q:
|
||||
placeA("ldr", dst, src, 0b11110011, 0b00, 4);
|
||||
break;
|
||||
case KindA64::none:
|
||||
LUAU_ASSERT(!"Unexpected register kind");
|
||||
}
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::ldrb(RegisterA64 dst, AddressA64 src)
|
||||
{
|
||||
LUAU_ASSERT(dst.kind == KindA64::w);
|
||||
|
||||
placeA("ldrb", dst, src, 0b11100001, 0b00);
|
||||
placeA("ldrb", dst, src, 0b11100001, 0b00, 2);
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::ldrh(RegisterA64 dst, AddressA64 src)
|
||||
{
|
||||
LUAU_ASSERT(dst.kind == KindA64::w);
|
||||
|
||||
placeA("ldrh", dst, src, 0b11100001, 0b01);
|
||||
placeA("ldrh", dst, src, 0b11100001, 0b01, 2);
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::ldrsb(RegisterA64 dst, AddressA64 src)
|
||||
{
|
||||
LUAU_ASSERT(dst.kind == KindA64::x || dst.kind == KindA64::w);
|
||||
|
||||
placeA("ldrsb", dst, src, 0b11100010 | uint8_t(dst.kind == KindA64::w), 0b00);
|
||||
placeA("ldrsb", dst, src, 0b11100010 | uint8_t(dst.kind == KindA64::w), 0b00, 0);
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::ldrsh(RegisterA64 dst, AddressA64 src)
|
||||
{
|
||||
LUAU_ASSERT(dst.kind == KindA64::x || dst.kind == KindA64::w);
|
||||
|
||||
placeA("ldrsh", dst, src, 0b11100010 | uint8_t(dst.kind == KindA64::w), 0b01);
|
||||
placeA("ldrsh", dst, src, 0b11100010 | uint8_t(dst.kind == KindA64::w), 0b01, 1);
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::ldrsw(RegisterA64 dst, AddressA64 src)
|
||||
{
|
||||
LUAU_ASSERT(dst.kind == KindA64::x);
|
||||
|
||||
placeA("ldrsw", dst, src, 0b11100010, 0b10);
|
||||
placeA("ldrsw", dst, src, 0b11100010, 0b10, 2);
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::ldp(RegisterA64 dst1, RegisterA64 dst2, AddressA64 src)
|
||||
@ -212,28 +245,44 @@ void AssemblyBuilderA64::ldp(RegisterA64 dst1, RegisterA64 dst2, AddressA64 src)
|
||||
LUAU_ASSERT(dst1.kind == KindA64::x || dst1.kind == KindA64::w);
|
||||
LUAU_ASSERT(dst1.kind == dst2.kind);
|
||||
|
||||
placeP("ldp", dst1, dst2, src, 0b101'0'010'1, 0b10 | uint8_t(dst1.kind == KindA64::x));
|
||||
placeP("ldp", dst1, dst2, src, 0b101'0'010'1, uint8_t(dst1.kind == KindA64::x) << 1, dst1.kind == KindA64::x ? 3 : 2);
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::str(RegisterA64 src, AddressA64 dst)
|
||||
{
|
||||
LUAU_ASSERT(src.kind == KindA64::x || src.kind == KindA64::w);
|
||||
LUAU_ASSERT(src.kind == KindA64::x || src.kind == KindA64::w || src.kind == KindA64::d || src.kind == KindA64::q);
|
||||
|
||||
placeA("str", src, dst, 0b11100000, 0b10 | uint8_t(src.kind == KindA64::x));
|
||||
switch (src.kind)
|
||||
{
|
||||
case KindA64::w:
|
||||
placeA("str", src, dst, 0b11100000, 0b10, 2);
|
||||
break;
|
||||
case KindA64::x:
|
||||
placeA("str", src, dst, 0b11100000, 0b11, 3);
|
||||
break;
|
||||
case KindA64::d:
|
||||
placeA("str", src, dst, 0b11110000, 0b11, 3);
|
||||
break;
|
||||
case KindA64::q:
|
||||
placeA("str", src, dst, 0b11110010, 0b00, 4);
|
||||
break;
|
||||
case KindA64::none:
|
||||
LUAU_ASSERT(!"Unexpected register kind");
|
||||
}
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::strb(RegisterA64 src, AddressA64 dst)
|
||||
{
|
||||
LUAU_ASSERT(src.kind == KindA64::w);
|
||||
|
||||
placeA("strb", src, dst, 0b11100000, 0b00);
|
||||
placeA("strb", src, dst, 0b11100000, 0b00, 2);
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::strh(RegisterA64 src, AddressA64 dst)
|
||||
{
|
||||
LUAU_ASSERT(src.kind == KindA64::w);
|
||||
|
||||
placeA("strh", src, dst, 0b11100000, 0b01);
|
||||
placeA("strh", src, dst, 0b11100000, 0b01, 2);
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::stp(RegisterA64 src1, RegisterA64 src2, AddressA64 dst)
|
||||
@ -241,7 +290,7 @@ void AssemblyBuilderA64::stp(RegisterA64 src1, RegisterA64 src2, AddressA64 dst)
|
||||
LUAU_ASSERT(src1.kind == KindA64::x || src1.kind == KindA64::w);
|
||||
LUAU_ASSERT(src1.kind == src2.kind);
|
||||
|
||||
placeP("stp", src1, src2, dst, 0b101'0'010'0, 0b10 | uint8_t(src1.kind == KindA64::x));
|
||||
placeP("stp", src1, src2, dst, 0b101'0'010'0, uint8_t(src1.kind == KindA64::x) << 1, src1.kind == KindA64::x ? 3 : 2);
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::b(Label& label)
|
||||
@ -318,6 +367,145 @@ void AssemblyBuilderA64::adr(RegisterA64 dst, Label& label)
|
||||
placeADR("adr", dst, 0b10000, label);
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::fmov(RegisterA64 dst, RegisterA64 src)
|
||||
{
|
||||
LUAU_ASSERT(dst.kind == KindA64::d && src.kind == KindA64::d);
|
||||
|
||||
placeR1("fmov", dst, src, 0b000'11110'01'1'0000'00'10000);
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::fabs(RegisterA64 dst, RegisterA64 src)
|
||||
{
|
||||
LUAU_ASSERT(dst.kind == KindA64::d && src.kind == KindA64::d);
|
||||
|
||||
placeR1("fabs", dst, src, 0b000'11110'01'1'0000'01'10000);
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::fadd(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2)
|
||||
{
|
||||
LUAU_ASSERT(dst.kind == KindA64::d && src1.kind == KindA64::d && src2.kind == KindA64::d);
|
||||
|
||||
placeR3("fadd", dst, src1, src2, 0b11110'01'1, 0b0010'10);
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::fdiv(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2)
|
||||
{
|
||||
LUAU_ASSERT(dst.kind == KindA64::d && src1.kind == KindA64::d && src2.kind == KindA64::d);
|
||||
|
||||
placeR3("fdiv", dst, src1, src2, 0b11110'01'1, 0b0001'10);
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::fmul(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2)
|
||||
{
|
||||
LUAU_ASSERT(dst.kind == KindA64::d && src1.kind == KindA64::d && src2.kind == KindA64::d);
|
||||
|
||||
placeR3("fmul", dst, src1, src2, 0b11110'01'1, 0b0000'10);
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::fneg(RegisterA64 dst, RegisterA64 src)
|
||||
{
|
||||
LUAU_ASSERT(dst.kind == KindA64::d && src.kind == KindA64::d);
|
||||
|
||||
placeR1("fneg", dst, src, 0b000'11110'01'1'0000'10'10000);
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::fsqrt(RegisterA64 dst, RegisterA64 src)
|
||||
{
|
||||
LUAU_ASSERT(dst.kind == KindA64::d && src.kind == KindA64::d);
|
||||
|
||||
placeR1("fsqrt", dst, src, 0b000'11110'01'1'0000'11'10000);
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::fsub(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2)
|
||||
{
|
||||
LUAU_ASSERT(dst.kind == KindA64::d && src1.kind == KindA64::d && src2.kind == KindA64::d);
|
||||
|
||||
placeR3("fsub", dst, src1, src2, 0b11110'01'1, 0b0011'10);
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::frinta(RegisterA64 dst, RegisterA64 src)
|
||||
{
|
||||
LUAU_ASSERT(dst.kind == KindA64::d && src.kind == KindA64::d);
|
||||
|
||||
placeR1("frinta", dst, src, 0b000'11110'01'1'001'100'10000);
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::frintm(RegisterA64 dst, RegisterA64 src)
|
||||
{
|
||||
LUAU_ASSERT(dst.kind == KindA64::d && src.kind == KindA64::d);
|
||||
|
||||
placeR1("frintm", dst, src, 0b000'11110'01'1'001'010'10000);
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::frintp(RegisterA64 dst, RegisterA64 src)
|
||||
{
|
||||
LUAU_ASSERT(dst.kind == KindA64::d && src.kind == KindA64::d);
|
||||
|
||||
placeR1("frintp", dst, src, 0b000'11110'01'1'001'001'10000);
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::fcvtzs(RegisterA64 dst, RegisterA64 src)
|
||||
{
|
||||
LUAU_ASSERT(dst.kind == KindA64::w || dst.kind == KindA64::x);
|
||||
LUAU_ASSERT(src.kind == KindA64::d);
|
||||
|
||||
placeR1("fcvtzs", dst, src, 0b000'11110'01'1'11'000'000000);
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::fcvtzu(RegisterA64 dst, RegisterA64 src)
|
||||
{
|
||||
LUAU_ASSERT(dst.kind == KindA64::w || dst.kind == KindA64::x);
|
||||
LUAU_ASSERT(src.kind == KindA64::d);
|
||||
|
||||
placeR1("fcvtzu", dst, src, 0b000'11110'01'1'11'001'000000);
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::scvtf(RegisterA64 dst, RegisterA64 src)
|
||||
{
|
||||
LUAU_ASSERT(dst.kind == KindA64::d);
|
||||
LUAU_ASSERT(src.kind == KindA64::w || src.kind == KindA64::x);
|
||||
|
||||
placeR1("scvtf", dst, src, 0b000'11110'01'1'00'010'000000);
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::ucvtf(RegisterA64 dst, RegisterA64 src)
|
||||
{
|
||||
LUAU_ASSERT(dst.kind == KindA64::d);
|
||||
LUAU_ASSERT(src.kind == KindA64::w || src.kind == KindA64::x);
|
||||
|
||||
placeR1("ucvtf", dst, src, 0b000'11110'01'1'00'011'000000);
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::fjcvtzs(RegisterA64 dst, RegisterA64 src)
|
||||
{
|
||||
LUAU_ASSERT(dst.kind == KindA64::w);
|
||||
LUAU_ASSERT(src.kind == KindA64::d);
|
||||
LUAU_ASSERT(features & Feature_JSCVT);
|
||||
|
||||
placeR1("fjcvtzs", dst, src, 0b000'11110'01'1'11'110'000000);
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::fcmp(RegisterA64 src1, RegisterA64 src2)
|
||||
{
|
||||
LUAU_ASSERT(src1.kind == KindA64::d && src2.kind == KindA64::d);
|
||||
|
||||
placeFCMP("fcmp", src1, src2, 0b11110'01'1, 0b00);
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::fcmpz(RegisterA64 src)
|
||||
{
|
||||
LUAU_ASSERT(src.kind == KindA64::d);
|
||||
|
||||
placeFCMP("fcmp", src, RegisterA64{src.kind, 0}, 0b11110'01'1, 0b01);
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::fcsel(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2, ConditionA64 cond)
|
||||
{
|
||||
LUAU_ASSERT(dst.kind == KindA64::d);
|
||||
|
||||
placeCS("fcsel", dst, src1, src2, cond, 0b11110'01'1, 0b11);
|
||||
}
|
||||
|
||||
bool AssemblyBuilderA64::finalize()
|
||||
{
|
||||
code.resize(codePos - code.data());
|
||||
@ -429,7 +617,7 @@ void AssemblyBuilderA64::placeR3(const char* name, RegisterA64 dst, RegisterA64
|
||||
if (logText)
|
||||
log(name, dst, src1, src2);
|
||||
|
||||
LUAU_ASSERT(dst.kind == KindA64::w || dst.kind == KindA64::x);
|
||||
LUAU_ASSERT(dst.kind == KindA64::w || dst.kind == KindA64::x || dst.kind == KindA64::d);
|
||||
LUAU_ASSERT(dst.kind == src1.kind && dst.kind == src2.kind);
|
||||
|
||||
uint32_t sf = (dst.kind == KindA64::x) ? 0x80000000 : 0;
|
||||
@ -443,10 +631,7 @@ void AssemblyBuilderA64::placeR1(const char* name, RegisterA64 dst, RegisterA64
|
||||
if (logText)
|
||||
log(name, dst, src);
|
||||
|
||||
LUAU_ASSERT(dst.kind == KindA64::w || dst.kind == KindA64::x || dst == sp);
|
||||
LUAU_ASSERT(dst.kind == src.kind || (dst.kind == KindA64::x && src == sp) || (dst == sp && src.kind == KindA64::x));
|
||||
|
||||
uint32_t sf = (dst.kind != KindA64::w) ? 0x80000000 : 0;
|
||||
uint32_t sf = (dst.kind == KindA64::x || src.kind == KindA64::x) ? 0x80000000 : 0;
|
||||
|
||||
place(dst.index | (src.index << 5) | (op << 10) | sf);
|
||||
commit();
|
||||
@ -482,7 +667,7 @@ void AssemblyBuilderA64::placeI16(const char* name, RegisterA64 dst, int src, ui
|
||||
commit();
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::placeA(const char* name, RegisterA64 dst, AddressA64 src, uint8_t op, uint8_t size)
|
||||
void AssemblyBuilderA64::placeA(const char* name, RegisterA64 dst, AddressA64 src, uint8_t op, uint8_t size, int sizelog)
|
||||
{
|
||||
if (logText)
|
||||
log(name, dst, src);
|
||||
@ -490,8 +675,8 @@ void AssemblyBuilderA64::placeA(const char* name, RegisterA64 dst, AddressA64 sr
|
||||
switch (src.kind)
|
||||
{
|
||||
case AddressKindA64::imm:
|
||||
if (src.data >= 0 && src.data % (1 << size) == 0)
|
||||
place(dst.index | (src.base.index << 5) | ((src.data >> size) << 10) | (op << 22) | (1 << 24) | (size << 30));
|
||||
if (src.data >= 0 && (src.data >> sizelog) < 1024 && (src.data & ((1 << sizelog) - 1)) == 0)
|
||||
place(dst.index | (src.base.index << 5) | ((src.data >> sizelog) << 10) | (op << 22) | (1 << 24) | (size << 30));
|
||||
else if (src.data >= -256 && src.data <= 255)
|
||||
place(dst.index | (src.base.index << 5) | ((src.data & ((1 << 9) - 1)) << 12) | (op << 22) | (size << 30));
|
||||
else
|
||||
@ -566,16 +751,45 @@ void AssemblyBuilderA64::placeADR(const char* name, RegisterA64 dst, uint8_t op,
|
||||
log(name, dst, label);
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::placeP(const char* name, RegisterA64 src1, RegisterA64 src2, AddressA64 dst, uint8_t op, uint8_t size)
|
||||
void AssemblyBuilderA64::placeP(const char* name, RegisterA64 src1, RegisterA64 src2, AddressA64 dst, uint8_t op, uint8_t opc, int sizelog)
|
||||
{
|
||||
if (logText)
|
||||
log(name, src1, src2, dst);
|
||||
|
||||
LUAU_ASSERT(dst.kind == AddressKindA64::imm);
|
||||
LUAU_ASSERT(dst.data >= -128 * (1 << size) && dst.data <= 127 * (1 << size));
|
||||
LUAU_ASSERT(dst.data % (1 << size) == 0);
|
||||
LUAU_ASSERT(dst.data >= -128 * (1 << sizelog) && dst.data <= 127 * (1 << sizelog));
|
||||
LUAU_ASSERT(dst.data % (1 << sizelog) == 0);
|
||||
|
||||
place(src1.index | (dst.base.index << 5) | (src2.index << 10) | (((dst.data >> size) & 127) << 15) | (op << 22) | (size << 31));
|
||||
place(src1.index | (dst.base.index << 5) | (src2.index << 10) | (((dst.data >> sizelog) & 127) << 15) | (op << 22) | (opc << 30));
|
||||
commit();
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::placeCS(const char* name, RegisterA64 dst, RegisterA64 src1, RegisterA64 src2, ConditionA64 cond, uint8_t op, uint8_t opc)
|
||||
{
|
||||
if (logText)
|
||||
log(name, dst, src1, src2, cond);
|
||||
|
||||
LUAU_ASSERT(dst.kind == src1.kind && dst.kind == src2.kind);
|
||||
|
||||
uint32_t sf = (dst.kind == KindA64::x) ? 0x80000000 : 0;
|
||||
|
||||
place(dst.index | (src1.index << 5) | (opc << 10) | (codeForCondition[int(cond)] << 12) | (src2.index << 16) | (op << 21) | sf);
|
||||
commit();
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::placeFCMP(const char* name, RegisterA64 src1, RegisterA64 src2, uint8_t op, uint8_t opc)
|
||||
{
|
||||
if (logText)
|
||||
{
|
||||
if (opc)
|
||||
log(name, src1, 0);
|
||||
else
|
||||
log(name, src1, src2);
|
||||
}
|
||||
|
||||
LUAU_ASSERT(src1.kind == src2.kind);
|
||||
|
||||
place((opc << 3) | (src1.index << 5) | (0b1000 << 10) | (src2.index << 16) | (op << 21));
|
||||
commit();
|
||||
}
|
||||
|
||||
@ -747,6 +961,19 @@ void AssemblyBuilderA64::log(const char* opcode, Label label)
|
||||
logAppend(" %-12s.L%d\n", opcode, label.id);
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::log(const char* opcode, RegisterA64 dst, RegisterA64 src1, RegisterA64 src2, ConditionA64 cond)
|
||||
{
|
||||
logAppend(" %-12s", opcode);
|
||||
log(dst);
|
||||
text.append(",");
|
||||
log(src1);
|
||||
text.append(",");
|
||||
log(src2);
|
||||
text.append(",");
|
||||
text.append(textForCondition[int(cond)] + 2); // skip b.
|
||||
text.append("\n");
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::log(Label label)
|
||||
{
|
||||
logAppend(".L%d:\n", label.id);
|
||||
@ -770,6 +997,14 @@ void AssemblyBuilderA64::log(RegisterA64 reg)
|
||||
logAppend("x%d", reg.index);
|
||||
break;
|
||||
|
||||
case KindA64::d:
|
||||
logAppend("d%d", reg.index);
|
||||
break;
|
||||
|
||||
case KindA64::q:
|
||||
logAppend("q%d", reg.index);
|
||||
break;
|
||||
|
||||
case KindA64::none:
|
||||
if (reg.index == 31)
|
||||
text.append("sp");
|
||||
|
@ -71,9 +71,9 @@ static ABIX64 getCurrentX64ABI()
|
||||
#endif
|
||||
}
|
||||
|
||||
AssemblyBuilderX64::AssemblyBuilderX64(bool logText)
|
||||
AssemblyBuilderX64::AssemblyBuilderX64(bool logText, ABIX64 abi)
|
||||
: logText(logText)
|
||||
, abi(getCurrentX64ABI())
|
||||
, abi(abi)
|
||||
{
|
||||
data.resize(4096);
|
||||
dataPos = data.size(); // data is filled backwards
|
||||
@ -83,6 +83,11 @@ AssemblyBuilderX64::AssemblyBuilderX64(bool logText)
|
||||
codeEnd = code.data() + code.size();
|
||||
}
|
||||
|
||||
AssemblyBuilderX64::AssemblyBuilderX64(bool logText)
|
||||
: AssemblyBuilderX64(logText, getCurrentX64ABI())
|
||||
{
|
||||
}
|
||||
|
||||
AssemblyBuilderX64::~AssemblyBuilderX64()
|
||||
{
|
||||
LUAU_ASSERT(finalized);
|
||||
|
@ -43,6 +43,12 @@
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(__aarch64__)
|
||||
#ifdef __APPLE__
|
||||
#include <sys/sysctl.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(DebugCodegenNoOpt, false)
|
||||
|
||||
namespace Luau
|
||||
@ -209,7 +215,7 @@ static void lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction&
|
||||
}
|
||||
}
|
||||
|
||||
[[maybe_unused]] static void lowerIr(
|
||||
[[maybe_unused]] static bool lowerIr(
|
||||
X64::AssemblyBuilderX64& build, IrBuilder& ir, NativeState& data, ModuleHelpers& helpers, Proto* proto, AssemblyOptions options)
|
||||
{
|
||||
constexpr uint32_t kFunctionAlignment = 32;
|
||||
@ -221,31 +227,21 @@ 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;
|
||||
}
|
||||
|
||||
[[maybe_unused]] static void lowerIr(
|
||||
[[maybe_unused]] static bool lowerIr(
|
||||
A64::AssemblyBuilderA64& build, IrBuilder& ir, NativeState& data, ModuleHelpers& helpers, Proto* proto, AssemblyOptions options)
|
||||
{
|
||||
if (A64::IrLoweringA64::canLower(ir.function))
|
||||
{
|
||||
if (!A64::IrLoweringA64::canLower(ir.function))
|
||||
return false;
|
||||
|
||||
A64::IrLoweringA64 lowering(build, helpers, data, proto, ir.function);
|
||||
|
||||
lowerImpl(build, lowering, ir.function, proto->bytecodeid, options);
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: This is only needed while we don't support all IR opcodes
|
||||
// When we can't translate some parts of the function, we instead encode a dummy assembly sequence that hands off control to VM
|
||||
// In the future we could return nullptr from assembleFunction and handle it because there may be other reasons for why we refuse to assemble.
|
||||
Label start = build.setLabel();
|
||||
|
||||
build.mov(A64::x0, 1); // finish function in VM
|
||||
build.ldr(A64::x1, A64::mem(A64::rNativeContext, offsetof(NativeContext, gateExit)));
|
||||
build.br(A64::x1);
|
||||
|
||||
for (int i = 0; i < proto->sizecode; i++)
|
||||
ir.function.bcMapping[i].asmLocation = build.getLabelOffset(start);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename AssemblyBuilder>
|
||||
@ -289,7 +285,13 @@ static NativeProto* assembleFunction(AssemblyBuilder& build, NativeState& data,
|
||||
constPropInBlockChains(ir);
|
||||
}
|
||||
|
||||
lowerIr(build, ir, data, helpers, proto, options);
|
||||
if (!lowerIr(build, ir, data, helpers, proto, options))
|
||||
{
|
||||
if (build.logText)
|
||||
build.logAppend("; skipping (can't lower)\n\n");
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (build.logText)
|
||||
build.logAppend("\n");
|
||||
@ -345,6 +347,22 @@ static void onSetBreakpoint(lua_State* L, Proto* proto, int instruction)
|
||||
LUAU_ASSERT(!"native breakpoints are not implemented");
|
||||
}
|
||||
|
||||
#if defined(__aarch64__)
|
||||
static unsigned int getCpuFeaturesA64()
|
||||
{
|
||||
unsigned int result = 0;
|
||||
|
||||
#ifdef __APPLE__
|
||||
int jscvt = 0;
|
||||
size_t jscvtLen = sizeof(jscvt);
|
||||
if (sysctlbyname("hw.optional.arm.FEAT_JSCVT", &jscvt, &jscvtLen, nullptr, 0) == 0 && jscvt == 1)
|
||||
result |= A64::Feature_JSCVT;
|
||||
#endif
|
||||
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool isSupported()
|
||||
{
|
||||
#if !LUA_CUSTOM_EXECUTION
|
||||
@ -374,8 +392,20 @@ bool isSupported()
|
||||
|
||||
return true;
|
||||
#elif defined(__aarch64__)
|
||||
if (LUA_EXTRA_SIZE != 1)
|
||||
return false;
|
||||
|
||||
if (sizeof(TValue) != 16)
|
||||
return false;
|
||||
|
||||
if (sizeof(LuaNode) != 32)
|
||||
return false;
|
||||
|
||||
// TODO: A64 codegen does not generate correct unwind info at the moment so it requires longjmp instead of C++ exceptions
|
||||
return bool(LUA_USE_LONGJMP);
|
||||
if (!LUA_USE_LONGJMP)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
@ -447,7 +477,7 @@ void compile(lua_State* L, int idx)
|
||||
return;
|
||||
|
||||
#if defined(__aarch64__)
|
||||
A64::AssemblyBuilderA64 build(/* logText= */ false);
|
||||
A64::AssemblyBuilderA64 build(/* logText= */ false, getCpuFeaturesA64());
|
||||
#else
|
||||
X64::AssemblyBuilderX64 build(/* logText= */ false);
|
||||
#endif
|
||||
@ -470,10 +500,15 @@ void compile(lua_State* L, int idx)
|
||||
// Skip protos that have been compiled during previous invocations of CodeGen::compile
|
||||
for (Proto* p : protos)
|
||||
if (p && getProtoExecData(p) == nullptr)
|
||||
results.push_back(assembleFunction(build, *data, helpers, p, {}));
|
||||
if (NativeProto* np = assembleFunction(build, *data, helpers, p, {}))
|
||||
results.push_back(np);
|
||||
|
||||
build.finalize();
|
||||
|
||||
// If no functions were assembled, we don't need to allocate/copy executable pages for helpers
|
||||
if (results.empty())
|
||||
return;
|
||||
|
||||
uint8_t* nativeData = nullptr;
|
||||
size_t sizeNativeData = 0;
|
||||
uint8_t* codeStart = nullptr;
|
||||
@ -507,7 +542,7 @@ std::string getAssembly(lua_State* L, int idx, AssemblyOptions options)
|
||||
const TValue* func = luaA_toobject(L, idx);
|
||||
|
||||
#if defined(__aarch64__)
|
||||
A64::AssemblyBuilderA64 build(/* logText= */ options.includeAssembly);
|
||||
A64::AssemblyBuilderA64 build(/* logText= */ options.includeAssembly, getCpuFeaturesA64());
|
||||
#else
|
||||
X64::AssemblyBuilderX64 build(/* logText= */ options.includeAssembly);
|
||||
#endif
|
||||
@ -527,10 +562,8 @@ std::string getAssembly(lua_State* L, int idx, AssemblyOptions options)
|
||||
|
||||
for (Proto* p : protos)
|
||||
if (p)
|
||||
{
|
||||
NativeProto* nativeProto = assembleFunction(build, data, helpers, p, options);
|
||||
destroyNativeProto(nativeProto);
|
||||
}
|
||||
if (NativeProto* np = assembleFunction(build, data, helpers, p, options))
|
||||
destroyNativeProto(np);
|
||||
|
||||
build.finalize();
|
||||
|
||||
|
@ -100,6 +100,16 @@ void assembleHelpers(AssemblyBuilderA64& build, ModuleHelpers& helpers)
|
||||
build.logAppend("; exitNoContinueVm\n");
|
||||
helpers.exitNoContinueVm = build.setLabel();
|
||||
emitExit(build, /* continueInVm */ false);
|
||||
|
||||
if (build.logText)
|
||||
build.logAppend("; reentry\n");
|
||||
helpers.reentry = build.setLabel();
|
||||
emitReentry(build, helpers);
|
||||
|
||||
if (build.logText)
|
||||
build.logAppend("; interrupt\n");
|
||||
helpers.interrupt = build.setLabel();
|
||||
emitInterrupt(build);
|
||||
}
|
||||
|
||||
} // namespace A64
|
||||
|
@ -126,7 +126,89 @@ void callEpilogC(lua_State* L, int nresults, int n)
|
||||
L->top = (nresults == LUA_MULTRET) ? res : cip->top;
|
||||
}
|
||||
|
||||
const Instruction* returnFallback(lua_State* L, StkId ra, int n)
|
||||
// Extracted as-is from lvmexecute.cpp with the exception of control flow (reentry) and removed interrupts/savedpc
|
||||
Closure* callFallback(lua_State* L, StkId ra, StkId argtop, int nresults)
|
||||
{
|
||||
// slow-path: not a function call
|
||||
if (LUAU_UNLIKELY(!ttisfunction(ra)))
|
||||
{
|
||||
luaV_tryfuncTM(L, ra);
|
||||
argtop++; // __call adds an extra self
|
||||
}
|
||||
|
||||
Closure* ccl = clvalue(ra);
|
||||
|
||||
CallInfo* ci = incr_ci(L);
|
||||
ci->func = ra;
|
||||
ci->base = ra + 1;
|
||||
ci->top = argtop + ccl->stacksize; // note: technically UB since we haven't reallocated the stack yet
|
||||
ci->savedpc = NULL;
|
||||
ci->flags = 0;
|
||||
ci->nresults = nresults;
|
||||
|
||||
L->base = ci->base;
|
||||
L->top = argtop;
|
||||
|
||||
// note: this reallocs stack, but we don't need to VM_PROTECT this
|
||||
// this is because we're going to modify base/savedpc manually anyhow
|
||||
// crucially, we can't use ra/argtop after this line
|
||||
luaD_checkstack(L, ccl->stacksize);
|
||||
|
||||
LUAU_ASSERT(ci->top <= L->stack_last);
|
||||
|
||||
if (!ccl->isC)
|
||||
{
|
||||
Proto* p = ccl->l.p;
|
||||
|
||||
// fill unused parameters with nil
|
||||
StkId argi = L->top;
|
||||
StkId argend = L->base + p->numparams;
|
||||
while (argi < argend)
|
||||
setnilvalue(argi++); // complete missing arguments
|
||||
L->top = p->is_vararg ? argi : ci->top;
|
||||
|
||||
// keep executing new function
|
||||
ci->savedpc = p->code;
|
||||
return ccl;
|
||||
}
|
||||
else
|
||||
{
|
||||
lua_CFunction func = ccl->c.f;
|
||||
int n = func(L);
|
||||
|
||||
// yield
|
||||
if (n < 0)
|
||||
return NULL;
|
||||
|
||||
// ci is our callinfo, cip is our parent
|
||||
CallInfo* ci = L->ci;
|
||||
CallInfo* cip = ci - 1;
|
||||
|
||||
// copy return values into parent stack (but only up to nresults!), fill the rest with nil
|
||||
// note: in MULTRET context nresults starts as -1 so i != 0 condition never activates intentionally
|
||||
StkId res = ci->func;
|
||||
StkId vali = L->top - n;
|
||||
StkId valend = L->top;
|
||||
|
||||
int i;
|
||||
for (i = nresults; i != 0 && vali < valend; i--)
|
||||
setobj2s(L, res++, vali++);
|
||||
while (i-- > 0)
|
||||
setnilvalue(res++);
|
||||
|
||||
// pop the stack frame
|
||||
L->ci = cip;
|
||||
L->base = cip->base;
|
||||
L->top = (nresults == LUA_MULTRET) ? res : cip->top;
|
||||
|
||||
// keep executing current function
|
||||
LUAU_ASSERT(isLua(cip));
|
||||
return clvalue(cip->func);
|
||||
}
|
||||
}
|
||||
|
||||
// Extracted as-is from lvmexecute.cpp with the exception of control flow (reentry) and removed interrupts
|
||||
Closure* returnFallback(lua_State* L, StkId ra, int n)
|
||||
{
|
||||
// ci is our callinfo, cip is our parent
|
||||
CallInfo* ci = L->ci;
|
||||
@ -159,8 +241,9 @@ const Instruction* returnFallback(lua_State* L, StkId ra, int n)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// keep executing new function
|
||||
LUAU_ASSERT(isLua(cip));
|
||||
return cip->savedpc;
|
||||
return clvalue(cip->func);
|
||||
}
|
||||
|
||||
} // namespace CodeGen
|
||||
|
@ -16,7 +16,8 @@ void forgPrepXnextFallback(lua_State* L, TValue* ra, int pc);
|
||||
Closure* callProlog(lua_State* L, TValue* ra, StkId argtop, int nresults);
|
||||
void callEpilogC(lua_State* L, int nresults, int n);
|
||||
|
||||
const Instruction* returnFallback(lua_State* L, StkId ra, int n);
|
||||
Closure* callFallback(lua_State* L, StkId ra, StkId argtop, int nresults);
|
||||
Closure* returnFallback(lua_State* L, StkId ra, int n);
|
||||
|
||||
} // namespace CodeGen
|
||||
} // namespace Luau
|
||||
|
@ -3,9 +3,10 @@
|
||||
|
||||
#include "Luau/AssemblyBuilderX64.h"
|
||||
#include "Luau/Bytecode.h"
|
||||
#include "Luau/IrCallWrapperX64.h"
|
||||
#include "Luau/IrRegAllocX64.h"
|
||||
|
||||
#include "EmitCommonX64.h"
|
||||
#include "IrRegAllocX64.h"
|
||||
#include "NativeState.h"
|
||||
|
||||
#include "lstate.h"
|
||||
@ -19,40 +20,11 @@ namespace CodeGen
|
||||
namespace X64
|
||||
{
|
||||
|
||||
void emitBuiltinMathFloor(IrRegAllocX64& regs, AssemblyBuilderX64& build, int nparams, int ra, int arg, OperandX64 args, int nresults)
|
||||
{
|
||||
ScopedRegX64 tmp{regs, SizeX64::xmmword};
|
||||
build.vroundsd(tmp.reg, tmp.reg, luauRegValue(arg), RoundingModeX64::RoundToNegativeInfinity);
|
||||
build.vmovsd(luauRegValue(ra), tmp.reg);
|
||||
}
|
||||
|
||||
void emitBuiltinMathCeil(IrRegAllocX64& regs, AssemblyBuilderX64& build, int nparams, int ra, int arg, OperandX64 args, int nresults)
|
||||
{
|
||||
ScopedRegX64 tmp{regs, SizeX64::xmmword};
|
||||
build.vroundsd(tmp.reg, tmp.reg, luauRegValue(arg), RoundingModeX64::RoundToPositiveInfinity);
|
||||
build.vmovsd(luauRegValue(ra), tmp.reg);
|
||||
}
|
||||
|
||||
void emitBuiltinMathSqrt(IrRegAllocX64& regs, AssemblyBuilderX64& build, int nparams, int ra, int arg, OperandX64 args, int nresults)
|
||||
{
|
||||
ScopedRegX64 tmp{regs, SizeX64::xmmword};
|
||||
build.vsqrtsd(tmp.reg, tmp.reg, luauRegValue(arg));
|
||||
build.vmovsd(luauRegValue(ra), tmp.reg);
|
||||
}
|
||||
|
||||
void emitBuiltinMathAbs(IrRegAllocX64& regs, AssemblyBuilderX64& build, int nparams, int ra, int arg, OperandX64 args, int nresults)
|
||||
{
|
||||
ScopedRegX64 tmp{regs, SizeX64::xmmword};
|
||||
build.vmovsd(tmp.reg, luauRegValue(arg));
|
||||
build.vandpd(tmp.reg, tmp.reg, build.i64(~(1LL << 63)));
|
||||
build.vmovsd(luauRegValue(ra), tmp.reg);
|
||||
}
|
||||
|
||||
static void emitBuiltinMathSingleArgFunc(IrRegAllocX64& regs, AssemblyBuilderX64& build, int ra, int arg, int32_t offset)
|
||||
{
|
||||
regs.assertAllFree();
|
||||
build.vmovsd(xmm0, luauRegValue(arg));
|
||||
build.call(qword[rNativeContext + offset]);
|
||||
IrCallWrapperX64 callWrap(regs, build);
|
||||
callWrap.addArgument(SizeX64::xmmword, luauRegValue(arg));
|
||||
callWrap.call(qword[rNativeContext + offset]);
|
||||
|
||||
build.vmovsd(luauRegValue(ra), xmm0);
|
||||
}
|
||||
@ -64,20 +36,10 @@ void emitBuiltinMathExp(IrRegAllocX64& regs, AssemblyBuilderX64& build, int npar
|
||||
|
||||
void emitBuiltinMathFmod(IrRegAllocX64& regs, AssemblyBuilderX64& build, int nparams, int ra, int arg, OperandX64 args, int nresults)
|
||||
{
|
||||
regs.assertAllFree();
|
||||
build.vmovsd(xmm0, luauRegValue(arg));
|
||||
build.vmovsd(xmm1, qword[args + offsetof(TValue, value)]);
|
||||
build.call(qword[rNativeContext + offsetof(NativeContext, libm_fmod)]);
|
||||
|
||||
build.vmovsd(luauRegValue(ra), xmm0);
|
||||
}
|
||||
|
||||
void emitBuiltinMathPow(IrRegAllocX64& regs, AssemblyBuilderX64& build, int nparams, int ra, int arg, OperandX64 args, int nresults)
|
||||
{
|
||||
regs.assertAllFree();
|
||||
build.vmovsd(xmm0, luauRegValue(arg));
|
||||
build.vmovsd(xmm1, qword[args + offsetof(TValue, value)]);
|
||||
build.call(qword[rNativeContext + offsetof(NativeContext, libm_pow)]);
|
||||
IrCallWrapperX64 callWrap(regs, build);
|
||||
callWrap.addArgument(SizeX64::xmmword, luauRegValue(arg));
|
||||
callWrap.addArgument(SizeX64::xmmword, qword[args + offsetof(TValue, value)]);
|
||||
callWrap.call(qword[rNativeContext + offsetof(NativeContext, libm_fmod)]);
|
||||
|
||||
build.vmovsd(luauRegValue(ra), xmm0);
|
||||
}
|
||||
@ -129,10 +91,10 @@ void emitBuiltinMathTanh(IrRegAllocX64& regs, AssemblyBuilderX64& build, int npa
|
||||
|
||||
void emitBuiltinMathAtan2(IrRegAllocX64& regs, AssemblyBuilderX64& build, int nparams, int ra, int arg, OperandX64 args, int nresults)
|
||||
{
|
||||
regs.assertAllFree();
|
||||
build.vmovsd(xmm0, luauRegValue(arg));
|
||||
build.vmovsd(xmm1, qword[args + offsetof(TValue, value)]);
|
||||
build.call(qword[rNativeContext + offsetof(NativeContext, libm_atan2)]);
|
||||
IrCallWrapperX64 callWrap(regs, build);
|
||||
callWrap.addArgument(SizeX64::xmmword, luauRegValue(arg));
|
||||
callWrap.addArgument(SizeX64::xmmword, qword[args + offsetof(TValue, value)]);
|
||||
callWrap.call(qword[rNativeContext + offsetof(NativeContext, libm_atan2)]);
|
||||
|
||||
build.vmovsd(luauRegValue(ra), xmm0);
|
||||
}
|
||||
@ -194,46 +156,23 @@ void emitBuiltinMathLog(IrRegAllocX64& regs, AssemblyBuilderX64& build, int npar
|
||||
|
||||
void emitBuiltinMathLdexp(IrRegAllocX64& regs, AssemblyBuilderX64& build, int nparams, int ra, int arg, OperandX64 args, int nresults)
|
||||
{
|
||||
regs.assertAllFree();
|
||||
build.vmovsd(xmm0, luauRegValue(arg));
|
||||
ScopedRegX64 tmp{regs, SizeX64::qword};
|
||||
build.vcvttsd2si(tmp.reg, qword[args + offsetof(TValue, value)]);
|
||||
|
||||
if (build.abi == ABIX64::Windows)
|
||||
build.vcvttsd2si(rArg2, qword[args + offsetof(TValue, value)]);
|
||||
else
|
||||
build.vcvttsd2si(rArg1, qword[args + offsetof(TValue, value)]);
|
||||
|
||||
build.call(qword[rNativeContext + offsetof(NativeContext, libm_ldexp)]);
|
||||
IrCallWrapperX64 callWrap(regs, build);
|
||||
callWrap.addArgument(SizeX64::xmmword, luauRegValue(arg));
|
||||
callWrap.addArgument(SizeX64::qword, tmp);
|
||||
callWrap.call(qword[rNativeContext + offsetof(NativeContext, libm_ldexp)]);
|
||||
|
||||
build.vmovsd(luauRegValue(ra), xmm0);
|
||||
}
|
||||
|
||||
void emitBuiltinMathRound(IrRegAllocX64& regs, AssemblyBuilderX64& build, int nparams, int ra, int arg, OperandX64 args, int nresults)
|
||||
{
|
||||
ScopedRegX64 tmp0{regs, SizeX64::xmmword};
|
||||
ScopedRegX64 tmp1{regs, SizeX64::xmmword};
|
||||
ScopedRegX64 tmp2{regs, SizeX64::xmmword};
|
||||
|
||||
build.vmovsd(tmp0.reg, luauRegValue(arg));
|
||||
build.vandpd(tmp1.reg, tmp0.reg, build.f64x2(-0.0, -0.0));
|
||||
build.vmovsd(tmp2.reg, build.i64(0x3fdfffffffffffff)); // 0.49999999999999994
|
||||
build.vorpd(tmp1.reg, tmp1.reg, tmp2.reg);
|
||||
build.vaddsd(tmp0.reg, tmp0.reg, tmp1.reg);
|
||||
build.vroundsd(tmp0.reg, tmp0.reg, tmp0.reg, RoundingModeX64::RoundToZero);
|
||||
|
||||
build.vmovsd(luauRegValue(ra), tmp0.reg);
|
||||
}
|
||||
|
||||
void emitBuiltinMathFrexp(IrRegAllocX64& regs, AssemblyBuilderX64& build, int nparams, int ra, int arg, OperandX64 args, int nresults)
|
||||
{
|
||||
regs.assertAllFree();
|
||||
build.vmovsd(xmm0, luauRegValue(arg));
|
||||
|
||||
if (build.abi == ABIX64::Windows)
|
||||
build.lea(rArg2, sTemporarySlot);
|
||||
else
|
||||
build.lea(rArg1, sTemporarySlot);
|
||||
|
||||
build.call(qword[rNativeContext + offsetof(NativeContext, libm_frexp)]);
|
||||
IrCallWrapperX64 callWrap(regs, build);
|
||||
callWrap.addArgument(SizeX64::xmmword, luauRegValue(arg));
|
||||
callWrap.addArgument(SizeX64::qword, sTemporarySlot);
|
||||
callWrap.call(qword[rNativeContext + offsetof(NativeContext, libm_frexp)]);
|
||||
|
||||
build.vmovsd(luauRegValue(ra), xmm0);
|
||||
|
||||
@ -243,15 +182,10 @@ void emitBuiltinMathFrexp(IrRegAllocX64& regs, AssemblyBuilderX64& build, int np
|
||||
|
||||
void emitBuiltinMathModf(IrRegAllocX64& regs, AssemblyBuilderX64& build, int nparams, int ra, int arg, OperandX64 args, int nresults)
|
||||
{
|
||||
regs.assertAllFree();
|
||||
build.vmovsd(xmm0, luauRegValue(arg));
|
||||
|
||||
if (build.abi == ABIX64::Windows)
|
||||
build.lea(rArg2, sTemporarySlot);
|
||||
else
|
||||
build.lea(rArg1, sTemporarySlot);
|
||||
|
||||
build.call(qword[rNativeContext + offsetof(NativeContext, libm_modf)]);
|
||||
IrCallWrapperX64 callWrap(regs, build);
|
||||
callWrap.addArgument(SizeX64::xmmword, luauRegValue(arg));
|
||||
callWrap.addArgument(SizeX64::qword, sTemporarySlot);
|
||||
callWrap.call(qword[rNativeContext + offsetof(NativeContext, libm_modf)]);
|
||||
|
||||
build.vmovsd(xmm1, qword[sTemporarySlot + 0]);
|
||||
build.vmovsd(luauRegValue(ra), xmm1);
|
||||
@ -301,12 +235,10 @@ void emitBuiltinType(IrRegAllocX64& regs, AssemblyBuilderX64& build, int nparams
|
||||
|
||||
void emitBuiltinTypeof(IrRegAllocX64& regs, AssemblyBuilderX64& build, int nparams, int ra, int arg, OperandX64 args, int nresults)
|
||||
{
|
||||
regs.assertAllFree();
|
||||
|
||||
build.mov(rArg1, rState);
|
||||
build.lea(rArg2, luauRegAddress(arg));
|
||||
|
||||
build.call(qword[rNativeContext + offsetof(NativeContext, luaT_objtypenamestr)]);
|
||||
IrCallWrapperX64 callWrap(regs, build);
|
||||
callWrap.addArgument(SizeX64::qword, rState);
|
||||
callWrap.addArgument(SizeX64::qword, luauRegAddress(arg));
|
||||
callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaT_objtypenamestr)]);
|
||||
|
||||
build.mov(luauRegValue(ra), rax);
|
||||
}
|
||||
@ -328,22 +260,18 @@ void emitBuiltin(IrRegAllocX64& regs, AssemblyBuilderX64& build, int bfid, int r
|
||||
case LBF_MATH_MIN:
|
||||
case LBF_MATH_MAX:
|
||||
case LBF_MATH_CLAMP:
|
||||
case LBF_MATH_FLOOR:
|
||||
case LBF_MATH_CEIL:
|
||||
case LBF_MATH_SQRT:
|
||||
case LBF_MATH_POW:
|
||||
case LBF_MATH_ABS:
|
||||
case LBF_MATH_ROUND:
|
||||
// These instructions are fully translated to IR
|
||||
break;
|
||||
case LBF_MATH_FLOOR:
|
||||
return emitBuiltinMathFloor(regs, build, nparams, ra, arg, argsOp, nresults);
|
||||
case LBF_MATH_CEIL:
|
||||
return emitBuiltinMathCeil(regs, build, nparams, ra, arg, argsOp, nresults);
|
||||
case LBF_MATH_SQRT:
|
||||
return emitBuiltinMathSqrt(regs, build, nparams, ra, arg, argsOp, nresults);
|
||||
case LBF_MATH_ABS:
|
||||
return emitBuiltinMathAbs(regs, build, nparams, ra, arg, argsOp, nresults);
|
||||
case LBF_MATH_EXP:
|
||||
return emitBuiltinMathExp(regs, build, nparams, ra, arg, argsOp, nresults);
|
||||
case LBF_MATH_FMOD:
|
||||
return emitBuiltinMathFmod(regs, build, nparams, ra, arg, argsOp, nresults);
|
||||
case LBF_MATH_POW:
|
||||
return emitBuiltinMathPow(regs, build, nparams, ra, arg, argsOp, nresults);
|
||||
case LBF_MATH_ASIN:
|
||||
return emitBuiltinMathAsin(regs, build, nparams, ra, arg, argsOp, nresults);
|
||||
case LBF_MATH_SIN:
|
||||
@ -370,8 +298,6 @@ void emitBuiltin(IrRegAllocX64& regs, AssemblyBuilderX64& build, int bfid, int r
|
||||
return emitBuiltinMathLog(regs, build, nparams, ra, arg, argsOp, nresults);
|
||||
case LBF_MATH_LDEXP:
|
||||
return emitBuiltinMathLdexp(regs, build, nparams, ra, arg, argsOp, nresults);
|
||||
case LBF_MATH_ROUND:
|
||||
return emitBuiltinMathRound(regs, build, nparams, ra, arg, argsOp, nresults);
|
||||
case LBF_MATH_FREXP:
|
||||
return emitBuiltinMathFrexp(regs, build, nparams, ra, arg, argsOp, nresults);
|
||||
case LBF_MATH_MODF:
|
||||
|
@ -20,9 +20,16 @@ constexpr unsigned kOffsetOfInstructionC = 3;
|
||||
// Leaf functions that are placed in every module to perform common instruction sequences
|
||||
struct ModuleHelpers
|
||||
{
|
||||
// A64/X64
|
||||
Label exitContinueVm;
|
||||
Label exitNoContinueVm;
|
||||
|
||||
// X64
|
||||
Label continueCallInVm;
|
||||
|
||||
// A64
|
||||
Label reentry; // x0: closure
|
||||
Label interrupt; // x0: pc offset, x1: return address, x2: interrupt
|
||||
};
|
||||
|
||||
} // namespace CodeGen
|
||||
|
@ -11,6 +11,11 @@ namespace CodeGen
|
||||
namespace A64
|
||||
{
|
||||
|
||||
void emitUpdateBase(AssemblyBuilderA64& build)
|
||||
{
|
||||
build.ldr(rBase, mem(rState, offsetof(lua_State, base)));
|
||||
}
|
||||
|
||||
void emitExit(AssemblyBuilderA64& build, bool continueInVm)
|
||||
{
|
||||
build.mov(x0, continueInVm);
|
||||
@ -18,56 +23,82 @@ void emitExit(AssemblyBuilderA64& build, bool continueInVm)
|
||||
build.br(x1);
|
||||
}
|
||||
|
||||
void emitUpdateBase(AssemblyBuilderA64& build)
|
||||
void emitInterrupt(AssemblyBuilderA64& build)
|
||||
{
|
||||
build.ldr(rBase, mem(rState, offsetof(lua_State, base)));
|
||||
}
|
||||
// x0 = pc offset
|
||||
// x1 = return address in native code
|
||||
// x2 = interrupt
|
||||
|
||||
void emitSetSavedPc(AssemblyBuilderA64& build, int pcpos)
|
||||
{
|
||||
if (pcpos * sizeof(Instruction) <= AssemblyBuilderA64::kMaxImmediate)
|
||||
{
|
||||
build.add(x0, rCode, uint16_t(pcpos * sizeof(Instruction)));
|
||||
}
|
||||
else
|
||||
{
|
||||
build.mov(x0, pcpos * sizeof(Instruction));
|
||||
// Stash return address in rBase; we need to reload rBase anyway
|
||||
build.mov(rBase, x1);
|
||||
|
||||
// Update savedpc; required in case interrupt errors
|
||||
build.add(x0, rCode, x0);
|
||||
}
|
||||
|
||||
build.ldr(x1, mem(rState, offsetof(lua_State, ci)));
|
||||
build.str(x0, mem(x1, offsetof(CallInfo, savedpc)));
|
||||
}
|
||||
|
||||
void emitInterrupt(AssemblyBuilderA64& build, int pcpos)
|
||||
{
|
||||
Label skip;
|
||||
|
||||
build.ldr(x2, mem(rState, offsetof(lua_State, global)));
|
||||
build.ldr(x2, mem(x2, offsetof(global_State, cb.interrupt)));
|
||||
build.cbz(x2, skip);
|
||||
|
||||
emitSetSavedPc(build, pcpos + 1); // uses x0/x1
|
||||
|
||||
// Call interrupt
|
||||
// TODO: This code should be outlined so that it can be shared by multiple interruptible instructions
|
||||
build.mov(x0, rState);
|
||||
build.mov(w1, -1);
|
||||
build.blr(x2);
|
||||
|
||||
// Check if we need to exit
|
||||
Label skip;
|
||||
build.ldrb(w0, mem(rState, offsetof(lua_State, status)));
|
||||
build.cbz(w0, skip);
|
||||
|
||||
// L->ci->savedpc--
|
||||
build.ldr(x0, mem(rState, offsetof(lua_State, ci)));
|
||||
build.ldr(x1, mem(x0, offsetof(CallInfo, savedpc)));
|
||||
build.sub(x1, x1, sizeof(Instruction));
|
||||
build.str(x1, mem(x0, offsetof(CallInfo, savedpc)));
|
||||
// note: recomputing this avoids having to stash x0
|
||||
build.ldr(x1, mem(rState, offsetof(lua_State, ci)));
|
||||
build.ldr(x0, mem(x1, offsetof(CallInfo, savedpc)));
|
||||
build.sub(x0, x0, sizeof(Instruction));
|
||||
build.str(x0, mem(x1, offsetof(CallInfo, savedpc)));
|
||||
|
||||
emitExit(build, /* continueInVm */ false);
|
||||
|
||||
build.setLabel(skip);
|
||||
|
||||
// Return back to caller; rBase has stashed return address
|
||||
build.mov(x0, rBase);
|
||||
|
||||
emitUpdateBase(build); // interrupt may have reallocated stack
|
||||
|
||||
build.br(x0);
|
||||
}
|
||||
|
||||
void emitReentry(AssemblyBuilderA64& build, ModuleHelpers& helpers)
|
||||
{
|
||||
// x0 = closure object to reentry (equal to clvalue(L->ci->func))
|
||||
|
||||
// If the fallback requested an exit, we need to do this right away
|
||||
build.cbz(x0, helpers.exitNoContinueVm);
|
||||
|
||||
emitUpdateBase(build);
|
||||
|
||||
// Need to update state of the current function before we jump away
|
||||
build.ldr(x1, mem(x0, offsetof(Closure, l.p))); // cl->l.p aka proto
|
||||
|
||||
build.mov(rClosure, x0);
|
||||
build.ldr(rConstants, mem(x1, offsetof(Proto, k))); // proto->k
|
||||
build.ldr(rCode, mem(x1, offsetof(Proto, code))); // proto->code
|
||||
|
||||
// Get instruction index from instruction pointer
|
||||
// To get instruction index from instruction pointer, we need to divide byte offset by 4
|
||||
// But we will actually need to scale instruction index by 8 back to byte offset later so it cancels out
|
||||
build.ldr(x2, mem(rState, offsetof(lua_State, ci))); // L->ci
|
||||
build.ldr(x2, mem(x2, offsetof(CallInfo, savedpc))); // L->ci->savedpc
|
||||
build.sub(x2, x2, rCode);
|
||||
build.add(x2, x2, x2); // TODO: this would not be necessary if we supported shifted register offsets in loads
|
||||
|
||||
// We need to check if the new function can be executed natively
|
||||
// TODO: This can be done earlier in the function flow, to reduce the JIT->VM transition penalty
|
||||
build.ldr(x1, mem(x1, offsetofProtoExecData));
|
||||
build.cbz(x1, helpers.exitContinueVm);
|
||||
|
||||
// Get new instruction location and jump to it
|
||||
build.ldr(x1, mem(x1, offsetof(NativeProto, instTargets)));
|
||||
build.ldr(x1, mem(x1, x2));
|
||||
build.br(x1);
|
||||
}
|
||||
|
||||
} // namespace A64
|
||||
|
@ -11,7 +11,7 @@
|
||||
// AArch64 ABI reminder:
|
||||
// Arguments: x0-x7, v0-v7
|
||||
// Return: x0, v0 (or x8 that points to the address of the resulting structure)
|
||||
// Volatile: x9-x14, v16-v31 ("caller-saved", any call may change them)
|
||||
// Volatile: x9-x15, v16-v31 ("caller-saved", any call may change them)
|
||||
// Non-volatile: x19-x28, v8-v15 ("callee-saved", preserved after calls, only bottom half of SIMD registers is preserved!)
|
||||
// Reserved: x16-x18: reserved for linker/platform use; x29: frame pointer (unless omitted); x30: link register; x31: stack pointer
|
||||
|
||||
@ -25,52 +25,27 @@ struct NativeState;
|
||||
namespace A64
|
||||
{
|
||||
|
||||
// Data that is very common to access is placed in non-volatile registers
|
||||
// Data that is very common to access is placed in non-volatile registers:
|
||||
// 1. Constant registers (only loaded during codegen entry)
|
||||
constexpr RegisterA64 rState = x19; // lua_State* L
|
||||
constexpr RegisterA64 rBase = x20; // StkId base
|
||||
constexpr RegisterA64 rNativeContext = x21; // NativeContext* context
|
||||
constexpr RegisterA64 rConstants = x22; // TValue* k
|
||||
constexpr RegisterA64 rClosure = x23; // Closure* cl
|
||||
constexpr RegisterA64 rCode = x24; // Instruction* code
|
||||
constexpr RegisterA64 rNativeContext = x20; // NativeContext* context
|
||||
|
||||
// 2. Frame registers (reloaded when call frame changes; rBase is also reloaded after all calls that may reallocate stack)
|
||||
constexpr RegisterA64 rConstants = x21; // TValue* k
|
||||
constexpr RegisterA64 rClosure = x22; // Closure* cl
|
||||
constexpr RegisterA64 rCode = x23; // Instruction* code
|
||||
constexpr RegisterA64 rBase = x24; // StkId base
|
||||
|
||||
// Native code is as stackless as the interpreter, so we can place some data on the stack once and have it accessible at any point
|
||||
// See CodeGenA64.cpp for layout
|
||||
constexpr unsigned kStackSize = 64; // 8 stashed registers
|
||||
|
||||
inline AddressA64 luauReg(int ri)
|
||||
{
|
||||
return mem(rBase, ri * sizeof(TValue));
|
||||
}
|
||||
|
||||
inline AddressA64 luauRegValue(int ri)
|
||||
{
|
||||
return mem(rBase, ri * sizeof(TValue) + offsetof(TValue, value));
|
||||
}
|
||||
|
||||
inline AddressA64 luauRegTag(int ri)
|
||||
{
|
||||
return mem(rBase, ri * sizeof(TValue) + offsetof(TValue, tt));
|
||||
}
|
||||
|
||||
inline AddressA64 luauConstant(int ki)
|
||||
{
|
||||
return mem(rConstants, ki * sizeof(TValue));
|
||||
}
|
||||
|
||||
inline AddressA64 luauConstantTag(int ki)
|
||||
{
|
||||
return mem(rConstants, ki * sizeof(TValue) + offsetof(TValue, tt));
|
||||
}
|
||||
|
||||
inline AddressA64 luauConstantValue(int ki)
|
||||
{
|
||||
return mem(rConstants, ki * sizeof(TValue) + offsetof(TValue, value));
|
||||
}
|
||||
|
||||
void emitExit(AssemblyBuilderA64& build, bool continueInVm);
|
||||
void emitUpdateBase(AssemblyBuilderA64& build);
|
||||
void emitSetSavedPc(AssemblyBuilderA64& build, int pcpos); // invalidates x0/x1
|
||||
void emitInterrupt(AssemblyBuilderA64& build, int pcpos);
|
||||
|
||||
// TODO: Move these to CodeGenA64 so that they can't be accidentally called during lowering
|
||||
void emitExit(AssemblyBuilderA64& build, bool continueInVm);
|
||||
void emitInterrupt(AssemblyBuilderA64& build);
|
||||
void emitReentry(AssemblyBuilderA64& build, ModuleHelpers& helpers);
|
||||
|
||||
} // namespace A64
|
||||
} // namespace CodeGen
|
||||
|
@ -2,7 +2,9 @@
|
||||
#include "EmitCommonX64.h"
|
||||
|
||||
#include "Luau/AssemblyBuilderX64.h"
|
||||
#include "Luau/IrCallWrapperX64.h"
|
||||
#include "Luau/IrData.h"
|
||||
#include "Luau/IrRegAllocX64.h"
|
||||
|
||||
#include "CustomExecUtils.h"
|
||||
#include "NativeState.h"
|
||||
@ -64,18 +66,19 @@ void jumpOnNumberCmp(AssemblyBuilderX64& build, RegisterX64 tmp, OperandX64 lhs,
|
||||
}
|
||||
}
|
||||
|
||||
void jumpOnAnyCmpFallback(AssemblyBuilderX64& build, int ra, int rb, IrCondition cond, Label& label)
|
||||
void jumpOnAnyCmpFallback(IrRegAllocX64& regs, AssemblyBuilderX64& build, int ra, int rb, IrCondition cond, Label& label)
|
||||
{
|
||||
build.mov(rArg1, rState);
|
||||
build.lea(rArg2, luauRegAddress(ra));
|
||||
build.lea(rArg3, luauRegAddress(rb));
|
||||
IrCallWrapperX64 callWrap(regs, build);
|
||||
callWrap.addArgument(SizeX64::qword, rState);
|
||||
callWrap.addArgument(SizeX64::qword, luauRegAddress(ra));
|
||||
callWrap.addArgument(SizeX64::qword, luauRegAddress(rb));
|
||||
|
||||
if (cond == IrCondition::NotLessEqual || cond == IrCondition::LessEqual)
|
||||
build.call(qword[rNativeContext + offsetof(NativeContext, luaV_lessequal)]);
|
||||
callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaV_lessequal)]);
|
||||
else if (cond == IrCondition::NotLess || cond == IrCondition::Less)
|
||||
build.call(qword[rNativeContext + offsetof(NativeContext, luaV_lessthan)]);
|
||||
callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaV_lessthan)]);
|
||||
else if (cond == IrCondition::NotEqual || cond == IrCondition::Equal)
|
||||
build.call(qword[rNativeContext + offsetof(NativeContext, luaV_equalval)]);
|
||||
callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaV_equalval)]);
|
||||
else
|
||||
LUAU_ASSERT(!"Unsupported condition");
|
||||
|
||||
@ -119,68 +122,66 @@ void convertNumberToIndexOrJump(AssemblyBuilderX64& build, RegisterX64 tmp, Regi
|
||||
build.jcc(ConditionX64::NotZero, label);
|
||||
}
|
||||
|
||||
void callArithHelper(AssemblyBuilderX64& build, int ra, int rb, OperandX64 c, TMS tm)
|
||||
void callArithHelper(IrRegAllocX64& regs, AssemblyBuilderX64& build, int ra, int rb, OperandX64 c, TMS tm)
|
||||
{
|
||||
if (build.abi == ABIX64::Windows)
|
||||
build.mov(sArg5, tm);
|
||||
else
|
||||
build.mov(rArg5, tm);
|
||||
|
||||
build.mov(rArg1, rState);
|
||||
build.lea(rArg2, luauRegAddress(ra));
|
||||
build.lea(rArg3, luauRegAddress(rb));
|
||||
build.lea(rArg4, c);
|
||||
build.call(qword[rNativeContext + offsetof(NativeContext, luaV_doarith)]);
|
||||
IrCallWrapperX64 callWrap(regs, build);
|
||||
callWrap.addArgument(SizeX64::qword, rState);
|
||||
callWrap.addArgument(SizeX64::qword, luauRegAddress(ra));
|
||||
callWrap.addArgument(SizeX64::qword, luauRegAddress(rb));
|
||||
callWrap.addArgument(SizeX64::qword, c);
|
||||
callWrap.addArgument(SizeX64::dword, tm);
|
||||
callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaV_doarith)]);
|
||||
|
||||
emitUpdateBase(build);
|
||||
}
|
||||
|
||||
void callLengthHelper(AssemblyBuilderX64& build, int ra, int rb)
|
||||
void callLengthHelper(IrRegAllocX64& regs, AssemblyBuilderX64& build, int ra, int rb)
|
||||
{
|
||||
build.mov(rArg1, rState);
|
||||
build.lea(rArg2, luauRegAddress(ra));
|
||||
build.lea(rArg3, luauRegAddress(rb));
|
||||
build.call(qword[rNativeContext + offsetof(NativeContext, luaV_dolen)]);
|
||||
IrCallWrapperX64 callWrap(regs, build);
|
||||
callWrap.addArgument(SizeX64::qword, rState);
|
||||
callWrap.addArgument(SizeX64::qword, luauRegAddress(ra));
|
||||
callWrap.addArgument(SizeX64::qword, luauRegAddress(rb));
|
||||
callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaV_dolen)]);
|
||||
|
||||
emitUpdateBase(build);
|
||||
}
|
||||
|
||||
void callPrepareForN(AssemblyBuilderX64& build, int limit, int step, int init)
|
||||
void callPrepareForN(IrRegAllocX64& regs, AssemblyBuilderX64& build, int limit, int step, int init)
|
||||
{
|
||||
build.mov(rArg1, rState);
|
||||
build.lea(rArg2, luauRegAddress(limit));
|
||||
build.lea(rArg3, luauRegAddress(step));
|
||||
build.lea(rArg4, luauRegAddress(init));
|
||||
build.call(qword[rNativeContext + offsetof(NativeContext, luaV_prepareFORN)]);
|
||||
IrCallWrapperX64 callWrap(regs, build);
|
||||
callWrap.addArgument(SizeX64::qword, rState);
|
||||
callWrap.addArgument(SizeX64::qword, luauRegAddress(limit));
|
||||
callWrap.addArgument(SizeX64::qword, luauRegAddress(step));
|
||||
callWrap.addArgument(SizeX64::qword, luauRegAddress(init));
|
||||
callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaV_prepareFORN)]);
|
||||
}
|
||||
|
||||
void callGetTable(AssemblyBuilderX64& build, int rb, OperandX64 c, int ra)
|
||||
void callGetTable(IrRegAllocX64& regs, AssemblyBuilderX64& build, int rb, OperandX64 c, int ra)
|
||||
{
|
||||
build.mov(rArg1, rState);
|
||||
build.lea(rArg2, luauRegAddress(rb));
|
||||
build.lea(rArg3, c);
|
||||
build.lea(rArg4, luauRegAddress(ra));
|
||||
build.call(qword[rNativeContext + offsetof(NativeContext, luaV_gettable)]);
|
||||
IrCallWrapperX64 callWrap(regs, build);
|
||||
callWrap.addArgument(SizeX64::qword, rState);
|
||||
callWrap.addArgument(SizeX64::qword, luauRegAddress(rb));
|
||||
callWrap.addArgument(SizeX64::qword, c);
|
||||
callWrap.addArgument(SizeX64::qword, luauRegAddress(ra));
|
||||
callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaV_gettable)]);
|
||||
|
||||
emitUpdateBase(build);
|
||||
}
|
||||
|
||||
void callSetTable(AssemblyBuilderX64& build, int rb, OperandX64 c, int ra)
|
||||
void callSetTable(IrRegAllocX64& regs, AssemblyBuilderX64& build, int rb, OperandX64 c, int ra)
|
||||
{
|
||||
build.mov(rArg1, rState);
|
||||
build.lea(rArg2, luauRegAddress(rb));
|
||||
build.lea(rArg3, c);
|
||||
build.lea(rArg4, luauRegAddress(ra));
|
||||
build.call(qword[rNativeContext + offsetof(NativeContext, luaV_settable)]);
|
||||
IrCallWrapperX64 callWrap(regs, build);
|
||||
callWrap.addArgument(SizeX64::qword, rState);
|
||||
callWrap.addArgument(SizeX64::qword, luauRegAddress(rb));
|
||||
callWrap.addArgument(SizeX64::qword, c);
|
||||
callWrap.addArgument(SizeX64::qword, luauRegAddress(ra));
|
||||
callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaV_settable)]);
|
||||
|
||||
emitUpdateBase(build);
|
||||
}
|
||||
|
||||
// works for luaC_barriertable, luaC_barrierf
|
||||
static void callBarrierImpl(AssemblyBuilderX64& build, RegisterX64 tmp, RegisterX64 object, int ra, Label& skip, int contextOffset)
|
||||
void checkObjectBarrierConditions(AssemblyBuilderX64& build, RegisterX64 tmp, RegisterX64 object, int ra, Label& skip)
|
||||
{
|
||||
LUAU_ASSERT(tmp != object);
|
||||
|
||||
// iscollectable(ra)
|
||||
build.cmp(luauRegTag(ra), LUA_TSTRING);
|
||||
build.jcc(ConditionX64::Less, skip);
|
||||
@ -193,86 +194,50 @@ static void callBarrierImpl(AssemblyBuilderX64& build, RegisterX64 tmp, Register
|
||||
build.mov(tmp, luauRegValue(ra));
|
||||
build.test(byte[tmp + offsetof(GCheader, marked)], bit2mask(WHITE0BIT, WHITE1BIT));
|
||||
build.jcc(ConditionX64::Zero, skip);
|
||||
}
|
||||
|
||||
// TODO: even with re-ordering we have a chance of failure, we have a task to fix this in the future
|
||||
if (object == rArg3)
|
||||
void callBarrierObject(IrRegAllocX64& regs, AssemblyBuilderX64& build, RegisterX64 object, IrOp objectOp, int ra, Label& skip)
|
||||
{
|
||||
LUAU_ASSERT(tmp != rArg2);
|
||||
ScopedRegX64 tmp{regs, SizeX64::qword};
|
||||
checkObjectBarrierConditions(build, tmp.reg, object, ra, skip);
|
||||
|
||||
if (rArg2 != object)
|
||||
build.mov(rArg2, object);
|
||||
|
||||
if (rArg3 != tmp)
|
||||
build.mov(rArg3, tmp);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (rArg3 != tmp)
|
||||
build.mov(rArg3, tmp);
|
||||
|
||||
if (rArg2 != object)
|
||||
build.mov(rArg2, object);
|
||||
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.mov(rArg1, rState);
|
||||
build.call(qword[rNativeContext + contextOffset]);
|
||||
}
|
||||
|
||||
void callBarrierTable(AssemblyBuilderX64& build, RegisterX64 tmp, RegisterX64 table, int ra, Label& skip)
|
||||
{
|
||||
callBarrierImpl(build, tmp, table, ra, skip, offsetof(NativeContext, luaC_barriertable));
|
||||
}
|
||||
|
||||
void callBarrierObject(AssemblyBuilderX64& build, RegisterX64 tmp, RegisterX64 object, int ra, Label& skip)
|
||||
{
|
||||
callBarrierImpl(build, tmp, object, ra, skip, offsetof(NativeContext, luaC_barrierf));
|
||||
}
|
||||
|
||||
void callBarrierTableFast(AssemblyBuilderX64& build, RegisterX64 table, 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);
|
||||
|
||||
// Argument setup re-ordered to avoid conflicts with table register
|
||||
if (table != rArg2)
|
||||
build.mov(rArg2, table);
|
||||
build.lea(rArg3, addr[rArg2 + offsetof(Table, gclist)]);
|
||||
build.mov(rArg1, rState);
|
||||
build.call(qword[rNativeContext + offsetof(NativeContext, luaC_barrierback)]);
|
||||
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)]);
|
||||
}
|
||||
|
||||
void callCheckGc(AssemblyBuilderX64& build, int pcpos, bool savepc, Label& skip)
|
||||
void callCheckGc(IrRegAllocX64& regs, AssemblyBuilderX64& build, Label& skip)
|
||||
{
|
||||
build.mov(rax, qword[rState + offsetof(lua_State, global)]);
|
||||
build.mov(rdx, qword[rax + offsetof(global_State, totalbytes)]);
|
||||
build.cmp(rdx, qword[rax + offsetof(global_State, GCthreshold)]);
|
||||
{
|
||||
ScopedRegX64 tmp1{regs, SizeX64::qword};
|
||||
ScopedRegX64 tmp2{regs, SizeX64::qword};
|
||||
|
||||
build.mov(tmp1.reg, qword[rState + offsetof(lua_State, global)]);
|
||||
build.mov(tmp2.reg, qword[tmp1.reg + offsetof(global_State, totalbytes)]);
|
||||
build.cmp(tmp2.reg, qword[tmp1.reg + offsetof(global_State, GCthreshold)]);
|
||||
build.jcc(ConditionX64::Below, skip);
|
||||
|
||||
if (savepc)
|
||||
emitSetSavedPc(build, pcpos + 1);
|
||||
|
||||
build.mov(rArg1, rState);
|
||||
build.mov(dwordReg(rArg2), 1);
|
||||
build.call(qword[rNativeContext + offsetof(NativeContext, luaC_step)]);
|
||||
|
||||
emitUpdateBase(build);
|
||||
}
|
||||
|
||||
void callGetFastTmOrFallback(AssemblyBuilderX64& build, RegisterX64 table, TMS tm, Label& fallback)
|
||||
{
|
||||
build.mov(rArg1, qword[table + offsetof(Table, metatable)]);
|
||||
build.test(rArg1, rArg1);
|
||||
build.jcc(ConditionX64::Zero, fallback); // no metatable
|
||||
|
||||
build.test(byte[rArg1 + offsetof(Table, tmcache)], 1 << tm);
|
||||
build.jcc(ConditionX64::NotZero, fallback); // no tag method
|
||||
|
||||
// rArg1 is already prepared
|
||||
build.mov(rArg2, tm);
|
||||
build.mov(rax, qword[rState + offsetof(lua_State, global)]);
|
||||
build.mov(rArg3, qword[rax + offsetof(global_State, tmname) + tm * sizeof(TString*)]);
|
||||
build.call(qword[rNativeContext + offsetof(NativeContext, luaT_gettm)]);
|
||||
IrCallWrapperX64 callWrap(regs, build);
|
||||
callWrap.addArgument(SizeX64::qword, rState);
|
||||
callWrap.addArgument(SizeX64::dword, 1);
|
||||
callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaC_step)]);
|
||||
emitUpdateBase(build);
|
||||
}
|
||||
|
||||
void emitExit(AssemblyBuilderX64& build, bool continueInVm)
|
||||
@ -317,6 +282,8 @@ void emitInterrupt(AssemblyBuilderX64& build, int pcpos)
|
||||
build.mov(dwordReg(rArg2), -1); // function accepts 'int' here and using qword reg would've forced 8 byte constant here
|
||||
build.call(r8);
|
||||
|
||||
emitUpdateBase(build); // interrupt may have reallocated stack
|
||||
|
||||
// Check if we need to exit
|
||||
build.mov(al, byte[rState + offsetof(lua_State, status)]);
|
||||
build.test(al, al);
|
||||
|
@ -27,10 +27,13 @@ namespace CodeGen
|
||||
|
||||
enum class IrCondition : uint8_t;
|
||||
struct NativeState;
|
||||
struct IrOp;
|
||||
|
||||
namespace X64
|
||||
{
|
||||
|
||||
struct IrRegAllocX64;
|
||||
|
||||
// Data that is very common to access is placed in non-volatile registers
|
||||
constexpr RegisterX64 rState = r15; // lua_State* L
|
||||
constexpr RegisterX64 rBase = r14; // StkId base
|
||||
@ -233,21 +236,20 @@ inline void jumpIfNodeKeyNotInExpectedSlot(AssemblyBuilderX64& build, RegisterX6
|
||||
}
|
||||
|
||||
void jumpOnNumberCmp(AssemblyBuilderX64& build, RegisterX64 tmp, OperandX64 lhs, OperandX64 rhs, IrCondition cond, Label& label);
|
||||
void jumpOnAnyCmpFallback(AssemblyBuilderX64& build, int ra, int rb, IrCondition cond, Label& label);
|
||||
void jumpOnAnyCmpFallback(IrRegAllocX64& regs, AssemblyBuilderX64& build, int ra, int rb, IrCondition cond, Label& label);
|
||||
|
||||
void getTableNodeAtCachedSlot(AssemblyBuilderX64& build, RegisterX64 tmp, RegisterX64 node, RegisterX64 table, int pcpos);
|
||||
void convertNumberToIndexOrJump(AssemblyBuilderX64& build, RegisterX64 tmp, RegisterX64 numd, RegisterX64 numi, Label& label);
|
||||
|
||||
void callArithHelper(AssemblyBuilderX64& build, int ra, int rb, OperandX64 c, TMS tm);
|
||||
void callLengthHelper(AssemblyBuilderX64& build, int ra, int rb);
|
||||
void callPrepareForN(AssemblyBuilderX64& build, int limit, int step, int init);
|
||||
void callGetTable(AssemblyBuilderX64& build, int rb, OperandX64 c, int ra);
|
||||
void callSetTable(AssemblyBuilderX64& build, int rb, OperandX64 c, int ra);
|
||||
void callBarrierTable(AssemblyBuilderX64& build, RegisterX64 tmp, RegisterX64 table, int ra, Label& skip);
|
||||
void callBarrierObject(AssemblyBuilderX64& build, RegisterX64 tmp, RegisterX64 object, int ra, Label& skip);
|
||||
void callBarrierTableFast(AssemblyBuilderX64& build, RegisterX64 table, Label& skip);
|
||||
void callCheckGc(AssemblyBuilderX64& build, int pcpos, bool savepc, Label& skip);
|
||||
void callGetFastTmOrFallback(AssemblyBuilderX64& build, RegisterX64 table, TMS tm, Label& fallback);
|
||||
void callArithHelper(IrRegAllocX64& regs, AssemblyBuilderX64& build, int ra, int rb, OperandX64 c, TMS tm);
|
||||
void callLengthHelper(IrRegAllocX64& regs, AssemblyBuilderX64& build, int ra, int rb);
|
||||
void callPrepareForN(IrRegAllocX64& regs, AssemblyBuilderX64& build, int limit, int step, int init);
|
||||
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 emitExit(AssemblyBuilderX64& build, bool continueInVm);
|
||||
void emitUpdateBase(AssemblyBuilderX64& build);
|
||||
|
@ -23,35 +23,50 @@ void emitInstReturn(AssemblyBuilderA64& build, ModuleHelpers& helpers, int ra, i
|
||||
build.ldr(x3, mem(rNativeContext, offsetof(NativeContext, returnFallback)));
|
||||
build.blr(x3);
|
||||
|
||||
// reentry with x0=closure (NULL will trigger exit)
|
||||
build.b(helpers.reentry);
|
||||
}
|
||||
|
||||
void emitInstCall(AssemblyBuilderA64& build, ModuleHelpers& helpers, int ra, int nparams, int nresults)
|
||||
{
|
||||
// argtop = (nparams == LUA_MULTRET) ? L->top : ra + 1 + nparams;
|
||||
if (nparams == LUA_MULTRET)
|
||||
build.ldr(x2, mem(rState, offsetof(lua_State, top)));
|
||||
else
|
||||
build.add(x2, rBase, uint16_t((ra + 1 + nparams) * sizeof(TValue)));
|
||||
|
||||
// callFallback(L, ra, argtop, nresults)
|
||||
build.mov(x0, rState);
|
||||
build.add(x1, rBase, uint16_t(ra * sizeof(TValue)));
|
||||
build.mov(w3, nresults);
|
||||
build.ldr(x4, mem(rNativeContext, offsetof(NativeContext, callFallback)));
|
||||
build.blr(x4);
|
||||
|
||||
// reentry with x0=closure (NULL will trigger exit)
|
||||
build.b(helpers.reentry);
|
||||
}
|
||||
|
||||
void emitInstGetImport(AssemblyBuilderA64& build, int ra, uint32_t aux)
|
||||
{
|
||||
// luaV_getimport(L, cl->env, k, aux, /* propagatenil= */ false)
|
||||
build.mov(x0, rState);
|
||||
build.ldr(x1, mem(rClosure, offsetof(Closure, env)));
|
||||
build.mov(x2, rConstants);
|
||||
build.mov(w3, aux);
|
||||
build.mov(w4, 0);
|
||||
build.ldr(x5, mem(rNativeContext, offsetof(NativeContext, luaV_getimport)));
|
||||
build.blr(x5);
|
||||
|
||||
emitUpdateBase(build);
|
||||
|
||||
// If the fallback requested an exit, we need to do this right away
|
||||
build.cbz(x0, helpers.exitNoContinueVm);
|
||||
// setobj2s(L, ra, L->top - 1)
|
||||
build.ldr(x0, mem(rState, offsetof(lua_State, top)));
|
||||
build.sub(x0, x0, sizeof(TValue));
|
||||
build.ldr(q0, x0);
|
||||
build.str(q0, mem(rBase, ra * sizeof(TValue)));
|
||||
|
||||
// Need to update state of the current function before we jump away
|
||||
build.ldr(x1, mem(rState, offsetof(lua_State, ci))); // L->ci
|
||||
build.ldr(x1, mem(x1, offsetof(CallInfo, func))); // L->ci->func
|
||||
build.ldr(rClosure, mem(x1, offsetof(TValue, value.gc))); // L->ci->func->value.gc aka cl
|
||||
|
||||
build.ldr(x1, mem(rClosure, offsetof(Closure, l.p))); // cl->l.p aka proto
|
||||
|
||||
build.ldr(rConstants, mem(x1, offsetof(Proto, k))); // proto->k
|
||||
build.ldr(rCode, mem(x1, offsetof(Proto, code))); // proto->code
|
||||
|
||||
// Get instruction index from instruction pointer
|
||||
// To get instruction index from instruction pointer, we need to divide byte offset by 4
|
||||
// But we will actually need to scale instruction index by 8 back to byte offset later so it cancels out
|
||||
build.sub(x2, x0, rCode);
|
||||
build.add(x2, x2, x2); // TODO: this would not be necessary if we supported shifted register offsets in loads
|
||||
|
||||
// We need to check if the new function can be executed natively
|
||||
build.ldr(x1, mem(x1, offsetofProtoExecData));
|
||||
build.cbz(x1, helpers.exitContinueVm);
|
||||
|
||||
// Get new instruction location and jump to it
|
||||
build.ldr(x1, mem(x1, offsetof(NativeProto, instTargets)));
|
||||
build.ldr(x1, mem(x1, x2));
|
||||
build.br(x1);
|
||||
// L->top--
|
||||
build.str(x0, mem(rState, offsetof(lua_State, top)));
|
||||
}
|
||||
|
||||
} // namespace A64
|
||||
|
@ -1,6 +1,8 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
namespace CodeGen
|
||||
@ -14,6 +16,8 @@ namespace A64
|
||||
class AssemblyBuilderA64;
|
||||
|
||||
void emitInstReturn(AssemblyBuilderA64& build, ModuleHelpers& helpers, int ra, int n);
|
||||
void emitInstCall(AssemblyBuilderA64& build, ModuleHelpers& helpers, int ra, int nparams, int nresults);
|
||||
void emitInstGetImport(AssemblyBuilderA64& build, int ra, uint32_t aux);
|
||||
|
||||
} // namespace A64
|
||||
} // namespace CodeGen
|
||||
|
@ -2,6 +2,7 @@
|
||||
#include "EmitInstructionX64.h"
|
||||
|
||||
#include "Luau/AssemblyBuilderX64.h"
|
||||
#include "Luau/IrRegAllocX64.h"
|
||||
|
||||
#include "CustomExecUtils.h"
|
||||
#include "EmitCommonX64.h"
|
||||
@ -315,7 +316,7 @@ void emitInstReturn(AssemblyBuilderX64& build, ModuleHelpers& helpers, int ra, i
|
||||
build.jmp(qword[rdx + rax * 2]);
|
||||
}
|
||||
|
||||
void emitInstSetList(AssemblyBuilderX64& build, Label& next, int ra, int rb, int count, uint32_t index)
|
||||
void emitInstSetList(IrRegAllocX64& regs, AssemblyBuilderX64& build, Label& next, int ra, int rb, int count, uint32_t index)
|
||||
{
|
||||
OperandX64 last = index + count - 1;
|
||||
|
||||
@ -346,7 +347,7 @@ void emitInstSetList(AssemblyBuilderX64& build, Label& next, int ra, int rb, int
|
||||
|
||||
Label skipResize;
|
||||
|
||||
RegisterX64 table = rax;
|
||||
RegisterX64 table = regs.takeReg(rax);
|
||||
|
||||
build.mov(table, luauRegValue(ra));
|
||||
|
||||
@ -411,7 +412,7 @@ void emitInstSetList(AssemblyBuilderX64& build, Label& next, int ra, int rb, int
|
||||
build.setLabel(endLoop);
|
||||
}
|
||||
|
||||
callBarrierTableFast(build, table, next);
|
||||
callBarrierTableFast(regs, build, table, {}, next);
|
||||
}
|
||||
|
||||
void emitinstForGLoop(AssemblyBuilderX64& build, int ra, int aux, Label& loopRepeat, Label& loopExit)
|
||||
@ -483,10 +484,8 @@ void emitinstForGLoop(AssemblyBuilderX64& build, int ra, int aux, Label& loopRep
|
||||
build.jcc(ConditionX64::NotZero, loopRepeat);
|
||||
}
|
||||
|
||||
void emitinstForGLoopFallback(AssemblyBuilderX64& build, int pcpos, int ra, int aux, Label& loopRepeat)
|
||||
void emitinstForGLoopFallback(AssemblyBuilderX64& build, int ra, int aux, Label& loopRepeat)
|
||||
{
|
||||
emitSetSavedPc(build, pcpos + 1);
|
||||
|
||||
build.mov(rArg1, rState);
|
||||
build.mov(dwordReg(rArg2), ra);
|
||||
build.mov(dwordReg(rArg3), aux);
|
||||
|
@ -15,12 +15,13 @@ namespace X64
|
||||
{
|
||||
|
||||
class AssemblyBuilderX64;
|
||||
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(AssemblyBuilderX64& build, Label& next, int ra, int rb, int count, uint32_t index);
|
||||
void emitInstSetList(IrRegAllocX64& regs, AssemblyBuilderX64& build, Label& next, int ra, int rb, int count, uint32_t index);
|
||||
void emitinstForGLoop(AssemblyBuilderX64& build, int ra, int aux, Label& loopRepeat, Label& loopExit);
|
||||
void emitinstForGLoopFallback(AssemblyBuilderX64& build, int pcpos, int ra, int aux, Label& loopRepeat);
|
||||
void 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);
|
||||
|
@ -300,17 +300,17 @@ static RegisterSet computeBlockLiveInRegSet(IrFunction& function, const IrBlock&
|
||||
if (function.boolOp(inst.b))
|
||||
capturedRegs.set(inst.a.index, true);
|
||||
break;
|
||||
case IrCmd::LOP_SETLIST:
|
||||
case IrCmd::SETLIST:
|
||||
use(inst.b);
|
||||
useRange(inst.c.index, function.intOp(inst.d));
|
||||
break;
|
||||
case IrCmd::LOP_CALL:
|
||||
case IrCmd::CALL:
|
||||
use(inst.a);
|
||||
useRange(inst.a.index + 1, function.intOp(inst.b));
|
||||
|
||||
defRange(inst.a.index, function.intOp(inst.c));
|
||||
break;
|
||||
case IrCmd::LOP_RETURN:
|
||||
case IrCmd::RETURN:
|
||||
useRange(inst.a.index, function.intOp(inst.b));
|
||||
break;
|
||||
case IrCmd::FASTCALL:
|
||||
@ -341,7 +341,7 @@ static RegisterSet computeBlockLiveInRegSet(IrFunction& function, const IrBlock&
|
||||
if (int count = function.intOp(inst.f); count != -1)
|
||||
defRange(inst.b.index, count);
|
||||
break;
|
||||
case IrCmd::LOP_FORGLOOP:
|
||||
case IrCmd::FORGLOOP:
|
||||
// First register is not used by instruction, we check that it's still 'nil' with CHECK_TAG
|
||||
use(inst.a, 1);
|
||||
use(inst.a, 2);
|
||||
@ -349,26 +349,26 @@ static RegisterSet computeBlockLiveInRegSet(IrFunction& function, const IrBlock&
|
||||
def(inst.a, 2);
|
||||
defRange(inst.a.index + 3, function.intOp(inst.b));
|
||||
break;
|
||||
case IrCmd::LOP_FORGLOOP_FALLBACK:
|
||||
useRange(inst.b.index, 3);
|
||||
case IrCmd::FORGLOOP_FALLBACK:
|
||||
useRange(inst.a.index, 3);
|
||||
|
||||
def(inst.b, 2);
|
||||
defRange(inst.b.index + 3, uint8_t(function.intOp(inst.c))); // ignore most significant bit
|
||||
def(inst.a, 2);
|
||||
defRange(inst.a.index + 3, uint8_t(function.intOp(inst.b))); // ignore most significant bit
|
||||
break;
|
||||
case IrCmd::LOP_FORGPREP_XNEXT_FALLBACK:
|
||||
case IrCmd::FORGPREP_XNEXT_FALLBACK:
|
||||
use(inst.b);
|
||||
break;
|
||||
// A <- B, C
|
||||
case IrCmd::LOP_AND:
|
||||
case IrCmd::LOP_OR:
|
||||
case IrCmd::AND:
|
||||
case IrCmd::OR:
|
||||
use(inst.b);
|
||||
use(inst.c);
|
||||
|
||||
def(inst.a);
|
||||
break;
|
||||
// A <- B
|
||||
case IrCmd::LOP_ANDK:
|
||||
case IrCmd::LOP_ORK:
|
||||
case IrCmd::ANDK:
|
||||
case IrCmd::ORK:
|
||||
use(inst.b);
|
||||
|
||||
def(inst.a);
|
||||
|
@ -135,7 +135,7 @@ void IrBuilder::translateInst(LuauOpcode op, const Instruction* pc, int i)
|
||||
inst(IrCmd::INTERRUPT, constUint(i));
|
||||
inst(IrCmd::SET_SAVEDPC, constUint(i + 1));
|
||||
|
||||
inst(IrCmd::LOP_CALL, vmReg(LUAU_INSN_A(*pc)), constInt(LUAU_INSN_B(*pc) - 1), constInt(LUAU_INSN_C(*pc) - 1));
|
||||
inst(IrCmd::CALL, vmReg(LUAU_INSN_A(*pc)), constInt(LUAU_INSN_B(*pc) - 1), constInt(LUAU_INSN_C(*pc) - 1));
|
||||
|
||||
if (activeFastcallFallback)
|
||||
{
|
||||
@ -149,7 +149,7 @@ void IrBuilder::translateInst(LuauOpcode op, const Instruction* pc, int i)
|
||||
case LOP_RETURN:
|
||||
inst(IrCmd::INTERRUPT, constUint(i));
|
||||
|
||||
inst(IrCmd::LOP_RETURN, vmReg(LUAU_INSN_A(*pc)), constInt(LUAU_INSN_B(*pc) - 1));
|
||||
inst(IrCmd::RETURN, vmReg(LUAU_INSN_A(*pc)), constInt(LUAU_INSN_B(*pc) - 1));
|
||||
break;
|
||||
case LOP_GETTABLE:
|
||||
translateInstGetTable(*this, pc, i);
|
||||
@ -266,7 +266,7 @@ void IrBuilder::translateInst(LuauOpcode op, const Instruction* pc, int i)
|
||||
translateInstDupTable(*this, pc, i);
|
||||
break;
|
||||
case LOP_SETLIST:
|
||||
inst(IrCmd::LOP_SETLIST, constUint(i), vmReg(LUAU_INSN_A(*pc)), vmReg(LUAU_INSN_B(*pc)), constInt(LUAU_INSN_C(*pc) - 1), constUint(pc[1]));
|
||||
inst(IrCmd::SETLIST, constUint(i), vmReg(LUAU_INSN_A(*pc)), vmReg(LUAU_INSN_B(*pc)), constInt(LUAU_INSN_C(*pc) - 1), constUint(pc[1]));
|
||||
break;
|
||||
case LOP_GETUPVAL:
|
||||
translateInstGetUpval(*this, pc, i);
|
||||
@ -347,10 +347,11 @@ void IrBuilder::translateInst(LuauOpcode op, const Instruction* pc, int i)
|
||||
inst(IrCmd::INTERRUPT, constUint(i));
|
||||
loadAndCheckTag(vmReg(ra), LUA_TNIL, fallback);
|
||||
|
||||
inst(IrCmd::LOP_FORGLOOP, vmReg(ra), constInt(aux), loopRepeat, loopExit);
|
||||
inst(IrCmd::FORGLOOP, vmReg(ra), constInt(aux), loopRepeat, loopExit);
|
||||
|
||||
beginBlock(fallback);
|
||||
inst(IrCmd::LOP_FORGLOOP_FALLBACK, constUint(i), vmReg(ra), constInt(aux), loopRepeat, loopExit);
|
||||
inst(IrCmd::SET_SAVEDPC, constUint(i + 1));
|
||||
inst(IrCmd::FORGLOOP_FALLBACK, vmReg(ra), constInt(aux), loopRepeat, loopExit);
|
||||
|
||||
beginBlock(loopExit);
|
||||
}
|
||||
@ -363,19 +364,19 @@ void IrBuilder::translateInst(LuauOpcode op, const Instruction* pc, int i)
|
||||
translateInstForGPrepInext(*this, pc, i);
|
||||
break;
|
||||
case LOP_AND:
|
||||
inst(IrCmd::LOP_AND, vmReg(LUAU_INSN_A(*pc)), vmReg(LUAU_INSN_B(*pc)), vmReg(LUAU_INSN_C(*pc)));
|
||||
inst(IrCmd::AND, vmReg(LUAU_INSN_A(*pc)), vmReg(LUAU_INSN_B(*pc)), vmReg(LUAU_INSN_C(*pc)));
|
||||
break;
|
||||
case LOP_ANDK:
|
||||
inst(IrCmd::LOP_ANDK, vmReg(LUAU_INSN_A(*pc)), vmReg(LUAU_INSN_B(*pc)), vmConst(LUAU_INSN_C(*pc)));
|
||||
inst(IrCmd::ANDK, vmReg(LUAU_INSN_A(*pc)), vmReg(LUAU_INSN_B(*pc)), vmConst(LUAU_INSN_C(*pc)));
|
||||
break;
|
||||
case LOP_OR:
|
||||
inst(IrCmd::LOP_OR, vmReg(LUAU_INSN_A(*pc)), vmReg(LUAU_INSN_B(*pc)), vmReg(LUAU_INSN_C(*pc)));
|
||||
inst(IrCmd::OR, vmReg(LUAU_INSN_A(*pc)), vmReg(LUAU_INSN_B(*pc)), vmReg(LUAU_INSN_C(*pc)));
|
||||
break;
|
||||
case LOP_ORK:
|
||||
inst(IrCmd::LOP_ORK, vmReg(LUAU_INSN_A(*pc)), vmReg(LUAU_INSN_B(*pc)), vmConst(LUAU_INSN_C(*pc)));
|
||||
inst(IrCmd::ORK, vmReg(LUAU_INSN_A(*pc)), vmReg(LUAU_INSN_B(*pc)), vmConst(LUAU_INSN_C(*pc)));
|
||||
break;
|
||||
case LOP_COVERAGE:
|
||||
inst(IrCmd::LOP_COVERAGE, constUint(i));
|
||||
inst(IrCmd::COVERAGE, constUint(i));
|
||||
break;
|
||||
case LOP_GETIMPORT:
|
||||
translateInstGetImport(*this, pc, i);
|
||||
|
400
CodeGen/src/IrCallWrapperX64.cpp
Normal file
400
CodeGen/src/IrCallWrapperX64.cpp
Normal file
@ -0,0 +1,400 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/IrCallWrapperX64.h"
|
||||
|
||||
#include "Luau/AssemblyBuilderX64.h"
|
||||
#include "Luau/IrRegAllocX64.h"
|
||||
|
||||
#include "EmitCommonX64.h"
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
namespace CodeGen
|
||||
{
|
||||
namespace X64
|
||||
{
|
||||
|
||||
static bool sameUnderlyingRegister(RegisterX64 a, RegisterX64 b)
|
||||
{
|
||||
SizeX64 underlyingSizeA = a.size == SizeX64::xmmword ? SizeX64::xmmword : SizeX64::qword;
|
||||
SizeX64 underlyingSizeB = b.size == SizeX64::xmmword ? SizeX64::xmmword : SizeX64::qword;
|
||||
|
||||
return underlyingSizeA == underlyingSizeB && a.index == b.index;
|
||||
}
|
||||
|
||||
IrCallWrapperX64::IrCallWrapperX64(IrRegAllocX64& regs, AssemblyBuilderX64& build, uint32_t instIdx)
|
||||
: regs(regs)
|
||||
, build(build)
|
||||
, instIdx(instIdx)
|
||||
, funcOp(noreg)
|
||||
{
|
||||
gprUses.fill(0);
|
||||
xmmUses.fill(0);
|
||||
}
|
||||
|
||||
void IrCallWrapperX64::addArgument(SizeX64 targetSize, OperandX64 source, IrOp sourceOp)
|
||||
{
|
||||
// Instruction operands rely on current instruction index for lifetime tracking
|
||||
LUAU_ASSERT(instIdx != kInvalidInstIdx || sourceOp.kind == IrOpKind::None);
|
||||
|
||||
LUAU_ASSERT(argCount < kMaxCallArguments);
|
||||
args[argCount++] = {targetSize, source, sourceOp};
|
||||
}
|
||||
|
||||
void IrCallWrapperX64::addArgument(SizeX64 targetSize, ScopedRegX64& scopedReg)
|
||||
{
|
||||
LUAU_ASSERT(argCount < kMaxCallArguments);
|
||||
args[argCount++] = {targetSize, scopedReg.release(), {}};
|
||||
}
|
||||
|
||||
void IrCallWrapperX64::call(const OperandX64& func)
|
||||
{
|
||||
funcOp = func;
|
||||
|
||||
assignTargetRegisters();
|
||||
|
||||
countRegisterUses();
|
||||
|
||||
for (int i = 0; i < argCount; ++i)
|
||||
{
|
||||
CallArgument& arg = args[i];
|
||||
|
||||
// If source is the last use of IrInst, clear the register
|
||||
// Source registers are recorded separately in CallArgument
|
||||
if (arg.sourceOp.kind != IrOpKind::None)
|
||||
{
|
||||
if (IrInst* inst = regs.function.asInstOp(arg.sourceOp))
|
||||
{
|
||||
if (regs.isLastUseReg(*inst, instIdx))
|
||||
inst->regX64 = noreg;
|
||||
}
|
||||
}
|
||||
|
||||
// Immediate values are stored at the end since they are not interfering and target register can still be used temporarily
|
||||
if (arg.source.cat == CategoryX64::imm)
|
||||
{
|
||||
arg.candidate = false;
|
||||
}
|
||||
// Arguments passed through stack can be handled immediately
|
||||
else if (arg.target.cat == CategoryX64::mem)
|
||||
{
|
||||
if (arg.source.cat == CategoryX64::mem)
|
||||
{
|
||||
ScopedRegX64 tmp{regs, arg.target.memSize};
|
||||
|
||||
freeSourceRegisters(arg);
|
||||
|
||||
build.mov(tmp.reg, arg.source);
|
||||
build.mov(arg.target, tmp.reg);
|
||||
}
|
||||
else
|
||||
{
|
||||
freeSourceRegisters(arg);
|
||||
|
||||
build.mov(arg.target, arg.source);
|
||||
}
|
||||
|
||||
arg.candidate = false;
|
||||
}
|
||||
// Skip arguments that are already in their place
|
||||
else if (arg.source.cat == CategoryX64::reg && sameUnderlyingRegister(arg.target.base, arg.source.base))
|
||||
{
|
||||
freeSourceRegisters(arg);
|
||||
|
||||
// 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);
|
||||
else // Otherwise, make sure we won't free it when last source use is completed
|
||||
addRegisterUse(arg.target.base);
|
||||
|
||||
arg.candidate = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Repeat until we run out of arguments to pass
|
||||
while (true)
|
||||
{
|
||||
// Find target argument register that is not an active source
|
||||
if (CallArgument* candidate = findNonInterferingArgument())
|
||||
{
|
||||
// This section is only for handling register targets
|
||||
LUAU_ASSERT(candidate->target.cat == CategoryX64::reg);
|
||||
|
||||
freeSourceRegisters(*candidate);
|
||||
|
||||
LUAU_ASSERT(getRegisterUses(candidate->target.base) == 0);
|
||||
regs.takeReg(candidate->target.base);
|
||||
|
||||
moveToTarget(*candidate);
|
||||
|
||||
candidate->candidate = false;
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < argCount; ++i)
|
||||
LUAU_ASSERT(!args[i].candidate);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle immediate arguments last
|
||||
for (int i = 0; i < argCount; ++i)
|
||||
{
|
||||
CallArgument& arg = args[i];
|
||||
|
||||
if (arg.source.cat == CategoryX64::imm)
|
||||
{
|
||||
if (arg.target.cat == CategoryX64::reg)
|
||||
regs.takeReg(arg.target.base);
|
||||
|
||||
moveToTarget(arg);
|
||||
}
|
||||
}
|
||||
|
||||
// Free registers used in the function call
|
||||
removeRegisterUse(funcOp.base);
|
||||
removeRegisterUse(funcOp.index);
|
||||
|
||||
// Just before the call is made, argument registers are all marked as free in register allocator
|
||||
for (int i = 0; i < argCount; ++i)
|
||||
{
|
||||
CallArgument& arg = args[i];
|
||||
|
||||
if (arg.target.cat == CategoryX64::reg)
|
||||
regs.freeReg(arg.target.base);
|
||||
}
|
||||
|
||||
build.call(funcOp);
|
||||
}
|
||||
|
||||
void IrCallWrapperX64::assignTargetRegisters()
|
||||
{
|
||||
static const std::array<OperandX64, 6> kWindowsGprOrder = {rcx, rdx, r8, r9, addr[rsp + 32], addr[rsp + 40]};
|
||||
static const std::array<OperandX64, 6> kSystemvGprOrder = {rdi, rsi, rdx, rcx, r8, r9};
|
||||
|
||||
const std::array<OperandX64, 6>& gprOrder = build.abi == ABIX64::Windows ? kWindowsGprOrder : kSystemvGprOrder;
|
||||
static const std::array<OperandX64, 4> kXmmOrder = {xmm0, xmm1, xmm2, xmm3}; // Common order for first 4 fp arguments on Windows/SystemV
|
||||
|
||||
int gprPos = 0;
|
||||
int xmmPos = 0;
|
||||
|
||||
for (int i = 0; i < argCount; i++)
|
||||
{
|
||||
CallArgument& arg = args[i];
|
||||
|
||||
if (arg.targetSize == SizeX64::xmmword)
|
||||
{
|
||||
LUAU_ASSERT(size_t(xmmPos) < kXmmOrder.size());
|
||||
arg.target = kXmmOrder[xmmPos++];
|
||||
|
||||
if (build.abi == ABIX64::Windows)
|
||||
gprPos++; // On Windows, gpr/xmm register positions move in sync
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_ASSERT(size_t(gprPos) < gprOrder.size());
|
||||
arg.target = gprOrder[gprPos++];
|
||||
|
||||
if (build.abi == ABIX64::Windows)
|
||||
xmmPos++; // On Windows, gpr/xmm register positions move in sync
|
||||
|
||||
// Keep requested argument size
|
||||
if (arg.target.cat == CategoryX64::reg)
|
||||
arg.target.base.size = arg.targetSize;
|
||||
else if (arg.target.cat == CategoryX64::mem)
|
||||
arg.target.memSize = arg.targetSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IrCallWrapperX64::countRegisterUses()
|
||||
{
|
||||
for (int i = 0; i < argCount; ++i)
|
||||
{
|
||||
addRegisterUse(args[i].source.base);
|
||||
addRegisterUse(args[i].source.index);
|
||||
}
|
||||
|
||||
addRegisterUse(funcOp.base);
|
||||
addRegisterUse(funcOp.index);
|
||||
}
|
||||
|
||||
CallArgument* IrCallWrapperX64::findNonInterferingArgument()
|
||||
{
|
||||
for (int i = 0; i < argCount; ++i)
|
||||
{
|
||||
CallArgument& arg = args[i];
|
||||
|
||||
if (arg.candidate && !interferesWithActiveSources(arg, i) && !interferesWithOperand(funcOp, arg.target.base))
|
||||
return &arg;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool IrCallWrapperX64::interferesWithOperand(const OperandX64& op, RegisterX64 reg) const
|
||||
{
|
||||
return sameUnderlyingRegister(op.base, reg) || sameUnderlyingRegister(op.index, reg);
|
||||
}
|
||||
|
||||
bool IrCallWrapperX64::interferesWithActiveSources(const CallArgument& targetArg, int targetArgIndex) const
|
||||
{
|
||||
for (int i = 0; i < argCount; ++i)
|
||||
{
|
||||
const CallArgument& arg = args[i];
|
||||
|
||||
if (arg.candidate && i != targetArgIndex && interferesWithOperand(arg.source, targetArg.target.base))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IrCallWrapperX64::interferesWithActiveTarget(RegisterX64 sourceReg) const
|
||||
{
|
||||
for (int i = 0; i < argCount; ++i)
|
||||
{
|
||||
const CallArgument& arg = args[i];
|
||||
|
||||
if (arg.candidate && sameUnderlyingRegister(arg.target.base, sourceReg))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void IrCallWrapperX64::moveToTarget(CallArgument& arg)
|
||||
{
|
||||
if (arg.source.cat == CategoryX64::reg)
|
||||
{
|
||||
RegisterX64 source = arg.source.base;
|
||||
|
||||
if (source.size == SizeX64::xmmword)
|
||||
build.vmovsd(arg.target, source, source);
|
||||
else
|
||||
build.mov(arg.target, source);
|
||||
}
|
||||
else if (arg.source.cat == CategoryX64::imm)
|
||||
{
|
||||
build.mov(arg.target, arg.source);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (arg.source.memSize == SizeX64::none)
|
||||
build.lea(arg.target, arg.source);
|
||||
else if (arg.target.base.size == SizeX64::xmmword && arg.source.memSize == SizeX64::xmmword)
|
||||
build.vmovups(arg.target, arg.source);
|
||||
else if (arg.target.base.size == SizeX64::xmmword)
|
||||
build.vmovsd(arg.target, arg.source);
|
||||
else
|
||||
build.mov(arg.target, arg.source);
|
||||
}
|
||||
}
|
||||
|
||||
void IrCallWrapperX64::freeSourceRegisters(CallArgument& arg)
|
||||
{
|
||||
removeRegisterUse(arg.source.base);
|
||||
removeRegisterUse(arg.source.index);
|
||||
}
|
||||
|
||||
void IrCallWrapperX64::renameRegister(RegisterX64& target, RegisterX64 reg, RegisterX64 replacement)
|
||||
{
|
||||
if (sameUnderlyingRegister(target, reg))
|
||||
{
|
||||
addRegisterUse(replacement);
|
||||
removeRegisterUse(target);
|
||||
|
||||
target.index = replacement.index; // Only change index, size is preserved
|
||||
}
|
||||
}
|
||||
|
||||
void IrCallWrapperX64::renameSourceRegisters(RegisterX64 reg, RegisterX64 replacement)
|
||||
{
|
||||
for (int i = 0; i < argCount; ++i)
|
||||
{
|
||||
CallArgument& arg = args[i];
|
||||
|
||||
if (arg.candidate)
|
||||
{
|
||||
renameRegister(arg.source.base, reg, replacement);
|
||||
renameRegister(arg.source.index, reg, replacement);
|
||||
}
|
||||
}
|
||||
|
||||
renameRegister(funcOp.base, reg, replacement);
|
||||
renameRegister(funcOp.index, reg, replacement);
|
||||
}
|
||||
|
||||
RegisterX64 IrCallWrapperX64::findConflictingTarget() const
|
||||
{
|
||||
for (int i = 0; i < argCount; ++i)
|
||||
{
|
||||
const CallArgument& arg = args[i];
|
||||
|
||||
if (arg.candidate)
|
||||
{
|
||||
if (interferesWithActiveTarget(arg.source.base))
|
||||
return arg.source.base;
|
||||
|
||||
if (interferesWithActiveTarget(arg.source.index))
|
||||
return arg.source.index;
|
||||
}
|
||||
}
|
||||
|
||||
if (interferesWithActiveTarget(funcOp.base))
|
||||
return funcOp.base;
|
||||
|
||||
if (interferesWithActiveTarget(funcOp.index))
|
||||
return funcOp.index;
|
||||
|
||||
return noreg;
|
||||
}
|
||||
|
||||
int IrCallWrapperX64::getRegisterUses(RegisterX64 reg) const
|
||||
{
|
||||
return reg.size == SizeX64::xmmword ? xmmUses[reg.index] : (reg.size != SizeX64::none ? gprUses[reg.index] : 0);
|
||||
}
|
||||
|
||||
void IrCallWrapperX64::addRegisterUse(RegisterX64 reg)
|
||||
{
|
||||
if (reg.size == SizeX64::xmmword)
|
||||
xmmUses[reg.index]++;
|
||||
else if (reg.size != SizeX64::none)
|
||||
gprUses[reg.index]++;
|
||||
}
|
||||
|
||||
void IrCallWrapperX64::removeRegisterUse(RegisterX64 reg)
|
||||
{
|
||||
if (reg.size == SizeX64::xmmword)
|
||||
{
|
||||
LUAU_ASSERT(xmmUses[reg.index] != 0);
|
||||
xmmUses[reg.index]--;
|
||||
|
||||
if (xmmUses[reg.index] == 0) // we don't use persistent xmm regs so no need to call shouldFreeRegister
|
||||
regs.freeReg(reg);
|
||||
}
|
||||
else if (reg.size != SizeX64::none)
|
||||
{
|
||||
LUAU_ASSERT(gprUses[reg.index] != 0);
|
||||
gprUses[reg.index]--;
|
||||
|
||||
if (gprUses[reg.index] == 0 && regs.shouldFreeGpr(reg))
|
||||
regs.freeReg(reg);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace X64
|
||||
} // namespace CodeGen
|
||||
} // namespace Luau
|
@ -126,6 +126,16 @@ const char* getCmdName(IrCmd cmd)
|
||||
return "MAX_NUM";
|
||||
case IrCmd::UNM_NUM:
|
||||
return "UNM_NUM";
|
||||
case IrCmd::FLOOR_NUM:
|
||||
return "FLOOR_NUM";
|
||||
case IrCmd::CEIL_NUM:
|
||||
return "CEIL_NUM";
|
||||
case IrCmd::ROUND_NUM:
|
||||
return "ROUND_NUM";
|
||||
case IrCmd::SQRT_NUM:
|
||||
return "SQRT_NUM";
|
||||
case IrCmd::ABS_NUM:
|
||||
return "ABS_NUM";
|
||||
case IrCmd::NOT_ANY:
|
||||
return "NOT_ANY";
|
||||
case IrCmd::JUMP:
|
||||
@ -216,28 +226,28 @@ const char* getCmdName(IrCmd cmd)
|
||||
return "CLOSE_UPVALS";
|
||||
case IrCmd::CAPTURE:
|
||||
return "CAPTURE";
|
||||
case IrCmd::LOP_SETLIST:
|
||||
return "LOP_SETLIST";
|
||||
case IrCmd::LOP_CALL:
|
||||
return "LOP_CALL";
|
||||
case IrCmd::LOP_RETURN:
|
||||
return "LOP_RETURN";
|
||||
case IrCmd::LOP_FORGLOOP:
|
||||
return "LOP_FORGLOOP";
|
||||
case IrCmd::LOP_FORGLOOP_FALLBACK:
|
||||
return "LOP_FORGLOOP_FALLBACK";
|
||||
case IrCmd::LOP_FORGPREP_XNEXT_FALLBACK:
|
||||
return "LOP_FORGPREP_XNEXT_FALLBACK";
|
||||
case IrCmd::LOP_AND:
|
||||
return "LOP_AND";
|
||||
case IrCmd::LOP_ANDK:
|
||||
return "LOP_ANDK";
|
||||
case IrCmd::LOP_OR:
|
||||
return "LOP_OR";
|
||||
case IrCmd::LOP_ORK:
|
||||
return "LOP_ORK";
|
||||
case IrCmd::LOP_COVERAGE:
|
||||
return "LOP_COVERAGE";
|
||||
case IrCmd::SETLIST:
|
||||
return "SETLIST";
|
||||
case IrCmd::CALL:
|
||||
return "CALL";
|
||||
case IrCmd::RETURN:
|
||||
return "RETURN";
|
||||
case IrCmd::FORGLOOP:
|
||||
return "FORGLOOP";
|
||||
case IrCmd::FORGLOOP_FALLBACK:
|
||||
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:
|
||||
return "FALLBACK_GETGLOBAL";
|
||||
case IrCmd::FALLBACK_SETGLOBAL:
|
||||
|
@ -13,6 +13,9 @@
|
||||
|
||||
#include "lstate.h"
|
||||
|
||||
// TODO: Eventually this can go away
|
||||
// #define TRACE
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
namespace CodeGen
|
||||
@ -20,12 +23,67 @@ namespace CodeGen
|
||||
namespace A64
|
||||
{
|
||||
|
||||
#ifdef TRACE
|
||||
struct LoweringStatsA64
|
||||
{
|
||||
size_t can;
|
||||
size_t total;
|
||||
|
||||
~LoweringStatsA64()
|
||||
{
|
||||
if (total)
|
||||
printf("A64 lowering succeded for %.1f%% functions (%d/%d)\n", double(can) / double(total) * 100, int(can), int(total));
|
||||
}
|
||||
} gStatsA64;
|
||||
#endif
|
||||
|
||||
inline ConditionA64 getConditionFP(IrCondition cond)
|
||||
{
|
||||
switch (cond)
|
||||
{
|
||||
case IrCondition::Equal:
|
||||
return ConditionA64::Equal;
|
||||
|
||||
case IrCondition::NotEqual:
|
||||
return ConditionA64::NotEqual;
|
||||
|
||||
case IrCondition::Less:
|
||||
return ConditionA64::Minus;
|
||||
|
||||
case IrCondition::NotLess:
|
||||
return ConditionA64::Plus;
|
||||
|
||||
case IrCondition::LessEqual:
|
||||
return ConditionA64::UnsignedLessEqual;
|
||||
|
||||
case IrCondition::NotLessEqual:
|
||||
return ConditionA64::UnsignedGreater;
|
||||
|
||||
case IrCondition::Greater:
|
||||
return ConditionA64::Greater;
|
||||
|
||||
case IrCondition::NotGreater:
|
||||
return ConditionA64::LessEqual;
|
||||
|
||||
case IrCondition::GreaterEqual:
|
||||
return ConditionA64::GreaterEqual;
|
||||
|
||||
case IrCondition::NotGreaterEqual:
|
||||
return ConditionA64::Less;
|
||||
|
||||
default:
|
||||
LUAU_ASSERT(!"Unexpected condition code");
|
||||
return ConditionA64::Always;
|
||||
}
|
||||
}
|
||||
|
||||
IrLoweringA64::IrLoweringA64(AssemblyBuilderA64& build, ModuleHelpers& helpers, NativeState& data, Proto* proto, IrFunction& function)
|
||||
: build(build)
|
||||
, helpers(helpers)
|
||||
, data(data)
|
||||
, proto(proto)
|
||||
, function(function)
|
||||
, regs(function, {{x0, x15}, {q0, q7}, {q16, q31}})
|
||||
{
|
||||
// In order to allocate registers during lowering, we need to know where instruction results are last used
|
||||
updateLastUseLocations(function);
|
||||
@ -34,20 +92,61 @@ IrLoweringA64::IrLoweringA64(AssemblyBuilderA64& build, ModuleHelpers& helpers,
|
||||
// TODO: Eventually this can go away
|
||||
bool IrLoweringA64::canLower(const IrFunction& function)
|
||||
{
|
||||
#ifdef TRACE
|
||||
gStatsA64.total++;
|
||||
#endif
|
||||
|
||||
for (const IrInst& inst : function.instructions)
|
||||
{
|
||||
switch (inst.cmd)
|
||||
{
|
||||
case IrCmd::NOP:
|
||||
case IrCmd::SUBSTITUTE:
|
||||
case IrCmd::LOAD_TAG:
|
||||
case IrCmd::LOAD_POINTER:
|
||||
case IrCmd::LOAD_DOUBLE:
|
||||
case IrCmd::LOAD_INT:
|
||||
case IrCmd::LOAD_TVALUE:
|
||||
case IrCmd::LOAD_NODE_VALUE_TV:
|
||||
case IrCmd::LOAD_ENV:
|
||||
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_NUM:
|
||||
case IrCmd::SUB_NUM:
|
||||
case IrCmd::MUL_NUM:
|
||||
case IrCmd::DIV_NUM:
|
||||
case IrCmd::MOD_NUM:
|
||||
case IrCmd::UNM_NUM:
|
||||
case IrCmd::JUMP:
|
||||
case IrCmd::JUMP_EQ_TAG:
|
||||
case IrCmd::JUMP_CMP_NUM:
|
||||
case IrCmd::JUMP_CMP_ANY:
|
||||
case IrCmd::DO_ARITH:
|
||||
case IrCmd::GET_IMPORT:
|
||||
case IrCmd::GET_UPVALUE:
|
||||
case IrCmd::CHECK_TAG:
|
||||
case IrCmd::CHECK_READONLY:
|
||||
case IrCmd::CHECK_NO_METATABLE:
|
||||
case IrCmd::CHECK_SAFE_ENV:
|
||||
case IrCmd::INTERRUPT:
|
||||
case IrCmd::LOP_RETURN:
|
||||
case IrCmd::SET_SAVEDPC:
|
||||
case IrCmd::CALL:
|
||||
case IrCmd::RETURN:
|
||||
case IrCmd::SUBSTITUTE:
|
||||
continue;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef TRACE
|
||||
gStatsA64.can++;
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -55,23 +154,338 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
{
|
||||
switch (inst.cmd)
|
||||
{
|
||||
case IrCmd::LOAD_TAG:
|
||||
{
|
||||
inst.regA64 = regs.allocReg(KindA64::w);
|
||||
AddressA64 addr = tempAddr(inst.a, offsetof(TValue, tt));
|
||||
build.ldr(inst.regA64, addr);
|
||||
break;
|
||||
}
|
||||
case IrCmd::LOAD_POINTER:
|
||||
{
|
||||
inst.regA64 = regs.allocReg(KindA64::x);
|
||||
AddressA64 addr = tempAddr(inst.a, offsetof(TValue, value));
|
||||
build.ldr(inst.regA64, addr);
|
||||
break;
|
||||
}
|
||||
case IrCmd::LOAD_DOUBLE:
|
||||
{
|
||||
inst.regA64 = regs.allocReg(KindA64::d);
|
||||
AddressA64 addr = tempAddr(inst.a, offsetof(TValue, value));
|
||||
build.ldr(inst.regA64, addr);
|
||||
break;
|
||||
}
|
||||
case IrCmd::LOAD_INT:
|
||||
{
|
||||
inst.regA64 = regs.allocReg(KindA64::w);
|
||||
AddressA64 addr = tempAddr(inst.a, offsetof(TValue, value));
|
||||
build.ldr(inst.regA64, addr);
|
||||
break;
|
||||
}
|
||||
case IrCmd::LOAD_TVALUE:
|
||||
{
|
||||
inst.regA64 = regs.allocReg(KindA64::q);
|
||||
AddressA64 addr = tempAddr(inst.a, 0);
|
||||
build.ldr(inst.regA64, addr);
|
||||
break;
|
||||
}
|
||||
case IrCmd::LOAD_NODE_VALUE_TV:
|
||||
{
|
||||
inst.regA64 = regs.allocReg(KindA64::q);
|
||||
build.ldr(inst.regA64, mem(regOp(inst.a), offsetof(LuaNode, val)));
|
||||
break;
|
||||
}
|
||||
case IrCmd::LOAD_ENV:
|
||||
inst.regA64 = regs.allocReg(KindA64::x);
|
||||
build.ldr(inst.regA64, mem(rClosure, offsetof(Closure, env)));
|
||||
break;
|
||||
case IrCmd::STORE_TAG:
|
||||
{
|
||||
RegisterA64 temp = regs.allocTemp(KindA64::w);
|
||||
AddressA64 addr = tempAddr(inst.a, offsetof(TValue, tt));
|
||||
build.mov(temp, tagOp(inst.b));
|
||||
build.str(temp, addr);
|
||||
break;
|
||||
}
|
||||
case IrCmd::STORE_POINTER:
|
||||
{
|
||||
AddressA64 addr = tempAddr(inst.a, offsetof(TValue, value));
|
||||
build.str(regOp(inst.b), addr);
|
||||
break;
|
||||
}
|
||||
case IrCmd::STORE_DOUBLE:
|
||||
{
|
||||
RegisterA64 temp = tempDouble(inst.b);
|
||||
AddressA64 addr = tempAddr(inst.a, offsetof(TValue, value));
|
||||
build.str(temp, addr);
|
||||
break;
|
||||
}
|
||||
case IrCmd::STORE_INT:
|
||||
{
|
||||
RegisterA64 temp = tempInt(inst.b);
|
||||
AddressA64 addr = tempAddr(inst.a, offsetof(TValue, value));
|
||||
build.str(temp, addr);
|
||||
break;
|
||||
}
|
||||
case IrCmd::STORE_TVALUE:
|
||||
{
|
||||
AddressA64 addr = tempAddr(inst.a, 0);
|
||||
build.str(regOp(inst.b), addr);
|
||||
break;
|
||||
}
|
||||
case IrCmd::STORE_NODE_VALUE_TV:
|
||||
build.str(regOp(inst.b), mem(regOp(inst.a), offsetof(LuaNode, val)));
|
||||
break;
|
||||
case IrCmd::ADD_NUM:
|
||||
{
|
||||
inst.regA64 = regs.allocReuse(KindA64::d, index, {inst.a, inst.b});
|
||||
RegisterA64 temp1 = tempDouble(inst.a);
|
||||
RegisterA64 temp2 = tempDouble(inst.b);
|
||||
build.fadd(inst.regA64, temp1, temp2);
|
||||
break;
|
||||
}
|
||||
case IrCmd::SUB_NUM:
|
||||
{
|
||||
inst.regA64 = regs.allocReuse(KindA64::d, index, {inst.a, inst.b});
|
||||
RegisterA64 temp1 = tempDouble(inst.a);
|
||||
RegisterA64 temp2 = tempDouble(inst.b);
|
||||
build.fsub(inst.regA64, temp1, temp2);
|
||||
break;
|
||||
}
|
||||
case IrCmd::MUL_NUM:
|
||||
{
|
||||
inst.regA64 = regs.allocReuse(KindA64::d, index, {inst.a, inst.b});
|
||||
RegisterA64 temp1 = tempDouble(inst.a);
|
||||
RegisterA64 temp2 = tempDouble(inst.b);
|
||||
build.fmul(inst.regA64, temp1, temp2);
|
||||
break;
|
||||
}
|
||||
case IrCmd::DIV_NUM:
|
||||
{
|
||||
inst.regA64 = regs.allocReuse(KindA64::d, index, {inst.a, inst.b});
|
||||
RegisterA64 temp1 = tempDouble(inst.a);
|
||||
RegisterA64 temp2 = tempDouble(inst.b);
|
||||
build.fdiv(inst.regA64, temp1, temp2);
|
||||
break;
|
||||
}
|
||||
case IrCmd::MOD_NUM:
|
||||
{
|
||||
inst.regA64 = regs.allocReg(KindA64::d);
|
||||
RegisterA64 temp1 = tempDouble(inst.a);
|
||||
RegisterA64 temp2 = tempDouble(inst.b);
|
||||
build.fdiv(inst.regA64, temp1, temp2);
|
||||
build.frintm(inst.regA64, inst.regA64);
|
||||
build.fmul(inst.regA64, inst.regA64, temp2);
|
||||
build.fsub(inst.regA64, temp1, inst.regA64);
|
||||
break;
|
||||
}
|
||||
case IrCmd::UNM_NUM:
|
||||
{
|
||||
inst.regA64 = regs.allocReuse(KindA64::d, index, {inst.a});
|
||||
RegisterA64 temp = tempDouble(inst.a);
|
||||
build.fneg(inst.regA64, temp);
|
||||
break;
|
||||
}
|
||||
case IrCmd::JUMP:
|
||||
jumpOrFallthrough(blockOp(inst.a), next);
|
||||
break;
|
||||
case IrCmd::JUMP_EQ_TAG:
|
||||
if (inst.b.kind == IrOpKind::Constant)
|
||||
build.cmp(regOp(inst.a), tagOp(inst.b));
|
||||
else if (inst.b.kind == IrOpKind::Inst)
|
||||
build.cmp(regOp(inst.a), regOp(inst.b));
|
||||
else
|
||||
LUAU_ASSERT(!"Unsupported instruction form");
|
||||
|
||||
if (isFallthroughBlock(blockOp(inst.d), next))
|
||||
{
|
||||
build.b(ConditionA64::Equal, labelOp(inst.c));
|
||||
jumpOrFallthrough(blockOp(inst.d), next);
|
||||
}
|
||||
else
|
||||
{
|
||||
build.b(ConditionA64::NotEqual, labelOp(inst.d));
|
||||
jumpOrFallthrough(blockOp(inst.c), next);
|
||||
}
|
||||
break;
|
||||
case IrCmd::JUMP_CMP_NUM:
|
||||
{
|
||||
IrCondition cond = conditionOp(inst.c);
|
||||
|
||||
RegisterA64 temp1 = tempDouble(inst.a);
|
||||
RegisterA64 temp2 = tempDouble(inst.b);
|
||||
|
||||
build.fcmp(temp1, temp2);
|
||||
build.b(getConditionFP(cond), labelOp(inst.d));
|
||||
jumpOrFallthrough(blockOp(inst.e), next);
|
||||
break;
|
||||
}
|
||||
case IrCmd::JUMP_CMP_ANY:
|
||||
{
|
||||
IrCondition cond = conditionOp(inst.c);
|
||||
|
||||
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)));
|
||||
|
||||
if (cond == IrCondition::NotLessEqual || cond == IrCondition::LessEqual)
|
||||
build.ldr(x3, mem(rNativeContext, offsetof(NativeContext, luaV_lessequal)));
|
||||
else if (cond == IrCondition::NotLess || cond == IrCondition::Less)
|
||||
build.ldr(x3, mem(rNativeContext, offsetof(NativeContext, luaV_lessthan)));
|
||||
else if (cond == IrCondition::NotEqual || cond == IrCondition::Equal)
|
||||
build.ldr(x3, mem(rNativeContext, offsetof(NativeContext, luaV_equalval)));
|
||||
else
|
||||
LUAU_ASSERT(!"Unsupported condition");
|
||||
|
||||
build.blr(x3);
|
||||
|
||||
emitUpdateBase(build);
|
||||
|
||||
if (cond == IrCondition::NotLessEqual || cond == IrCondition::NotLess || cond == IrCondition::NotEqual)
|
||||
build.cbz(x0, labelOp(inst.d));
|
||||
else
|
||||
build.cbnz(x0, labelOp(inst.d));
|
||||
jumpOrFallthrough(blockOp(inst.e), next);
|
||||
break;
|
||||
}
|
||||
case IrCmd::DO_ARITH:
|
||||
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)));
|
||||
|
||||
if (inst.c.kind == IrOpKind::VmConst)
|
||||
{
|
||||
// TODO: refactor into a common helper
|
||||
if (vmConstOp(inst.c) * sizeof(TValue) <= AssemblyBuilderA64::kMaxImmediate)
|
||||
{
|
||||
build.add(x3, rConstants, uint16_t(vmConstOp(inst.c) * sizeof(TValue)));
|
||||
}
|
||||
else
|
||||
{
|
||||
build.mov(x3, vmConstOp(inst.c) * sizeof(TValue));
|
||||
build.add(x3, rConstants, x3);
|
||||
}
|
||||
}
|
||||
else
|
||||
build.add(x3, rBase, uint16_t(vmRegOp(inst.c) * sizeof(TValue)));
|
||||
|
||||
build.mov(w4, TMS(intOp(inst.d)));
|
||||
build.ldr(x5, mem(rNativeContext, offsetof(NativeContext, luaV_doarith)));
|
||||
build.blr(x5);
|
||||
|
||||
emitUpdateBase(build);
|
||||
break;
|
||||
case IrCmd::GET_IMPORT:
|
||||
regs.assertAllFree();
|
||||
emitInstGetImport(build, vmRegOp(inst.a), uintOp(inst.b));
|
||||
break;
|
||||
case IrCmd::GET_UPVALUE:
|
||||
{
|
||||
RegisterA64 temp1 = regs.allocTemp(KindA64::x);
|
||||
RegisterA64 temp2 = regs.allocTemp(KindA64::q);
|
||||
RegisterA64 temp3 = regs.allocTemp(KindA64::w);
|
||||
|
||||
build.add(temp1, rClosure, uint16_t(offsetof(Closure, l.uprefs) + sizeof(TValue) * vmUpvalueOp(inst.b)));
|
||||
|
||||
// uprefs[] is either an actual value, or it points to UpVal object which has a pointer to value
|
||||
Label skip;
|
||||
build.ldr(temp3, mem(temp1, offsetof(TValue, tt)));
|
||||
build.cmp(temp3, LUA_TUPVAL);
|
||||
build.b(ConditionA64::NotEqual, skip);
|
||||
|
||||
// UpVal.v points to the value (either on stack, or on heap inside each UpVal, but we can deref it unconditionally)
|
||||
build.ldr(temp1, mem(temp1, offsetof(TValue, value.gc)));
|
||||
build.ldr(temp1, mem(temp1, offsetof(UpVal, v)));
|
||||
|
||||
build.setLabel(skip);
|
||||
|
||||
build.ldr(temp2, temp1);
|
||||
build.str(temp2, mem(rBase, vmRegOp(inst.a) * sizeof(TValue)));
|
||||
break;
|
||||
}
|
||||
case IrCmd::CHECK_TAG:
|
||||
build.cmp(regOp(inst.a), tagOp(inst.b));
|
||||
build.b(ConditionA64::NotEqual, labelOp(inst.c));
|
||||
break;
|
||||
case IrCmd::CHECK_READONLY:
|
||||
{
|
||||
RegisterA64 temp = regs.allocTemp(KindA64::w);
|
||||
build.ldrb(temp, mem(regOp(inst.a), offsetof(Table, readonly)));
|
||||
build.cbnz(temp, labelOp(inst.b));
|
||||
break;
|
||||
}
|
||||
case IrCmd::CHECK_NO_METATABLE:
|
||||
{
|
||||
RegisterA64 temp = regs.allocTemp(KindA64::x);
|
||||
build.ldr(temp, mem(regOp(inst.a), offsetof(Table, metatable)));
|
||||
build.cbnz(temp, labelOp(inst.b));
|
||||
break;
|
||||
}
|
||||
case IrCmd::CHECK_SAFE_ENV:
|
||||
{
|
||||
RegisterA64 temp = regs.allocTemp(KindA64::x);
|
||||
RegisterA64 tempw{KindA64::w, temp.index};
|
||||
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::INTERRUPT:
|
||||
{
|
||||
emitInterrupt(build, uintOp(inst.a));
|
||||
unsigned int pcpos = uintOp(inst.a);
|
||||
regs.assertAllFree();
|
||||
|
||||
Label skip;
|
||||
build.ldr(x2, mem(rState, offsetof(lua_State, global)));
|
||||
build.ldr(x2, mem(x2, offsetof(global_State, cb.interrupt)));
|
||||
build.cbz(x2, skip);
|
||||
|
||||
// Jump to outlined interrupt handler, it will give back control to x1
|
||||
build.mov(x0, (pcpos + 1) * sizeof(Instruction));
|
||||
build.adr(x1, skip);
|
||||
build.b(helpers.interrupt);
|
||||
|
||||
build.setLabel(skip);
|
||||
break;
|
||||
}
|
||||
case IrCmd::LOP_RETURN:
|
||||
case IrCmd::SET_SAVEDPC:
|
||||
{
|
||||
unsigned int pcpos = uintOp(inst.a);
|
||||
RegisterA64 temp1 = regs.allocTemp(KindA64::x);
|
||||
RegisterA64 temp2 = regs.allocTemp(KindA64::x);
|
||||
|
||||
// TODO: refactor into a common helper
|
||||
if (pcpos * sizeof(Instruction) <= AssemblyBuilderA64::kMaxImmediate)
|
||||
{
|
||||
build.add(temp1, rCode, uint16_t(pcpos * sizeof(Instruction)));
|
||||
}
|
||||
else
|
||||
{
|
||||
build.mov(temp1, pcpos * sizeof(Instruction));
|
||||
build.add(temp1, rCode, temp1);
|
||||
}
|
||||
|
||||
build.ldr(temp2, mem(rState, offsetof(lua_State, ci)));
|
||||
build.str(temp1, mem(temp2, offsetof(CallInfo, savedpc)));
|
||||
break;
|
||||
}
|
||||
case IrCmd::CALL:
|
||||
regs.assertAllFree();
|
||||
emitInstCall(build, helpers, vmRegOp(inst.a), intOp(inst.b), intOp(inst.c));
|
||||
break;
|
||||
case IrCmd::RETURN:
|
||||
regs.assertAllFree();
|
||||
emitInstReturn(build, helpers, vmRegOp(inst.a), intOp(inst.b));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LUAU_ASSERT(!"Not supported yet");
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO
|
||||
// regs.freeLastUseRegs(inst, index);
|
||||
regs.freeLastUseRegs(inst, index);
|
||||
regs.freeTempRegs();
|
||||
}
|
||||
|
||||
bool IrLoweringA64::isFallthroughBlock(IrBlock target, IrBlock next)
|
||||
@ -85,6 +499,83 @@ void IrLoweringA64::jumpOrFallthrough(IrBlock& target, IrBlock& next)
|
||||
build.b(target.label);
|
||||
}
|
||||
|
||||
RegisterA64 IrLoweringA64::tempDouble(IrOp op)
|
||||
{
|
||||
if (op.kind == IrOpKind::Inst)
|
||||
return regOp(op);
|
||||
else if (op.kind == IrOpKind::Constant)
|
||||
{
|
||||
RegisterA64 temp1 = regs.allocTemp(KindA64::x);
|
||||
RegisterA64 temp2 = regs.allocTemp(KindA64::d);
|
||||
build.adr(temp1, doubleOp(op));
|
||||
build.ldr(temp2, temp1);
|
||||
return temp2;
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_ASSERT(!"Unsupported instruction form");
|
||||
return noreg;
|
||||
}
|
||||
}
|
||||
|
||||
RegisterA64 IrLoweringA64::tempInt(IrOp op)
|
||||
{
|
||||
if (op.kind == IrOpKind::Inst)
|
||||
return regOp(op);
|
||||
else if (op.kind == IrOpKind::Constant)
|
||||
{
|
||||
RegisterA64 temp = regs.allocTemp(KindA64::w);
|
||||
build.mov(temp, intOp(op));
|
||||
return temp;
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_ASSERT(!"Unsupported instruction form");
|
||||
return noreg;
|
||||
}
|
||||
}
|
||||
|
||||
AddressA64 IrLoweringA64::tempAddr(IrOp op, int offset)
|
||||
{
|
||||
// This is needed to tighten the bounds checks in the VmConst case below
|
||||
LUAU_ASSERT(offset % 4 == 0);
|
||||
|
||||
if (op.kind == IrOpKind::VmReg)
|
||||
return mem(rBase, vmRegOp(op) * sizeof(TValue) + offset);
|
||||
else if (op.kind == IrOpKind::VmConst)
|
||||
{
|
||||
size_t constantOffset = vmConstOp(op) * sizeof(TValue) + offset;
|
||||
|
||||
// Note: cumulative offset is guaranteed to be divisible by 4; we can use that to expand the useful range that doesn't require temporaries
|
||||
if (constantOffset / 4 <= AddressA64::kMaxOffset)
|
||||
return mem(rConstants, int(constantOffset));
|
||||
|
||||
RegisterA64 temp = regs.allocTemp(KindA64::x);
|
||||
|
||||
// TODO: refactor into a common helper
|
||||
if (constantOffset <= AssemblyBuilderA64::kMaxImmediate)
|
||||
{
|
||||
build.add(temp, rConstants, uint16_t(constantOffset));
|
||||
}
|
||||
else
|
||||
{
|
||||
build.mov(temp, int(constantOffset));
|
||||
build.add(temp, rConstants, temp);
|
||||
}
|
||||
|
||||
return temp;
|
||||
}
|
||||
// If we have a register, we assume it's a pointer to TValue
|
||||
// We might introduce explicit operand types in the future to make this more robust
|
||||
else if (op.kind == IrOpKind::Inst)
|
||||
return mem(regOp(op), offset);
|
||||
else
|
||||
{
|
||||
LUAU_ASSERT(!"Unsupported instruction form");
|
||||
return noreg;
|
||||
}
|
||||
}
|
||||
|
||||
RegisterA64 IrLoweringA64::regOp(IrOp op) const
|
||||
{
|
||||
IrInst& inst = function.instOp(op);
|
||||
|
@ -4,6 +4,8 @@
|
||||
#include "Luau/AssemblyBuilderA64.h"
|
||||
#include "Luau/IrData.h"
|
||||
|
||||
#include "IrRegAllocA64.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
struct Proto;
|
||||
@ -31,6 +33,11 @@ struct IrLoweringA64
|
||||
bool isFallthroughBlock(IrBlock target, IrBlock next);
|
||||
void jumpOrFallthrough(IrBlock& target, IrBlock& next);
|
||||
|
||||
// Operand data build helpers
|
||||
RegisterA64 tempDouble(IrOp op);
|
||||
RegisterA64 tempInt(IrOp op);
|
||||
AddressA64 tempAddr(IrOp op, int offset);
|
||||
|
||||
// Operand data lookup helpers
|
||||
RegisterA64 regOp(IrOp op) const;
|
||||
|
||||
@ -51,8 +58,7 @@ struct IrLoweringA64
|
||||
|
||||
IrFunction& function;
|
||||
|
||||
// TODO:
|
||||
// IrRegAllocA64 regs;
|
||||
IrRegAllocA64 regs;
|
||||
};
|
||||
|
||||
} // namespace A64
|
||||
|
@ -4,6 +4,7 @@
|
||||
#include "Luau/CodeGen.h"
|
||||
#include "Luau/DenseHash.h"
|
||||
#include "Luau/IrAnalysis.h"
|
||||
#include "Luau/IrCallWrapperX64.h"
|
||||
#include "Luau/IrDump.h"
|
||||
#include "Luau/IrUtils.h"
|
||||
|
||||
@ -141,7 +142,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
inst.regX64 = regs.allocGprReg(SizeX64::qword);
|
||||
|
||||
// Custom bit shift value can only be placed in cl
|
||||
ScopedRegX64 shiftTmp{regs, regs.takeGprReg(rcx)};
|
||||
ScopedRegX64 shiftTmp{regs, regs.takeReg(rcx)};
|
||||
|
||||
ScopedRegX64 tmp{regs, SizeX64::qword};
|
||||
|
||||
@ -325,82 +326,11 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
}
|
||||
case IrCmd::POW_NUM:
|
||||
{
|
||||
inst.regX64 = regs.allocXmmRegOrReuse(index, {inst.a, inst.b});
|
||||
|
||||
ScopedRegX64 optLhsTmp{regs};
|
||||
RegisterX64 lhs;
|
||||
|
||||
if (inst.a.kind == IrOpKind::Constant)
|
||||
{
|
||||
optLhsTmp.alloc(SizeX64::xmmword);
|
||||
|
||||
build.vmovsd(optLhsTmp.reg, memRegDoubleOp(inst.a));
|
||||
lhs = optLhsTmp.reg;
|
||||
}
|
||||
else
|
||||
{
|
||||
lhs = regOp(inst.a);
|
||||
}
|
||||
|
||||
if (inst.b.kind == IrOpKind::Inst)
|
||||
{
|
||||
// TODO: this doesn't happen with current local-only register allocation, but has to be handled in the future
|
||||
LUAU_ASSERT(regOp(inst.b) != xmm0);
|
||||
|
||||
if (lhs != xmm0)
|
||||
build.vmovsd(xmm0, lhs, lhs);
|
||||
|
||||
if (regOp(inst.b) != xmm1)
|
||||
build.vmovsd(xmm1, regOp(inst.b), regOp(inst.b));
|
||||
|
||||
build.call(qword[rNativeContext + offsetof(NativeContext, libm_pow)]);
|
||||
|
||||
if (inst.regX64 != xmm0)
|
||||
build.vmovsd(inst.regX64, xmm0, xmm0);
|
||||
}
|
||||
else if (inst.b.kind == IrOpKind::Constant)
|
||||
{
|
||||
double rhs = doubleOp(inst.b);
|
||||
|
||||
if (rhs == 2.0)
|
||||
{
|
||||
build.vmulsd(inst.regX64, lhs, lhs);
|
||||
}
|
||||
else if (rhs == 0.5)
|
||||
{
|
||||
build.vsqrtsd(inst.regX64, lhs, lhs);
|
||||
}
|
||||
else if (rhs == 3.0)
|
||||
{
|
||||
ScopedRegX64 tmp{regs, SizeX64::xmmword};
|
||||
|
||||
build.vmulsd(tmp.reg, lhs, lhs);
|
||||
build.vmulsd(inst.regX64, lhs, tmp.reg);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (lhs != xmm0)
|
||||
build.vmovsd(xmm0, xmm0, lhs);
|
||||
|
||||
build.vmovsd(xmm1, build.f64(rhs));
|
||||
build.call(qword[rNativeContext + offsetof(NativeContext, libm_pow)]);
|
||||
|
||||
if (inst.regX64 != xmm0)
|
||||
build.vmovsd(inst.regX64, xmm0, xmm0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (lhs != xmm0)
|
||||
build.vmovsd(xmm0, lhs, lhs);
|
||||
|
||||
build.vmovsd(xmm1, memRegDoubleOp(inst.b));
|
||||
build.call(qword[rNativeContext + offsetof(NativeContext, libm_pow)]);
|
||||
|
||||
if (inst.regX64 != xmm0)
|
||||
build.vmovsd(inst.regX64, xmm0, xmm0);
|
||||
}
|
||||
|
||||
IrCallWrapperX64 callWrap(regs, build, index);
|
||||
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);
|
||||
break;
|
||||
}
|
||||
case IrCmd::MIN_NUM:
|
||||
@ -451,6 +381,46 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
|
||||
break;
|
||||
}
|
||||
case IrCmd::FLOOR_NUM:
|
||||
inst.regX64 = regs.allocXmmRegOrReuse(index, {inst.a});
|
||||
|
||||
build.vroundsd(inst.regX64, inst.regX64, memRegDoubleOp(inst.a), RoundingModeX64::RoundToNegativeInfinity);
|
||||
break;
|
||||
case IrCmd::CEIL_NUM:
|
||||
inst.regX64 = regs.allocXmmRegOrReuse(index, {inst.a});
|
||||
|
||||
build.vroundsd(inst.regX64, inst.regX64, memRegDoubleOp(inst.a), RoundingModeX64::RoundToPositiveInfinity);
|
||||
break;
|
||||
case IrCmd::ROUND_NUM:
|
||||
{
|
||||
inst.regX64 = regs.allocXmmRegOrReuse(index, {inst.a});
|
||||
|
||||
ScopedRegX64 tmp1{regs, SizeX64::xmmword};
|
||||
ScopedRegX64 tmp2{regs, SizeX64::xmmword};
|
||||
|
||||
if (inst.a.kind != IrOpKind::Inst || regOp(inst.a) != inst.regX64)
|
||||
build.vmovsd(inst.regX64, memRegDoubleOp(inst.a));
|
||||
|
||||
build.vandpd(tmp1.reg, inst.regX64, build.f64x2(-0.0, -0.0));
|
||||
build.vmovsd(tmp2.reg, build.i64(0x3fdfffffffffffff)); // 0.49999999999999994
|
||||
build.vorpd(tmp1.reg, tmp1.reg, tmp2.reg);
|
||||
build.vaddsd(inst.regX64, inst.regX64, tmp1.reg);
|
||||
build.vroundsd(inst.regX64, inst.regX64, inst.regX64, RoundingModeX64::RoundToZero);
|
||||
break;
|
||||
}
|
||||
case IrCmd::SQRT_NUM:
|
||||
inst.regX64 = regs.allocXmmRegOrReuse(index, {inst.a});
|
||||
|
||||
build.vsqrtsd(inst.regX64, inst.regX64, memRegDoubleOp(inst.a));
|
||||
break;
|
||||
case IrCmd::ABS_NUM:
|
||||
inst.regX64 = regs.allocXmmRegOrReuse(index, {inst.a});
|
||||
|
||||
if (inst.a.kind != IrOpKind::Inst || regOp(inst.a) != inst.regX64)
|
||||
build.vmovsd(inst.regX64, memRegDoubleOp(inst.a));
|
||||
|
||||
build.vandpd(inst.regX64, inst.regX64, build.i64(~(1LL << 63)));
|
||||
break;
|
||||
case IrCmd::NOT_ANY:
|
||||
{
|
||||
// TODO: if we have a single user which is a STORE_INT, we are missing the opportunity to write directly to target
|
||||
@ -539,7 +509,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
break;
|
||||
}
|
||||
case IrCmd::JUMP_CMP_ANY:
|
||||
jumpOnAnyCmpFallback(build, vmRegOp(inst.a), vmRegOp(inst.b), conditionOp(inst.c), labelOp(inst.d));
|
||||
jumpOnAnyCmpFallback(regs, build, vmRegOp(inst.a), vmRegOp(inst.b), conditionOp(inst.c), labelOp(inst.d));
|
||||
jumpOrFallthrough(blockOp(inst.e), next);
|
||||
break;
|
||||
case IrCmd::JUMP_SLOT_MATCH:
|
||||
@ -551,34 +521,34 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
break;
|
||||
}
|
||||
case IrCmd::TABLE_LEN:
|
||||
inst.regX64 = regs.allocXmmReg();
|
||||
{
|
||||
IrCallWrapperX64 callWrap(regs, build, index);
|
||||
callWrap.addArgument(SizeX64::qword, regOp(inst.a), inst.a);
|
||||
callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaH_getn)]);
|
||||
|
||||
build.mov(rArg1, regOp(inst.a));
|
||||
build.call(qword[rNativeContext + offsetof(NativeContext, luaH_getn)]);
|
||||
inst.regX64 = regs.allocXmmReg();
|
||||
build.vcvtsi2sd(inst.regX64, inst.regX64, eax);
|
||||
break;
|
||||
}
|
||||
case IrCmd::NEW_TABLE:
|
||||
inst.regX64 = regs.allocGprReg(SizeX64::qword);
|
||||
|
||||
build.mov(rArg1, rState);
|
||||
build.mov(dwordReg(rArg2), uintOp(inst.a));
|
||||
build.mov(dwordReg(rArg3), uintOp(inst.b));
|
||||
build.call(qword[rNativeContext + offsetof(NativeContext, luaH_new)]);
|
||||
|
||||
if (inst.regX64 != rax)
|
||||
build.mov(inst.regX64, rax);
|
||||
{
|
||||
IrCallWrapperX64 callWrap(regs, build, index);
|
||||
callWrap.addArgument(SizeX64::qword, rState);
|
||||
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);
|
||||
break;
|
||||
}
|
||||
case IrCmd::DUP_TABLE:
|
||||
inst.regX64 = regs.allocGprReg(SizeX64::qword);
|
||||
|
||||
// Re-ordered to avoid register conflict
|
||||
build.mov(rArg2, regOp(inst.a));
|
||||
build.mov(rArg1, rState);
|
||||
build.call(qword[rNativeContext + offsetof(NativeContext, luaH_clone)]);
|
||||
|
||||
if (inst.regX64 != rax)
|
||||
build.mov(inst.regX64, rax);
|
||||
{
|
||||
IrCallWrapperX64 callWrap(regs, build, index);
|
||||
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);
|
||||
break;
|
||||
}
|
||||
case IrCmd::TRY_NUM_TO_INDEX:
|
||||
{
|
||||
inst.regX64 = regs.allocGprReg(SizeX64::dword);
|
||||
@ -590,12 +560,26 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
}
|
||||
case IrCmd::TRY_CALL_FASTGETTM:
|
||||
{
|
||||
inst.regX64 = regs.allocGprReg(SizeX64::qword);
|
||||
ScopedRegX64 tmp{regs, SizeX64::qword};
|
||||
|
||||
callGetFastTmOrFallback(build, regOp(inst.a), TMS(intOp(inst.b)), labelOp(inst.c));
|
||||
build.mov(tmp.reg, qword[regOp(inst.a) + offsetof(Table, metatable)]);
|
||||
regs.freeLastUseReg(function.instOp(inst.a), index); // Release before the call if it's the last use
|
||||
|
||||
if (inst.regX64 != rax)
|
||||
build.mov(inst.regX64, rax);
|
||||
build.test(tmp.reg, tmp.reg);
|
||||
build.jcc(ConditionX64::Zero, labelOp(inst.c)); // No metatable
|
||||
|
||||
build.test(byte[tmp.reg + offsetof(Table, tmcache)], 1 << intOp(inst.b));
|
||||
build.jcc(ConditionX64::NotZero, labelOp(inst.c)); // No tag method
|
||||
|
||||
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);
|
||||
break;
|
||||
}
|
||||
case IrCmd::INT_TO_NUM:
|
||||
@ -701,7 +685,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
|
||||
build.call(rax);
|
||||
|
||||
inst.regX64 = regs.takeGprReg(eax); // Result of a builtin call is returned in eax
|
||||
inst.regX64 = regs.takeReg(eax); // Result of a builtin call is returned in eax
|
||||
break;
|
||||
}
|
||||
case IrCmd::CHECK_FASTCALL_RES:
|
||||
@ -714,23 +698,23 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
}
|
||||
case IrCmd::DO_ARITH:
|
||||
if (inst.c.kind == IrOpKind::VmReg)
|
||||
callArithHelper(build, vmRegOp(inst.a), vmRegOp(inst.b), luauRegAddress(vmRegOp(inst.c)), TMS(intOp(inst.d)));
|
||||
callArithHelper(regs, build, vmRegOp(inst.a), vmRegOp(inst.b), luauRegAddress(vmRegOp(inst.c)), TMS(intOp(inst.d)));
|
||||
else
|
||||
callArithHelper(build, vmRegOp(inst.a), vmRegOp(inst.b), luauConstantAddress(vmConstOp(inst.c)), TMS(intOp(inst.d)));
|
||||
callArithHelper(regs, build, vmRegOp(inst.a), vmRegOp(inst.b), luauConstantAddress(vmConstOp(inst.c)), TMS(intOp(inst.d)));
|
||||
break;
|
||||
case IrCmd::DO_LEN:
|
||||
callLengthHelper(build, vmRegOp(inst.a), vmRegOp(inst.b));
|
||||
callLengthHelper(regs, build, vmRegOp(inst.a), vmRegOp(inst.b));
|
||||
break;
|
||||
case IrCmd::GET_TABLE:
|
||||
if (inst.c.kind == IrOpKind::VmReg)
|
||||
{
|
||||
callGetTable(build, vmRegOp(inst.b), luauRegAddress(vmRegOp(inst.c)), vmRegOp(inst.a));
|
||||
callGetTable(regs, build, vmRegOp(inst.b), luauRegAddress(vmRegOp(inst.c)), vmRegOp(inst.a));
|
||||
}
|
||||
else if (inst.c.kind == IrOpKind::Constant)
|
||||
{
|
||||
TValue n;
|
||||
setnvalue(&n, uintOp(inst.c));
|
||||
callGetTable(build, vmRegOp(inst.b), build.bytes(&n, sizeof(n)), vmRegOp(inst.a));
|
||||
callGetTable(regs, build, vmRegOp(inst.b), build.bytes(&n, sizeof(n)), vmRegOp(inst.a));
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -740,13 +724,13 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
case IrCmd::SET_TABLE:
|
||||
if (inst.c.kind == IrOpKind::VmReg)
|
||||
{
|
||||
callSetTable(build, vmRegOp(inst.b), luauRegAddress(vmRegOp(inst.c)), vmRegOp(inst.a));
|
||||
callSetTable(regs, build, vmRegOp(inst.b), luauRegAddress(vmRegOp(inst.c)), vmRegOp(inst.a));
|
||||
}
|
||||
else if (inst.c.kind == IrOpKind::Constant)
|
||||
{
|
||||
TValue n;
|
||||
setnvalue(&n, uintOp(inst.c));
|
||||
callSetTable(build, vmRegOp(inst.b), build.bytes(&n, sizeof(n)), vmRegOp(inst.a));
|
||||
callSetTable(regs, build, vmRegOp(inst.b), build.bytes(&n, sizeof(n)), vmRegOp(inst.a));
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -757,13 +741,16 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
emitInstGetImportFallback(build, vmRegOp(inst.a), uintOp(inst.b));
|
||||
break;
|
||||
case IrCmd::CONCAT:
|
||||
build.mov(rArg1, rState);
|
||||
build.mov(dwordReg(rArg2), uintOp(inst.b));
|
||||
build.mov(dwordReg(rArg3), vmRegOp(inst.a) + uintOp(inst.b) - 1);
|
||||
build.call(qword[rNativeContext + offsetof(NativeContext, luaV_concat)]);
|
||||
{
|
||||
IrCallWrapperX64 callWrap(regs, build, index);
|
||||
callWrap.addArgument(SizeX64::qword, rState);
|
||||
callWrap.addArgument(SizeX64::dword, int32_t(uintOp(inst.b)));
|
||||
callWrap.addArgument(SizeX64::dword, int32_t(vmRegOp(inst.a) + uintOp(inst.b) - 1));
|
||||
callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaV_concat)]);
|
||||
|
||||
emitUpdateBase(build);
|
||||
break;
|
||||
}
|
||||
case IrCmd::GET_UPVALUE:
|
||||
{
|
||||
ScopedRegX64 tmp1{regs, SizeX64::qword};
|
||||
@ -793,21 +780,26 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
Label next;
|
||||
ScopedRegX64 tmp1{regs, SizeX64::qword};
|
||||
ScopedRegX64 tmp2{regs, SizeX64::qword};
|
||||
ScopedRegX64 tmp3{regs, SizeX64::xmmword};
|
||||
|
||||
build.mov(tmp1.reg, sClosure);
|
||||
build.mov(tmp2.reg, qword[tmp1.reg + offsetof(Closure, l.uprefs) + sizeof(TValue) * vmUpvalueOp(inst.a) + offsetof(TValue, value.gc)]);
|
||||
|
||||
build.mov(tmp1.reg, qword[tmp2.reg + offsetof(UpVal, v)]);
|
||||
|
||||
{
|
||||
ScopedRegX64 tmp3{regs, SizeX64::xmmword};
|
||||
build.vmovups(tmp3.reg, luauReg(vmRegOp(inst.b)));
|
||||
build.vmovups(xmmword[tmp1.reg], tmp3.reg);
|
||||
}
|
||||
|
||||
callBarrierObject(build, tmp1.reg, tmp2.reg, vmRegOp(inst.b), next);
|
||||
tmp1.free();
|
||||
|
||||
callBarrierObject(regs, build, tmp2.release(), {}, vmRegOp(inst.b), next);
|
||||
build.setLabel(next);
|
||||
break;
|
||||
}
|
||||
case IrCmd::PREPARE_FORN:
|
||||
callPrepareForN(build, vmRegOp(inst.a), vmRegOp(inst.b), vmRegOp(inst.c));
|
||||
callPrepareForN(regs, build, vmRegOp(inst.a), vmRegOp(inst.b), vmRegOp(inst.c));
|
||||
break;
|
||||
case IrCmd::CHECK_TAG:
|
||||
if (inst.a.kind == IrOpKind::Inst)
|
||||
@ -863,38 +855,43 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
jumpIfNodeHasNext(build, regOp(inst.a), labelOp(inst.b));
|
||||
break;
|
||||
case IrCmd::INTERRUPT:
|
||||
regs.assertAllFree();
|
||||
emitInterrupt(build, uintOp(inst.a));
|
||||
break;
|
||||
case IrCmd::CHECK_GC:
|
||||
{
|
||||
Label skip;
|
||||
callCheckGc(build, -1, false, skip);
|
||||
callCheckGc(regs, build, skip);
|
||||
build.setLabel(skip);
|
||||
break;
|
||||
}
|
||||
case IrCmd::BARRIER_OBJ:
|
||||
{
|
||||
Label skip;
|
||||
ScopedRegX64 tmp{regs, SizeX64::qword};
|
||||
|
||||
callBarrierObject(build, tmp.reg, regOp(inst.a), vmRegOp(inst.b), skip);
|
||||
callBarrierObject(regs, build, regOp(inst.a), inst.a, vmRegOp(inst.b), skip);
|
||||
build.setLabel(skip);
|
||||
break;
|
||||
}
|
||||
case IrCmd::BARRIER_TABLE_BACK:
|
||||
{
|
||||
Label skip;
|
||||
|
||||
callBarrierTableFast(build, regOp(inst.a), skip);
|
||||
callBarrierTableFast(regs, build, regOp(inst.a), inst.a, skip);
|
||||
build.setLabel(skip);
|
||||
break;
|
||||
}
|
||||
case IrCmd::BARRIER_TABLE_FORWARD:
|
||||
{
|
||||
Label skip;
|
||||
ScopedRegX64 tmp{regs, SizeX64::qword};
|
||||
|
||||
callBarrierTable(build, tmp.reg, regOp(inst.a), vmRegOp(inst.b), skip);
|
||||
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)]);
|
||||
|
||||
build.setLabel(skip);
|
||||
break;
|
||||
}
|
||||
@ -926,11 +923,12 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
build.cmp(tmp2.reg, qword[tmp1.reg + offsetof(UpVal, v)]);
|
||||
build.jcc(ConditionX64::Above, next);
|
||||
|
||||
if (rArg2 != tmp2.reg)
|
||||
build.mov(rArg2, tmp2.reg);
|
||||
tmp1.free();
|
||||
|
||||
build.mov(rArg1, rState);
|
||||
build.call(qword[rNativeContext + offsetof(NativeContext, luaF_close)]);
|
||||
IrCallWrapperX64 callWrap(regs, build, index);
|
||||
callWrap.addArgument(SizeX64::qword, rState);
|
||||
callWrap.addArgument(SizeX64::qword, tmp2);
|
||||
callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaF_close)]);
|
||||
|
||||
build.setLabel(next);
|
||||
break;
|
||||
@ -940,42 +938,53 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
break;
|
||||
|
||||
// Fallbacks to non-IR instruction implementations
|
||||
case IrCmd::LOP_SETLIST:
|
||||
case IrCmd::SETLIST:
|
||||
{
|
||||
Label next;
|
||||
emitInstSetList(build, next, vmRegOp(inst.b), vmRegOp(inst.c), intOp(inst.d), uintOp(inst.e));
|
||||
regs.assertAllFree();
|
||||
emitInstSetList(regs, build, next, vmRegOp(inst.b), vmRegOp(inst.c), intOp(inst.d), uintOp(inst.e));
|
||||
build.setLabel(next);
|
||||
break;
|
||||
}
|
||||
case IrCmd::LOP_CALL:
|
||||
case IrCmd::CALL:
|
||||
regs.assertAllFree();
|
||||
emitInstCall(build, helpers, vmRegOp(inst.a), intOp(inst.b), intOp(inst.c));
|
||||
break;
|
||||
case IrCmd::LOP_RETURN:
|
||||
case IrCmd::RETURN:
|
||||
regs.assertAllFree();
|
||||
emitInstReturn(build, helpers, vmRegOp(inst.a), intOp(inst.b));
|
||||
break;
|
||||
case IrCmd::LOP_FORGLOOP:
|
||||
case IrCmd::FORGLOOP:
|
||||
regs.assertAllFree();
|
||||
emitinstForGLoop(build, vmRegOp(inst.a), intOp(inst.b), labelOp(inst.c), labelOp(inst.d));
|
||||
break;
|
||||
case IrCmd::LOP_FORGLOOP_FALLBACK:
|
||||
emitinstForGLoopFallback(build, uintOp(inst.a), vmRegOp(inst.b), intOp(inst.c), labelOp(inst.d));
|
||||
build.jmp(labelOp(inst.e));
|
||||
case IrCmd::FORGLOOP_FALLBACK:
|
||||
regs.assertAllFree();
|
||||
emitinstForGLoopFallback(build, vmRegOp(inst.a), intOp(inst.b), labelOp(inst.c));
|
||||
build.jmp(labelOp(inst.d));
|
||||
break;
|
||||
case IrCmd::LOP_FORGPREP_XNEXT_FALLBACK:
|
||||
case IrCmd::FORGPREP_XNEXT_FALLBACK:
|
||||
regs.assertAllFree();
|
||||
emitInstForGPrepXnextFallback(build, uintOp(inst.a), vmRegOp(inst.b), labelOp(inst.c));
|
||||
break;
|
||||
case IrCmd::LOP_AND:
|
||||
case IrCmd::AND:
|
||||
regs.assertAllFree();
|
||||
emitInstAnd(build, vmRegOp(inst.a), vmRegOp(inst.b), vmRegOp(inst.c));
|
||||
break;
|
||||
case IrCmd::LOP_ANDK:
|
||||
case IrCmd::ANDK:
|
||||
regs.assertAllFree();
|
||||
emitInstAndK(build, vmRegOp(inst.a), vmRegOp(inst.b), vmConstOp(inst.c));
|
||||
break;
|
||||
case IrCmd::LOP_OR:
|
||||
case IrCmd::OR:
|
||||
regs.assertAllFree();
|
||||
emitInstOr(build, vmRegOp(inst.a), vmRegOp(inst.b), vmRegOp(inst.c));
|
||||
break;
|
||||
case IrCmd::LOP_ORK:
|
||||
case IrCmd::ORK:
|
||||
regs.assertAllFree();
|
||||
emitInstOrK(build, vmRegOp(inst.a), vmRegOp(inst.b), vmConstOp(inst.c));
|
||||
break;
|
||||
case IrCmd::LOP_COVERAGE:
|
||||
case IrCmd::COVERAGE:
|
||||
regs.assertAllFree();
|
||||
emitInstCoverage(build, uintOp(inst.a));
|
||||
break;
|
||||
|
||||
@ -984,12 +993,14 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
LUAU_ASSERT(inst.b.kind == IrOpKind::VmReg);
|
||||
LUAU_ASSERT(inst.c.kind == IrOpKind::VmConst);
|
||||
|
||||
regs.assertAllFree();
|
||||
emitFallback(build, data, 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, data, LOP_SETGLOBAL, uintOp(inst.a));
|
||||
break;
|
||||
case IrCmd::FALLBACK_GETTABLEKS:
|
||||
@ -997,6 +1008,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
LUAU_ASSERT(inst.c.kind == IrOpKind::VmReg);
|
||||
LUAU_ASSERT(inst.d.kind == IrOpKind::VmConst);
|
||||
|
||||
regs.assertAllFree();
|
||||
emitFallback(build, data, LOP_GETTABLEKS, uintOp(inst.a));
|
||||
break;
|
||||
case IrCmd::FALLBACK_SETTABLEKS:
|
||||
@ -1004,6 +1016,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
LUAU_ASSERT(inst.c.kind == IrOpKind::VmReg);
|
||||
LUAU_ASSERT(inst.d.kind == IrOpKind::VmConst);
|
||||
|
||||
regs.assertAllFree();
|
||||
emitFallback(build, data, LOP_SETTABLEKS, uintOp(inst.a));
|
||||
break;
|
||||
case IrCmd::FALLBACK_NAMECALL:
|
||||
@ -1011,32 +1024,38 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
LUAU_ASSERT(inst.c.kind == IrOpKind::VmReg);
|
||||
LUAU_ASSERT(inst.d.kind == IrOpKind::VmConst);
|
||||
|
||||
regs.assertAllFree();
|
||||
emitFallback(build, data, LOP_NAMECALL, uintOp(inst.a));
|
||||
break;
|
||||
case IrCmd::FALLBACK_PREPVARARGS:
|
||||
LUAU_ASSERT(inst.b.kind == IrOpKind::Constant);
|
||||
|
||||
regs.assertAllFree();
|
||||
emitFallback(build, data, 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, data, 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, data, 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, data, LOP_DUPCLOSURE, uintOp(inst.a));
|
||||
break;
|
||||
case IrCmd::FALLBACK_FORGPREP:
|
||||
regs.assertAllFree();
|
||||
emitFallback(build, data, LOP_FORGPREP, uintOp(inst.a));
|
||||
break;
|
||||
default:
|
||||
|
@ -3,8 +3,7 @@
|
||||
|
||||
#include "Luau/AssemblyBuilderX64.h"
|
||||
#include "Luau/IrData.h"
|
||||
|
||||
#include "IrRegAllocX64.h"
|
||||
#include "Luau/IrRegAllocX64.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
|
174
CodeGen/src/IrRegAllocA64.cpp
Normal file
174
CodeGen/src/IrRegAllocA64.cpp
Normal file
@ -0,0 +1,174 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "IrRegAllocA64.h"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <intrin.h>
|
||||
#endif
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
namespace CodeGen
|
||||
{
|
||||
namespace A64
|
||||
{
|
||||
|
||||
inline int setBit(uint32_t n)
|
||||
{
|
||||
LUAU_ASSERT(n);
|
||||
|
||||
#ifdef _MSC_VER
|
||||
unsigned long rl;
|
||||
_BitScanReverse(&rl, n);
|
||||
return int(rl);
|
||||
#else
|
||||
return 31 - __builtin_clz(n);
|
||||
#endif
|
||||
}
|
||||
|
||||
IrRegAllocA64::IrRegAllocA64(IrFunction& function, std::initializer_list<std::pair<RegisterA64, RegisterA64>> regs)
|
||||
: function(function)
|
||||
{
|
||||
for (auto& p : regs)
|
||||
{
|
||||
LUAU_ASSERT(p.first.kind == p.second.kind && p.first.index <= p.second.index);
|
||||
|
||||
Set& set = getSet(p.first.kind);
|
||||
|
||||
for (int i = p.first.index; i <= p.second.index; ++i)
|
||||
set.base |= 1u << i;
|
||||
}
|
||||
|
||||
gpr.free = gpr.base;
|
||||
simd.free = simd.base;
|
||||
}
|
||||
|
||||
RegisterA64 IrRegAllocA64::allocReg(KindA64 kind)
|
||||
{
|
||||
Set& set = getSet(kind);
|
||||
|
||||
if (set.free == 0)
|
||||
{
|
||||
LUAU_ASSERT(!"Out of registers to allocate");
|
||||
return noreg;
|
||||
}
|
||||
|
||||
int index = setBit(set.free);
|
||||
set.free &= ~(1u << index);
|
||||
|
||||
return RegisterA64{kind, uint8_t(index)};
|
||||
}
|
||||
|
||||
RegisterA64 IrRegAllocA64::allocTemp(KindA64 kind)
|
||||
{
|
||||
Set& set = getSet(kind);
|
||||
|
||||
if (set.free == 0)
|
||||
{
|
||||
LUAU_ASSERT(!"Out of registers to allocate");
|
||||
return noreg;
|
||||
}
|
||||
|
||||
int index = setBit(set.free);
|
||||
|
||||
set.free &= ~(1u << index);
|
||||
set.temp |= 1u << index;
|
||||
|
||||
return RegisterA64{kind, uint8_t(index)};
|
||||
}
|
||||
|
||||
RegisterA64 IrRegAllocA64::allocReuse(KindA64 kind, uint32_t index, std::initializer_list<IrOp> oprefs)
|
||||
{
|
||||
for (IrOp op : oprefs)
|
||||
{
|
||||
if (op.kind != IrOpKind::Inst)
|
||||
continue;
|
||||
|
||||
IrInst& source = function.instructions[op.index];
|
||||
|
||||
if (source.lastUse == index && !source.reusedReg)
|
||||
{
|
||||
LUAU_ASSERT(source.regA64.kind == kind);
|
||||
|
||||
source.reusedReg = true;
|
||||
return source.regA64;
|
||||
}
|
||||
}
|
||||
|
||||
return allocReg(kind);
|
||||
}
|
||||
|
||||
void IrRegAllocA64::freeReg(RegisterA64 reg)
|
||||
{
|
||||
Set& set = getSet(reg.kind);
|
||||
|
||||
LUAU_ASSERT((set.base & (1u << reg.index)) != 0);
|
||||
LUAU_ASSERT((set.free & (1u << reg.index)) == 0);
|
||||
set.free |= 1u << reg.index;
|
||||
}
|
||||
|
||||
void IrRegAllocA64::freeLastUseReg(IrInst& target, uint32_t index)
|
||||
{
|
||||
if (target.lastUse == index && !target.reusedReg)
|
||||
{
|
||||
// Register might have already been freed if it had multiple uses inside a single instruction
|
||||
if (target.regA64 == noreg)
|
||||
return;
|
||||
|
||||
freeReg(target.regA64);
|
||||
target.regA64 = noreg;
|
||||
}
|
||||
}
|
||||
|
||||
void IrRegAllocA64::freeLastUseRegs(const IrInst& inst, uint32_t index)
|
||||
{
|
||||
auto checkOp = [this, index](IrOp op) {
|
||||
if (op.kind == IrOpKind::Inst)
|
||||
freeLastUseReg(function.instructions[op.index], index);
|
||||
};
|
||||
|
||||
checkOp(inst.a);
|
||||
checkOp(inst.b);
|
||||
checkOp(inst.c);
|
||||
checkOp(inst.d);
|
||||
checkOp(inst.e);
|
||||
checkOp(inst.f);
|
||||
}
|
||||
|
||||
void IrRegAllocA64::freeTempRegs()
|
||||
{
|
||||
LUAU_ASSERT((gpr.free & gpr.temp) == 0);
|
||||
gpr.free |= gpr.temp;
|
||||
gpr.temp = 0;
|
||||
|
||||
LUAU_ASSERT((simd.free & simd.temp) == 0);
|
||||
simd.free |= simd.temp;
|
||||
simd.temp = 0;
|
||||
}
|
||||
|
||||
void IrRegAllocA64::assertAllFree() const
|
||||
{
|
||||
LUAU_ASSERT(gpr.free == gpr.base);
|
||||
LUAU_ASSERT(simd.free == simd.base);
|
||||
}
|
||||
|
||||
IrRegAllocA64::Set& IrRegAllocA64::getSet(KindA64 kind)
|
||||
{
|
||||
switch (kind)
|
||||
{
|
||||
case KindA64::x:
|
||||
case KindA64::w:
|
||||
return gpr;
|
||||
|
||||
case KindA64::d:
|
||||
case KindA64::q:
|
||||
return simd;
|
||||
|
||||
default:
|
||||
LUAU_ASSERT(!"Unexpected register kind");
|
||||
LUAU_UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace A64
|
||||
} // namespace CodeGen
|
||||
} // namespace Luau
|
55
CodeGen/src/IrRegAllocA64.h
Normal file
55
CodeGen/src/IrRegAllocA64.h
Normal file
@ -0,0 +1,55 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/IrData.h"
|
||||
#include "Luau/RegisterA64.h"
|
||||
|
||||
#include <initializer_list>
|
||||
#include <utility>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
namespace CodeGen
|
||||
{
|
||||
namespace A64
|
||||
{
|
||||
|
||||
struct IrRegAllocA64
|
||||
{
|
||||
IrRegAllocA64(IrFunction& function, std::initializer_list<std::pair<RegisterA64, RegisterA64>> regs);
|
||||
|
||||
RegisterA64 allocReg(KindA64 kind);
|
||||
RegisterA64 allocTemp(KindA64 kind);
|
||||
RegisterA64 allocReuse(KindA64 kind, uint32_t index, std::initializer_list<IrOp> oprefs);
|
||||
|
||||
void freeReg(RegisterA64 reg);
|
||||
|
||||
void freeLastUseReg(IrInst& target, uint32_t index);
|
||||
void freeLastUseRegs(const IrInst& inst, uint32_t index);
|
||||
|
||||
void freeTempRegs();
|
||||
|
||||
void assertAllFree() const;
|
||||
|
||||
IrFunction& function;
|
||||
|
||||
struct Set
|
||||
{
|
||||
// which registers are in the set that the allocator manages (initialized at construction)
|
||||
uint32_t base = 0;
|
||||
|
||||
// which subset of initial set is free
|
||||
uint32_t free = 0;
|
||||
|
||||
// which subset of initial set is allocated as temporary
|
||||
uint32_t temp = 0;
|
||||
};
|
||||
|
||||
Set gpr, simd;
|
||||
|
||||
Set& getSet(KindA64 kind);
|
||||
};
|
||||
|
||||
} // namespace A64
|
||||
} // namespace CodeGen
|
||||
} // namespace Luau
|
@ -1,19 +1,5 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "IrRegAllocX64.h"
|
||||
|
||||
#include "Luau/CodeGen.h"
|
||||
#include "Luau/DenseHash.h"
|
||||
#include "Luau/IrAnalysis.h"
|
||||
#include "Luau/IrDump.h"
|
||||
#include "Luau/IrUtils.h"
|
||||
|
||||
#include "EmitCommonX64.h"
|
||||
#include "EmitInstructionX64.h"
|
||||
#include "NativeState.h"
|
||||
|
||||
#include "lstate.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include "Luau/IrRegAllocX64.h"
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -108,13 +94,21 @@ RegisterX64 IrRegAllocX64::allocXmmRegOrReuse(uint32_t index, std::initializer_l
|
||||
return allocXmmReg();
|
||||
}
|
||||
|
||||
RegisterX64 IrRegAllocX64::takeGprReg(RegisterX64 reg)
|
||||
RegisterX64 IrRegAllocX64::takeReg(RegisterX64 reg)
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
LUAU_ASSERT(freeXmmMap[reg.index]);
|
||||
freeXmmMap[reg.index] = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_ASSERT(freeGprMap[reg.index]);
|
||||
|
||||
freeGprMap[reg.index] = false;
|
||||
}
|
||||
|
||||
return reg;
|
||||
}
|
||||
|
||||
@ -134,7 +128,7 @@ void IrRegAllocX64::freeReg(RegisterX64 reg)
|
||||
|
||||
void IrRegAllocX64::freeLastUseReg(IrInst& target, uint32_t index)
|
||||
{
|
||||
if (target.lastUse == index && !target.reusedReg)
|
||||
if (isLastUseReg(target, index))
|
||||
{
|
||||
// Register might have already been freed if it had multiple uses inside a single instruction
|
||||
if (target.regX64 == noreg)
|
||||
@ -160,6 +154,35 @@ void IrRegAllocX64::freeLastUseRegs(const IrInst& inst, uint32_t index)
|
||||
checkOp(inst.f);
|
||||
}
|
||||
|
||||
bool IrRegAllocX64::isLastUseReg(const IrInst& target, uint32_t index) const
|
||||
{
|
||||
return target.lastUse == index && !target.reusedReg;
|
||||
}
|
||||
|
||||
bool IrRegAllocX64::shouldFreeGpr(RegisterX64 reg) const
|
||||
{
|
||||
if (reg == noreg)
|
||||
return false;
|
||||
|
||||
LUAU_ASSERT(reg.size != SizeX64::xmmword);
|
||||
|
||||
for (RegisterX64 gpr : kGprAllocOrder)
|
||||
{
|
||||
if (reg.index == gpr.index)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void IrRegAllocX64::assertFree(RegisterX64 reg) const
|
||||
{
|
||||
if (reg.size == SizeX64::xmmword)
|
||||
LUAU_ASSERT(freeXmmMap[reg.index]);
|
||||
else
|
||||
LUAU_ASSERT(freeGprMap[reg.index]);
|
||||
}
|
||||
|
||||
void IrRegAllocX64::assertAllFree() const
|
||||
{
|
||||
for (RegisterX64 reg : kGprAllocOrder)
|
||||
@ -211,6 +234,13 @@ void ScopedRegX64::free()
|
||||
reg = noreg;
|
||||
}
|
||||
|
||||
RegisterX64 ScopedRegX64::release()
|
||||
{
|
||||
RegisterX64 tmp = reg;
|
||||
reg = noreg;
|
||||
return tmp;
|
||||
}
|
||||
|
||||
} // namespace X64
|
||||
} // namespace CodeGen
|
||||
} // namespace Luau
|
||||
|
@ -6,7 +6,6 @@
|
||||
|
||||
#include "lstate.h"
|
||||
|
||||
// TODO: should be possible to handle fastcalls in contexts where nresults is -1 by adding the adjustment instruction
|
||||
// TODO: when nresults is less than our actual result count, we can skip computing/writing unused results
|
||||
|
||||
namespace Luau
|
||||
@ -26,7 +25,7 @@ BuiltinImplResult translateBuiltinNumberToNumber(
|
||||
build.loadAndCheckTag(build.vmReg(arg), LUA_TNUMBER, fallback);
|
||||
build.inst(IrCmd::FASTCALL, build.constUint(bfid), build.vmReg(ra), build.vmReg(arg), args, build.constInt(nparams), build.constInt(nresults));
|
||||
|
||||
// TODO: tag update might not be required, we place it here now because FASTCALL is not modeled in constant propagation yet
|
||||
if (ra != arg)
|
||||
build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TNUMBER));
|
||||
|
||||
return {BuiltinImplType::UsesFallback, 1};
|
||||
@ -43,7 +42,7 @@ BuiltinImplResult translateBuiltin2NumberToNumber(
|
||||
build.loadAndCheckTag(args, LUA_TNUMBER, fallback);
|
||||
build.inst(IrCmd::FASTCALL, build.constUint(bfid), build.vmReg(ra), build.vmReg(arg), args, build.constInt(nparams), build.constInt(nresults));
|
||||
|
||||
// TODO:tag update might not be required, we place it here now because FASTCALL is not modeled in constant propagation yet
|
||||
if (ra != arg)
|
||||
build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TNUMBER));
|
||||
|
||||
return {BuiltinImplType::UsesFallback, 1};
|
||||
@ -59,8 +58,9 @@ BuiltinImplResult translateBuiltinNumberTo2Number(
|
||||
build.loadAndCheckTag(build.vmReg(arg), LUA_TNUMBER, fallback);
|
||||
build.inst(IrCmd::FASTCALL, build.constUint(bfid), build.vmReg(ra), build.vmReg(arg), args, build.constInt(nparams), build.constInt(nresults));
|
||||
|
||||
// TODO: some tag updates might not be required, we place them here now because FASTCALL is not modeled in constant propagation yet
|
||||
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));
|
||||
|
||||
return {BuiltinImplType::UsesFallback, 2};
|
||||
@ -131,7 +131,7 @@ BuiltinImplResult translateBuiltinMathLog(
|
||||
|
||||
build.inst(IrCmd::FASTCALL, build.constUint(bfid), build.vmReg(ra), build.vmReg(arg), args, build.constInt(nparams), build.constInt(nresults));
|
||||
|
||||
// TODO: tag update might not be required, we place it here now because FASTCALL is not modeled in constant propagation yet
|
||||
if (ra != arg)
|
||||
build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TNUMBER));
|
||||
|
||||
return {BuiltinImplType::UsesFallback, 1};
|
||||
@ -210,6 +210,44 @@ BuiltinImplResult translateBuiltinMathClamp(IrBuilder& build, int nparams, int r
|
||||
return {BuiltinImplType::UsesFallback, 1};
|
||||
}
|
||||
|
||||
BuiltinImplResult translateBuiltinMathUnary(IrBuilder& build, IrCmd cmd, int nparams, int ra, int arg, int nresults, IrOp fallback)
|
||||
{
|
||||
if (nparams < 1 || nresults > 1)
|
||||
return {BuiltinImplType::None, -1};
|
||||
|
||||
build.loadAndCheckTag(build.vmReg(arg), LUA_TNUMBER, fallback);
|
||||
|
||||
IrOp varg = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(arg));
|
||||
IrOp result = build.inst(cmd, varg);
|
||||
|
||||
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(ra), result);
|
||||
|
||||
if (ra != arg)
|
||||
build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TNUMBER));
|
||||
|
||||
return {BuiltinImplType::UsesFallback, 1};
|
||||
}
|
||||
|
||||
BuiltinImplResult translateBuiltinMathBinary(IrBuilder& build, IrCmd cmd, int nparams, int ra, int arg, IrOp args, int nresults, IrOp fallback)
|
||||
{
|
||||
if (nparams < 2 || nresults > 1)
|
||||
return {BuiltinImplType::None, -1};
|
||||
|
||||
build.loadAndCheckTag(build.vmReg(arg), LUA_TNUMBER, fallback);
|
||||
build.loadAndCheckTag(args, LUA_TNUMBER, fallback);
|
||||
|
||||
IrOp lhs = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(arg));
|
||||
IrOp rhs = build.inst(IrCmd::LOAD_DOUBLE, args);
|
||||
IrOp result = build.inst(cmd, lhs, rhs);
|
||||
|
||||
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(ra), result);
|
||||
|
||||
if (ra != arg)
|
||||
build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TNUMBER));
|
||||
|
||||
return {BuiltinImplType::UsesFallback, 1};
|
||||
}
|
||||
|
||||
BuiltinImplResult translateBuiltinType(IrBuilder& build, int nparams, int ra, int arg, IrOp args, int nresults, IrOp fallback)
|
||||
{
|
||||
if (nparams < 1 || nresults > 1)
|
||||
@ -218,7 +256,6 @@ BuiltinImplResult translateBuiltinType(IrBuilder& build, int nparams, int ra, in
|
||||
build.inst(
|
||||
IrCmd::FASTCALL, build.constUint(LBF_TYPE), build.vmReg(ra), build.vmReg(arg), args, build.constInt(nparams), build.constInt(nresults));
|
||||
|
||||
// TODO: tag update might not be required, we place it here now because FASTCALL is not modeled in constant propagation yet
|
||||
build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TSTRING));
|
||||
|
||||
return {BuiltinImplType::UsesFallback, 1};
|
||||
@ -232,7 +269,6 @@ BuiltinImplResult translateBuiltinTypeof(IrBuilder& build, int nparams, int ra,
|
||||
build.inst(
|
||||
IrCmd::FASTCALL, build.constUint(LBF_TYPEOF), build.vmReg(ra), build.vmReg(arg), args, build.constInt(nparams), build.constInt(nresults));
|
||||
|
||||
// TODO: tag update might not be required, we place it here now because FASTCALL is not modeled in constant propagation yet
|
||||
build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TSTRING));
|
||||
|
||||
return {BuiltinImplType::UsesFallback, 1};
|
||||
@ -261,9 +297,17 @@ BuiltinImplResult translateBuiltin(IrBuilder& build, int bfid, int ra, int arg,
|
||||
case LBF_MATH_CLAMP:
|
||||
return translateBuiltinMathClamp(build, nparams, ra, arg, args, nresults, fallback);
|
||||
case LBF_MATH_FLOOR:
|
||||
return translateBuiltinMathUnary(build, IrCmd::FLOOR_NUM, nparams, ra, arg, nresults, fallback);
|
||||
case LBF_MATH_CEIL:
|
||||
return translateBuiltinMathUnary(build, IrCmd::CEIL_NUM, nparams, ra, arg, nresults, fallback);
|
||||
case LBF_MATH_SQRT:
|
||||
return translateBuiltinMathUnary(build, IrCmd::SQRT_NUM, nparams, ra, arg, nresults, fallback);
|
||||
case LBF_MATH_ABS:
|
||||
return translateBuiltinMathUnary(build, IrCmd::ABS_NUM, nparams, ra, arg, nresults, fallback);
|
||||
case LBF_MATH_ROUND:
|
||||
return translateBuiltinMathUnary(build, IrCmd::ROUND_NUM, nparams, ra, arg, nresults, fallback);
|
||||
case LBF_MATH_POW:
|
||||
return translateBuiltinMathBinary(build, IrCmd::POW_NUM, nparams, ra, arg, args, nresults, fallback);
|
||||
case LBF_MATH_EXP:
|
||||
case LBF_MATH_ASIN:
|
||||
case LBF_MATH_SIN:
|
||||
@ -275,11 +319,9 @@ BuiltinImplResult translateBuiltin(IrBuilder& build, int bfid, int ra, int arg,
|
||||
case LBF_MATH_TAN:
|
||||
case LBF_MATH_TANH:
|
||||
case LBF_MATH_LOG10:
|
||||
case LBF_MATH_ROUND:
|
||||
case LBF_MATH_SIGN:
|
||||
return translateBuiltinNumberToNumber(build, LuauBuiltinFunction(bfid), nparams, ra, arg, args, nresults, fallback);
|
||||
case LBF_MATH_FMOD:
|
||||
case LBF_MATH_POW:
|
||||
case LBF_MATH_ATAN2:
|
||||
case LBF_MATH_LDEXP:
|
||||
return translateBuiltin2NumberToNumber(build, LuauBuiltinFunction(bfid), nparams, ra, arg, args, nresults, fallback);
|
||||
|
@ -296,12 +296,23 @@ static void translateInstBinaryNumeric(IrBuilder& build, int ra, int rb, int rc,
|
||||
IrOp vb = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(rb));
|
||||
IrOp vc;
|
||||
|
||||
IrOp result;
|
||||
|
||||
if (opc.kind == IrOpKind::VmConst)
|
||||
{
|
||||
LUAU_ASSERT(build.function.proto);
|
||||
TValue protok = build.function.proto->k[opc.index];
|
||||
|
||||
LUAU_ASSERT(protok.tt == LUA_TNUMBER);
|
||||
|
||||
// VM has special cases for exponentiation with constants
|
||||
if (tm == TM_POW && protok.value.n == 0.5)
|
||||
result = build.inst(IrCmd::SQRT_NUM, vb);
|
||||
else if (tm == TM_POW && protok.value.n == 2.0)
|
||||
result = build.inst(IrCmd::MUL_NUM, vb, vb);
|
||||
else if (tm == TM_POW && protok.value.n == 3.0)
|
||||
result = build.inst(IrCmd::MUL_NUM, vb, build.inst(IrCmd::MUL_NUM, vb, vb));
|
||||
else
|
||||
vc = build.constDouble(protok.value.n);
|
||||
}
|
||||
else
|
||||
@ -309,33 +320,36 @@ static void translateInstBinaryNumeric(IrBuilder& build, int ra, int rb, int rc,
|
||||
vc = build.inst(IrCmd::LOAD_DOUBLE, opc);
|
||||
}
|
||||
|
||||
IrOp va;
|
||||
if (result.kind == IrOpKind::None)
|
||||
{
|
||||
LUAU_ASSERT(vc.kind != IrOpKind::None);
|
||||
|
||||
switch (tm)
|
||||
{
|
||||
case TM_ADD:
|
||||
va = build.inst(IrCmd::ADD_NUM, vb, vc);
|
||||
result = build.inst(IrCmd::ADD_NUM, vb, vc);
|
||||
break;
|
||||
case TM_SUB:
|
||||
va = build.inst(IrCmd::SUB_NUM, vb, vc);
|
||||
result = build.inst(IrCmd::SUB_NUM, vb, vc);
|
||||
break;
|
||||
case TM_MUL:
|
||||
va = build.inst(IrCmd::MUL_NUM, vb, vc);
|
||||
result = build.inst(IrCmd::MUL_NUM, vb, vc);
|
||||
break;
|
||||
case TM_DIV:
|
||||
va = build.inst(IrCmd::DIV_NUM, vb, vc);
|
||||
result = build.inst(IrCmd::DIV_NUM, vb, vc);
|
||||
break;
|
||||
case TM_MOD:
|
||||
va = build.inst(IrCmd::MOD_NUM, vb, vc);
|
||||
result = build.inst(IrCmd::MOD_NUM, vb, vc);
|
||||
break;
|
||||
case TM_POW:
|
||||
va = build.inst(IrCmd::POW_NUM, vb, vc);
|
||||
result = build.inst(IrCmd::POW_NUM, vb, vc);
|
||||
break;
|
||||
default:
|
||||
LUAU_ASSERT(!"unsupported binary op");
|
||||
}
|
||||
}
|
||||
|
||||
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(ra), va);
|
||||
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(ra), result);
|
||||
|
||||
if (ra != rb && ra != rc) // TODO: optimization should handle second check, but we'll test this later
|
||||
build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TNUMBER));
|
||||
@ -638,7 +652,7 @@ void translateInstForGPrepNext(IrBuilder& build, const Instruction* pc, int pcpo
|
||||
build.inst(IrCmd::JUMP, target);
|
||||
|
||||
build.beginBlock(fallback);
|
||||
build.inst(IrCmd::LOP_FORGPREP_XNEXT_FALLBACK, build.constUint(pcpos), build.vmReg(ra), target);
|
||||
build.inst(IrCmd::FORGPREP_XNEXT_FALLBACK, build.constUint(pcpos), build.vmReg(ra), target);
|
||||
}
|
||||
|
||||
void translateInstForGPrepInext(IrBuilder& build, const Instruction* pc, int pcpos)
|
||||
@ -670,7 +684,7 @@ void translateInstForGPrepInext(IrBuilder& build, const Instruction* pc, int pcp
|
||||
build.inst(IrCmd::JUMP, target);
|
||||
|
||||
build.beginBlock(fallback);
|
||||
build.inst(IrCmd::LOP_FORGPREP_XNEXT_FALLBACK, build.constUint(pcpos), build.vmReg(ra), target);
|
||||
build.inst(IrCmd::FORGPREP_XNEXT_FALLBACK, build.constUint(pcpos), build.vmReg(ra), target);
|
||||
}
|
||||
|
||||
void translateInstForGLoopIpairs(IrBuilder& build, const Instruction* pc, int pcpos)
|
||||
@ -721,7 +735,8 @@ void translateInstForGLoopIpairs(IrBuilder& build, const Instruction* pc, int pc
|
||||
build.inst(IrCmd::JUMP, loopRepeat);
|
||||
|
||||
build.beginBlock(fallback);
|
||||
build.inst(IrCmd::LOP_FORGLOOP_FALLBACK, build.constUint(pcpos), build.vmReg(ra), build.constInt(int(pc[1])), loopRepeat, loopExit);
|
||||
build.inst(IrCmd::SET_SAVEDPC, build.constUint(pcpos + 1));
|
||||
build.inst(IrCmd::FORGLOOP_FALLBACK, build.vmReg(ra), build.constInt(int(pc[1])), loopRepeat, loopExit);
|
||||
|
||||
// Fallthrough in original bytecode is implicit, so we start next internal block here
|
||||
if (build.isInternalBlock(loopExit))
|
||||
|
@ -320,6 +320,26 @@ void foldConstants(IrBuilder& build, IrFunction& function, IrBlock& block, uint3
|
||||
if (inst.a.kind == IrOpKind::Constant)
|
||||
substitute(function, inst, build.constDouble(-function.doubleOp(inst.a)));
|
||||
break;
|
||||
case IrCmd::FLOOR_NUM:
|
||||
if (inst.a.kind == IrOpKind::Constant)
|
||||
substitute(function, inst, build.constDouble(floor(function.doubleOp(inst.a))));
|
||||
break;
|
||||
case IrCmd::CEIL_NUM:
|
||||
if (inst.a.kind == IrOpKind::Constant)
|
||||
substitute(function, inst, build.constDouble(ceil(function.doubleOp(inst.a))));
|
||||
break;
|
||||
case IrCmd::ROUND_NUM:
|
||||
if (inst.a.kind == IrOpKind::Constant)
|
||||
substitute(function, inst, build.constDouble(round(function.doubleOp(inst.a))));
|
||||
break;
|
||||
case IrCmd::SQRT_NUM:
|
||||
if (inst.a.kind == IrOpKind::Constant)
|
||||
substitute(function, inst, build.constDouble(sqrt(function.doubleOp(inst.a))));
|
||||
break;
|
||||
case IrCmd::ABS_NUM:
|
||||
if (inst.a.kind == IrOpKind::Constant)
|
||||
substitute(function, inst, build.constDouble(fabs(function.doubleOp(inst.a))));
|
||||
break;
|
||||
case IrCmd::NOT_ANY:
|
||||
if (inst.a.kind == IrOpKind::Constant)
|
||||
{
|
||||
|
@ -109,6 +109,8 @@ void initHelperFunctions(NativeState& data)
|
||||
data.context.forgPrepXnextFallback = forgPrepXnextFallback;
|
||||
data.context.callProlog = callProlog;
|
||||
data.context.callEpilogC = callEpilogC;
|
||||
|
||||
data.context.callFallback = callFallback;
|
||||
data.context.returnFallback = returnFallback;
|
||||
}
|
||||
|
||||
|
@ -101,7 +101,9 @@ struct NativeContext
|
||||
void (*forgPrepXnextFallback)(lua_State* L, TValue* ra, int pc) = nullptr;
|
||||
Closure* (*callProlog)(lua_State* L, TValue* ra, StkId argtop, int nresults) = nullptr;
|
||||
void (*callEpilogC)(lua_State* L, int nresults, int n) = nullptr;
|
||||
const Instruction* (*returnFallback)(lua_State* L, StkId ra, int n) = nullptr;
|
||||
|
||||
Closure* (*callFallback)(lua_State* L, StkId ra, StkId argtop, int nresults) = nullptr;
|
||||
Closure* (*returnFallback)(lua_State* L, StkId ra, int n) = nullptr;
|
||||
|
||||
// Opcode fallbacks, implemented in C
|
||||
NativeFallback fallback[LOP__COUNT] = {};
|
||||
|
@ -503,10 +503,10 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
|
||||
}
|
||||
}
|
||||
break;
|
||||
case IrCmd::LOP_AND:
|
||||
case IrCmd::LOP_ANDK:
|
||||
case IrCmd::LOP_OR:
|
||||
case IrCmd::LOP_ORK:
|
||||
case IrCmd::AND:
|
||||
case IrCmd::ANDK:
|
||||
case IrCmd::OR:
|
||||
case IrCmd::ORK:
|
||||
state.invalidate(inst.a);
|
||||
break;
|
||||
case IrCmd::FASTCALL:
|
||||
@ -533,6 +533,11 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
|
||||
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::NOT_ANY:
|
||||
case IrCmd::JUMP:
|
||||
case IrCmd::JUMP_EQ_POINTER:
|
||||
@ -547,10 +552,10 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
|
||||
case IrCmd::CHECK_SLOT_MATCH:
|
||||
case IrCmd::CHECK_NODE_NO_NEXT:
|
||||
case IrCmd::BARRIER_TABLE_BACK:
|
||||
case IrCmd::LOP_RETURN:
|
||||
case IrCmd::LOP_COVERAGE:
|
||||
case IrCmd::RETURN:
|
||||
case IrCmd::COVERAGE:
|
||||
case IrCmd::SET_UPVALUE:
|
||||
case IrCmd::LOP_SETLIST: // We don't track table state that this can invalidate
|
||||
case IrCmd::SETLIST: // We don't track table state that this can invalidate
|
||||
case IrCmd::SET_SAVEDPC: // TODO: we may be able to remove some updates to PC
|
||||
case IrCmd::CLOSE_UPVALS: // Doesn't change memory that we track
|
||||
case IrCmd::CAPTURE:
|
||||
@ -599,18 +604,18 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
|
||||
case IrCmd::INTERRUPT:
|
||||
state.invalidateUserCall();
|
||||
break;
|
||||
case IrCmd::LOP_CALL:
|
||||
case IrCmd::CALL:
|
||||
state.invalidateRegistersFrom(inst.a.index);
|
||||
state.invalidateUserCall();
|
||||
break;
|
||||
case IrCmd::LOP_FORGLOOP:
|
||||
case IrCmd::FORGLOOP:
|
||||
state.invalidateRegistersFrom(inst.a.index + 2); // Rn and Rn+1 are not modified
|
||||
break;
|
||||
case IrCmd::LOP_FORGLOOP_FALLBACK:
|
||||
state.invalidateRegistersFrom(inst.b.index + 2); // Rn and Rn+1 are not modified
|
||||
case IrCmd::FORGLOOP_FALLBACK:
|
||||
state.invalidateRegistersFrom(inst.a.index + 2); // Rn and Rn+1 are not modified
|
||||
state.invalidateUserCall();
|
||||
break;
|
||||
case IrCmd::LOP_FORGPREP_XNEXT_FALLBACK:
|
||||
case IrCmd::FORGPREP_XNEXT_FALLBACK:
|
||||
// This fallback only conditionally throws an exception
|
||||
break;
|
||||
case IrCmd::FALLBACK_GETGLOBAL:
|
||||
|
@ -25,8 +25,6 @@ LUAU_FASTINTVARIABLE(LuauCompileInlineThreshold, 25)
|
||||
LUAU_FASTINTVARIABLE(LuauCompileInlineThresholdMaxBoost, 300)
|
||||
LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauCompileBuiltinArity, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
@ -295,7 +293,7 @@ struct Compiler
|
||||
|
||||
// handles builtin calls that can't be constant-folded but are known to return one value
|
||||
// note: optimizationLevel check is technically redundant but it's important that we never optimize based on builtins in O1
|
||||
if (FFlag::LuauCompileBuiltinArity && options.optimizationLevel >= 2)
|
||||
if (options.optimizationLevel >= 2)
|
||||
if (int* bfid = builtins.find(expr))
|
||||
return getBuiltinInfo(*bfid).results != 1;
|
||||
|
||||
@ -766,7 +764,7 @@ struct Compiler
|
||||
{
|
||||
if (!isExprMultRet(expr->args.data[expr->args.size - 1]))
|
||||
return compileExprFastcallN(expr, target, targetCount, targetTop, multRet, regs, bfid);
|
||||
else if (FFlag::LuauCompileBuiltinArity && options.optimizationLevel >= 2 && int(expr->args.size) == getBuiltinInfo(bfid).params)
|
||||
else if (options.optimizationLevel >= 2 && int(expr->args.size) == getBuiltinInfo(bfid).params)
|
||||
return compileExprFastcallN(expr, target, targetCount, targetTop, multRet, regs, bfid);
|
||||
}
|
||||
|
||||
|
@ -65,8 +65,10 @@ target_sources(Luau.CodeGen PRIVATE
|
||||
CodeGen/include/Luau/ConditionX64.h
|
||||
CodeGen/include/Luau/IrAnalysis.h
|
||||
CodeGen/include/Luau/IrBuilder.h
|
||||
CodeGen/include/Luau/IrCallWrapperX64.h
|
||||
CodeGen/include/Luau/IrDump.h
|
||||
CodeGen/include/Luau/IrData.h
|
||||
CodeGen/include/Luau/IrRegAllocX64.h
|
||||
CodeGen/include/Luau/IrUtils.h
|
||||
CodeGen/include/Luau/Label.h
|
||||
CodeGen/include/Luau/OperandX64.h
|
||||
@ -94,9 +96,11 @@ target_sources(Luau.CodeGen PRIVATE
|
||||
CodeGen/src/Fallbacks.cpp
|
||||
CodeGen/src/IrAnalysis.cpp
|
||||
CodeGen/src/IrBuilder.cpp
|
||||
CodeGen/src/IrCallWrapperX64.cpp
|
||||
CodeGen/src/IrDump.cpp
|
||||
CodeGen/src/IrLoweringA64.cpp
|
||||
CodeGen/src/IrLoweringX64.cpp
|
||||
CodeGen/src/IrRegAllocA64.cpp
|
||||
CodeGen/src/IrRegAllocX64.cpp
|
||||
CodeGen/src/IrTranslateBuiltins.cpp
|
||||
CodeGen/src/IrTranslation.cpp
|
||||
@ -122,7 +126,7 @@ target_sources(Luau.CodeGen PRIVATE
|
||||
CodeGen/src/FallbacksProlog.h
|
||||
CodeGen/src/IrLoweringA64.h
|
||||
CodeGen/src/IrLoweringX64.h
|
||||
CodeGen/src/IrRegAllocX64.h
|
||||
CodeGen/src/IrRegAllocA64.h
|
||||
CodeGen/src/IrTranslateBuiltins.h
|
||||
CodeGen/src/IrTranslation.h
|
||||
CodeGen/src/NativeState.h
|
||||
@ -342,6 +346,7 @@ if(TARGET Luau.UnitTest)
|
||||
tests/Fixture.h
|
||||
tests/IostreamOptional.h
|
||||
tests/ScopedFlags.h
|
||||
tests/AssemblyBuilderA64.test.cpp
|
||||
tests/AssemblyBuilderX64.test.cpp
|
||||
tests/AstJsonEncoder.test.cpp
|
||||
tests/AstQuery.test.cpp
|
||||
@ -358,6 +363,7 @@ if(TARGET Luau.UnitTest)
|
||||
tests/Error.test.cpp
|
||||
tests/Frontend.test.cpp
|
||||
tests/IrBuilder.test.cpp
|
||||
tests/IrCallWrapperX64.test.cpp
|
||||
tests/JsonEmitter.test.cpp
|
||||
tests/Lexer.test.cpp
|
||||
tests/Linter.test.cpp
|
||||
|
@ -23,8 +23,6 @@
|
||||
#endif
|
||||
#endif
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauBuiltinSSE41, false)
|
||||
|
||||
// luauF functions implement FASTCALL instruction that performs a direct execution of some builtin functions from the VM
|
||||
// The rule of thumb is that FASTCALL functions can not call user code, yield, fail, or reallocate stack.
|
||||
// If types of the arguments mismatch, luauF_* needs to return -1 and the execution will fall back to the usual call path
|
||||
@ -105,9 +103,7 @@ static int luauF_atan(lua_State* L, StkId res, TValue* arg0, int nresults, StkId
|
||||
return -1;
|
||||
}
|
||||
|
||||
// TODO: LUAU_NOINLINE can be removed with LuauBuiltinSSE41
|
||||
LUAU_FASTMATH_BEGIN
|
||||
LUAU_NOINLINE
|
||||
static int luauF_ceil(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams)
|
||||
{
|
||||
if (nparams >= 1 && nresults <= 1 && ttisnumber(arg0))
|
||||
@ -170,9 +166,7 @@ static int luauF_exp(lua_State* L, StkId res, TValue* arg0, int nresults, StkId
|
||||
return -1;
|
||||
}
|
||||
|
||||
// TODO: LUAU_NOINLINE can be removed with LuauBuiltinSSE41
|
||||
LUAU_FASTMATH_BEGIN
|
||||
LUAU_NOINLINE
|
||||
static int luauF_floor(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams)
|
||||
{
|
||||
if (nparams >= 1 && nresults <= 1 && ttisnumber(arg0))
|
||||
@ -949,9 +943,7 @@ static int luauF_sign(lua_State* L, StkId res, TValue* arg0, int nresults, StkId
|
||||
return -1;
|
||||
}
|
||||
|
||||
// TODO: LUAU_NOINLINE can be removed with LuauBuiltinSSE41
|
||||
LUAU_FASTMATH_BEGIN
|
||||
LUAU_NOINLINE
|
||||
static int luauF_round(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams)
|
||||
{
|
||||
if (nparams >= 1 && nresults <= 1 && ttisnumber(arg0))
|
||||
@ -1271,9 +1263,6 @@ LUAU_TARGET_SSE41 inline double roundsd_sse41(double v)
|
||||
|
||||
LUAU_TARGET_SSE41 static int luauF_floor_sse41(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams)
|
||||
{
|
||||
if (!FFlag::LuauBuiltinSSE41)
|
||||
return luauF_floor(L, res, arg0, nresults, args, nparams);
|
||||
|
||||
if (nparams >= 1 && nresults <= 1 && ttisnumber(arg0))
|
||||
{
|
||||
double a1 = nvalue(arg0);
|
||||
@ -1286,9 +1275,6 @@ LUAU_TARGET_SSE41 static int luauF_floor_sse41(lua_State* L, StkId res, TValue*
|
||||
|
||||
LUAU_TARGET_SSE41 static int luauF_ceil_sse41(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams)
|
||||
{
|
||||
if (!FFlag::LuauBuiltinSSE41)
|
||||
return luauF_ceil(L, res, arg0, nresults, args, nparams);
|
||||
|
||||
if (nparams >= 1 && nresults <= 1 && ttisnumber(arg0))
|
||||
{
|
||||
double a1 = nvalue(arg0);
|
||||
@ -1301,9 +1287,6 @@ LUAU_TARGET_SSE41 static int luauF_ceil_sse41(lua_State* L, StkId res, TValue* a
|
||||
|
||||
LUAU_TARGET_SSE41 static int luauF_round_sse41(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams)
|
||||
{
|
||||
if (!FFlag::LuauBuiltinSSE41)
|
||||
return luauF_round(L, res, arg0, nresults, args, nparams);
|
||||
|
||||
if (nparams >= 1 && nresults <= 1 && ttisnumber(arg0))
|
||||
{
|
||||
double a1 = nvalue(arg0);
|
||||
|
@ -1,6 +1,7 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/Common.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <vector>
|
||||
|
||||
|
@ -3,10 +3,10 @@
|
||||
|
||||
#include "Luau/BuiltinDefinitions.h"
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/Frontend.h"
|
||||
#include "Luau/Linter.h"
|
||||
#include "Luau/ModuleResolver.h"
|
||||
#include "Luau/Parser.h"
|
||||
#include "Luau/TypeInfer.h"
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size)
|
||||
{
|
||||
@ -18,18 +18,17 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size)
|
||||
Luau::ParseResult parseResult = Luau::Parser::parse(reinterpret_cast<const char*>(Data), Size, names, allocator, options);
|
||||
|
||||
// "static" here is to accelerate fuzzing process by only creating and populating the type environment once
|
||||
static Luau::NullModuleResolver moduleResolver;
|
||||
static Luau::InternalErrorReporter iceHandler;
|
||||
static Luau::TypeChecker sharedEnv(&moduleResolver, &iceHandler);
|
||||
static int once = (Luau::registerBuiltinGlobals(sharedEnv), 1);
|
||||
static Luau::NullFileResolver fileResolver;
|
||||
static Luau::NullConfigResolver configResolver;
|
||||
static Luau::Frontend frontend{&fileResolver, &configResolver};
|
||||
static int once = (Luau::registerBuiltinGlobals(frontend), 1);
|
||||
(void)once;
|
||||
static int once2 = (Luau::freeze(sharedEnv.globalTypes), 1);
|
||||
static int once2 = (Luau::freeze(frontend.globals.globalTypes), 1);
|
||||
(void)once2;
|
||||
|
||||
if (parseResult.errors.empty())
|
||||
{
|
||||
Luau::TypeChecker typeck(&moduleResolver, &iceHandler);
|
||||
typeck.globalScope = sharedEnv.globalScope;
|
||||
Luau::TypeChecker typeck(frontend.globals.globalScope, &frontend.moduleResolver, frontend.builtinTypes, &frontend.iceHandler);
|
||||
|
||||
Luau::LintOptions lintOptions;
|
||||
lintOptions.warningMask = ~0ull;
|
||||
|
@ -261,8 +261,8 @@ DEFINE_PROTO_FUZZER(const luau::ModuleSet& message)
|
||||
{
|
||||
static FuzzFileResolver fileResolver;
|
||||
static FuzzConfigResolver configResolver;
|
||||
static Luau::FrontendOptions options{true, true};
|
||||
static Luau::Frontend frontend(&fileResolver, &configResolver, options);
|
||||
static Luau::FrontendOptions defaultOptions{/*retainFullTypeGraphs*/ true, /*forAutocomplete*/ false, /*runLintChecks*/ kFuzzLinter};
|
||||
static Luau::Frontend frontend(&fileResolver, &configResolver, defaultOptions);
|
||||
|
||||
static int once = (setupFrontend(frontend), 0);
|
||||
(void)once;
|
||||
@ -285,16 +285,12 @@ DEFINE_PROTO_FUZZER(const luau::ModuleSet& message)
|
||||
|
||||
try
|
||||
{
|
||||
Luau::CheckResult result = frontend.check(name, std::nullopt);
|
||||
|
||||
// lint (note that we need access to types so we need to do this with typeck in scope)
|
||||
if (kFuzzLinter && result.errors.empty())
|
||||
frontend.lint(name, std::nullopt);
|
||||
frontend.check(name);
|
||||
|
||||
// Second pass in strict mode (forced by auto-complete)
|
||||
Luau::FrontendOptions opts;
|
||||
opts.forAutocomplete = true;
|
||||
frontend.check(name, opts);
|
||||
Luau::FrontendOptions options = defaultOptions;
|
||||
options.forAutocomplete = true;
|
||||
frontend.check(name, options);
|
||||
}
|
||||
catch (std::exception&)
|
||||
{
|
||||
|
@ -3,9 +3,9 @@
|
||||
|
||||
#include "Luau/BuiltinDefinitions.h"
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/Frontend.h"
|
||||
#include "Luau/ModuleResolver.h"
|
||||
#include "Luau/Parser.h"
|
||||
#include "Luau/TypeInfer.h"
|
||||
|
||||
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||
LUAU_FASTINT(LuauTypeInferTypePackLoopLimit)
|
||||
@ -23,23 +23,22 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size)
|
||||
Luau::ParseResult parseResult = Luau::Parser::parse(reinterpret_cast<const char*>(Data), Size, names, allocator, options);
|
||||
|
||||
// "static" here is to accelerate fuzzing process by only creating and populating the type environment once
|
||||
static Luau::NullModuleResolver moduleResolver;
|
||||
static Luau::InternalErrorReporter iceHandler;
|
||||
static Luau::TypeChecker sharedEnv(&moduleResolver, &iceHandler);
|
||||
static int once = (Luau::registerBuiltinGlobals(sharedEnv), 1);
|
||||
static Luau::NullFileResolver fileResolver;
|
||||
static Luau::NullConfigResolver configResolver;
|
||||
static Luau::Frontend frontend{&fileResolver, &configResolver};
|
||||
static int once = (Luau::registerBuiltinGlobals(frontend), 1);
|
||||
(void)once;
|
||||
static int once2 = (Luau::freeze(sharedEnv.globalTypes), 1);
|
||||
static int once2 = (Luau::freeze(frontend.globals.globalTypes), 1);
|
||||
(void)once2;
|
||||
|
||||
if (parseResult.errors.empty())
|
||||
{
|
||||
Luau::TypeChecker typeck(frontend.globals.globalScope, &frontend.moduleResolver, frontend.builtinTypes, &frontend.iceHandler);
|
||||
|
||||
Luau::SourceModule module;
|
||||
module.root = parseResult.root;
|
||||
module.mode = Luau::Mode::Nonstrict;
|
||||
|
||||
Luau::TypeChecker typeck(&moduleResolver, &iceHandler);
|
||||
typeck.globalScope = sharedEnv.globalScope;
|
||||
|
||||
try
|
||||
{
|
||||
typeck.check(module, Luau::Mode::Nonstrict);
|
||||
|
@ -32,9 +32,9 @@ static std::string bytecodeAsArray(const std::vector<uint32_t>& code)
|
||||
class AssemblyBuilderA64Fixture
|
||||
{
|
||||
public:
|
||||
bool check(void (*f)(AssemblyBuilderA64& build), std::vector<uint32_t> code, std::vector<uint8_t> data = {})
|
||||
bool check(void (*f)(AssemblyBuilderA64& build), std::vector<uint32_t> code, std::vector<uint8_t> data = {}, unsigned int features = 0)
|
||||
{
|
||||
AssemblyBuilderA64 build(/* logText= */ false);
|
||||
AssemblyBuilderA64 build(/* logText= */ false, features);
|
||||
|
||||
f(build);
|
||||
|
||||
@ -285,6 +285,87 @@ TEST_CASE_FIXTURE(AssemblyBuilderA64Fixture, "AddressOfLabel")
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(AssemblyBuilderA64Fixture, "FPBasic")
|
||||
{
|
||||
SINGLE_COMPARE(fmov(d0, d1), 0x1E604020);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(AssemblyBuilderA64Fixture, "FPMath")
|
||||
{
|
||||
SINGLE_COMPARE(fabs(d1, d2), 0x1E60C041);
|
||||
SINGLE_COMPARE(fadd(d1, d2, d3), 0x1E632841);
|
||||
SINGLE_COMPARE(fdiv(d1, d2, d3), 0x1E631841);
|
||||
SINGLE_COMPARE(fmul(d1, d2, d3), 0x1E630841);
|
||||
SINGLE_COMPARE(fneg(d1, d2), 0x1E614041);
|
||||
SINGLE_COMPARE(fsqrt(d1, d2), 0x1E61C041);
|
||||
SINGLE_COMPARE(fsub(d1, d2, d3), 0x1E633841);
|
||||
|
||||
SINGLE_COMPARE(frinta(d1, d2), 0x1E664041);
|
||||
SINGLE_COMPARE(frintm(d1, d2), 0x1E654041);
|
||||
SINGLE_COMPARE(frintp(d1, d2), 0x1E64C041);
|
||||
|
||||
SINGLE_COMPARE(fcvtzs(w1, d2), 0x1E780041);
|
||||
SINGLE_COMPARE(fcvtzs(x1, d2), 0x9E780041);
|
||||
SINGLE_COMPARE(fcvtzu(w1, d2), 0x1E790041);
|
||||
SINGLE_COMPARE(fcvtzu(x1, d2), 0x9E790041);
|
||||
|
||||
SINGLE_COMPARE(scvtf(d1, w2), 0x1E620041);
|
||||
SINGLE_COMPARE(scvtf(d1, x2), 0x9E620041);
|
||||
SINGLE_COMPARE(ucvtf(d1, w2), 0x1E630041);
|
||||
SINGLE_COMPARE(ucvtf(d1, x2), 0x9E630041);
|
||||
|
||||
CHECK(check(
|
||||
[](AssemblyBuilderA64& build) {
|
||||
build.fjcvtzs(w1, d2);
|
||||
},
|
||||
{0x1E7E0041}, {}, A64::Feature_JSCVT));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(AssemblyBuilderA64Fixture, "FPLoadStore")
|
||||
{
|
||||
// address forms
|
||||
SINGLE_COMPARE(ldr(d0, x1), 0xFD400020);
|
||||
SINGLE_COMPARE(ldr(d0, mem(x1, 8)), 0xFD400420);
|
||||
SINGLE_COMPARE(ldr(d0, mem(x1, x7)), 0xFC676820);
|
||||
SINGLE_COMPARE(ldr(d0, mem(x1, -7)), 0xFC5F9020);
|
||||
SINGLE_COMPARE(str(d0, x1), 0xFD000020);
|
||||
SINGLE_COMPARE(str(d0, mem(x1, 8)), 0xFD000420);
|
||||
SINGLE_COMPARE(str(d0, mem(x1, x7)), 0xFC276820);
|
||||
SINGLE_COMPARE(str(d0, mem(x1, -7)), 0xFC1F9020);
|
||||
|
||||
// load/store sizes
|
||||
SINGLE_COMPARE(ldr(d0, x1), 0xFD400020);
|
||||
SINGLE_COMPARE(ldr(q0, x1), 0x3DC00020);
|
||||
SINGLE_COMPARE(str(d0, x1), 0xFD000020);
|
||||
SINGLE_COMPARE(str(q0, x1), 0x3D800020);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(AssemblyBuilderA64Fixture, "FPCompare")
|
||||
{
|
||||
SINGLE_COMPARE(fcmp(d0, d1), 0x1E612000);
|
||||
SINGLE_COMPARE(fcmpz(d1), 0x1E602028);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(AssemblyBuilderA64Fixture, "AddressOffsetSize")
|
||||
{
|
||||
SINGLE_COMPARE(ldr(w0, mem(x1, 16)), 0xB9401020);
|
||||
SINGLE_COMPARE(ldr(x0, mem(x1, 16)), 0xF9400820);
|
||||
SINGLE_COMPARE(ldr(d0, mem(x1, 16)), 0xFD400820);
|
||||
SINGLE_COMPARE(ldr(q0, mem(x1, 16)), 0x3DC00420);
|
||||
|
||||
SINGLE_COMPARE(str(w0, mem(x1, 16)), 0xB9001020);
|
||||
SINGLE_COMPARE(str(x0, mem(x1, 16)), 0xF9000820);
|
||||
SINGLE_COMPARE(str(d0, mem(x1, 16)), 0xFD000820);
|
||||
SINGLE_COMPARE(str(q0, mem(x1, 16)), 0x3D800420);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(AssemblyBuilderA64Fixture, "ConditionalSelect")
|
||||
{
|
||||
SINGLE_COMPARE(csel(x0, x1, x2, ConditionA64::Equal), 0x9A820020);
|
||||
SINGLE_COMPARE(csel(w0, w1, w2, ConditionA64::Equal), 0x1A820020);
|
||||
SINGLE_COMPARE(fcsel(d0, d1, d2, ConditionA64::Equal), 0x1E620C20);
|
||||
}
|
||||
|
||||
TEST_CASE("LogTest")
|
||||
{
|
||||
AssemblyBuilderA64 build(/* logText= */ true);
|
||||
@ -309,6 +390,14 @@ TEST_CASE("LogTest")
|
||||
build.ldp(x0, x1, mem(x8, 8));
|
||||
build.adr(x0, l);
|
||||
|
||||
build.fabs(d1, d2);
|
||||
build.ldr(q1, x2);
|
||||
|
||||
build.csel(x0, x1, x2, ConditionA64::Equal);
|
||||
|
||||
build.fcmp(d0, d1);
|
||||
build.fcmpz(d0);
|
||||
|
||||
build.setLabel(l);
|
||||
build.ret();
|
||||
|
||||
@ -331,6 +420,11 @@ TEST_CASE("LogTest")
|
||||
cbz x7,.L1
|
||||
ldp x0,x1,[x8,#8]
|
||||
adr x0,.L1
|
||||
fabs d1,d2
|
||||
ldr q1,[x2]
|
||||
csel x0,x1,x2,eq
|
||||
fcmp d0,d1
|
||||
fcmp d0,#0
|
||||
.L1:
|
||||
ret
|
||||
)";
|
||||
|
@ -2995,8 +2995,6 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_string_singletons")
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "string_singleton_as_table_key")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauCompleteTableKeysBetter", true};
|
||||
|
||||
check(R"(
|
||||
type Direction = "up" | "down"
|
||||
|
||||
|
@ -4691,8 +4691,6 @@ RETURN R0 0
|
||||
|
||||
TEST_CASE("LoopUnrollCost")
|
||||
{
|
||||
ScopedFastFlag sff("LuauCompileBuiltinArity", true);
|
||||
|
||||
ScopedFastInt sfis[] = {
|
||||
{"LuauCompileLoopUnrollThreshold", 25},
|
||||
{"LuauCompileLoopUnrollThresholdMaxBoost", 300},
|
||||
@ -5962,8 +5960,6 @@ RETURN R2 1
|
||||
|
||||
TEST_CASE("InlineMultret")
|
||||
{
|
||||
ScopedFastFlag sff("LuauCompileBuiltinArity", true);
|
||||
|
||||
// inlining a function in multret context is prohibited since we can't adjust L->top outside of CALL/GETVARARGS
|
||||
CHECK_EQ("\n" + compileFunction(R"(
|
||||
local function foo(a)
|
||||
@ -6301,8 +6297,6 @@ RETURN R0 52
|
||||
|
||||
TEST_CASE("BuiltinFoldingProhibited")
|
||||
{
|
||||
ScopedFastFlag sff("LuauCompileBuiltinArity", true);
|
||||
|
||||
CHECK_EQ("\n" + compileFunction(R"(
|
||||
return
|
||||
math.abs(),
|
||||
@ -6905,8 +6899,6 @@ L3: RETURN R0 0
|
||||
|
||||
TEST_CASE("BuiltinArity")
|
||||
{
|
||||
ScopedFastFlag sff("LuauCompileBuiltinArity", true);
|
||||
|
||||
// by default we can't assume that we know parameter/result count for builtins as they can be overridden at runtime
|
||||
CHECK_EQ("\n" + compileFunction(R"(
|
||||
return math.abs(unknown())
|
||||
|
@ -504,7 +504,7 @@ TEST_CASE("Types")
|
||||
Luau::InternalErrorReporter iceHandler;
|
||||
Luau::BuiltinTypes builtinTypes;
|
||||
Luau::GlobalTypes globals{Luau::NotNull{&builtinTypes}};
|
||||
Luau::TypeChecker env(globals, &moduleResolver, Luau::NotNull{&builtinTypes}, &iceHandler);
|
||||
Luau::TypeChecker env(globals.globalScope, &moduleResolver, Luau::NotNull{&builtinTypes}, &iceHandler);
|
||||
|
||||
Luau::registerBuiltinGlobals(env, globals);
|
||||
Luau::freeze(globals.globalTypes);
|
||||
|
@ -31,8 +31,7 @@ void ConstraintGraphBuilderFixture::generateConstraints(const std::string& code)
|
||||
void ConstraintGraphBuilderFixture::solve(const std::string& code)
|
||||
{
|
||||
generateConstraints(code);
|
||||
ConstraintSolver cs{NotNull{&normalizer}, NotNull{rootScope}, constraints, "MainModule", NotNull{mainModule->reduction.get()},
|
||||
NotNull(&moduleResolver), {}, &logger};
|
||||
ConstraintSolver cs{NotNull{&normalizer}, NotNull{rootScope}, constraints, "MainModule", NotNull(&moduleResolver), {}, &logger};
|
||||
cs.run();
|
||||
}
|
||||
|
||||
|
@ -42,7 +42,7 @@ public:
|
||||
f(a);
|
||||
|
||||
build.beginBlock(a);
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(1));
|
||||
build.inst(IrCmd::RETURN, build.constUint(1));
|
||||
};
|
||||
|
||||
template<typename F>
|
||||
@ -56,10 +56,10 @@ public:
|
||||
f(a, b);
|
||||
|
||||
build.beginBlock(a);
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(1));
|
||||
build.inst(IrCmd::RETURN, build.constUint(1));
|
||||
|
||||
build.beginBlock(b);
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(2));
|
||||
build.inst(IrCmd::RETURN, build.constUint(2));
|
||||
};
|
||||
|
||||
void checkEq(IrOp instOp, const IrInst& inst)
|
||||
@ -94,10 +94,10 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "FinalX64OptCheckTag")
|
||||
build.inst(IrCmd::CHECK_TAG, tag1, build.constTag(0), fallback);
|
||||
IrOp tag2 = build.inst(IrCmd::LOAD_TAG, build.vmConst(5));
|
||||
build.inst(IrCmd::CHECK_TAG, tag2, build.constTag(0), fallback);
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(0));
|
||||
build.inst(IrCmd::RETURN, build.constUint(0));
|
||||
|
||||
build.beginBlock(fallback);
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(1));
|
||||
build.inst(IrCmd::RETURN, build.constUint(1));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
optimizeMemoryOperandsX64(build.function);
|
||||
@ -107,10 +107,10 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "FinalX64OptCheckTag")
|
||||
bb_0:
|
||||
CHECK_TAG R2, tnil, bb_fallback_1
|
||||
CHECK_TAG K5, tnil, bb_fallback_1
|
||||
LOP_RETURN 0u
|
||||
RETURN 0u
|
||||
|
||||
bb_fallback_1:
|
||||
LOP_RETURN 1u
|
||||
RETURN 1u
|
||||
|
||||
)");
|
||||
}
|
||||
@ -123,7 +123,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "FinalX64OptBinaryArith")
|
||||
IrOp opA = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(1));
|
||||
IrOp opB = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(2));
|
||||
build.inst(IrCmd::ADD_NUM, opA, opB);
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(0));
|
||||
build.inst(IrCmd::RETURN, build.constUint(0));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
optimizeMemoryOperandsX64(build.function);
|
||||
@ -133,7 +133,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "FinalX64OptBinaryArith")
|
||||
bb_0:
|
||||
%0 = LOAD_DOUBLE R1
|
||||
%2 = ADD_NUM %0, R2
|
||||
LOP_RETURN 0u
|
||||
RETURN 0u
|
||||
|
||||
)");
|
||||
}
|
||||
@ -150,10 +150,10 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "FinalX64OptEqTag1")
|
||||
build.inst(IrCmd::JUMP_EQ_TAG, opA, opB, trueBlock, falseBlock);
|
||||
|
||||
build.beginBlock(trueBlock);
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(0));
|
||||
build.inst(IrCmd::RETURN, build.constUint(0));
|
||||
|
||||
build.beginBlock(falseBlock);
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(0));
|
||||
build.inst(IrCmd::RETURN, build.constUint(0));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
optimizeMemoryOperandsX64(build.function);
|
||||
@ -165,10 +165,10 @@ bb_0:
|
||||
JUMP_EQ_TAG R1, %1, bb_1, bb_2
|
||||
|
||||
bb_1:
|
||||
LOP_RETURN 0u
|
||||
RETURN 0u
|
||||
|
||||
bb_2:
|
||||
LOP_RETURN 0u
|
||||
RETURN 0u
|
||||
|
||||
)");
|
||||
}
|
||||
@ -186,10 +186,10 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "FinalX64OptEqTag2")
|
||||
build.inst(IrCmd::JUMP_EQ_TAG, opA, opB, trueBlock, falseBlock);
|
||||
|
||||
build.beginBlock(trueBlock);
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(0));
|
||||
build.inst(IrCmd::RETURN, build.constUint(0));
|
||||
|
||||
build.beginBlock(falseBlock);
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(0));
|
||||
build.inst(IrCmd::RETURN, build.constUint(0));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
optimizeMemoryOperandsX64(build.function);
|
||||
@ -203,10 +203,10 @@ bb_0:
|
||||
JUMP_EQ_TAG R2, %0, bb_1, bb_2
|
||||
|
||||
bb_1:
|
||||
LOP_RETURN 0u
|
||||
RETURN 0u
|
||||
|
||||
bb_2:
|
||||
LOP_RETURN 0u
|
||||
RETURN 0u
|
||||
|
||||
)");
|
||||
}
|
||||
@ -224,10 +224,10 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "FinalX64OptEqTag3")
|
||||
build.inst(IrCmd::JUMP_EQ_TAG, opA, build.constTag(0), trueBlock, falseBlock);
|
||||
|
||||
build.beginBlock(trueBlock);
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(0));
|
||||
build.inst(IrCmd::RETURN, build.constUint(0));
|
||||
|
||||
build.beginBlock(falseBlock);
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(0));
|
||||
build.inst(IrCmd::RETURN, build.constUint(0));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
optimizeMemoryOperandsX64(build.function);
|
||||
@ -241,10 +241,10 @@ bb_0:
|
||||
JUMP_EQ_TAG %2, tnil, bb_1, bb_2
|
||||
|
||||
bb_1:
|
||||
LOP_RETURN 0u
|
||||
RETURN 0u
|
||||
|
||||
bb_2:
|
||||
LOP_RETURN 0u
|
||||
RETURN 0u
|
||||
|
||||
)");
|
||||
}
|
||||
@ -261,10 +261,10 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "FinalX64OptJumpCmpNum")
|
||||
build.inst(IrCmd::JUMP_CMP_NUM, opA, opB, trueBlock, falseBlock);
|
||||
|
||||
build.beginBlock(trueBlock);
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(0));
|
||||
build.inst(IrCmd::RETURN, build.constUint(0));
|
||||
|
||||
build.beginBlock(falseBlock);
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(0));
|
||||
build.inst(IrCmd::RETURN, build.constUint(0));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
optimizeMemoryOperandsX64(build.function);
|
||||
@ -276,10 +276,10 @@ bb_0:
|
||||
JUMP_CMP_NUM R1, %1, bb_1, bb_2
|
||||
|
||||
bb_1:
|
||||
LOP_RETURN 0u
|
||||
RETURN 0u
|
||||
|
||||
bb_2:
|
||||
LOP_RETURN 0u
|
||||
RETURN 0u
|
||||
|
||||
)");
|
||||
}
|
||||
@ -317,7 +317,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "Numeric")
|
||||
|
||||
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.inst(IrCmd::INT_TO_NUM, build.constInt(8)));
|
||||
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(0));
|
||||
build.inst(IrCmd::RETURN, build.constUint(0));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constantFold();
|
||||
@ -342,7 +342,7 @@ bb_0:
|
||||
STORE_INT R0, 1i
|
||||
STORE_INT R0, 0i
|
||||
STORE_DOUBLE R0, 8
|
||||
LOP_RETURN 0u
|
||||
RETURN 0u
|
||||
|
||||
)");
|
||||
}
|
||||
@ -373,25 +373,25 @@ bb_0:
|
||||
JUMP bb_1
|
||||
|
||||
bb_1:
|
||||
LOP_RETURN 1u
|
||||
RETURN 1u
|
||||
|
||||
bb_3:
|
||||
JUMP bb_5
|
||||
|
||||
bb_5:
|
||||
LOP_RETURN 2u
|
||||
RETURN 2u
|
||||
|
||||
bb_6:
|
||||
JUMP bb_7
|
||||
|
||||
bb_7:
|
||||
LOP_RETURN 1u
|
||||
RETURN 1u
|
||||
|
||||
bb_9:
|
||||
JUMP bb_11
|
||||
|
||||
bb_11:
|
||||
LOP_RETURN 2u
|
||||
RETURN 2u
|
||||
|
||||
)");
|
||||
}
|
||||
@ -400,18 +400,18 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "NumToIndex")
|
||||
{
|
||||
withOneBlock([this](IrOp a) {
|
||||
build.inst(IrCmd::STORE_INT, build.vmReg(0), build.inst(IrCmd::TRY_NUM_TO_INDEX, build.constDouble(4), a));
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(0));
|
||||
build.inst(IrCmd::RETURN, build.constUint(0));
|
||||
});
|
||||
|
||||
withOneBlock([this](IrOp a) {
|
||||
build.inst(IrCmd::STORE_INT, build.vmReg(0), build.inst(IrCmd::TRY_NUM_TO_INDEX, build.constDouble(1.2), a));
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(0));
|
||||
build.inst(IrCmd::RETURN, build.constUint(0));
|
||||
});
|
||||
|
||||
withOneBlock([this](IrOp a) {
|
||||
IrOp nan = build.inst(IrCmd::DIV_NUM, build.constDouble(0.0), build.constDouble(0.0));
|
||||
build.inst(IrCmd::STORE_INT, build.vmReg(0), build.inst(IrCmd::TRY_NUM_TO_INDEX, nan, a));
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(0));
|
||||
build.inst(IrCmd::RETURN, build.constUint(0));
|
||||
});
|
||||
|
||||
updateUseCounts(build.function);
|
||||
@ -420,19 +420,19 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "NumToIndex")
|
||||
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"(
|
||||
bb_0:
|
||||
STORE_INT R0, 4i
|
||||
LOP_RETURN 0u
|
||||
RETURN 0u
|
||||
|
||||
bb_2:
|
||||
JUMP bb_3
|
||||
|
||||
bb_3:
|
||||
LOP_RETURN 1u
|
||||
RETURN 1u
|
||||
|
||||
bb_4:
|
||||
JUMP bb_5
|
||||
|
||||
bb_5:
|
||||
LOP_RETURN 1u
|
||||
RETURN 1u
|
||||
|
||||
)");
|
||||
}
|
||||
@ -441,12 +441,12 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "Guards")
|
||||
{
|
||||
withOneBlock([this](IrOp a) {
|
||||
build.inst(IrCmd::CHECK_TAG, build.constTag(tnumber), build.constTag(tnumber), a);
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(0));
|
||||
build.inst(IrCmd::RETURN, build.constUint(0));
|
||||
});
|
||||
|
||||
withOneBlock([this](IrOp a) {
|
||||
build.inst(IrCmd::CHECK_TAG, build.constTag(tnil), build.constTag(tnumber), a);
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(0));
|
||||
build.inst(IrCmd::RETURN, build.constUint(0));
|
||||
});
|
||||
|
||||
updateUseCounts(build.function);
|
||||
@ -454,13 +454,13 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "Guards")
|
||||
|
||||
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"(
|
||||
bb_0:
|
||||
LOP_RETURN 0u
|
||||
RETURN 0u
|
||||
|
||||
bb_2:
|
||||
JUMP bb_3
|
||||
|
||||
bb_3:
|
||||
LOP_RETURN 1u
|
||||
RETURN 1u
|
||||
|
||||
)");
|
||||
}
|
||||
@ -568,7 +568,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "RememberTagsAndValues")
|
||||
build.inst(IrCmd::STORE_INT, build.vmReg(10), build.inst(IrCmd::LOAD_INT, build.vmReg(1)));
|
||||
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(11), build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(2)));
|
||||
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(0));
|
||||
build.inst(IrCmd::RETURN, build.constUint(0));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build);
|
||||
@ -593,7 +593,7 @@ bb_0:
|
||||
STORE_INT R10, %20
|
||||
%22 = LOAD_DOUBLE R2
|
||||
STORE_DOUBLE R11, %22
|
||||
LOP_RETURN 0u
|
||||
RETURN 0u
|
||||
|
||||
)");
|
||||
}
|
||||
@ -614,7 +614,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "PropagateThroughTvalue")
|
||||
build.inst(IrCmd::STORE_TAG, build.vmReg(3), build.inst(IrCmd::LOAD_TAG, build.vmReg(1)));
|
||||
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(3), build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(1)));
|
||||
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(0));
|
||||
build.inst(IrCmd::RETURN, build.constUint(0));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build);
|
||||
@ -627,7 +627,7 @@ bb_0:
|
||||
STORE_TVALUE R1, %2
|
||||
STORE_TAG R3, tnumber
|
||||
STORE_DOUBLE R3, 0.5
|
||||
LOP_RETURN 0u
|
||||
RETURN 0u
|
||||
|
||||
)");
|
||||
}
|
||||
@ -641,10 +641,10 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "SkipCheckTag")
|
||||
|
||||
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
|
||||
build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(0)), build.constTag(tnumber), fallback);
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(0));
|
||||
build.inst(IrCmd::RETURN, build.constUint(0));
|
||||
|
||||
build.beginBlock(fallback);
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(1));
|
||||
build.inst(IrCmd::RETURN, build.constUint(1));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build);
|
||||
@ -652,7 +652,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "SkipCheckTag")
|
||||
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"(
|
||||
bb_0:
|
||||
STORE_TAG R0, tnumber
|
||||
LOP_RETURN 0u
|
||||
RETURN 0u
|
||||
|
||||
)");
|
||||
}
|
||||
@ -671,7 +671,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "SkipOncePerBlockChecks")
|
||||
build.inst(IrCmd::DO_LEN, build.vmReg(1), build.vmReg(2)); // Can make env unsafe
|
||||
build.inst(IrCmd::CHECK_SAFE_ENV);
|
||||
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(0));
|
||||
build.inst(IrCmd::RETURN, build.constUint(0));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build);
|
||||
@ -682,7 +682,7 @@ bb_0:
|
||||
CHECK_GC
|
||||
DO_LEN R1, R2
|
||||
CHECK_SAFE_ENV
|
||||
LOP_RETURN 0u
|
||||
RETURN 0u
|
||||
|
||||
)");
|
||||
}
|
||||
@ -707,10 +707,10 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "RememberTableState")
|
||||
build.inst(IrCmd::CHECK_NO_METATABLE, table, fallback);
|
||||
build.inst(IrCmd::CHECK_READONLY, table, fallback);
|
||||
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(0));
|
||||
build.inst(IrCmd::RETURN, build.constUint(0));
|
||||
|
||||
build.beginBlock(fallback);
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(1));
|
||||
build.inst(IrCmd::RETURN, build.constUint(1));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build);
|
||||
@ -723,10 +723,10 @@ bb_0:
|
||||
DO_LEN R1, R2
|
||||
CHECK_NO_METATABLE %0, bb_fallback_1
|
||||
CHECK_READONLY %0, bb_fallback_1
|
||||
LOP_RETURN 0u
|
||||
RETURN 0u
|
||||
|
||||
bb_fallback_1:
|
||||
LOP_RETURN 1u
|
||||
RETURN 1u
|
||||
|
||||
)");
|
||||
}
|
||||
@ -742,7 +742,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "SkipUselessBarriers")
|
||||
build.inst(IrCmd::BARRIER_TABLE_FORWARD, table, build.vmReg(0));
|
||||
IrOp something = build.inst(IrCmd::LOAD_POINTER, build.vmReg(2));
|
||||
build.inst(IrCmd::BARRIER_OBJ, something, build.vmReg(0));
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(0));
|
||||
build.inst(IrCmd::RETURN, build.constUint(0));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build);
|
||||
@ -750,7 +750,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "SkipUselessBarriers")
|
||||
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"(
|
||||
bb_0:
|
||||
STORE_TAG R0, tnumber
|
||||
LOP_RETURN 0u
|
||||
RETURN 0u
|
||||
|
||||
)");
|
||||
}
|
||||
@ -773,7 +773,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "ConcatInvalidation")
|
||||
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(6), build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(2)));
|
||||
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(7), build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(3)));
|
||||
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(0));
|
||||
build.inst(IrCmd::RETURN, build.constUint(0));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build);
|
||||
@ -792,7 +792,7 @@ bb_0:
|
||||
%9 = LOAD_DOUBLE R2
|
||||
STORE_DOUBLE R6, %9
|
||||
STORE_DOUBLE R7, 2
|
||||
LOP_RETURN 0u
|
||||
RETURN 0u
|
||||
|
||||
)");
|
||||
}
|
||||
@ -819,10 +819,10 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "BuiltinFastcallsMayInvalidateMemory")
|
||||
|
||||
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(0))); // At least R0 wasn't touched
|
||||
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(0));
|
||||
build.inst(IrCmd::RETURN, build.constUint(0));
|
||||
|
||||
build.beginBlock(fallback);
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(1));
|
||||
build.inst(IrCmd::RETURN, build.constUint(1));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build);
|
||||
@ -837,10 +837,10 @@ bb_0:
|
||||
CHECK_NO_METATABLE %1, bb_fallback_1
|
||||
CHECK_READONLY %1, bb_fallback_1
|
||||
STORE_DOUBLE R1, 0.5
|
||||
LOP_RETURN 0u
|
||||
RETURN 0u
|
||||
|
||||
bb_fallback_1:
|
||||
LOP_RETURN 1u
|
||||
RETURN 1u
|
||||
|
||||
)");
|
||||
}
|
||||
@ -855,7 +855,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "RedundantStoreCheckConstantType")
|
||||
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(0.5));
|
||||
build.inst(IrCmd::STORE_INT, build.vmReg(0), build.constInt(10));
|
||||
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(0));
|
||||
build.inst(IrCmd::RETURN, build.constUint(0));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build);
|
||||
@ -865,7 +865,7 @@ bb_0:
|
||||
STORE_INT R0, 10i
|
||||
STORE_DOUBLE R0, 0.5
|
||||
STORE_INT R0, 10i
|
||||
LOP_RETURN 0u
|
||||
RETURN 0u
|
||||
|
||||
)");
|
||||
}
|
||||
@ -882,10 +882,10 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "TagCheckPropagation")
|
||||
build.inst(IrCmd::CHECK_TAG, unknown, build.constTag(tnumber), fallback);
|
||||
build.inst(IrCmd::CHECK_TAG, unknown, build.constTag(tnumber), fallback);
|
||||
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(0));
|
||||
build.inst(IrCmd::RETURN, build.constUint(0));
|
||||
|
||||
build.beginBlock(fallback);
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(1));
|
||||
build.inst(IrCmd::RETURN, build.constUint(1));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build);
|
||||
@ -894,10 +894,10 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "TagCheckPropagation")
|
||||
bb_0:
|
||||
%0 = LOAD_TAG R0
|
||||
CHECK_TAG %0, tnumber, bb_fallback_1
|
||||
LOP_RETURN 0u
|
||||
RETURN 0u
|
||||
|
||||
bb_fallback_1:
|
||||
LOP_RETURN 1u
|
||||
RETURN 1u
|
||||
|
||||
)");
|
||||
}
|
||||
@ -914,10 +914,10 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "TagCheckPropagationConflicting")
|
||||
build.inst(IrCmd::CHECK_TAG, unknown, build.constTag(tnumber), fallback);
|
||||
build.inst(IrCmd::CHECK_TAG, unknown, build.constTag(tnil), fallback);
|
||||
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(0));
|
||||
build.inst(IrCmd::RETURN, build.constUint(0));
|
||||
|
||||
build.beginBlock(fallback);
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(1));
|
||||
build.inst(IrCmd::RETURN, build.constUint(1));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build);
|
||||
@ -929,7 +929,7 @@ bb_0:
|
||||
JUMP bb_fallback_1
|
||||
|
||||
bb_fallback_1:
|
||||
LOP_RETURN 1u
|
||||
RETURN 1u
|
||||
|
||||
)");
|
||||
}
|
||||
@ -947,13 +947,13 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "TruthyTestRemoval")
|
||||
build.inst(IrCmd::JUMP_IF_TRUTHY, build.vmReg(1), trueBlock, falseBlock);
|
||||
|
||||
build.beginBlock(trueBlock);
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(1));
|
||||
build.inst(IrCmd::RETURN, build.constUint(1));
|
||||
|
||||
build.beginBlock(falseBlock);
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(2));
|
||||
build.inst(IrCmd::RETURN, build.constUint(2));
|
||||
|
||||
build.beginBlock(fallback);
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(3));
|
||||
build.inst(IrCmd::RETURN, build.constUint(3));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build);
|
||||
@ -965,10 +965,10 @@ bb_0:
|
||||
JUMP bb_1
|
||||
|
||||
bb_1:
|
||||
LOP_RETURN 1u
|
||||
RETURN 1u
|
||||
|
||||
bb_fallback_3:
|
||||
LOP_RETURN 3u
|
||||
RETURN 3u
|
||||
|
||||
)");
|
||||
}
|
||||
@ -986,13 +986,13 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "FalsyTestRemoval")
|
||||
build.inst(IrCmd::JUMP_IF_FALSY, build.vmReg(1), trueBlock, falseBlock);
|
||||
|
||||
build.beginBlock(trueBlock);
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(1));
|
||||
build.inst(IrCmd::RETURN, build.constUint(1));
|
||||
|
||||
build.beginBlock(falseBlock);
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(2));
|
||||
build.inst(IrCmd::RETURN, build.constUint(2));
|
||||
|
||||
build.beginBlock(fallback);
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(3));
|
||||
build.inst(IrCmd::RETURN, build.constUint(3));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build);
|
||||
@ -1004,10 +1004,10 @@ bb_0:
|
||||
JUMP bb_2
|
||||
|
||||
bb_2:
|
||||
LOP_RETURN 2u
|
||||
RETURN 2u
|
||||
|
||||
bb_fallback_3:
|
||||
LOP_RETURN 3u
|
||||
RETURN 3u
|
||||
|
||||
)");
|
||||
}
|
||||
@ -1024,10 +1024,10 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "TagEqRemoval")
|
||||
build.inst(IrCmd::JUMP_EQ_TAG, tag, build.constTag(tnumber), trueBlock, falseBlock);
|
||||
|
||||
build.beginBlock(trueBlock);
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(1));
|
||||
build.inst(IrCmd::RETURN, build.constUint(1));
|
||||
|
||||
build.beginBlock(falseBlock);
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(2));
|
||||
build.inst(IrCmd::RETURN, build.constUint(2));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build);
|
||||
@ -1039,7 +1039,7 @@ bb_0:
|
||||
JUMP bb_2
|
||||
|
||||
bb_2:
|
||||
LOP_RETURN 2u
|
||||
RETURN 2u
|
||||
|
||||
)");
|
||||
}
|
||||
@ -1056,10 +1056,10 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "IntEqRemoval")
|
||||
build.inst(IrCmd::JUMP_EQ_INT, value, build.constInt(5), trueBlock, falseBlock);
|
||||
|
||||
build.beginBlock(trueBlock);
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(1));
|
||||
build.inst(IrCmd::RETURN, build.constUint(1));
|
||||
|
||||
build.beginBlock(falseBlock);
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(2));
|
||||
build.inst(IrCmd::RETURN, build.constUint(2));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build);
|
||||
@ -1070,7 +1070,7 @@ bb_0:
|
||||
JUMP bb_1
|
||||
|
||||
bb_1:
|
||||
LOP_RETURN 1u
|
||||
RETURN 1u
|
||||
|
||||
)");
|
||||
}
|
||||
@ -1087,10 +1087,10 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "NumCmpRemoval")
|
||||
build.inst(IrCmd::JUMP_CMP_NUM, value, build.constDouble(8.0), build.cond(IrCondition::Greater), trueBlock, falseBlock);
|
||||
|
||||
build.beginBlock(trueBlock);
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(1));
|
||||
build.inst(IrCmd::RETURN, build.constUint(1));
|
||||
|
||||
build.beginBlock(falseBlock);
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(2));
|
||||
build.inst(IrCmd::RETURN, build.constUint(2));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build);
|
||||
@ -1101,7 +1101,7 @@ bb_0:
|
||||
JUMP bb_2
|
||||
|
||||
bb_2:
|
||||
LOP_RETURN 2u
|
||||
RETURN 2u
|
||||
|
||||
)");
|
||||
}
|
||||
@ -1118,7 +1118,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "DataFlowsThroughDirectJumpToUniqueSuccessor
|
||||
|
||||
build.beginBlock(block2);
|
||||
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.inst(IrCmd::LOAD_TAG, build.vmReg(0)));
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(1));
|
||||
build.inst(IrCmd::RETURN, build.constUint(1));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build);
|
||||
@ -1130,7 +1130,7 @@ bb_0:
|
||||
|
||||
bb_1:
|
||||
STORE_TAG R1, tnumber
|
||||
LOP_RETURN 1u
|
||||
RETURN 1u
|
||||
|
||||
)");
|
||||
}
|
||||
@ -1148,7 +1148,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "DataDoesNotFlowThroughDirectJumpToNonUnique
|
||||
|
||||
build.beginBlock(block2);
|
||||
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.inst(IrCmd::LOAD_TAG, build.vmReg(0)));
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(1));
|
||||
build.inst(IrCmd::RETURN, build.constUint(1));
|
||||
|
||||
build.beginBlock(block3);
|
||||
build.inst(IrCmd::JUMP, block2);
|
||||
@ -1164,7 +1164,7 @@ bb_0:
|
||||
bb_1:
|
||||
%2 = LOAD_TAG R0
|
||||
STORE_TAG R1, %2
|
||||
LOP_RETURN 1u
|
||||
RETURN 1u
|
||||
|
||||
bb_2:
|
||||
JUMP bb_1
|
||||
@ -1183,7 +1183,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "EntryBlockUseRemoval")
|
||||
build.inst(IrCmd::JUMP_IF_TRUTHY, build.vmReg(0), exit, repeat);
|
||||
|
||||
build.beginBlock(exit);
|
||||
build.inst(IrCmd::LOP_RETURN, build.vmReg(0), build.constInt(0));
|
||||
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(0));
|
||||
|
||||
build.beginBlock(repeat);
|
||||
build.inst(IrCmd::INTERRUPT, build.constUint(0));
|
||||
@ -1198,7 +1198,7 @@ bb_0:
|
||||
JUMP bb_1
|
||||
|
||||
bb_1:
|
||||
LOP_RETURN R0, 0i
|
||||
RETURN R0, 0i
|
||||
|
||||
)");
|
||||
}
|
||||
@ -1211,14 +1211,14 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "RecursiveSccUseRemoval1")
|
||||
IrOp repeat = build.block(IrBlockKind::Internal);
|
||||
|
||||
build.beginBlock(entry);
|
||||
build.inst(IrCmd::LOP_RETURN, build.vmReg(0), build.constInt(0));
|
||||
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(0));
|
||||
|
||||
build.beginBlock(block);
|
||||
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
|
||||
build.inst(IrCmd::JUMP_IF_TRUTHY, build.vmReg(0), exit, repeat);
|
||||
|
||||
build.beginBlock(exit);
|
||||
build.inst(IrCmd::LOP_RETURN, build.vmReg(0), build.constInt(0));
|
||||
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(0));
|
||||
|
||||
build.beginBlock(repeat);
|
||||
build.inst(IrCmd::INTERRUPT, build.constUint(0));
|
||||
@ -1229,14 +1229,14 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "RecursiveSccUseRemoval1")
|
||||
|
||||
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"(
|
||||
bb_0:
|
||||
LOP_RETURN R0, 0i
|
||||
RETURN R0, 0i
|
||||
|
||||
bb_1:
|
||||
STORE_TAG R0, tnumber
|
||||
JUMP bb_2
|
||||
|
||||
bb_2:
|
||||
LOP_RETURN R0, 0i
|
||||
RETURN R0, 0i
|
||||
|
||||
)");
|
||||
}
|
||||
@ -1253,14 +1253,14 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "RecursiveSccUseRemoval2")
|
||||
build.inst(IrCmd::JUMP_EQ_INT, build.constInt(0), build.constInt(1), block, exit1);
|
||||
|
||||
build.beginBlock(exit1);
|
||||
build.inst(IrCmd::LOP_RETURN, build.vmReg(0), build.constInt(0));
|
||||
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(0));
|
||||
|
||||
build.beginBlock(block);
|
||||
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
|
||||
build.inst(IrCmd::JUMP_IF_TRUTHY, build.vmReg(0), exit2, repeat);
|
||||
|
||||
build.beginBlock(exit2);
|
||||
build.inst(IrCmd::LOP_RETURN, build.vmReg(0), build.constInt(0));
|
||||
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(0));
|
||||
|
||||
build.beginBlock(repeat);
|
||||
build.inst(IrCmd::INTERRUPT, build.constUint(0));
|
||||
@ -1274,14 +1274,14 @@ bb_0:
|
||||
JUMP bb_1
|
||||
|
||||
bb_1:
|
||||
LOP_RETURN R0, 0i
|
||||
RETURN R0, 0i
|
||||
|
||||
bb_2:
|
||||
STORE_TAG R0, tnumber
|
||||
JUMP bb_3
|
||||
|
||||
bb_3:
|
||||
LOP_RETURN R0, 0i
|
||||
RETURN R0, 0i
|
||||
|
||||
)");
|
||||
}
|
||||
@ -1322,7 +1322,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "SimplePathExtraction")
|
||||
build.inst(IrCmd::JUMP, block4);
|
||||
|
||||
build.beginBlock(block4);
|
||||
build.inst(IrCmd::LOP_RETURN, build.vmReg(0), build.constInt(0));
|
||||
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(0));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build);
|
||||
@ -1350,10 +1350,10 @@ bb_4:
|
||||
JUMP bb_5
|
||||
|
||||
bb_5:
|
||||
LOP_RETURN R0, 0i
|
||||
RETURN R0, 0i
|
||||
|
||||
bb_linear_6:
|
||||
LOP_RETURN R0, 0i
|
||||
RETURN R0, 0i
|
||||
|
||||
)");
|
||||
}
|
||||
@ -1393,11 +1393,11 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "NoPathExtractionForBlocksWithLiveOutValues"
|
||||
|
||||
build.beginBlock(block4a);
|
||||
build.inst(IrCmd::STORE_TAG, build.vmReg(0), tag3a);
|
||||
build.inst(IrCmd::LOP_RETURN, build.vmReg(0), build.constInt(0));
|
||||
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(0));
|
||||
|
||||
build.beginBlock(block4b);
|
||||
build.inst(IrCmd::STORE_TAG, build.vmReg(0), tag3a);
|
||||
build.inst(IrCmd::LOP_RETURN, build.vmReg(0), build.constInt(0));
|
||||
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(0));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build);
|
||||
@ -1427,11 +1427,11 @@ bb_4:
|
||||
|
||||
bb_5:
|
||||
STORE_TAG R0, %10
|
||||
LOP_RETURN R0, 0i
|
||||
RETURN R0, 0i
|
||||
|
||||
bb_6:
|
||||
STORE_TAG R0, %10
|
||||
LOP_RETURN R0, 0i
|
||||
RETURN R0, 0i
|
||||
|
||||
)");
|
||||
}
|
||||
@ -1488,7 +1488,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "SimpleDiamond")
|
||||
build.inst(IrCmd::JUMP, exit);
|
||||
|
||||
build.beginBlock(exit);
|
||||
build.inst(IrCmd::LOP_RETURN, build.vmReg(2), build.constInt(2));
|
||||
build.inst(IrCmd::RETURN, build.vmReg(2), build.constInt(2));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
computeCfgInfo(build.function);
|
||||
@ -1522,7 +1522,7 @@ bb_2:
|
||||
bb_3:
|
||||
; predecessors: bb_1, bb_2
|
||||
; in regs: R2, R3
|
||||
LOP_RETURN R2, 2i
|
||||
RETURN R2, 2i
|
||||
|
||||
)");
|
||||
}
|
||||
@ -1534,11 +1534,11 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "ImplicitFixedRegistersInVarargCall")
|
||||
|
||||
build.beginBlock(entry);
|
||||
build.inst(IrCmd::FALLBACK_GETVARARGS, build.constUint(0), build.vmReg(3), build.constInt(-1));
|
||||
build.inst(IrCmd::LOP_CALL, build.vmReg(0), build.constInt(-1), build.constInt(5));
|
||||
build.inst(IrCmd::CALL, build.vmReg(0), build.constInt(-1), build.constInt(5));
|
||||
build.inst(IrCmd::JUMP, exit);
|
||||
|
||||
build.beginBlock(exit);
|
||||
build.inst(IrCmd::LOP_RETURN, build.vmReg(0), build.constInt(5));
|
||||
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(5));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
computeCfgInfo(build.function);
|
||||
@ -1549,13 +1549,13 @@ bb_0:
|
||||
; in regs: R0, R1, R2
|
||||
; out regs: R0, R1, R2, R3, R4
|
||||
FALLBACK_GETVARARGS 0u, R3, -1i
|
||||
LOP_CALL R0, -1i, 5i
|
||||
CALL R0, -1i, 5i
|
||||
JUMP bb_1
|
||||
|
||||
bb_1:
|
||||
; predecessors: bb_0
|
||||
; in regs: R0, R1, R2, R3, R4
|
||||
LOP_RETURN R0, 5i
|
||||
RETURN R0, 5i
|
||||
|
||||
)");
|
||||
}
|
||||
@ -1573,7 +1573,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "ExplicitUseOfRegisterInVarargSequence")
|
||||
build.inst(IrCmd::JUMP, exit);
|
||||
|
||||
build.beginBlock(exit);
|
||||
build.inst(IrCmd::LOP_RETURN, build.vmReg(0), build.constInt(-1));
|
||||
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(-1));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
computeCfgInfo(build.function);
|
||||
@ -1590,7 +1590,7 @@ bb_0:
|
||||
bb_1:
|
||||
; predecessors: bb_0
|
||||
; in regs: R0...
|
||||
LOP_RETURN R0, -1i
|
||||
RETURN R0, -1i
|
||||
|
||||
)");
|
||||
}
|
||||
@ -1601,12 +1601,12 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "VariadicSequenceRestart")
|
||||
IrOp exit = build.block(IrBlockKind::Internal);
|
||||
|
||||
build.beginBlock(entry);
|
||||
build.inst(IrCmd::LOP_CALL, build.vmReg(1), build.constInt(0), build.constInt(-1));
|
||||
build.inst(IrCmd::LOP_CALL, build.vmReg(0), build.constInt(-1), build.constInt(-1));
|
||||
build.inst(IrCmd::CALL, build.vmReg(1), build.constInt(0), build.constInt(-1));
|
||||
build.inst(IrCmd::CALL, build.vmReg(0), build.constInt(-1), build.constInt(-1));
|
||||
build.inst(IrCmd::JUMP, exit);
|
||||
|
||||
build.beginBlock(exit);
|
||||
build.inst(IrCmd::LOP_RETURN, build.vmReg(0), build.constInt(-1));
|
||||
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(-1));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
computeCfgInfo(build.function);
|
||||
@ -1616,14 +1616,14 @@ bb_0:
|
||||
; successors: bb_1
|
||||
; in regs: R0, R1
|
||||
; out regs: R0...
|
||||
LOP_CALL R1, 0i, -1i
|
||||
LOP_CALL R0, -1i, -1i
|
||||
CALL R1, 0i, -1i
|
||||
CALL R0, -1i, -1i
|
||||
JUMP bb_1
|
||||
|
||||
bb_1:
|
||||
; predecessors: bb_0
|
||||
; in regs: R0...
|
||||
LOP_RETURN R0, -1i
|
||||
RETURN R0, -1i
|
||||
|
||||
)");
|
||||
}
|
||||
@ -1637,15 +1637,15 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "FallbackDoesNotFlowUp")
|
||||
build.beginBlock(entry);
|
||||
build.inst(IrCmd::FALLBACK_GETVARARGS, build.constUint(0), build.vmReg(1), build.constInt(-1));
|
||||
build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(0)), build.constTag(tnumber), fallback);
|
||||
build.inst(IrCmd::LOP_CALL, build.vmReg(0), build.constInt(-1), build.constInt(-1));
|
||||
build.inst(IrCmd::CALL, build.vmReg(0), build.constInt(-1), build.constInt(-1));
|
||||
build.inst(IrCmd::JUMP, exit);
|
||||
|
||||
build.beginBlock(fallback);
|
||||
build.inst(IrCmd::LOP_CALL, build.vmReg(0), build.constInt(-1), build.constInt(-1));
|
||||
build.inst(IrCmd::CALL, build.vmReg(0), build.constInt(-1), build.constInt(-1));
|
||||
build.inst(IrCmd::JUMP, exit);
|
||||
|
||||
build.beginBlock(exit);
|
||||
build.inst(IrCmd::LOP_RETURN, build.vmReg(0), build.constInt(-1));
|
||||
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(-1));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
computeCfgInfo(build.function);
|
||||
@ -1658,7 +1658,7 @@ bb_0:
|
||||
FALLBACK_GETVARARGS 0u, R1, -1i
|
||||
%1 = LOAD_TAG R0
|
||||
CHECK_TAG %1, tnumber, bb_fallback_1
|
||||
LOP_CALL R0, -1i, -1i
|
||||
CALL R0, -1i, -1i
|
||||
JUMP bb_2
|
||||
|
||||
bb_fallback_1:
|
||||
@ -1666,13 +1666,13 @@ bb_fallback_1:
|
||||
; successors: bb_2
|
||||
; in regs: R0, R1...
|
||||
; out regs: R0...
|
||||
LOP_CALL R0, -1i, -1i
|
||||
CALL R0, -1i, -1i
|
||||
JUMP bb_2
|
||||
|
||||
bb_2:
|
||||
; predecessors: bb_0, bb_fallback_1
|
||||
; in regs: R0...
|
||||
LOP_RETURN R0, -1i
|
||||
RETURN R0, -1i
|
||||
|
||||
)");
|
||||
}
|
||||
@ -1697,7 +1697,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "VariadicSequencePeeling")
|
||||
build.inst(IrCmd::JUMP, exit);
|
||||
|
||||
build.beginBlock(exit);
|
||||
build.inst(IrCmd::LOP_RETURN, build.vmReg(2), build.constInt(-1));
|
||||
build.inst(IrCmd::RETURN, build.vmReg(2), build.constInt(-1));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
computeCfgInfo(build.function);
|
||||
@ -1732,7 +1732,7 @@ bb_2:
|
||||
bb_3:
|
||||
; predecessors: bb_1, bb_2
|
||||
; in regs: R2...
|
||||
LOP_RETURN R2, -1i
|
||||
RETURN R2, -1i
|
||||
|
||||
)");
|
||||
}
|
||||
@ -1746,11 +1746,11 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "BuiltinVariadicStart")
|
||||
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), build.constDouble(1.0));
|
||||
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(2), build.constDouble(2.0));
|
||||
build.inst(IrCmd::ADJUST_STACK_TO_REG, build.vmReg(2), build.constInt(1));
|
||||
build.inst(IrCmd::LOP_CALL, build.vmReg(1), build.constInt(-1), build.constInt(1));
|
||||
build.inst(IrCmd::CALL, build.vmReg(1), build.constInt(-1), build.constInt(1));
|
||||
build.inst(IrCmd::JUMP, exit);
|
||||
|
||||
build.beginBlock(exit);
|
||||
build.inst(IrCmd::LOP_RETURN, build.vmReg(0), build.constInt(2));
|
||||
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(2));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
computeCfgInfo(build.function);
|
||||
@ -1763,13 +1763,13 @@ bb_0:
|
||||
STORE_DOUBLE R1, 1
|
||||
STORE_DOUBLE R2, 2
|
||||
ADJUST_STACK_TO_REG R2, 1i
|
||||
LOP_CALL R1, -1i, 1i
|
||||
CALL R1, -1i, 1i
|
||||
JUMP bb_1
|
||||
|
||||
bb_1:
|
||||
; predecessors: bb_0
|
||||
; in regs: R0, R1
|
||||
LOP_RETURN R0, 2i
|
||||
RETURN R0, 2i
|
||||
|
||||
)");
|
||||
}
|
||||
@ -1781,7 +1781,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "SetTable")
|
||||
|
||||
build.beginBlock(entry);
|
||||
build.inst(IrCmd::SET_TABLE, build.vmReg(0), build.vmReg(1), build.constUint(1));
|
||||
build.inst(IrCmd::LOP_RETURN, build.vmReg(0), build.constInt(1));
|
||||
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(1));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
computeCfgInfo(build.function);
|
||||
@ -1790,7 +1790,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "SetTable")
|
||||
bb_0:
|
||||
; in regs: R0, R1
|
||||
SET_TABLE R0, R1, 1u
|
||||
LOP_RETURN R0, 1i
|
||||
RETURN R0, 1i
|
||||
|
||||
)");
|
||||
}
|
||||
|
484
tests/IrCallWrapperX64.test.cpp
Normal file
484
tests/IrCallWrapperX64.test.cpp
Normal file
@ -0,0 +1,484 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/IrCallWrapperX64.h"
|
||||
#include "Luau/IrRegAllocX64.h"
|
||||
|
||||
#include "doctest.h"
|
||||
|
||||
using namespace Luau::CodeGen;
|
||||
using namespace Luau::CodeGen::X64;
|
||||
|
||||
class IrCallWrapperX64Fixture
|
||||
{
|
||||
public:
|
||||
IrCallWrapperX64Fixture()
|
||||
: build(/* logText */ true, ABIX64::Windows)
|
||||
, regs(function)
|
||||
, callWrap(regs, build, ~0u)
|
||||
{
|
||||
}
|
||||
|
||||
void checkMatch(std::string expected)
|
||||
{
|
||||
regs.assertAllFree();
|
||||
|
||||
build.finalize();
|
||||
|
||||
CHECK("\n" + build.text == expected);
|
||||
}
|
||||
|
||||
AssemblyBuilderX64 build;
|
||||
IrFunction function;
|
||||
IrRegAllocX64 regs;
|
||||
IrCallWrapperX64 callWrap;
|
||||
|
||||
// Tests rely on these to force interference between registers
|
||||
static constexpr RegisterX64 rArg1 = rcx;
|
||||
static constexpr RegisterX64 rArg1d = ecx;
|
||||
static constexpr RegisterX64 rArg2 = rdx;
|
||||
static constexpr RegisterX64 rArg2d = edx;
|
||||
static constexpr RegisterX64 rArg3 = r8;
|
||||
static constexpr RegisterX64 rArg3d = r8d;
|
||||
static constexpr RegisterX64 rArg4 = r9;
|
||||
static constexpr RegisterX64 rArg4d = r9d;
|
||||
};
|
||||
|
||||
TEST_SUITE_BEGIN("IrCallWrapperX64");
|
||||
|
||||
TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "SimpleRegs")
|
||||
{
|
||||
ScopedRegX64 tmp1{regs, regs.takeReg(rax)};
|
||||
ScopedRegX64 tmp2{regs, regs.takeReg(rArg2)};
|
||||
callWrap.addArgument(SizeX64::qword, tmp1);
|
||||
callWrap.addArgument(SizeX64::qword, tmp2); // Already in its place
|
||||
callWrap.call(qword[r12]);
|
||||
|
||||
checkMatch(R"(
|
||||
mov rcx,rax
|
||||
call qword ptr [r12]
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "TrickyUse1")
|
||||
{
|
||||
ScopedRegX64 tmp1{regs, regs.takeReg(rArg1)};
|
||||
callWrap.addArgument(SizeX64::qword, tmp1.reg); // Already in its place
|
||||
callWrap.addArgument(SizeX64::qword, tmp1.release());
|
||||
callWrap.call(qword[r12]);
|
||||
|
||||
checkMatch(R"(
|
||||
mov rdx,rcx
|
||||
call qword ptr [r12]
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "TrickyUse2")
|
||||
{
|
||||
ScopedRegX64 tmp1{regs, regs.takeReg(rArg1)};
|
||||
callWrap.addArgument(SizeX64::qword, qword[tmp1.reg]);
|
||||
callWrap.addArgument(SizeX64::qword, tmp1.release());
|
||||
callWrap.call(qword[r12]);
|
||||
|
||||
checkMatch(R"(
|
||||
mov rdx,rcx
|
||||
mov rcx,qword ptr [rcx]
|
||||
call qword ptr [r12]
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "SimpleMemImm")
|
||||
{
|
||||
ScopedRegX64 tmp1{regs, regs.takeReg(rax)};
|
||||
ScopedRegX64 tmp2{regs, regs.takeReg(rsi)};
|
||||
callWrap.addArgument(SizeX64::dword, 32);
|
||||
callWrap.addArgument(SizeX64::dword, -1);
|
||||
callWrap.addArgument(SizeX64::qword, qword[r14 + 32]);
|
||||
callWrap.addArgument(SizeX64::qword, qword[tmp1.release() + tmp2.release()]);
|
||||
callWrap.call(qword[r12]);
|
||||
|
||||
checkMatch(R"(
|
||||
mov r8,qword ptr [r14+020h]
|
||||
mov r9,qword ptr [rax+rsi]
|
||||
mov ecx,20h
|
||||
mov edx,FFFFFFFFh
|
||||
call qword ptr [r12]
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "SimpleStackArgs")
|
||||
{
|
||||
ScopedRegX64 tmp{regs, regs.takeReg(rax)};
|
||||
callWrap.addArgument(SizeX64::qword, tmp);
|
||||
callWrap.addArgument(SizeX64::qword, qword[r14 + 16]);
|
||||
callWrap.addArgument(SizeX64::qword, qword[r14 + 32]);
|
||||
callWrap.addArgument(SizeX64::qword, qword[r14 + 48]);
|
||||
callWrap.addArgument(SizeX64::dword, 1);
|
||||
callWrap.addArgument(SizeX64::qword, qword[r13]);
|
||||
callWrap.call(qword[r12]);
|
||||
|
||||
checkMatch(R"(
|
||||
mov rdx,qword ptr [r13]
|
||||
mov qword ptr [rsp+028h],rdx
|
||||
mov rcx,rax
|
||||
mov rdx,qword ptr [r14+010h]
|
||||
mov r8,qword ptr [r14+020h]
|
||||
mov r9,qword ptr [r14+030h]
|
||||
mov dword ptr [rsp+020h],1
|
||||
call qword ptr [r12]
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "FixedRegisters")
|
||||
{
|
||||
callWrap.addArgument(SizeX64::dword, 1);
|
||||
callWrap.addArgument(SizeX64::qword, 2);
|
||||
callWrap.addArgument(SizeX64::qword, 3);
|
||||
callWrap.addArgument(SizeX64::qword, 4);
|
||||
callWrap.addArgument(SizeX64::qword, r14);
|
||||
callWrap.call(qword[r12]);
|
||||
|
||||
checkMatch(R"(
|
||||
mov qword ptr [rsp+020h],r14
|
||||
mov ecx,1
|
||||
mov rdx,2
|
||||
mov r8,3
|
||||
mov r9,4
|
||||
call qword ptr [r12]
|
||||
)");
|
||||
}
|
||||
|
||||
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)};
|
||||
callWrap.addArgument(SizeX64::qword, tmp1);
|
||||
callWrap.addArgument(SizeX64::qword, tmp2);
|
||||
callWrap.addArgument(SizeX64::qword, tmp3);
|
||||
callWrap.addArgument(SizeX64::qword, tmp4);
|
||||
callWrap.call(qword[r12]);
|
||||
|
||||
checkMatch(R"(
|
||||
mov r8,rdx
|
||||
mov rdx,rsi
|
||||
mov r9,rcx
|
||||
mov rcx,rdi
|
||||
call qword ptr [r12]
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "FakeInterference")
|
||||
{
|
||||
ScopedRegX64 tmp1{regs, regs.takeReg(rArg1)};
|
||||
ScopedRegX64 tmp2{regs, regs.takeReg(rArg2)};
|
||||
callWrap.addArgument(SizeX64::qword, qword[tmp1.release() + 8]);
|
||||
callWrap.addArgument(SizeX64::qword, qword[tmp2.release() + 8]);
|
||||
callWrap.call(qword[r12]);
|
||||
|
||||
checkMatch(R"(
|
||||
mov rcx,qword ptr [rcx+8]
|
||||
mov rdx,qword ptr [rdx+8]
|
||||
call qword ptr [r12]
|
||||
)");
|
||||
}
|
||||
|
||||
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)};
|
||||
callWrap.addArgument(SizeX64::qword, tmp1);
|
||||
callWrap.addArgument(SizeX64::qword, tmp2);
|
||||
callWrap.addArgument(SizeX64::qword, tmp3);
|
||||
callWrap.addArgument(SizeX64::qword, tmp4);
|
||||
callWrap.call(qword[r12]);
|
||||
|
||||
checkMatch(R"(
|
||||
mov rax,r9
|
||||
mov r9,rcx
|
||||
mov rcx,rax
|
||||
mov rax,r8
|
||||
mov r8,rdx
|
||||
mov rdx,rax
|
||||
call qword ptr [r12]
|
||||
)");
|
||||
}
|
||||
|
||||
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)};
|
||||
callWrap.addArgument(SizeX64::dword, tmp1);
|
||||
callWrap.addArgument(SizeX64::dword, tmp2);
|
||||
callWrap.addArgument(SizeX64::dword, tmp3);
|
||||
callWrap.addArgument(SizeX64::dword, tmp4);
|
||||
callWrap.call(qword[r12]);
|
||||
|
||||
checkMatch(R"(
|
||||
mov eax,r9d
|
||||
mov r9d,ecx
|
||||
mov ecx,eax
|
||||
mov eax,r8d
|
||||
mov r8d,edx
|
||||
mov edx,eax
|
||||
call qword ptr [r12]
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "HardInterferenceFp")
|
||||
{
|
||||
ScopedRegX64 tmp1{regs, regs.takeReg(xmm1)};
|
||||
ScopedRegX64 tmp2{regs, regs.takeReg(xmm0)};
|
||||
callWrap.addArgument(SizeX64::xmmword, tmp1);
|
||||
callWrap.addArgument(SizeX64::xmmword, tmp2);
|
||||
callWrap.call(qword[r12]);
|
||||
|
||||
checkMatch(R"(
|
||||
vmovsd xmm2,xmm1,xmm1
|
||||
vmovsd xmm1,xmm0,xmm0
|
||||
vmovsd xmm0,xmm2,xmm2
|
||||
call qword ptr [r12]
|
||||
)");
|
||||
}
|
||||
|
||||
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)};
|
||||
callWrap.addArgument(SizeX64::qword, int1);
|
||||
callWrap.addArgument(SizeX64::qword, int2);
|
||||
callWrap.addArgument(SizeX64::xmmword, fp1);
|
||||
callWrap.addArgument(SizeX64::xmmword, fp2);
|
||||
callWrap.call(qword[r12]);
|
||||
|
||||
checkMatch(R"(
|
||||
mov rax,rdx
|
||||
mov rdx,rcx
|
||||
mov rcx,rax
|
||||
vmovsd xmm0,xmm3,xmm3
|
||||
vmovsd xmm3,xmm2,xmm2
|
||||
vmovsd xmm2,xmm0,xmm0
|
||||
call qword ptr [r12]
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "FakeMultiuseInterferenceMem")
|
||||
{
|
||||
ScopedRegX64 tmp1{regs, regs.takeReg(rArg1)};
|
||||
ScopedRegX64 tmp2{regs, regs.takeReg(rArg2)};
|
||||
callWrap.addArgument(SizeX64::qword, qword[tmp1.reg + tmp2.reg + 8]);
|
||||
callWrap.addArgument(SizeX64::qword, qword[tmp2.reg + 16]);
|
||||
tmp1.release();
|
||||
tmp2.release();
|
||||
callWrap.call(qword[r12]);
|
||||
|
||||
checkMatch(R"(
|
||||
mov rcx,qword ptr [rcx+rdx+8]
|
||||
mov rdx,qword ptr [rdx+010h]
|
||||
call qword ptr [r12]
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "HardMultiuseInterferenceMem1")
|
||||
{
|
||||
ScopedRegX64 tmp1{regs, regs.takeReg(rArg1)};
|
||||
ScopedRegX64 tmp2{regs, regs.takeReg(rArg2)};
|
||||
callWrap.addArgument(SizeX64::qword, qword[tmp1.reg + tmp2.reg + 8]);
|
||||
callWrap.addArgument(SizeX64::qword, qword[tmp1.reg + 16]);
|
||||
tmp1.release();
|
||||
tmp2.release();
|
||||
callWrap.call(qword[r12]);
|
||||
|
||||
checkMatch(R"(
|
||||
mov rax,rcx
|
||||
mov rcx,qword ptr [rax+rdx+8]
|
||||
mov rdx,qword ptr [rax+010h]
|
||||
call qword ptr [r12]
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "HardMultiuseInterferenceMem2")
|
||||
{
|
||||
ScopedRegX64 tmp1{regs, regs.takeReg(rArg1)};
|
||||
ScopedRegX64 tmp2{regs, regs.takeReg(rArg2)};
|
||||
callWrap.addArgument(SizeX64::qword, qword[tmp1.reg + tmp2.reg + 8]);
|
||||
callWrap.addArgument(SizeX64::qword, qword[tmp1.reg + tmp2.reg + 16]);
|
||||
tmp1.release();
|
||||
tmp2.release();
|
||||
callWrap.call(qword[r12]);
|
||||
|
||||
checkMatch(R"(
|
||||
mov rax,rcx
|
||||
mov rcx,qword ptr [rax+rdx+8]
|
||||
mov rdx,qword ptr [rax+rdx+010h]
|
||||
call qword ptr [r12]
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "HardMultiuseInterferenceMem3")
|
||||
{
|
||||
ScopedRegX64 tmp1{regs, regs.takeReg(rArg3)};
|
||||
ScopedRegX64 tmp2{regs, regs.takeReg(rArg2)};
|
||||
ScopedRegX64 tmp3{regs, regs.takeReg(rArg1)};
|
||||
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]);
|
||||
tmp1.release();
|
||||
tmp2.release();
|
||||
tmp3.release();
|
||||
callWrap.call(qword[r12]);
|
||||
|
||||
checkMatch(R"(
|
||||
mov rax,r8
|
||||
mov r8,qword ptr [rcx+rax+010h]
|
||||
mov rbx,rdx
|
||||
mov rdx,qword ptr [rbx+rcx+010h]
|
||||
mov rcx,qword ptr [rax+rbx+8]
|
||||
call qword ptr [r12]
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "InterferenceWithCallArg1")
|
||||
{
|
||||
ScopedRegX64 tmp1{regs, regs.takeReg(rArg1)};
|
||||
callWrap.addArgument(SizeX64::qword, qword[tmp1.reg + 8]);
|
||||
callWrap.call(qword[tmp1.release() + 16]);
|
||||
|
||||
checkMatch(R"(
|
||||
mov rax,rcx
|
||||
mov rcx,qword ptr [rax+8]
|
||||
call qword ptr [rax+010h]
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "InterferenceWithCallArg2")
|
||||
{
|
||||
ScopedRegX64 tmp1{regs, regs.takeReg(rArg1)};
|
||||
ScopedRegX64 tmp2{regs, regs.takeReg(rArg2)};
|
||||
callWrap.addArgument(SizeX64::qword, tmp2);
|
||||
callWrap.call(qword[tmp1.release() + 16]);
|
||||
|
||||
checkMatch(R"(
|
||||
mov rax,rcx
|
||||
mov rcx,rdx
|
||||
call qword ptr [rax+010h]
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "InterferenceWithCallArg3")
|
||||
{
|
||||
ScopedRegX64 tmp1{regs, regs.takeReg(rArg1)};
|
||||
callWrap.addArgument(SizeX64::qword, tmp1.reg);
|
||||
callWrap.call(qword[tmp1.release() + 16]);
|
||||
|
||||
checkMatch(R"(
|
||||
call qword ptr [rcx+010h]
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "WithLastIrInstUse1")
|
||||
{
|
||||
IrInst irInst1;
|
||||
IrOp irOp1 = {IrOpKind::Inst, 0};
|
||||
irInst1.regX64 = regs.takeReg(xmm0);
|
||||
irInst1.lastUse = 1;
|
||||
function.instructions.push_back(irInst1);
|
||||
callWrap.instIdx = irInst1.lastUse;
|
||||
|
||||
callWrap.addArgument(SizeX64::xmmword, irInst1.regX64, irOp1); // Already in its place
|
||||
callWrap.addArgument(SizeX64::xmmword, qword[r12 + 8]);
|
||||
callWrap.call(qword[r12]);
|
||||
|
||||
checkMatch(R"(
|
||||
vmovsd xmm1,qword ptr [r12+8]
|
||||
call qword ptr [r12]
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "WithLastIrInstUse2")
|
||||
{
|
||||
IrInst irInst1;
|
||||
IrOp irOp1 = {IrOpKind::Inst, 0};
|
||||
irInst1.regX64 = regs.takeReg(xmm0);
|
||||
irInst1.lastUse = 1;
|
||||
function.instructions.push_back(irInst1);
|
||||
callWrap.instIdx = irInst1.lastUse;
|
||||
|
||||
callWrap.addArgument(SizeX64::xmmword, qword[r12 + 8]);
|
||||
callWrap.addArgument(SizeX64::xmmword, irInst1.regX64, irOp1);
|
||||
callWrap.call(qword[r12]);
|
||||
|
||||
checkMatch(R"(
|
||||
vmovsd xmm1,xmm0,xmm0
|
||||
vmovsd xmm0,qword ptr [r12+8]
|
||||
call qword ptr [r12]
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "WithLastIrInstUse3")
|
||||
{
|
||||
IrInst irInst1;
|
||||
IrOp irOp1 = {IrOpKind::Inst, 0};
|
||||
irInst1.regX64 = regs.takeReg(xmm0);
|
||||
irInst1.lastUse = 1;
|
||||
function.instructions.push_back(irInst1);
|
||||
callWrap.instIdx = irInst1.lastUse;
|
||||
|
||||
callWrap.addArgument(SizeX64::xmmword, irInst1.regX64, irOp1);
|
||||
callWrap.addArgument(SizeX64::xmmword, irInst1.regX64, irOp1);
|
||||
callWrap.call(qword[r12]);
|
||||
|
||||
checkMatch(R"(
|
||||
vmovsd xmm1,xmm0,xmm0
|
||||
call qword ptr [r12]
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "WithLastIrInstUse4")
|
||||
{
|
||||
IrInst irInst1;
|
||||
IrOp irOp1 = {IrOpKind::Inst, 0};
|
||||
irInst1.regX64 = regs.takeReg(rax);
|
||||
irInst1.lastUse = 1;
|
||||
function.instructions.push_back(irInst1);
|
||||
callWrap.instIdx = irInst1.lastUse;
|
||||
|
||||
ScopedRegX64 tmp{regs, regs.takeReg(rdx)};
|
||||
callWrap.addArgument(SizeX64::qword, r15);
|
||||
callWrap.addArgument(SizeX64::qword, irInst1.regX64, irOp1);
|
||||
callWrap.addArgument(SizeX64::qword, tmp);
|
||||
callWrap.call(qword[r12]);
|
||||
|
||||
checkMatch(R"(
|
||||
mov rcx,r15
|
||||
mov r8,rdx
|
||||
mov rdx,rax
|
||||
call qword ptr [r12]
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "ExtraCoverage")
|
||||
{
|
||||
ScopedRegX64 tmp1{regs, regs.takeReg(rArg1)};
|
||||
ScopedRegX64 tmp2{regs, regs.takeReg(rArg2)};
|
||||
callWrap.addArgument(SizeX64::qword, addr[r12 + 8]);
|
||||
callWrap.addArgument(SizeX64::qword, addr[r12 + 16]);
|
||||
callWrap.addArgument(SizeX64::xmmword, xmmword[r13]);
|
||||
callWrap.call(qword[tmp1.release() + tmp2.release()]);
|
||||
|
||||
checkMatch(R"(
|
||||
vmovups xmm2,xmmword ptr [r13]
|
||||
mov rax,rcx
|
||||
lea rcx,none ptr [r12+8]
|
||||
mov rbx,rdx
|
||||
lea rdx,none ptr [r12+010h]
|
||||
call qword ptr [rax+rbx]
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
@ -157,8 +157,6 @@ TEST_CASE("string_interpolation_basic")
|
||||
|
||||
TEST_CASE("string_interpolation_full")
|
||||
{
|
||||
ScopedFastFlag sff("LuauFixInterpStringMid", true);
|
||||
|
||||
const std::string testInput = R"(`foo {"bar"} {"baz"} end`)";
|
||||
Luau::Allocator alloc;
|
||||
AstNameTable table(alloc);
|
||||
|
@ -1444,8 +1444,6 @@ TEST_CASE_FIXTURE(Fixture, "LintHygieneUAF")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "DeprecatedApiTyped")
|
||||
{
|
||||
ScopedFastFlag sff("LuauImproveDeprecatedApiLint", true);
|
||||
|
||||
unfreeze(frontend.globals.globalTypes);
|
||||
TypeId instanceType = frontend.globals.globalTypes.addType(ClassType{"Instance", {}, std::nullopt, std::nullopt, {}, {}, "Test"});
|
||||
persist(instanceType);
|
||||
@ -1496,8 +1494,6 @@ end
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "DeprecatedApiUntyped")
|
||||
{
|
||||
ScopedFastFlag sff("LuauImproveDeprecatedApiLint", true);
|
||||
|
||||
if (TableType* ttv = getMutable<TableType>(getGlobalBinding(frontend.globals, "table")))
|
||||
{
|
||||
ttv->props["foreach"].deprecated = true;
|
||||
|
@ -470,7 +470,6 @@ TEST_SUITE_END();
|
||||
|
||||
struct NormalizeFixture : Fixture
|
||||
{
|
||||
ScopedFastFlag sff1{"LuauNegatedFunctionTypes", true};
|
||||
ScopedFastFlag sff2{"LuauNegatedClassTypes", true};
|
||||
|
||||
TypeArena arena;
|
||||
|
@ -1040,8 +1040,6 @@ TEST_CASE_FIXTURE(Fixture, "parse_interpolated_string_call_without_parens")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "parse_interpolated_string_without_expression")
|
||||
{
|
||||
ScopedFastFlag sff("LuauFixInterpStringMid", true);
|
||||
|
||||
try
|
||||
{
|
||||
parse(R"(
|
||||
|
@ -1014,4 +1014,34 @@ TEST_CASE_FIXTURE(Fixture, "another_thing_from_roact")
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
/*
|
||||
* It is sometimes possible for type alias resolution to produce a TypeId that
|
||||
* belongs to a different module.
|
||||
*
|
||||
* We must not mutate any fields of the resulting type when this happens. The
|
||||
* memory has been frozen.
|
||||
*/
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "alias_expands_to_bare_reference_to_imported_type")
|
||||
{
|
||||
fileResolver.source["game/A"] = R"(
|
||||
--!strict
|
||||
export type Object = {[string]: any}
|
||||
return {}
|
||||
)";
|
||||
|
||||
fileResolver.source["game/B"] = R"(
|
||||
local A = require(script.Parent.A)
|
||||
|
||||
type Object = A.Object
|
||||
type ReadOnly<T> = T
|
||||
|
||||
local function f(): ReadOnly<Object>
|
||||
return nil :: any
|
||||
end
|
||||
)";
|
||||
|
||||
CheckResult result = frontend.check("game/B");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -1784,7 +1784,6 @@ z = y -- Not OK, so the line is colorable
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "function_is_supertype_of_concrete_functions")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauNegatedFunctionTypes", true};
|
||||
registerHiddenTypes(&frontend);
|
||||
|
||||
CheckResult result = check(R"(
|
||||
@ -1803,7 +1802,6 @@ TEST_CASE_FIXTURE(Fixture, "function_is_supertype_of_concrete_functions")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "concrete_functions_are_not_supertypes_of_function")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauNegatedFunctionTypes", true};
|
||||
registerHiddenTypes(&frontend);
|
||||
|
||||
CheckResult result = check(R"(
|
||||
@ -1824,7 +1822,6 @@ TEST_CASE_FIXTURE(Fixture, "concrete_functions_are_not_supertypes_of_function")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "other_things_are_not_related_to_function")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauNegatedFunctionTypes", true};
|
||||
registerHiddenTypes(&frontend);
|
||||
|
||||
CheckResult result = check(R"(
|
||||
|
@ -707,4 +707,26 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "cli_68448_iterators_need_not_accept_nil")
|
||||
CHECK(toString(requireType("makeEnum"), {true}) == "<a>({a}) -> {| [a]: a |}");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "iterate_over_free_table")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
function print(x) end
|
||||
|
||||
function dump(tbl)
|
||||
print(tbl.whatever)
|
||||
for k, v in tbl do
|
||||
print(k)
|
||||
print(v)
|
||||
end
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
GenericError* ge = get<GenericError>(result.errors[0]);
|
||||
REQUIRE(ge);
|
||||
|
||||
CHECK("Cannot iterate over a table without indexer" == ge->message);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -381,4 +381,29 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "react_style_oo")
|
||||
CHECK("string" == toString(requireType("hello")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "cycle_between_object_constructor_and_alias")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local T = {}
|
||||
T.__index = T
|
||||
|
||||
function T.new(): T
|
||||
return setmetatable({}, T)
|
||||
end
|
||||
|
||||
export type T = typeof(T.new())
|
||||
|
||||
return T
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
auto module = getMainModule();
|
||||
|
||||
REQUIRE(module->exportedTypeBindings.count("T"));
|
||||
|
||||
TypeId aliasType = module->exportedTypeBindings["T"].type;
|
||||
CHECK_MESSAGE(get<MetatableType>(follow(aliasType)), "Expected metatable type but got: " << toString(aliasType));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -860,8 +860,6 @@ TEST_CASE_FIXTURE(Fixture, "operator_eq_operands_are_not_subtypes_of_each_other_
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "operator_eq_completely_incompatible")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauIntersectionTestForEquality", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local a: string | number = "hi"
|
||||
local b: {x: string}? = {x = "bye"}
|
||||
@ -970,8 +968,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "expected_types_through_binary_or")
|
||||
|
||||
TEST_CASE_FIXTURE(ClassFixture, "unrelated_classes_cannot_be_compared")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauIntersectionTestForEquality", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local a = BaseClass.New()
|
||||
local b = UnrelatedClass.New()
|
||||
@ -984,8 +980,6 @@ TEST_CASE_FIXTURE(ClassFixture, "unrelated_classes_cannot_be_compared")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "unrelated_primitives_cannot_be_compared")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauIntersectionTestForEquality", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local c = 5 == true
|
||||
)");
|
||||
|
@ -176,8 +176,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "error_on_eq_metamethod_returning_a_type_othe
|
||||
// We need refine both operands as `never` in the `==` branch.
|
||||
TEST_CASE_FIXTURE(Fixture, "lvalue_equals_another_lvalue_with_no_overlap")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauIntersectionTestForEquality", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(a: string, b: boolean?)
|
||||
if a == b then
|
||||
|
@ -18,7 +18,6 @@ LUAU_FASTFLAG(LuauLowerBoundsCalculation);
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
LUAU_FASTFLAG(LuauTypeMismatchInvarianceInError)
|
||||
LUAU_FASTFLAG(LuauDontExtendUnsealedRValueTables)
|
||||
|
||||
TEST_SUITE_BEGIN("TableTests");
|
||||
|
||||
@ -913,10 +912,7 @@ TEST_CASE_FIXTURE(Fixture, "disallow_indexing_into_an_unsealed_table_with_no_ind
|
||||
local k1 = getConstant("key1")
|
||||
)");
|
||||
|
||||
if (FFlag::LuauDontExtendUnsealedRValueTables)
|
||||
CHECK("any" == toString(requireType("k1")));
|
||||
else
|
||||
CHECK("a" == toString(requireType("k1")));
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
@ -3542,8 +3538,6 @@ _ = {_,}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "when_augmenting_an_unsealed_table_with_an_indexer_apply_the_correct_scope_to_the_indexer_type")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauDontExtendUnsealedRValueTables", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local events = {}
|
||||
local mockObserveEvent = function(_, key, callback)
|
||||
@ -3572,8 +3566,6 @@ TEST_CASE_FIXTURE(Fixture, "when_augmenting_an_unsealed_table_with_an_indexer_ap
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "dont_extend_unsealed_tables_in_rvalue_position")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauDontExtendUnsealedRValueTables", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local testDictionary = {
|
||||
FruitName = "Lemon",
|
||||
|
@ -1194,7 +1194,6 @@ TEST_CASE_FIXTURE(Fixture, "dcr_delays_expansion_of_function_containing_blocked_
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{"DebugLuauDeferredConstraintResolution", true},
|
||||
{"LuauTinyUnifyNormalsFix", true},
|
||||
// If we run this with error-suppression, it triggers an assertion.
|
||||
// FATAL ERROR: Assertion failed: !"Internal error: Trying to normalize a BlockedType"
|
||||
{"LuauTransitiveSubtyping", false},
|
||||
|
@ -25,9 +25,6 @@ BuiltinTests.string_format_correctly_ordered_types
|
||||
BuiltinTests.string_format_report_all_type_errors_at_correct_positions
|
||||
BuiltinTests.string_format_tostring_specifier_type_constraint
|
||||
BuiltinTests.string_format_use_correct_argument2
|
||||
BuiltinTests.table_pack
|
||||
BuiltinTests.table_pack_reduce
|
||||
BuiltinTests.table_pack_variadic
|
||||
DefinitionTests.class_definition_overload_metamethods
|
||||
DefinitionTests.class_definition_string_props
|
||||
GenericsTests.apply_type_function_nested_generics2
|
||||
@ -114,7 +111,6 @@ TableTests.table_subtyping_with_missing_props_dont_report_multiple_errors
|
||||
TableTests.table_unification_4
|
||||
TableTests.used_colon_instead_of_dot
|
||||
TableTests.used_dot_instead_of_colon
|
||||
ToString.named_metatable_toStringNamedFunction
|
||||
ToString.toStringDetailed2
|
||||
ToString.toStringErrorPack
|
||||
ToString.toStringNamedFunction_generic_pack
|
||||
@ -137,6 +133,7 @@ TypeInfer.check_type_infer_recursion_count
|
||||
TypeInfer.cli_50041_committing_txnlog_in_apollo_client_error
|
||||
TypeInfer.dont_report_type_errors_within_an_AstExprError
|
||||
TypeInfer.dont_report_type_errors_within_an_AstStatError
|
||||
TypeInfer.follow_on_new_types_in_substitution
|
||||
TypeInfer.fuzz_free_table_type_change_during_index_check
|
||||
TypeInfer.infer_assignment_value_types_mutable_lval
|
||||
TypeInfer.no_stack_overflow_from_isoptional
|
||||
|
@ -1,45 +1,46 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
|
||||
|
||||
<Type Name="Luau::CodeGen::RegisterX64">
|
||||
<DisplayString Condition="size == Luau::CodeGen::SizeX64::none && index == 16">noreg</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::SizeX64::none && index == 0">rip</DisplayString>
|
||||
<Type Name="Luau::CodeGen::X64::RegisterX64">
|
||||
<DisplayString Condition="size == Luau::CodeGen::X64::SizeX64::none && index == 16">noreg</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::X64::SizeX64::none && index == 0">rip</DisplayString>
|
||||
|
||||
<DisplayString Condition="size == Luau::CodeGen::SizeX64::byte && index == 0">al</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::SizeX64::byte && index == 1">cl</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::SizeX64::byte && index == 2">dl</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::SizeX64::byte && index == 3">bl</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::X64::SizeX64::byte && index == 0">al</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::X64::SizeX64::byte && index == 1">cl</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::X64::SizeX64::byte && index == 2">dl</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::X64::SizeX64::byte && index == 3">bl</DisplayString>
|
||||
|
||||
<DisplayString Condition="size == Luau::CodeGen::SizeX64::dword && index == 0">eax</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::SizeX64::dword && index == 1">ecx</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::SizeX64::dword && index == 2">edx</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::SizeX64::dword && index == 3">ebx</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::SizeX64::dword && index == 4">esp</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::SizeX64::dword && index == 5">ebp</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::SizeX64::dword && index == 6">esi</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::SizeX64::dword && index == 7">edi</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::SizeX64::dword && index >= 8">e{(int)index,d}d</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::X64::SizeX64::dword && index == 0">eax</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::X64::SizeX64::dword && index == 1">ecx</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::X64::SizeX64::dword && index == 2">edx</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::X64::SizeX64::dword && index == 3">ebx</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::X64::SizeX64::dword && index == 4">esp</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::X64::SizeX64::dword && index == 5">ebp</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::X64::SizeX64::dword && index == 6">esi</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::X64::SizeX64::dword && index == 7">edi</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::X64::SizeX64::dword && index >= 8">e{(int)index,d}d</DisplayString>
|
||||
|
||||
<DisplayString Condition="size == Luau::CodeGen::SizeX64::qword && index == 0">rax</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::SizeX64::qword && index == 1">rcx</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::SizeX64::qword && index == 2">rdx</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::SizeX64::qword && index == 3">rbx</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::SizeX64::qword && index == 4">rsp</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::SizeX64::qword && index == 5">rbp</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::SizeX64::qword && index == 6">rsi</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::SizeX64::qword && index == 7">rdi</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::SizeX64::qword && index >= 8">r{(int)index,d}</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::X64::SizeX64::qword && index == 0">rax</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::X64::SizeX64::qword && index == 1">rcx</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::X64::SizeX64::qword && index == 2">rdx</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::X64::SizeX64::qword && index == 3">rbx</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::X64::SizeX64::qword && index == 4">rsp</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::X64::SizeX64::qword && index == 5">rbp</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::X64::SizeX64::qword && index == 6">rsi</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::X64::SizeX64::qword && index == 7">rdi</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::X64::SizeX64::qword && index >= 8">r{(int)index,d}</DisplayString>
|
||||
|
||||
<DisplayString Condition="size == Luau::CodeGen::SizeX64::xmmword">xmm{(int)index,d}</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::X64::SizeX64::xmmword">xmm{(int)index,d}</DisplayString>
|
||||
|
||||
<DisplayString Condition="size == Luau::CodeGen::SizeX64::ymmword">ymm{(int)index,d}</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::X64::SizeX64::ymmword">ymm{(int)index,d}</DisplayString>
|
||||
</Type>
|
||||
|
||||
<Type Name="Luau::CodeGen::OperandX64">
|
||||
<Type Name="Luau::CodeGen::X64::OperandX64">
|
||||
<DisplayString Condition="cat == 0">{base}</DisplayString>
|
||||
<DisplayString Condition="cat == 1 && base.size != 0 && index.size != 0">{memSize,en} ptr[{base} + {index}*{(int)scale,d} + {imm}]</DisplayString>
|
||||
<DisplayString Condition="cat == 1 && index.size != 0">{memSize,en} ptr[{index}*{(int)scale,d} + {imm}]</DisplayString>
|
||||
<DisplayString Condition="cat == 1 && base.size != 0">{memSize,en} ptr[{base} + {imm}]</DisplayString>
|
||||
<DisplayString Condition="cat == 1 && base.index == 0">{memSize,en} ptr[{base} + {imm}]</DisplayString>
|
||||
<DisplayString Condition="cat == 1">{memSize,en} ptr[{imm}]</DisplayString>
|
||||
<DisplayString Condition="cat == 2">{imm}</DisplayString>
|
||||
<Expand>
|
||||
|
Loading…
Reference in New Issue
Block a user