Sync to upstream/release/580 (#951)
* Added luau-compile executable target to build/test compilation without having full REPL included. In our new typechecker: * Fixed the order in which constraints are checked to get more deterministic errors in different environments * Fixed `isNumber`/`isString` checks to fix false positive errors in binary comparisons * CannotCallNonFunction error is reported when calling an intersection type of non-functions In our native code generation (jit): * Outlined X64 return instruction code to improve code size * Improved performance of return instruction on A64 * Added construction of a dominator tree for future optimizations
This commit is contained in:
parent
febebde72a
commit
3ecd3a82ab
|
@ -0,0 +1,134 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Common.h"
|
||||
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <type_traits>
|
||||
#include <iterator>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
template<typename K, typename V>
|
||||
struct InsertionOrderedMap
|
||||
{
|
||||
static_assert(std::is_trivially_copyable_v<K>, "key must be trivially copyable");
|
||||
|
||||
private:
|
||||
using vec = std::vector<std::pair<K, V>>;
|
||||
|
||||
public:
|
||||
using iterator = typename vec::iterator;
|
||||
using const_iterator = typename vec::const_iterator;
|
||||
|
||||
void insert(K k, V v)
|
||||
{
|
||||
if (indices.count(k) != 0)
|
||||
return;
|
||||
|
||||
pairs.push_back(std::make_pair(k, std::move(v)));
|
||||
indices[k] = pairs.size() - 1;
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
pairs.clear();
|
||||
indices.clear();
|
||||
}
|
||||
|
||||
size_t size() const
|
||||
{
|
||||
LUAU_ASSERT(pairs.size() == indices.size());
|
||||
return pairs.size();
|
||||
}
|
||||
|
||||
bool contains(const K& k) const
|
||||
{
|
||||
return indices.count(k) > 0;
|
||||
}
|
||||
|
||||
const V* get(const K& k) const
|
||||
{
|
||||
auto it = indices.find(k);
|
||||
if (it == indices.end())
|
||||
return nullptr;
|
||||
else
|
||||
return &pairs.at(it->second).second;
|
||||
}
|
||||
|
||||
V* get(const K& k)
|
||||
{
|
||||
auto it = indices.find(k);
|
||||
if (it == indices.end())
|
||||
return nullptr;
|
||||
else
|
||||
return &pairs.at(it->second).second;
|
||||
}
|
||||
|
||||
const_iterator begin() const
|
||||
{
|
||||
return pairs.begin();
|
||||
}
|
||||
|
||||
const_iterator end() const
|
||||
{
|
||||
return pairs.end();
|
||||
}
|
||||
|
||||
iterator begin()
|
||||
{
|
||||
return pairs.begin();
|
||||
}
|
||||
|
||||
iterator end()
|
||||
{
|
||||
return pairs.end();
|
||||
}
|
||||
|
||||
const_iterator find(K k) const
|
||||
{
|
||||
auto indicesIt = indices.find(k);
|
||||
if (indicesIt == indices.end())
|
||||
return end();
|
||||
else
|
||||
return begin() + indicesIt->second;
|
||||
}
|
||||
|
||||
iterator find(K k)
|
||||
{
|
||||
auto indicesIt = indices.find(k);
|
||||
if (indicesIt == indices.end())
|
||||
return end();
|
||||
else
|
||||
return begin() + indicesIt->second;
|
||||
}
|
||||
|
||||
void erase(iterator it)
|
||||
{
|
||||
if (it == pairs.end())
|
||||
return;
|
||||
|
||||
K k = it->first;
|
||||
auto indexIt = indices.find(k);
|
||||
if (indexIt == indices.end())
|
||||
return;
|
||||
|
||||
size_t removed = indexIt->second;
|
||||
indices.erase(indexIt);
|
||||
pairs.erase(it);
|
||||
|
||||
for (auto& [_, index] : indices)
|
||||
{
|
||||
if (index > removed)
|
||||
--index;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
vec pairs;
|
||||
std::unordered_map<K, size_t> indices;
|
||||
};
|
||||
|
||||
}
|
|
@ -64,6 +64,7 @@ public:
|
|||
|
||||
bool operator==(const TypeIds& there) const;
|
||||
size_t getHash() const;
|
||||
bool isNever() const;
|
||||
};
|
||||
|
||||
} // namespace Luau
|
||||
|
@ -269,12 +270,24 @@ struct NormalizedType
|
|||
NormalizedType& operator=(NormalizedType&&) = default;
|
||||
|
||||
// IsType functions
|
||||
/// Returns true if the type is exactly a number. Behaves like Type::isNumber()
|
||||
bool isExactlyNumber() const;
|
||||
|
||||
/// Returns true if the type is a subtype of function. This includes any and unknown.
|
||||
bool isFunction() const;
|
||||
/// Returns true if the type is a subtype of string(it could be a singleton). Behaves like Type::isString()
|
||||
bool isSubtypeOfString() const;
|
||||
|
||||
/// Returns true if the type is a subtype of number. This includes any and unknown.
|
||||
bool isNumber() const;
|
||||
// Helpers that improve readability of the above (they just say if the component is present)
|
||||
bool hasTops() const;
|
||||
bool hasBooleans() const;
|
||||
bool hasClasses() const;
|
||||
bool hasErrors() const;
|
||||
bool hasNils() const;
|
||||
bool hasNumbers() const;
|
||||
bool hasStrings() const;
|
||||
bool hasThreads() const;
|
||||
bool hasTables() const;
|
||||
bool hasFunctions() const;
|
||||
bool hasTyvars() const;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include "Luau/TypeFamily.h"
|
||||
#include "Luau/Simplify.h"
|
||||
#include "Luau/VisitType.h"
|
||||
#include "Luau/InsertionOrderedMap.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
|
@ -196,7 +197,7 @@ struct RefinementPartition
|
|||
bool shouldAppendNilType = false;
|
||||
};
|
||||
|
||||
using RefinementContext = std::unordered_map<DefId, RefinementPartition>;
|
||||
using RefinementContext = InsertionOrderedMap<DefId, RefinementPartition>;
|
||||
|
||||
static void unionRefinements(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, const RefinementContext& lhs, const RefinementContext& rhs,
|
||||
RefinementContext& dest, std::vector<ConstraintV>* constraints)
|
||||
|
@ -229,8 +230,9 @@ static void unionRefinements(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeAre
|
|||
TypeId rightDiscriminantTy =
|
||||
rhsIt->second.discriminantTypes.size() == 1 ? rhsIt->second.discriminantTypes[0] : intersect(rhsIt->second.discriminantTypes);
|
||||
|
||||
dest[def].discriminantTypes.push_back(simplifyUnion(builtinTypes, arena, leftDiscriminantTy, rightDiscriminantTy).result);
|
||||
dest[def].shouldAppendNilType |= partition.shouldAppendNilType || rhsIt->second.shouldAppendNilType;
|
||||
dest.insert(def, {});
|
||||
dest.get(def)->discriminantTypes.push_back(simplifyUnion(builtinTypes, arena, leftDiscriminantTy, rightDiscriminantTy).result);
|
||||
dest.get(def)->shouldAppendNilType |= partition.shouldAppendNilType || rhsIt->second.shouldAppendNilType;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -285,11 +287,12 @@ static void computeRefinement(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeAr
|
|||
}
|
||||
|
||||
RefinementContext uncommittedRefis;
|
||||
uncommittedRefis[proposition->breadcrumb->def].discriminantTypes.push_back(discriminantTy);
|
||||
uncommittedRefis.insert(proposition->breadcrumb->def, {});
|
||||
uncommittedRefis.get(proposition->breadcrumb->def)->discriminantTypes.push_back(discriminantTy);
|
||||
|
||||
// When the top-level expression is `t[x]`, we want to refine it into `nil`, not `never`.
|
||||
if ((sense || !eq) && getMetadata<SubscriptMetadata>(proposition->breadcrumb))
|
||||
uncommittedRefis[proposition->breadcrumb->def].shouldAppendNilType = true;
|
||||
uncommittedRefis.get(proposition->breadcrumb->def)->shouldAppendNilType = true;
|
||||
|
||||
for (NullableBreadcrumbId current = proposition->breadcrumb; current && current->previous; current = current->previous)
|
||||
{
|
||||
|
@ -302,17 +305,20 @@ static void computeRefinement(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeAr
|
|||
{
|
||||
TableType::Props props{{field->prop, Property{discriminantTy}}};
|
||||
discriminantTy = arena->addType(TableType{std::move(props), std::nullopt, TypeLevel{}, scope.get(), TableState::Sealed});
|
||||
uncommittedRefis[current->previous->def].discriminantTypes.push_back(discriminantTy);
|
||||
uncommittedRefis.insert(current->previous->def, {});
|
||||
uncommittedRefis.get(current->previous->def)->discriminantTypes.push_back(discriminantTy);
|
||||
}
|
||||
}
|
||||
|
||||
// And now it's time to commit it.
|
||||
for (auto& [def, partition] : uncommittedRefis)
|
||||
{
|
||||
for (TypeId discriminantTy : partition.discriminantTypes)
|
||||
(*refis)[def].discriminantTypes.push_back(discriminantTy);
|
||||
(*refis).insert(def, {});
|
||||
|
||||
(*refis)[def].shouldAppendNilType |= partition.shouldAppendNilType;
|
||||
for (TypeId discriminantTy : partition.discriminantTypes)
|
||||
(*refis).get(def)->discriminantTypes.push_back(discriminantTy);
|
||||
|
||||
(*refis).get(def)->shouldAppendNilType |= partition.shouldAppendNilType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -785,7 +785,8 @@ bool ConstraintSolver::tryDispatch(const BinaryConstraint& c, NotNull<const Cons
|
|||
const NormalizedType* normLeftTy = normalizer->normalize(leftType);
|
||||
if (hasTypeInIntersection<FreeType>(leftType) && force)
|
||||
asMutable(leftType)->ty.emplace<BoundType>(anyPresent ? builtinTypes->anyType : builtinTypes->numberType);
|
||||
if (normLeftTy && normLeftTy->isNumber())
|
||||
// We want to check if the left type has tops because `any` is a valid type for the lhs
|
||||
if (normLeftTy && (normLeftTy->isExactlyNumber() || get<AnyType>(normLeftTy->tops)))
|
||||
{
|
||||
unify(leftType, rightType, constraint->scope);
|
||||
asMutable(resultType)->ty.emplace<BoundType>(anyPresent ? builtinTypes->anyType : leftType);
|
||||
|
@ -805,9 +806,11 @@ bool ConstraintSolver::tryDispatch(const BinaryConstraint& c, NotNull<const Cons
|
|||
// For concatenation, if the LHS is a string, the RHS must be a string as
|
||||
// well. The result will also be a string.
|
||||
case AstExprBinary::Op::Concat:
|
||||
{
|
||||
if (hasTypeInIntersection<FreeType>(leftType) && force)
|
||||
asMutable(leftType)->ty.emplace<BoundType>(anyPresent ? builtinTypes->anyType : builtinTypes->stringType);
|
||||
if (isString(leftType))
|
||||
const NormalizedType* leftNormTy = normalizer->normalize(leftType);
|
||||
if (leftNormTy && leftNormTy->isSubtypeOfString())
|
||||
{
|
||||
unify(leftType, rightType, constraint->scope);
|
||||
asMutable(resultType)->ty.emplace<BoundType>(anyPresent ? builtinTypes->anyType : leftType);
|
||||
|
@ -823,14 +826,33 @@ bool ConstraintSolver::tryDispatch(const BinaryConstraint& c, NotNull<const Cons
|
|||
}
|
||||
|
||||
break;
|
||||
}
|
||||
// Inexact comparisons require that the types be both numbers or both
|
||||
// strings, and evaluate to a boolean.
|
||||
case AstExprBinary::Op::CompareGe:
|
||||
case AstExprBinary::Op::CompareGt:
|
||||
case AstExprBinary::Op::CompareLe:
|
||||
case AstExprBinary::Op::CompareLt:
|
||||
if ((isNumber(leftType) && isNumber(rightType)) || (isString(leftType) && isString(rightType)) || get<NeverType>(leftType) ||
|
||||
get<NeverType>(rightType))
|
||||
{
|
||||
const NormalizedType* lt = normalizer->normalize(leftType);
|
||||
const NormalizedType* rt = normalizer->normalize(rightType);
|
||||
// If the lhs is any, comparisons should be valid.
|
||||
if (lt && rt && (lt->isExactlyNumber() || get<AnyType>(lt->tops)) && rt->isExactlyNumber())
|
||||
{
|
||||
asMutable(resultType)->ty.emplace<BoundType>(builtinTypes->booleanType);
|
||||
unblock(resultType);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (lt && rt && (lt->isSubtypeOfString() || get<AnyType>(lt->tops)) && rt->isSubtypeOfString())
|
||||
{
|
||||
asMutable(resultType)->ty.emplace<BoundType>(builtinTypes->booleanType);
|
||||
unblock(resultType);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
if (get<NeverType>(leftType) || get<NeverType>(rightType))
|
||||
{
|
||||
asMutable(resultType)->ty.emplace<BoundType>(builtinTypes->booleanType);
|
||||
unblock(resultType);
|
||||
|
@ -838,6 +860,8 @@ bool ConstraintSolver::tryDispatch(const BinaryConstraint& c, NotNull<const Cons
|
|||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// == and ~= always evaluate to a boolean, and impose no other constraints
|
||||
// on their parameters.
|
||||
case AstExprBinary::Op::CompareEq:
|
||||
|
@ -1776,7 +1800,7 @@ struct FindRefineConstraintBlockers : TypeOnceVisitor
|
|||
}
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace
|
||||
|
||||
static bool isNegatedAny(TypeId ty)
|
||||
{
|
||||
|
@ -2319,7 +2343,7 @@ static TypePackId getErrorType(NotNull<BuiltinTypes> builtinTypes, TypePackId)
|
|||
return builtinTypes->errorRecoveryTypePack();
|
||||
}
|
||||
|
||||
template <typename TID>
|
||||
template<typename TID>
|
||||
bool ConstraintSolver::tryUnify(NotNull<const Constraint> constraint, TID subTy, TID superTy)
|
||||
{
|
||||
Unifier u{normalizer, constraint->scope, constraint->location, Covariant};
|
||||
|
|
|
@ -35,7 +35,6 @@ LUAU_FASTINTVARIABLE(LuauAutocompleteCheckTimeoutMs, 100)
|
|||
LUAU_FASTFLAGVARIABLE(DebugLuauDeferredConstraintResolution, false)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson, false)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauReadWriteProperties, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTypeCheckerUseCorrectScope, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -1196,8 +1195,7 @@ ModulePtr Frontend::check(const SourceModule& sourceModule, Mode mode, std::vect
|
|||
}
|
||||
else
|
||||
{
|
||||
TypeChecker typeChecker(FFlag::LuauTypeCheckerUseCorrectScope ? (forAutocomplete ? globalsForAutocomplete.globalScope : globals.globalScope)
|
||||
: globals.globalScope,
|
||||
TypeChecker typeChecker(forAutocomplete ? globalsForAutocomplete.globalScope : globals.globalScope,
|
||||
forAutocomplete ? &moduleResolverForAutocomplete : &moduleResolver, builtinTypes, &iceHandler);
|
||||
|
||||
if (prepareModuleScope)
|
||||
|
|
|
@ -108,6 +108,14 @@ size_t TypeIds::getHash() const
|
|||
return hash;
|
||||
}
|
||||
|
||||
bool TypeIds::isNever() const
|
||||
{
|
||||
return std::all_of(begin(), end(), [&](TypeId i) {
|
||||
// If each typeid is never, then I guess typeid's is also never?
|
||||
return get<NeverType>(i) != nullptr;
|
||||
});
|
||||
}
|
||||
|
||||
bool TypeIds::operator==(const TypeIds& there) const
|
||||
{
|
||||
return hash == there.hash && types == there.types;
|
||||
|
@ -228,14 +236,72 @@ NormalizedType::NormalizedType(NotNull<BuiltinTypes> builtinTypes)
|
|||
{
|
||||
}
|
||||
|
||||
bool NormalizedType::isFunction() const
|
||||
bool NormalizedType::isExactlyNumber() const
|
||||
{
|
||||
return !get<NeverType>(tops) || !functions.parts.empty();
|
||||
return hasNumbers() && !hasTops() && !hasBooleans() && !hasClasses() && !hasErrors() && !hasNils() && !hasStrings() && !hasThreads() &&
|
||||
!hasTables() && !hasFunctions() && !hasTyvars();
|
||||
}
|
||||
|
||||
bool NormalizedType::isNumber() const
|
||||
bool NormalizedType::isSubtypeOfString() const
|
||||
{
|
||||
return !get<NeverType>(tops) || !get<NeverType>(numbers);
|
||||
return hasStrings() && !hasTops() && !hasBooleans() && !hasClasses() && !hasErrors() && !hasNils() && !hasNumbers() && !hasThreads() &&
|
||||
!hasTables() && !hasFunctions() && !hasTyvars();
|
||||
}
|
||||
|
||||
bool NormalizedType::hasTops() const
|
||||
{
|
||||
return !get<NeverType>(tops);
|
||||
}
|
||||
|
||||
|
||||
bool NormalizedType::hasBooleans() const
|
||||
{
|
||||
return !get<NeverType>(booleans);
|
||||
}
|
||||
|
||||
bool NormalizedType::hasClasses() const
|
||||
{
|
||||
return !classes.isNever();
|
||||
}
|
||||
|
||||
bool NormalizedType::hasErrors() const
|
||||
{
|
||||
return !get<NeverType>(errors);
|
||||
}
|
||||
|
||||
bool NormalizedType::hasNils() const
|
||||
{
|
||||
return !get<NeverType>(nils);
|
||||
}
|
||||
|
||||
bool NormalizedType::hasNumbers() const
|
||||
{
|
||||
return !get<NeverType>(numbers);
|
||||
}
|
||||
|
||||
bool NormalizedType::hasStrings() const
|
||||
{
|
||||
return !strings.isNever();
|
||||
}
|
||||
|
||||
bool NormalizedType::hasThreads() const
|
||||
{
|
||||
return !get<NeverType>(threads);
|
||||
}
|
||||
|
||||
bool NormalizedType::hasTables() const
|
||||
{
|
||||
return !tables.isNever();
|
||||
}
|
||||
|
||||
bool NormalizedType::hasFunctions() const
|
||||
{
|
||||
return !functions.isNever();
|
||||
}
|
||||
|
||||
bool NormalizedType::hasTyvars() const
|
||||
{
|
||||
return !tyvars.empty();
|
||||
}
|
||||
|
||||
static bool isShallowInhabited(const NormalizedType& norm)
|
||||
|
|
|
@ -1067,9 +1067,7 @@ struct TypeChecker2
|
|||
std::vector<Location> argLocs;
|
||||
argLocs.reserve(call->args.size + 1);
|
||||
|
||||
TypeId* maybeOriginalCallTy = module->astOriginalCallTypes.find(call);
|
||||
TypeId* maybeSelectedOverload = module->astOverloadResolvedTypes.find(call);
|
||||
|
||||
auto maybeOriginalCallTy = module->astOriginalCallTypes.find(call);
|
||||
if (!maybeOriginalCallTy)
|
||||
return;
|
||||
|
||||
|
@ -1093,8 +1091,19 @@ struct TypeChecker2
|
|||
return;
|
||||
}
|
||||
}
|
||||
else if (get<FunctionType>(originalCallTy) || get<IntersectionType>(originalCallTy))
|
||||
else if (get<FunctionType>(originalCallTy))
|
||||
{
|
||||
// ok.
|
||||
}
|
||||
else if (get<IntersectionType>(originalCallTy))
|
||||
{
|
||||
auto norm = normalizer.normalize(originalCallTy);
|
||||
if (!norm)
|
||||
return reportError(CodeTooComplex{}, call->location);
|
||||
|
||||
// NormalizedType::hasFunction returns true if its' tops component is `unknown`, but for soundness we want the reverse.
|
||||
if (get<UnknownType>(norm->tops) || !norm->hasFunctions())
|
||||
return reportError(CannotCallNonFunction{originalCallTy}, call->func->location);
|
||||
}
|
||||
else if (auto utv = get<UnionType>(originalCallTy))
|
||||
{
|
||||
|
@ -1164,7 +1173,7 @@ struct TypeChecker2
|
|||
|
||||
TypePackId expectedArgTypes = testArena.addTypePack(args);
|
||||
|
||||
if (maybeSelectedOverload)
|
||||
if (auto maybeSelectedOverload = module->astOverloadResolvedTypes.find(call))
|
||||
{
|
||||
// This overload might not work still: the constraint solver will
|
||||
// pass the type checker an instantiated function type that matches
|
||||
|
@ -1414,7 +1423,7 @@ struct TypeChecker2
|
|||
{
|
||||
// Nothing
|
||||
}
|
||||
else if (!normalizedFnTy->isFunction())
|
||||
else if (!normalizedFnTy->hasFunctions())
|
||||
{
|
||||
ice->ice("Internal error: Lambda has non-function type " + toString(inferredFnTy), fn->location);
|
||||
}
|
||||
|
@ -1793,12 +1802,14 @@ struct TypeChecker2
|
|||
case AstExprBinary::Op::CompareGt:
|
||||
case AstExprBinary::Op::CompareLe:
|
||||
case AstExprBinary::Op::CompareLt:
|
||||
if (isNumber(leftType))
|
||||
{
|
||||
const NormalizedType* leftTyNorm = normalizer.normalize(leftType);
|
||||
if (leftTyNorm && leftTyNorm->isExactlyNumber())
|
||||
{
|
||||
reportErrors(tryUnify(scope, expr->right->location, rightType, builtinTypes->numberType));
|
||||
return builtinTypes->numberType;
|
||||
}
|
||||
else if (isString(leftType))
|
||||
else if (leftTyNorm && leftTyNorm->isSubtypeOfString())
|
||||
{
|
||||
reportErrors(tryUnify(scope, expr->right->location, rightType, builtinTypes->stringType));
|
||||
return builtinTypes->stringType;
|
||||
|
@ -1810,6 +1821,8 @@ struct TypeChecker2
|
|||
expr->location);
|
||||
return builtinTypes->errorRecoveryType();
|
||||
}
|
||||
}
|
||||
|
||||
case AstExprBinary::Op::And:
|
||||
case AstExprBinary::Op::Or:
|
||||
case AstExprBinary::Op::CompareEq:
|
||||
|
|
|
@ -346,7 +346,6 @@ TypeFamilyReductionResult<TypeId> addFamilyFn(std::vector<TypeId> typeParams, st
|
|||
TypeId rhsTy = log->follow(typeParams.at(1));
|
||||
const NormalizedType* normLhsTy = normalizer->normalize(lhsTy);
|
||||
const NormalizedType* normRhsTy = normalizer->normalize(rhsTy);
|
||||
|
||||
if (!normLhsTy || !normRhsTy)
|
||||
{
|
||||
return {std::nullopt, false, {}, {}};
|
||||
|
@ -355,7 +354,7 @@ TypeFamilyReductionResult<TypeId> addFamilyFn(std::vector<TypeId> typeParams, st
|
|||
{
|
||||
return {builtins->anyType, false, {}, {}};
|
||||
}
|
||||
else if (normLhsTy->isNumber() && normRhsTy->isNumber())
|
||||
else if ((normLhsTy->hasNumbers() || normLhsTy->hasTops()) && (normRhsTy->hasNumbers() || normRhsTy->hasTops()))
|
||||
{
|
||||
return {builtins->numberType, false, {}, {}};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,346 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "lua.h"
|
||||
#include "lualib.h"
|
||||
|
||||
#include "Luau/CodeGen.h"
|
||||
#include "Luau/Compiler.h"
|
||||
#include "Luau/BytecodeBuilder.h"
|
||||
#include "Luau/Parser.h"
|
||||
#include "Luau/TimeTrace.h"
|
||||
|
||||
#include "FileUtils.h"
|
||||
#include "Flags.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <io.h>
|
||||
#include <fcntl.h>
|
||||
#endif
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauTimeTracing)
|
||||
|
||||
enum class CompileFormat
|
||||
{
|
||||
Text,
|
||||
Binary,
|
||||
Remarks,
|
||||
Codegen, // Prints annotated native code including IR and assembly
|
||||
CodegenAsm, // Prints annotated native code assembly
|
||||
CodegenIr, // Prints annotated native code IR
|
||||
CodegenVerbose, // Prints annotated native code including IR, assembly and outlined code
|
||||
CodegenNull,
|
||||
Null
|
||||
};
|
||||
|
||||
struct GlobalOptions
|
||||
{
|
||||
int optimizationLevel = 1;
|
||||
int debugLevel = 1;
|
||||
} globalOptions;
|
||||
|
||||
static Luau::CompileOptions copts()
|
||||
{
|
||||
Luau::CompileOptions result = {};
|
||||
result.optimizationLevel = globalOptions.optimizationLevel;
|
||||
result.debugLevel = globalOptions.debugLevel;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static std::optional<CompileFormat> getCompileFormat(const char* name)
|
||||
{
|
||||
if (strcmp(name, "text") == 0)
|
||||
return CompileFormat::Text;
|
||||
else if (strcmp(name, "binary") == 0)
|
||||
return CompileFormat::Binary;
|
||||
else if (strcmp(name, "text") == 0)
|
||||
return CompileFormat::Text;
|
||||
else if (strcmp(name, "remarks") == 0)
|
||||
return CompileFormat::Remarks;
|
||||
else if (strcmp(name, "codegen") == 0)
|
||||
return CompileFormat::Codegen;
|
||||
else if (strcmp(name, "codegenasm") == 0)
|
||||
return CompileFormat::CodegenAsm;
|
||||
else if (strcmp(name, "codegenir") == 0)
|
||||
return CompileFormat::CodegenIr;
|
||||
else if (strcmp(name, "codegenverbose") == 0)
|
||||
return CompileFormat::CodegenVerbose;
|
||||
else if (strcmp(name, "codegennull") == 0)
|
||||
return CompileFormat::CodegenNull;
|
||||
else if (strcmp(name, "null") == 0)
|
||||
return CompileFormat::Null;
|
||||
else
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
static void report(const char* name, const Luau::Location& location, const char* type, const char* message)
|
||||
{
|
||||
fprintf(stderr, "%s(%d,%d): %s: %s\n", name, location.begin.line + 1, location.begin.column + 1, type, message);
|
||||
}
|
||||
|
||||
static void reportError(const char* name, const Luau::ParseError& error)
|
||||
{
|
||||
report(name, error.getLocation(), "SyntaxError", error.what());
|
||||
}
|
||||
|
||||
static void reportError(const char* name, const Luau::CompileError& error)
|
||||
{
|
||||
report(name, error.getLocation(), "CompileError", error.what());
|
||||
}
|
||||
|
||||
static std::string getCodegenAssembly(const char* name, const std::string& bytecode, Luau::CodeGen::AssemblyOptions options)
|
||||
{
|
||||
std::unique_ptr<lua_State, void (*)(lua_State*)> globalState(luaL_newstate(), lua_close);
|
||||
lua_State* L = globalState.get();
|
||||
|
||||
if (luau_load(L, name, bytecode.data(), bytecode.size(), 0) == 0)
|
||||
return Luau::CodeGen::getAssembly(L, -1, options);
|
||||
|
||||
fprintf(stderr, "Error loading bytecode %s\n", name);
|
||||
return "";
|
||||
}
|
||||
|
||||
static void annotateInstruction(void* context, std::string& text, int fid, int instpos)
|
||||
{
|
||||
Luau::BytecodeBuilder& bcb = *(Luau::BytecodeBuilder*)context;
|
||||
|
||||
bcb.annotateInstruction(text, fid, instpos);
|
||||
}
|
||||
|
||||
struct CompileStats
|
||||
{
|
||||
size_t lines;
|
||||
size_t bytecode;
|
||||
size_t codegen;
|
||||
|
||||
double readTime;
|
||||
double miscTime;
|
||||
double parseTime;
|
||||
double compileTime;
|
||||
double codegenTime;
|
||||
};
|
||||
|
||||
static double recordDeltaTime(double& timer)
|
||||
{
|
||||
double now = Luau::TimeTrace::getClock();
|
||||
double delta = now - timer;
|
||||
timer = now;
|
||||
return delta;
|
||||
}
|
||||
|
||||
static bool compileFile(const char* name, CompileFormat format, CompileStats& stats)
|
||||
{
|
||||
double currts = Luau::TimeTrace::getClock();
|
||||
|
||||
std::optional<std::string> source = readFile(name);
|
||||
if (!source)
|
||||
{
|
||||
fprintf(stderr, "Error opening %s\n", name);
|
||||
return false;
|
||||
}
|
||||
|
||||
stats.readTime += recordDeltaTime(currts);
|
||||
|
||||
// NOTE: Normally, you should use Luau::compile or luau_compile (see lua_require as an example)
|
||||
// This function is much more complicated because it supports many output human-readable formats through internal interfaces
|
||||
|
||||
try
|
||||
{
|
||||
Luau::BytecodeBuilder bcb;
|
||||
|
||||
Luau::CodeGen::AssemblyOptions options;
|
||||
options.outputBinary = format == CompileFormat::CodegenNull;
|
||||
|
||||
if (!options.outputBinary)
|
||||
{
|
||||
options.includeAssembly = format != CompileFormat::CodegenIr;
|
||||
options.includeIr = format != CompileFormat::CodegenAsm;
|
||||
options.includeOutlinedCode = format == CompileFormat::CodegenVerbose;
|
||||
}
|
||||
|
||||
options.annotator = annotateInstruction;
|
||||
options.annotatorContext = &bcb;
|
||||
|
||||
if (format == CompileFormat::Text)
|
||||
{
|
||||
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code | Luau::BytecodeBuilder::Dump_Source | Luau::BytecodeBuilder::Dump_Locals |
|
||||
Luau::BytecodeBuilder::Dump_Remarks);
|
||||
bcb.setDumpSource(*source);
|
||||
}
|
||||
else if (format == CompileFormat::Remarks)
|
||||
{
|
||||
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Source | Luau::BytecodeBuilder::Dump_Remarks);
|
||||
bcb.setDumpSource(*source);
|
||||
}
|
||||
else if (format == CompileFormat::Codegen || format == CompileFormat::CodegenAsm || format == CompileFormat::CodegenIr ||
|
||||
format == CompileFormat::CodegenVerbose)
|
||||
{
|
||||
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code | Luau::BytecodeBuilder::Dump_Source | Luau::BytecodeBuilder::Dump_Locals |
|
||||
Luau::BytecodeBuilder::Dump_Remarks);
|
||||
bcb.setDumpSource(*source);
|
||||
}
|
||||
|
||||
stats.miscTime += recordDeltaTime(currts);
|
||||
|
||||
Luau::Allocator allocator;
|
||||
Luau::AstNameTable names(allocator);
|
||||
Luau::ParseResult result = Luau::Parser::parse(source->c_str(), source->size(), names, allocator);
|
||||
|
||||
if (!result.errors.empty())
|
||||
throw Luau::ParseErrors(result.errors);
|
||||
|
||||
stats.lines += result.lines;
|
||||
stats.parseTime += recordDeltaTime(currts);
|
||||
|
||||
Luau::compileOrThrow(bcb, result, names, copts());
|
||||
stats.bytecode += bcb.getBytecode().size();
|
||||
stats.compileTime += recordDeltaTime(currts);
|
||||
|
||||
switch (format)
|
||||
{
|
||||
case CompileFormat::Text:
|
||||
printf("%s", bcb.dumpEverything().c_str());
|
||||
break;
|
||||
case CompileFormat::Remarks:
|
||||
printf("%s", bcb.dumpSourceRemarks().c_str());
|
||||
break;
|
||||
case CompileFormat::Binary:
|
||||
fwrite(bcb.getBytecode().data(), 1, bcb.getBytecode().size(), stdout);
|
||||
break;
|
||||
case CompileFormat::Codegen:
|
||||
case CompileFormat::CodegenAsm:
|
||||
case CompileFormat::CodegenIr:
|
||||
case CompileFormat::CodegenVerbose:
|
||||
printf("%s", getCodegenAssembly(name, bcb.getBytecode(), options).c_str());
|
||||
break;
|
||||
case CompileFormat::CodegenNull:
|
||||
stats.codegen += getCodegenAssembly(name, bcb.getBytecode(), options).size();
|
||||
stats.codegenTime += recordDeltaTime(currts);
|
||||
break;
|
||||
case CompileFormat::Null:
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Luau::ParseErrors& e)
|
||||
{
|
||||
for (auto& error : e.getErrors())
|
||||
reportError(name, error);
|
||||
return false;
|
||||
}
|
||||
catch (Luau::CompileError& e)
|
||||
{
|
||||
reportError(name, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static void displayHelp(const char* argv0)
|
||||
{
|
||||
printf("Usage: %s [--mode] [options] [file list]\n", argv0);
|
||||
printf("\n");
|
||||
printf("Available modes:\n");
|
||||
printf(" binary, text, remarks, codegen\n");
|
||||
printf("\n");
|
||||
printf("Available options:\n");
|
||||
printf(" -h, --help: Display this usage message.\n");
|
||||
printf(" -O<n>: compile with optimization level n (default 1, n should be between 0 and 2).\n");
|
||||
printf(" -g<n>: compile with debug level n (default 1, n should be between 0 and 2).\n");
|
||||
printf(" --timetrace: record compiler time tracing information into trace.json\n");
|
||||
}
|
||||
|
||||
static int assertionHandler(const char* expr, const char* file, int line, const char* function)
|
||||
{
|
||||
printf("%s(%d): ASSERTION FAILED: %s\n", file, line, expr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
Luau::assertHandler() = assertionHandler;
|
||||
|
||||
setLuauFlagsDefault();
|
||||
|
||||
CompileFormat compileFormat = CompileFormat::Text;
|
||||
|
||||
for (int i = 1; i < argc; i++)
|
||||
{
|
||||
if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0)
|
||||
{
|
||||
displayHelp(argv[0]);
|
||||
return 0;
|
||||
}
|
||||
else if (strncmp(argv[i], "-O", 2) == 0)
|
||||
{
|
||||
int level = atoi(argv[i] + 2);
|
||||
if (level < 0 || level > 2)
|
||||
{
|
||||
fprintf(stderr, "Error: Optimization level must be between 0 and 2 inclusive.\n");
|
||||
return 1;
|
||||
}
|
||||
globalOptions.optimizationLevel = level;
|
||||
}
|
||||
else if (strncmp(argv[i], "-g", 2) == 0)
|
||||
{
|
||||
int level = atoi(argv[i] + 2);
|
||||
if (level < 0 || level > 2)
|
||||
{
|
||||
fprintf(stderr, "Error: Debug level must be between 0 and 2 inclusive.\n");
|
||||
return 1;
|
||||
}
|
||||
globalOptions.debugLevel = level;
|
||||
}
|
||||
else if (strcmp(argv[i], "--timetrace") == 0)
|
||||
{
|
||||
FFlag::DebugLuauTimeTracing.value = true;
|
||||
}
|
||||
else if (strncmp(argv[i], "--fflags=", 9) == 0)
|
||||
{
|
||||
setLuauFlags(argv[i] + 9);
|
||||
}
|
||||
else if (argv[i][0] == '-' && argv[i][1] == '-' && getCompileFormat(argv[i] + 2))
|
||||
{
|
||||
compileFormat = *getCompileFormat(argv[i] + 2);
|
||||
}
|
||||
else if (argv[i][0] == '-')
|
||||
{
|
||||
fprintf(stderr, "Error: Unrecognized option '%s'.\n\n", argv[i]);
|
||||
displayHelp(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
#if !defined(LUAU_ENABLE_TIME_TRACE)
|
||||
if (FFlag::DebugLuauTimeTracing)
|
||||
{
|
||||
fprintf(stderr, "To run with --timetrace, Luau has to be built with LUAU_ENABLE_TIME_TRACE enabled\n");
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
const std::vector<std::string> files = getSourceFiles(argc, argv);
|
||||
|
||||
#ifdef _WIN32
|
||||
if (compileFormat == CompileFormat::Binary)
|
||||
_setmode(_fileno(stdout), _O_BINARY);
|
||||
#endif
|
||||
|
||||
CompileStats stats = {};
|
||||
int failed = 0;
|
||||
|
||||
for (const std::string& path : files)
|
||||
failed += !compileFile(path.c_str(), compileFormat, stats);
|
||||
|
||||
if (compileFormat == CompileFormat::Null)
|
||||
printf("Compiled %d KLOC into %d KB bytecode (read %.2fs, parse %.2fs, compile %.2fs)\n", int(stats.lines / 1000), int(stats.bytecode / 1024),
|
||||
stats.readTime, stats.parseTime, stats.compileTime);
|
||||
else if (compileFormat == CompileFormat::CodegenNull)
|
||||
printf("Compiled %d KLOC into %d KB bytecode => %d KB native code (%.2fx) (read %.2fs, parse %.2fs, compile %.2fs, codegen %.2fs)\n",
|
||||
int(stats.lines / 1000), int(stats.bytecode / 1024), int(stats.codegen / 1024),
|
||||
stats.bytecode == 0 ? 0.0 : double(stats.codegen) / double(stats.bytecode), stats.readTime, stats.parseTime, stats.compileTime,
|
||||
stats.codegenTime);
|
||||
|
||||
return failed ? 1 : 0;
|
||||
}
|
|
@ -36,12 +36,14 @@ if(LUAU_BUILD_CLI)
|
|||
add_executable(Luau.Analyze.CLI)
|
||||
add_executable(Luau.Ast.CLI)
|
||||
add_executable(Luau.Reduce.CLI)
|
||||
add_executable(Luau.Compile.CLI)
|
||||
|
||||
# This also adds target `name` on Linux/macOS and `name.exe` on Windows
|
||||
set_target_properties(Luau.Repl.CLI PROPERTIES OUTPUT_NAME luau)
|
||||
set_target_properties(Luau.Analyze.CLI PROPERTIES OUTPUT_NAME luau-analyze)
|
||||
set_target_properties(Luau.Ast.CLI PROPERTIES OUTPUT_NAME luau-ast)
|
||||
set_target_properties(Luau.Reduce.CLI PROPERTIES OUTPUT_NAME luau-reduce)
|
||||
set_target_properties(Luau.Compile.CLI PROPERTIES OUTPUT_NAME luau-compile)
|
||||
endif()
|
||||
|
||||
if(LUAU_BUILD_TESTS)
|
||||
|
@ -186,6 +188,7 @@ if(LUAU_BUILD_CLI)
|
|||
target_compile_options(Luau.Reduce.CLI PRIVATE ${LUAU_OPTIONS})
|
||||
target_compile_options(Luau.Analyze.CLI PRIVATE ${LUAU_OPTIONS})
|
||||
target_compile_options(Luau.Ast.CLI PRIVATE ${LUAU_OPTIONS})
|
||||
target_compile_options(Luau.Compile.CLI PRIVATE ${LUAU_OPTIONS})
|
||||
|
||||
target_include_directories(Luau.Repl.CLI PRIVATE extern extern/isocline/include)
|
||||
|
||||
|
@ -206,6 +209,8 @@ if(LUAU_BUILD_CLI)
|
|||
target_compile_features(Luau.Reduce.CLI PRIVATE cxx_std_17)
|
||||
target_include_directories(Luau.Reduce.CLI PUBLIC Reduce/include)
|
||||
target_link_libraries(Luau.Reduce.CLI PRIVATE Luau.Common Luau.Ast Luau.Analysis)
|
||||
|
||||
target_link_libraries(Luau.Compile.CLI PRIVATE Luau.Compiler Luau.VM Luau.CodeGen)
|
||||
endif()
|
||||
|
||||
if(LUAU_BUILD_TESTS)
|
||||
|
|
|
@ -14,13 +14,10 @@ namespace A64
|
|||
|
||||
enum class AddressKindA64 : uint8_t
|
||||
{
|
||||
imm, // reg + imm
|
||||
reg, // reg + reg
|
||||
|
||||
// TODO:
|
||||
// reg + reg << shift
|
||||
// reg + sext(reg) << shift
|
||||
// reg + uext(reg) << shift
|
||||
reg, // reg + reg
|
||||
imm, // reg + imm
|
||||
pre, // reg + imm, reg += imm
|
||||
post, // reg, reg += imm
|
||||
};
|
||||
|
||||
struct AddressA64
|
||||
|
@ -29,13 +26,14 @@ struct AddressA64
|
|||
// 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;
|
||||
|
||||
constexpr AddressA64(RegisterA64 base, int off = 0)
|
||||
: kind(AddressKindA64::imm)
|
||||
constexpr AddressA64(RegisterA64 base, int off = 0, AddressKindA64 kind = AddressKindA64::imm)
|
||||
: kind(kind)
|
||||
, base(base)
|
||||
, offset(xzr)
|
||||
, data(off)
|
||||
{
|
||||
LUAU_ASSERT(base.kind == KindA64::x || base == sp);
|
||||
LUAU_ASSERT(kind != AddressKindA64::reg);
|
||||
}
|
||||
|
||||
constexpr AddressA64(RegisterA64 base, RegisterA64 offset)
|
||||
|
|
|
@ -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 "Luau/Common.h"
|
||||
|
||||
#include <bitset>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
@ -37,6 +39,16 @@ struct RegisterSet
|
|||
|
||||
void requireVariadicSequence(RegisterSet& sourceRs, const RegisterSet& defRs, uint8_t varargStart);
|
||||
|
||||
struct BlockOrdering
|
||||
{
|
||||
uint32_t depth = 0;
|
||||
|
||||
uint32_t preOrder = ~0u;
|
||||
uint32_t postOrder = ~0u;
|
||||
|
||||
bool visited = false;
|
||||
};
|
||||
|
||||
struct CfgInfo
|
||||
{
|
||||
std::vector<uint32_t> predecessors;
|
||||
|
@ -45,6 +57,15 @@ struct CfgInfo
|
|||
std::vector<uint32_t> successors;
|
||||
std::vector<uint32_t> successorsOffsets;
|
||||
|
||||
// Immediate dominators (unique parent in the dominator tree)
|
||||
std::vector<uint32_t> idoms;
|
||||
|
||||
// Children in the dominator tree
|
||||
std::vector<uint32_t> domChildren;
|
||||
std::vector<uint32_t> domChildrenOffsets;
|
||||
|
||||
std::vector<BlockOrdering> domOrdering;
|
||||
|
||||
// VM registers that are live when the block is entered
|
||||
// Additionally, an active variadic sequence can exist at the entry of the block
|
||||
std::vector<RegisterSet> in;
|
||||
|
@ -64,6 +85,18 @@ struct CfgInfo
|
|||
RegisterSet captured;
|
||||
};
|
||||
|
||||
// A quick refresher on dominance and dominator trees:
|
||||
// * If A is a dominator of B (A dom B), you can never execute B without executing A first
|
||||
// * A is a strict dominator of B (A sdom B) is similar to previous one but A != B
|
||||
// * Immediate dominator node N (idom N) is a unique node T so that T sdom N,
|
||||
// but T does not strictly dominate any other node that dominates N.
|
||||
// * Dominance frontier is a set of nodes where dominance of a node X ends.
|
||||
// In practice this is where values established by node X might no longer hold because of join edges from other nodes coming in.
|
||||
// This is also where PHI instructions in SSA are placed.
|
||||
void computeCfgImmediateDominators(IrFunction& function);
|
||||
void computeCfgDominanceTreeChildren(IrFunction& function);
|
||||
|
||||
// Function used to update all CFG data
|
||||
void computeCfgInfo(IrFunction& function);
|
||||
|
||||
struct BlockIteratorWrapper
|
||||
|
@ -90,10 +123,17 @@ struct BlockIteratorWrapper
|
|||
{
|
||||
return itEnd;
|
||||
}
|
||||
|
||||
uint32_t operator[](size_t pos) const
|
||||
{
|
||||
LUAU_ASSERT(pos < size_t(itEnd - itBegin));
|
||||
return itBegin[pos];
|
||||
}
|
||||
};
|
||||
|
||||
BlockIteratorWrapper predecessors(const CfgInfo& cfg, uint32_t blockIdx);
|
||||
BlockIteratorWrapper successors(const CfgInfo& cfg, uint32_t blockIdx);
|
||||
BlockIteratorWrapper domChildren(const CfgInfo& cfg, uint32_t blockIdx);
|
||||
|
||||
} // namespace CodeGen
|
||||
} // namespace Luau
|
||||
|
|
|
@ -823,6 +823,7 @@ struct IrFunction
|
|||
uint32_t validRestoreOpBlockIdx = 0;
|
||||
|
||||
Proto* proto = nullptr;
|
||||
bool variadic = false;
|
||||
|
||||
CfgInfo cfg;
|
||||
|
||||
|
|
|
@ -876,6 +876,9 @@ void AssemblyBuilderA64::placeA(const char* name, RegisterA64 dst, AddressA64 sr
|
|||
|
||||
switch (src.kind)
|
||||
{
|
||||
case AddressKindA64::reg:
|
||||
place(dst.index | (src.base.index << 5) | (0b011'0'10 << 10) | (src.offset.index << 16) | (1 << 21) | (opsize << 22));
|
||||
break;
|
||||
case AddressKindA64::imm:
|
||||
if (unsigned(src.data >> sizelog) < 1024 && (src.data & ((1 << sizelog) - 1)) == 0)
|
||||
place(dst.index | (src.base.index << 5) | ((src.data >> sizelog) << 10) | (opsize << 22) | (1 << 24));
|
||||
|
@ -884,8 +887,13 @@ void AssemblyBuilderA64::placeA(const char* name, RegisterA64 dst, AddressA64 sr
|
|||
else
|
||||
LUAU_ASSERT(!"Unable to encode large immediate offset");
|
||||
break;
|
||||
case AddressKindA64::reg:
|
||||
place(dst.index | (src.base.index << 5) | (0b011'0'10 << 10) | (src.offset.index << 16) | (1 << 21) | (opsize << 22));
|
||||
case AddressKindA64::pre:
|
||||
LUAU_ASSERT(src.data >= -256 && src.data <= 255);
|
||||
place(dst.index | (src.base.index << 5) | (0b11 << 10) | ((src.data & ((1 << 9) - 1)) << 12) | (opsize << 22));
|
||||
break;
|
||||
case AddressKindA64::post:
|
||||
LUAU_ASSERT(src.data >= -256 && src.data <= 255);
|
||||
place(dst.index | (src.base.index << 5) | (0b01 << 10) | ((src.data & ((1 << 9) - 1)) << 12) | (opsize << 22));
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -1312,23 +1320,37 @@ void AssemblyBuilderA64::log(RegisterA64 reg)
|
|||
|
||||
void AssemblyBuilderA64::log(AddressA64 addr)
|
||||
{
|
||||
text.append("[");
|
||||
switch (addr.kind)
|
||||
{
|
||||
case AddressKindA64::imm:
|
||||
log(addr.base);
|
||||
if (addr.data != 0)
|
||||
logAppend(",#%d", addr.data);
|
||||
break;
|
||||
case AddressKindA64::reg:
|
||||
text.append("[");
|
||||
log(addr.base);
|
||||
text.append(",");
|
||||
log(addr.offset);
|
||||
text.append("]");
|
||||
break;
|
||||
case AddressKindA64::imm:
|
||||
text.append("[");
|
||||
log(addr.base);
|
||||
if (addr.data != 0)
|
||||
logAppend(" LSL #%d", addr.data);
|
||||
logAppend(",#%d", addr.data);
|
||||
text.append("]");
|
||||
break;
|
||||
case AddressKindA64::pre:
|
||||
text.append("[");
|
||||
log(addr.base);
|
||||
if (addr.data != 0)
|
||||
logAppend(",#%d", addr.data);
|
||||
text.append("]!");
|
||||
break;
|
||||
case AddressKindA64::post:
|
||||
text.append("[");
|
||||
log(addr.base);
|
||||
text.append("]!");
|
||||
if (addr.data != 0)
|
||||
logAppend(",#%d", addr.data);
|
||||
break;
|
||||
}
|
||||
text.append("]");
|
||||
}
|
||||
|
||||
} // namespace A64
|
||||
|
|
|
@ -1415,7 +1415,7 @@ void AssemblyBuilderX64::commit()
|
|||
{
|
||||
LUAU_ASSERT(codePos <= codeEnd);
|
||||
|
||||
if (unsigned(codeEnd - codePos) < kMaxInstructionLength)
|
||||
if (codeEnd - codePos < kMaxInstructionLength)
|
||||
extend();
|
||||
}
|
||||
|
||||
|
|
|
@ -56,10 +56,8 @@ static void makePagesExecutable(uint8_t* mem, size_t size)
|
|||
|
||||
static void flushInstructionCache(uint8_t* mem, size_t size)
|
||||
{
|
||||
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM)
|
||||
if (FlushInstructionCache(GetCurrentProcess(), mem, size) == 0)
|
||||
LUAU_ASSERT(!"Failed to flush instruction cache");
|
||||
#endif
|
||||
}
|
||||
#else
|
||||
static uint8_t* allocatePages(size_t size)
|
||||
|
|
|
@ -268,7 +268,7 @@ static bool lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction&
|
|||
[[maybe_unused]] static bool lowerIr(
|
||||
A64::AssemblyBuilderA64& build, IrBuilder& ir, NativeState& data, ModuleHelpers& helpers, Proto* proto, AssemblyOptions options)
|
||||
{
|
||||
A64::IrLoweringA64 lowering(build, helpers, data, proto, ir.function);
|
||||
A64::IrLoweringA64 lowering(build, helpers, data, ir.function);
|
||||
|
||||
return lowerImpl(build, lowering, ir.function, proto->bytecodeid, options);
|
||||
}
|
||||
|
|
|
@ -117,6 +117,81 @@ static void emitReentry(AssemblyBuilderA64& build, ModuleHelpers& helpers)
|
|||
build.br(x4);
|
||||
}
|
||||
|
||||
void emitReturn(AssemblyBuilderA64& build, ModuleHelpers& helpers)
|
||||
{
|
||||
// x1 = res
|
||||
// w2 = number of written values
|
||||
|
||||
// x0 = ci
|
||||
build.ldr(x0, mem(rState, offsetof(lua_State, ci)));
|
||||
// w3 = ci->nresults
|
||||
build.ldr(w3, mem(x0, offsetof(CallInfo, nresults)));
|
||||
|
||||
Label skipResultCopy;
|
||||
|
||||
// Fill the rest of the expected results (nresults - written) with 'nil'
|
||||
build.cmp(w2, w3);
|
||||
build.b(ConditionA64::GreaterEqual, skipResultCopy);
|
||||
|
||||
// TODO: cmp above could compute this and flags using subs
|
||||
build.sub(w2, w3, w2); // counter = nresults - written
|
||||
build.mov(w4, LUA_TNIL);
|
||||
|
||||
Label repeatNilLoop = build.setLabel();
|
||||
build.str(w4, mem(x1, offsetof(TValue, tt)));
|
||||
build.add(x1, x1, sizeof(TValue));
|
||||
build.sub(w2, w2, 1);
|
||||
build.cbnz(w2, repeatNilLoop);
|
||||
|
||||
build.setLabel(skipResultCopy);
|
||||
|
||||
// x2 = cip = ci - 1
|
||||
build.sub(x2, x0, sizeof(CallInfo));
|
||||
|
||||
// res = cip->top when nresults >= 0
|
||||
Label skipFixedRetTop;
|
||||
build.tbnz(w3, 31, skipFixedRetTop);
|
||||
build.ldr(x1, mem(x2, offsetof(CallInfo, top))); // res = cip->top
|
||||
build.setLabel(skipFixedRetTop);
|
||||
|
||||
// Update VM state (ci, base, top)
|
||||
build.str(x2, mem(rState, offsetof(lua_State, ci))); // L->ci = cip
|
||||
build.ldr(rBase, mem(x2, offsetof(CallInfo, base))); // sync base = L->base while we have a chance
|
||||
build.str(rBase, mem(rState, offsetof(lua_State, base))); // L->base = cip->base
|
||||
|
||||
build.str(x1, mem(rState, offsetof(lua_State, top))); // L->top = res
|
||||
|
||||
// Unlikely, but this might be the last return from VM
|
||||
build.ldr(w4, mem(x0, offsetof(CallInfo, flags)));
|
||||
build.tbnz(w4, countrz(LUA_CALLINFO_RETURN), helpers.exitNoContinueVm);
|
||||
|
||||
// Continue in interpreter if function has no native data
|
||||
build.ldr(w4, mem(x2, offsetof(CallInfo, flags)));
|
||||
build.tbz(w4, countrz(LUA_CALLINFO_NATIVE), helpers.exitContinueVm);
|
||||
|
||||
// Need to update state of the current function before we jump away
|
||||
build.ldr(rClosure, mem(x2, offsetof(CallInfo, func)));
|
||||
build.ldr(rClosure, mem(rClosure, offsetof(TValue, value.gc)));
|
||||
|
||||
build.ldr(x1, mem(rClosure, offsetof(Closure, l.p))); // cl->l.p aka proto
|
||||
|
||||
LUAU_ASSERT(offsetof(Proto, code) == offsetof(Proto, k) + 8);
|
||||
build.ldp(rConstants, rCode, mem(x1, offsetof(Proto, k))); // proto->k, 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 4 back to byte offset later so it cancels out
|
||||
build.ldr(x2, mem(x2, offsetof(CallInfo, savedpc))); // cip->savedpc
|
||||
build.sub(x2, x2, rCode);
|
||||
|
||||
// Get new instruction location and jump to it
|
||||
LUAU_ASSERT(offsetof(Proto, exectarget) == offsetof(Proto, execdata) + 8);
|
||||
build.ldp(x3, x4, mem(x1, offsetof(Proto, execdata)));
|
||||
build.ldr(w2, mem(x3, x2));
|
||||
build.add(x4, x4, x2);
|
||||
build.br(x4);
|
||||
}
|
||||
|
||||
static EntryLocations buildEntryFunction(AssemblyBuilderA64& build, UnwindBuilder& unwind)
|
||||
{
|
||||
EntryLocations locations;
|
||||
|
@ -230,6 +305,11 @@ void assembleHelpers(AssemblyBuilderA64& build, ModuleHelpers& helpers)
|
|||
build.logAppend("; interrupt\n");
|
||||
helpers.interrupt = build.setLabel();
|
||||
emitInterrupt(build);
|
||||
|
||||
if (build.logText)
|
||||
build.logAppend("; return\n");
|
||||
helpers.return_ = build.setLabel();
|
||||
emitReturn(build, helpers);
|
||||
}
|
||||
|
||||
} // namespace A64
|
||||
|
|
|
@ -17,8 +17,6 @@
|
|||
|
||||
#include <string.h>
|
||||
|
||||
LUAU_FASTFLAG(LuauUniformTopHandling)
|
||||
|
||||
// All external function calls that can cause stack realloc or Lua calls have to be wrapped in VM_PROTECT
|
||||
// This makes sure that we save the pc (in case the Lua call needs to generate a backtrace) before the call,
|
||||
// and restores the stack pointer after in case stack gets reallocated
|
||||
|
@ -306,44 +304,6 @@ Closure* callFallback(lua_State* L, StkId ra, StkId argtop, int nresults)
|
|||
}
|
||||
}
|
||||
|
||||
// Extracted as-is from lvmexecute.cpp with the exception of control flow (reentry) and removed interrupts
|
||||
Closure* returnFallback(lua_State* L, StkId ra, StkId valend)
|
||||
{
|
||||
// ci is our callinfo, cip is our parent
|
||||
CallInfo* ci = L->ci;
|
||||
CallInfo* cip = ci - 1;
|
||||
|
||||
StkId res = ci->func; // note: we assume CALL always puts func+args and expects results to start at func
|
||||
StkId vali = ra;
|
||||
|
||||
int nresults = ci->nresults;
|
||||
|
||||
// copy return values into parent stack (but only up to nresults!), fill the rest with nil
|
||||
// note: in MULTRET context nresults starts as -1 so i != 0 condition never activates intentionally
|
||||
int i;
|
||||
for (i = nresults; i != 0 && vali < valend; i--)
|
||||
setobj2s(L, res++, vali++);
|
||||
while (i-- > 0)
|
||||
setnilvalue(res++) |