mirror of
https://github.com/luau-lang/luau.git
synced 2024-11-15 14:25:44 +08:00
Merge branch 'upstream' into merge
This commit is contained in:
commit
1dca05d09b
@ -76,8 +76,8 @@ struct ConstraintSolver
|
||||
|
||||
DcrLogger* logger;
|
||||
|
||||
explicit ConstraintSolver(NotNull<Normalizer> normalizer, NotNull<Scope> rootScope, ModuleName moduleName,
|
||||
NotNull<ModuleResolver> moduleResolver, std::vector<RequireCycle> requireCycles, DcrLogger* logger);
|
||||
explicit ConstraintSolver(NotNull<Normalizer> normalizer, NotNull<Scope> rootScope, ModuleName moduleName, NotNull<ModuleResolver> moduleResolver,
|
||||
std::vector<RequireCycle> requireCycles, DcrLogger* logger);
|
||||
|
||||
// Randomize the order in which to dispatch constraints
|
||||
void randomize(unsigned seed);
|
||||
@ -88,7 +88,9 @@ struct ConstraintSolver
|
||||
**/
|
||||
void run();
|
||||
|
||||
bool done();
|
||||
bool isDone();
|
||||
|
||||
void finalizeModule();
|
||||
|
||||
/** Attempt to dispatch a constraint. Returns true if it was successful. If
|
||||
* tryDispatch() returns false, the constraint remains in the unsolved set
|
||||
|
@ -112,11 +112,13 @@ struct DcrLogger
|
||||
void popBlock(NotNull<const Constraint> block);
|
||||
|
||||
void captureInitialSolverState(const Scope* rootScope, const std::vector<NotNull<const Constraint>>& unsolvedConstraints);
|
||||
StepSnapshot prepareStepSnapshot(const Scope* rootScope, NotNull<const Constraint> current, bool force, const std::vector<NotNull<const Constraint>>& unsolvedConstraints);
|
||||
StepSnapshot prepareStepSnapshot(
|
||||
const Scope* rootScope, NotNull<const Constraint> current, bool force, const std::vector<NotNull<const Constraint>>& unsolvedConstraints);
|
||||
void commitStepSnapshot(StepSnapshot snapshot);
|
||||
void captureFinalSolverState(const Scope* rootScope, const std::vector<NotNull<const Constraint>>& unsolvedConstraints);
|
||||
|
||||
void captureTypeCheckError(const TypeError& error);
|
||||
|
||||
private:
|
||||
ConstraintGenerationLog generationLog;
|
||||
std::unordered_map<NotNull<const Constraint>, std::vector<ConstraintBlockTarget>> constraintBlocks;
|
||||
|
@ -33,7 +33,6 @@ struct UnknownSymbol
|
||||
{
|
||||
Binding,
|
||||
Type,
|
||||
Generic
|
||||
};
|
||||
Name name;
|
||||
Context context;
|
||||
|
@ -240,7 +240,7 @@ void write(JsonEmitter& emitter, const std::unordered_map<std::string, T>& map)
|
||||
|
||||
for (const auto& [k, v] : map)
|
||||
o.writePair(k, v);
|
||||
|
||||
|
||||
o.finish();
|
||||
}
|
||||
|
||||
|
@ -17,8 +17,10 @@ struct SingletonTypes;
|
||||
|
||||
using ModulePtr = std::shared_ptr<Module>;
|
||||
|
||||
bool isSubtype(TypeId subTy, TypeId superTy, NotNull<Scope> scope, NotNull<SingletonTypes> singletonTypes, InternalErrorReporter& ice, bool anyIsTop = true);
|
||||
bool isSubtype(TypePackId subTy, TypePackId superTy, NotNull<Scope> scope, NotNull<SingletonTypes> singletonTypes, InternalErrorReporter& ice, bool anyIsTop = true);
|
||||
bool isSubtype(
|
||||
TypeId subTy, TypeId superTy, NotNull<Scope> scope, NotNull<SingletonTypes> singletonTypes, InternalErrorReporter& ice, bool anyIsTop = true);
|
||||
bool isSubtype(TypePackId subTy, TypePackId superTy, NotNull<Scope> scope, NotNull<SingletonTypes> singletonTypes, InternalErrorReporter& ice,
|
||||
bool anyIsTop = true);
|
||||
|
||||
std::pair<TypeId, bool> normalize(
|
||||
TypeId ty, NotNull<Scope> scope, TypeArena& arena, NotNull<SingletonTypes> singletonTypes, InternalErrorReporter& ice);
|
||||
@ -68,13 +70,14 @@ public:
|
||||
insert(*it);
|
||||
}
|
||||
|
||||
bool operator ==(const TypeIds& there) const;
|
||||
bool operator==(const TypeIds& there) const;
|
||||
size_t getHash() const;
|
||||
};
|
||||
|
||||
} // namespace Luau
|
||||
|
||||
template<> struct std::hash<Luau::TypeIds>
|
||||
template<>
|
||||
struct std::hash<Luau::TypeIds>
|
||||
{
|
||||
std::size_t operator()(const Luau::TypeIds& tys) const
|
||||
{
|
||||
@ -82,7 +85,8 @@ template<> struct std::hash<Luau::TypeIds>
|
||||
}
|
||||
};
|
||||
|
||||
template<> struct std::hash<const Luau::TypeIds*>
|
||||
template<>
|
||||
struct std::hash<const Luau::TypeIds*>
|
||||
{
|
||||
std::size_t operator()(const Luau::TypeIds* tys) const
|
||||
{
|
||||
@ -90,7 +94,8 @@ template<> struct std::hash<const Luau::TypeIds*>
|
||||
}
|
||||
};
|
||||
|
||||
template<> struct std::equal_to<Luau::TypeIds>
|
||||
template<>
|
||||
struct std::equal_to<Luau::TypeIds>
|
||||
{
|
||||
bool operator()(const Luau::TypeIds& here, const Luau::TypeIds& there) const
|
||||
{
|
||||
@ -98,7 +103,8 @@ template<> struct std::equal_to<Luau::TypeIds>
|
||||
}
|
||||
};
|
||||
|
||||
template<> struct std::equal_to<const Luau::TypeIds*>
|
||||
template<>
|
||||
struct std::equal_to<const Luau::TypeIds*>
|
||||
{
|
||||
bool operator()(const Luau::TypeIds* here, const Luau::TypeIds* there) const
|
||||
{
|
||||
@ -160,7 +166,7 @@ struct NormalizedType
|
||||
|
||||
// The string part of the type.
|
||||
// This may be the `string` type, or a union of singletons.
|
||||
NormalizedStringType strings = std::map<std::string,TypeId>{};
|
||||
NormalizedStringType strings = std::map<std::string, TypeId>{};
|
||||
|
||||
// The thread part of the type.
|
||||
// This type is either never or thread.
|
||||
|
@ -397,7 +397,7 @@ private:
|
||||
std::vector<std::pair<TypeId, ScopePtr>> deferredQuantification;
|
||||
};
|
||||
|
||||
using PrintLineProc = void(*)(const std::string&);
|
||||
using PrintLineProc = void (*)(const std::string&);
|
||||
|
||||
extern PrintLineProc luauPrintLine;
|
||||
|
||||
|
@ -61,15 +61,15 @@ struct Unifier
|
||||
ErrorVec errors;
|
||||
Location location;
|
||||
Variance variance = Covariant;
|
||||
bool anyIsTop = false; // If true, we consider any to be a top type. If false, it is a familiar but weird mix of top and bottom all at once.
|
||||
bool normalize; // Normalize unions and intersections if necessary
|
||||
bool anyIsTop = false; // If true, we consider any to be a top type. If false, it is a familiar but weird mix of top and bottom all at once.
|
||||
bool normalize; // Normalize unions and intersections if necessary
|
||||
bool useScopes = false; // If true, we use the scope hierarchy rather than TypeLevels
|
||||
CountMismatch::Context ctx = CountMismatch::Arg;
|
||||
|
||||
UnifierSharedState& sharedState;
|
||||
|
||||
Unifier(NotNull<Normalizer> normalizer, Mode mode, NotNull<Scope> scope, const Location& location, Variance variance,
|
||||
TxnLog* parentLog = nullptr);
|
||||
Unifier(
|
||||
NotNull<Normalizer> normalizer, Mode mode, NotNull<Scope> scope, const Location& location, Variance variance, TxnLog* parentLog = nullptr);
|
||||
|
||||
// Test whether the two type vars unify. Never commits the result.
|
||||
ErrorVec canUnify(TypeId subTy, TypeId superTy);
|
||||
@ -87,7 +87,8 @@ private:
|
||||
void tryUnifyTypeWithUnion(TypeId subTy, TypeId superTy, const UnionTypeVar* uv, bool cacheEnabled, bool isFunctionCall);
|
||||
void tryUnifyTypeWithIntersection(TypeId subTy, TypeId superTy, const IntersectionTypeVar* uv);
|
||||
void tryUnifyIntersectionWithType(TypeId subTy, const IntersectionTypeVar* uv, TypeId superTy, bool cacheEnabled, bool isFunctionCall);
|
||||
void tryUnifyNormalizedTypes(TypeId subTy, TypeId superTy, const NormalizedType& subNorm, const NormalizedType& superNorm, std::string reason, std::optional<TypeError> error = std::nullopt);
|
||||
void tryUnifyNormalizedTypes(TypeId subTy, TypeId superTy, const NormalizedType& subNorm, const NormalizedType& superNorm, std::string reason,
|
||||
std::optional<TypeError> error = std::nullopt);
|
||||
void tryUnifyPrimitives(TypeId subTy, TypeId superTy);
|
||||
void tryUnifySingletons(TypeId subTy, TypeId superTy);
|
||||
void tryUnifyFunctions(TypeId subTy, TypeId superTy, bool isFunctionCall = false);
|
||||
|
@ -497,7 +497,7 @@ static bool dcrMagicFunctionSelect(MagicFunctionCallContext context)
|
||||
asMutable(context.result)->ty.emplace<BoundTypePack>(resTypePack);
|
||||
}
|
||||
else if (tail)
|
||||
asMutable(context.result)->ty.emplace<BoundTypePack>(*tail);
|
||||
asMutable(context.result)->ty.emplace<BoundTypePack>(*tail);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -507,7 +507,8 @@ static bool dcrMagicFunctionSelect(MagicFunctionCallContext context)
|
||||
|
||||
if (AstExprConstantString* str = arg1->as<AstExprConstantString>())
|
||||
{
|
||||
if (str->value.size == 1 && str->value.data[0] == '#') {
|
||||
if (str->value.size == 1 && str->value.data[0] == '#')
|
||||
{
|
||||
TypePackId numberTypePack = context.solver->arena->addTypePack({context.solver->singletonTypes->numberType});
|
||||
asMutable(context.result)->ty.emplace<BoundTypePack>(numberTypePack);
|
||||
return true;
|
||||
|
@ -43,7 +43,7 @@ static bool matchSetmetatable(const AstExprCall& call)
|
||||
|
||||
if (call.args.size != 2)
|
||||
return false;
|
||||
|
||||
|
||||
const AstExprGlobal* funcAsGlobal = call.func->as<AstExprGlobal>();
|
||||
if (!funcAsGlobal || funcAsGlobal->name != smt)
|
||||
return false;
|
||||
@ -52,7 +52,8 @@ static bool matchSetmetatable(const AstExprCall& call)
|
||||
}
|
||||
|
||||
ConstraintGraphBuilder::ConstraintGraphBuilder(const ModuleName& moduleName, ModulePtr module, TypeArena* arena,
|
||||
NotNull<ModuleResolver> moduleResolver, NotNull<SingletonTypes> singletonTypes, NotNull<InternalErrorReporter> ice, const ScopePtr& globalScope, DcrLogger* logger)
|
||||
NotNull<ModuleResolver> moduleResolver, NotNull<SingletonTypes> singletonTypes, NotNull<InternalErrorReporter> ice, const ScopePtr& globalScope,
|
||||
DcrLogger* logger)
|
||||
: moduleName(moduleName)
|
||||
, module(module)
|
||||
, singletonTypes(singletonTypes)
|
||||
|
@ -14,8 +14,6 @@
|
||||
#include "Luau/VisitTypeVar.h"
|
||||
#include "Luau/TypeUtils.h"
|
||||
|
||||
#include <random>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolver, false);
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson, false);
|
||||
LUAU_FASTFLAG(LuauFixNameMaps)
|
||||
@ -283,13 +281,27 @@ ConstraintSolver::ConstraintSolver(NotNull<Normalizer> normalizer, NotNull<Scope
|
||||
|
||||
void ConstraintSolver::randomize(unsigned seed)
|
||||
{
|
||||
std::mt19937 g(seed);
|
||||
std::shuffle(begin(unsolvedConstraints), end(unsolvedConstraints), g);
|
||||
if (unsolvedConstraints.empty())
|
||||
return;
|
||||
|
||||
unsigned int rng = seed;
|
||||
|
||||
for (size_t i = unsolvedConstraints.size() - 1; i > 0; --i)
|
||||
{
|
||||
// Fisher-Yates shuffle
|
||||
size_t j = rng % (i + 1);
|
||||
|
||||
std::swap(unsolvedConstraints[i], unsolvedConstraints[j]);
|
||||
|
||||
// LCG RNG, constants from Numerical Recipes
|
||||
// This may occasionally result in skewed shuffles due to distribution properties, but this is a debugging tool so it should be good enough
|
||||
rng = rng * 1664525 + 1013904223;
|
||||
}
|
||||
}
|
||||
|
||||
void ConstraintSolver::run()
|
||||
{
|
||||
if (done())
|
||||
if (isDone())
|
||||
return;
|
||||
|
||||
if (FFlag::DebugLuauLogSolver)
|
||||
@ -364,6 +376,8 @@ void ConstraintSolver::run()
|
||||
progress |= runSolverPass(true);
|
||||
} while (progress);
|
||||
|
||||
finalizeModule();
|
||||
|
||||
if (FFlag::DebugLuauLogSolver)
|
||||
{
|
||||
dumpBindings(rootScope, opts);
|
||||
@ -375,11 +389,24 @@ void ConstraintSolver::run()
|
||||
}
|
||||
}
|
||||
|
||||
bool ConstraintSolver::done()
|
||||
bool ConstraintSolver::isDone()
|
||||
{
|
||||
return unsolvedConstraints.empty();
|
||||
}
|
||||
|
||||
void ConstraintSolver::finalizeModule()
|
||||
{
|
||||
Anyification a{arena, rootScope, singletonTypes, &iceReporter, singletonTypes->anyType, singletonTypes->anyTypePack};
|
||||
std::optional<TypePackId> returnType = a.substitute(rootScope->returnType);
|
||||
if (!returnType)
|
||||
{
|
||||
reportError(CodeTooComplex{}, Location{});
|
||||
rootScope->returnType = singletonTypes->errorTypePack;
|
||||
}
|
||||
else
|
||||
rootScope->returnType = *returnType;
|
||||
}
|
||||
|
||||
bool ConstraintSolver::tryDispatch(NotNull<const Constraint> constraint, bool force)
|
||||
{
|
||||
if (!force && isBlocked(constraint))
|
||||
@ -506,25 +533,25 @@ bool ConstraintSolver::tryDispatch(const UnaryConstraint& c, NotNull<const Const
|
||||
|
||||
switch (c.op)
|
||||
{
|
||||
case AstExprUnary::Not:
|
||||
case AstExprUnary::Not:
|
||||
{
|
||||
asMutable(c.resultType)->ty.emplace<BoundTypeVar>(singletonTypes->booleanType);
|
||||
return true;
|
||||
}
|
||||
case AstExprUnary::Len:
|
||||
{
|
||||
asMutable(c.resultType)->ty.emplace<BoundTypeVar>(singletonTypes->numberType);
|
||||
return true;
|
||||
}
|
||||
case AstExprUnary::Minus:
|
||||
{
|
||||
if (isNumber(operandType) || get<AnyTypeVar>(operandType) || get<ErrorTypeVar>(operandType))
|
||||
{
|
||||
asMutable(c.resultType)->ty.emplace<BoundTypeVar>(singletonTypes->booleanType);
|
||||
asMutable(c.resultType)->ty.emplace<BoundTypeVar>(c.operandType);
|
||||
return true;
|
||||
}
|
||||
case AstExprUnary::Len:
|
||||
{
|
||||
asMutable(c.resultType)->ty.emplace<BoundTypeVar>(singletonTypes->numberType);
|
||||
return true;
|
||||
}
|
||||
case AstExprUnary::Minus:
|
||||
{
|
||||
if (isNumber(operandType) || get<AnyTypeVar>(operandType) || get<ErrorTypeVar>(operandType))
|
||||
{
|
||||
asMutable(c.resultType)->ty.emplace<BoundTypeVar>(c.operandType);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
LUAU_ASSERT(false); // TODO metatable handling
|
||||
|
@ -57,7 +57,7 @@ void write(JsonEmitter& emitter, const ConstraintGenerationLog& log)
|
||||
emitter.writeRaw(":");
|
||||
|
||||
ObjectEmitter locationEmitter = emitter.writeObject();
|
||||
|
||||
|
||||
for (const auto& [id, location] : log.constraintLocations)
|
||||
{
|
||||
locationEmitter.writePair(id, location);
|
||||
@ -232,7 +232,7 @@ void DcrLogger::captureSource(std::string source)
|
||||
void DcrLogger::captureGenerationError(const TypeError& error)
|
||||
{
|
||||
std::string stringifiedError = toString(error);
|
||||
generationLog.errors.push_back(ErrorSnapshot {
|
||||
generationLog.errors.push_back(ErrorSnapshot{
|
||||
/* message */ stringifiedError,
|
||||
/* location */ error.location,
|
||||
});
|
||||
@ -298,7 +298,8 @@ void DcrLogger::captureInitialSolverState(const Scope* rootScope, const std::vec
|
||||
}
|
||||
}
|
||||
|
||||
StepSnapshot DcrLogger::prepareStepSnapshot(const Scope* rootScope, NotNull<const Constraint> current, bool force, const std::vector<NotNull<const Constraint>>& unsolvedConstraints)
|
||||
StepSnapshot DcrLogger::prepareStepSnapshot(
|
||||
const Scope* rootScope, NotNull<const Constraint> current, bool force, const std::vector<NotNull<const Constraint>>& unsolvedConstraints)
|
||||
{
|
||||
ScopeSnapshot scopeSnapshot = snapshotScope(rootScope, opts);
|
||||
std::string currentId = toPointerId(current);
|
||||
@ -344,7 +345,7 @@ void DcrLogger::captureFinalSolverState(const Scope* rootScope, const std::vecto
|
||||
void DcrLogger::captureTypeCheckError(const TypeError& error)
|
||||
{
|
||||
std::string stringifiedError = toString(error);
|
||||
checkLog.errors.push_back(ErrorSnapshot {
|
||||
checkLog.errors.push_back(ErrorSnapshot{
|
||||
/* message */ stringifiedError,
|
||||
/* location */ error.location,
|
||||
});
|
||||
@ -359,7 +360,7 @@ std::vector<ConstraintBlock> DcrLogger::snapshotBlocks(NotNull<const Constraint>
|
||||
}
|
||||
|
||||
std::vector<ConstraintBlock> snapshot;
|
||||
|
||||
|
||||
for (const ConstraintBlockTarget& target : it->second)
|
||||
{
|
||||
if (const TypeId* ty = get_if<TypeId>(&target))
|
||||
|
@ -8,7 +8,6 @@
|
||||
#include <stdexcept>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauTypeMismatchModuleNameResolution, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUseInternalCompilerErrorException, false)
|
||||
|
||||
static std::string wrongNumberOfArgsString(
|
||||
size_t expectedCount, std::optional<size_t> maximumCount, size_t actualCount, const char* argPrefix = nullptr, bool isVariadic = false)
|
||||
@ -122,8 +121,6 @@ struct ErrorConverter
|
||||
return "Unknown global '" + e.name + "'";
|
||||
case UnknownSymbol::Type:
|
||||
return "Unknown type '" + e.name + "'";
|
||||
case UnknownSymbol::Generic:
|
||||
return "Unknown generic '" + e.name + "'";
|
||||
}
|
||||
|
||||
LUAU_ASSERT(!"Unexpected context for UnknownSymbol");
|
||||
@ -902,46 +899,22 @@ void copyErrors(ErrorVec& errors, TypeArena& destArena)
|
||||
|
||||
void InternalErrorReporter::ice(const std::string& message, const Location& location)
|
||||
{
|
||||
if (FFlag::LuauUseInternalCompilerErrorException)
|
||||
{
|
||||
InternalCompilerError error(message, moduleName, location);
|
||||
InternalCompilerError error(message, moduleName, location);
|
||||
|
||||
if (onInternalError)
|
||||
onInternalError(error.what());
|
||||
if (onInternalError)
|
||||
onInternalError(error.what());
|
||||
|
||||
throw error;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::runtime_error error("Internal error in " + moduleName + " at " + toString(location) + ": " + message);
|
||||
|
||||
if (onInternalError)
|
||||
onInternalError(error.what());
|
||||
|
||||
throw error;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
void InternalErrorReporter::ice(const std::string& message)
|
||||
{
|
||||
if (FFlag::LuauUseInternalCompilerErrorException)
|
||||
{
|
||||
InternalCompilerError error(message, moduleName);
|
||||
InternalCompilerError error(message, moduleName);
|
||||
|
||||
if (onInternalError)
|
||||
onInternalError(error.what());
|
||||
if (onInternalError)
|
||||
onInternalError(error.what());
|
||||
|
||||
throw error;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::runtime_error error("Internal error in " + moduleName + ": " + message);
|
||||
|
||||
if (onInternalError)
|
||||
onInternalError(error.what());
|
||||
|
||||
throw error;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
const char* InternalCompilerError::what() const throw()
|
||||
|
@ -14,7 +14,6 @@
|
||||
|
||||
LUAU_FASTINTVARIABLE(LuauSuggestionDistance, 4)
|
||||
LUAU_FASTFLAGVARIABLE(LuauLintGlobalNeverReadBeforeWritten, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauLintFixDeprecationMessage, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -307,22 +306,11 @@ private:
|
||||
emitWarning(*context, LintWarning::Code_UnknownGlobal, gv->location, "Unknown global '%s'", gv->name.value);
|
||||
else if (g->deprecated)
|
||||
{
|
||||
if (FFlag::LuauLintFixDeprecationMessage)
|
||||
{
|
||||
if (const char* replacement = *g->deprecated; replacement && strlen(replacement))
|
||||
emitWarning(*context, LintWarning::Code_DeprecatedGlobal, gv->location, "Global '%s' is deprecated, use '%s' instead",
|
||||
gv->name.value, replacement);
|
||||
else
|
||||
emitWarning(*context, LintWarning::Code_DeprecatedGlobal, gv->location, "Global '%s' is deprecated", gv->name.value);
|
||||
}
|
||||
if (const char* replacement = *g->deprecated; replacement && strlen(replacement))
|
||||
emitWarning(*context, LintWarning::Code_DeprecatedGlobal, gv->location, "Global '%s' is deprecated, use '%s' instead",
|
||||
gv->name.value, replacement);
|
||||
else
|
||||
{
|
||||
if (*g->deprecated)
|
||||
emitWarning(*context, LintWarning::Code_DeprecatedGlobal, gv->location, "Global '%s' is deprecated, use '%s' instead",
|
||||
gv->name.value, *g->deprecated);
|
||||
else
|
||||
emitWarning(*context, LintWarning::Code_DeprecatedGlobal, gv->location, "Global '%s' is deprecated", gv->name.value);
|
||||
}
|
||||
emitWarning(*context, LintWarning::Code_DeprecatedGlobal, gv->location, "Global '%s' is deprecated", gv->name.value);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,6 @@
|
||||
#include <algorithm>
|
||||
|
||||
LUAU_FASTFLAG(LuauAnyifyModuleReturnGenerics)
|
||||
LUAU_FASTFLAG(LuauLowerBoundsCalculation);
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
LUAU_FASTFLAGVARIABLE(LuauForceExportSurfacesToBeNormal, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauClonePublicInterfaceLess, false);
|
||||
@ -244,19 +243,6 @@ void Module::clonePublicInterface(NotNull<SingletonTypes> singletonTypes, Intern
|
||||
|
||||
ForceNormal forceNormal{&interfaceTypes};
|
||||
|
||||
if (FFlag::LuauLowerBoundsCalculation)
|
||||
{
|
||||
normalize(returnType, NotNull{this}, singletonTypes, ice);
|
||||
if (FFlag::LuauForceExportSurfacesToBeNormal)
|
||||
forceNormal.traverse(returnType);
|
||||
if (varargPack)
|
||||
{
|
||||
normalize(*varargPack, NotNull{this}, singletonTypes, ice);
|
||||
if (FFlag::LuauForceExportSurfacesToBeNormal)
|
||||
forceNormal.traverse(*varargPack);
|
||||
}
|
||||
}
|
||||
|
||||
if (exportedTypeBindings)
|
||||
{
|
||||
for (auto& [name, tf] : *exportedTypeBindings)
|
||||
@ -265,24 +251,6 @@ void Module::clonePublicInterface(NotNull<SingletonTypes> singletonTypes, Intern
|
||||
tf = clonePublicInterface.cloneTypeFun(tf);
|
||||
else
|
||||
tf = clone(tf, interfaceTypes, cloneState);
|
||||
if (FFlag::LuauLowerBoundsCalculation)
|
||||
{
|
||||
normalize(tf.type, NotNull{this}, singletonTypes, ice);
|
||||
|
||||
// We're about to freeze the memory. We know that the flag is conservative by design. Cyclic tables
|
||||
// won't be marked normal. If the types aren't normal by now, they never will be.
|
||||
forceNormal.traverse(tf.type);
|
||||
for (GenericTypeDefinition param : tf.typeParams)
|
||||
{
|
||||
forceNormal.traverse(param.ty);
|
||||
|
||||
if (param.defaultValue)
|
||||
{
|
||||
normalize(*param.defaultValue, NotNull{this}, singletonTypes, ice);
|
||||
forceNormal.traverse(*param.defaultValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -305,13 +273,6 @@ void Module::clonePublicInterface(NotNull<SingletonTypes> singletonTypes, Intern
|
||||
ty = clonePublicInterface.cloneType(ty);
|
||||
else
|
||||
ty = clone(ty, interfaceTypes, cloneState);
|
||||
if (FFlag::LuauLowerBoundsCalculation)
|
||||
{
|
||||
normalize(ty, NotNull{this}, singletonTypes, ice);
|
||||
|
||||
if (FFlag::LuauForceExportSurfacesToBeNormal)
|
||||
forceNormal.traverse(ty);
|
||||
}
|
||||
}
|
||||
|
||||
freeze(internalTypes);
|
||||
|
@ -119,17 +119,9 @@ NormalizedType::NormalizedType(NotNull<SingletonTypes> singletonTypes)
|
||||
|
||||
static bool isInhabited(const NormalizedType& norm)
|
||||
{
|
||||
return !get<NeverTypeVar>(norm.tops)
|
||||
|| !get<NeverTypeVar>(norm.booleans)
|
||||
|| !norm.classes.empty()
|
||||
|| !get<NeverTypeVar>(norm.errors)
|
||||
|| !get<NeverTypeVar>(norm.nils)
|
||||
|| !get<NeverTypeVar>(norm.numbers)
|
||||
|| !norm.strings || !norm.strings->empty()
|
||||
|| !get<NeverTypeVar>(norm.threads)
|
||||
|| norm.functions
|
||||
|| !norm.tables.empty()
|
||||
|| !norm.tyvars.empty();
|
||||
return !get<NeverTypeVar>(norm.tops) || !get<NeverTypeVar>(norm.booleans) || !norm.classes.empty() || !get<NeverTypeVar>(norm.errors) ||
|
||||
!get<NeverTypeVar>(norm.nils) || !get<NeverTypeVar>(norm.numbers) || !norm.strings || !norm.strings->empty() ||
|
||||
!get<NeverTypeVar>(norm.threads) || norm.functions || !norm.tables.empty() || !norm.tyvars.empty();
|
||||
}
|
||||
|
||||
static int tyvarIndex(TypeId ty)
|
||||
@ -139,7 +131,7 @@ static int tyvarIndex(TypeId ty)
|
||||
else if (const FreeTypeVar* ftv = get<FreeTypeVar>(ty))
|
||||
return ftv->index;
|
||||
else
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef LUAU_ASSERTENABLED
|
||||
@ -193,7 +185,7 @@ static bool isNormalizedString(const NormalizedStringType& ty)
|
||||
{
|
||||
if (!ty)
|
||||
return true;
|
||||
|
||||
|
||||
for (auto& [str, ty] : *ty)
|
||||
{
|
||||
if (const SingletonTypeVar* stv = get<SingletonTypeVar>(ty))
|
||||
@ -272,24 +264,24 @@ static bool isNormalizedTyvar(const NormalizedTyvars& tyvars)
|
||||
|
||||
static void assertInvariant(const NormalizedType& norm)
|
||||
{
|
||||
#ifdef LUAU_ASSERTENABLED
|
||||
if (!FFlag::DebugLuauCheckNormalizeInvariant)
|
||||
return;
|
||||
#ifdef LUAU_ASSERTENABLED
|
||||
if (!FFlag::DebugLuauCheckNormalizeInvariant)
|
||||
return;
|
||||
|
||||
LUAU_ASSERT(isNormalizedTop(norm.tops));
|
||||
LUAU_ASSERT(isNormalizedBoolean(norm.booleans));
|
||||
LUAU_ASSERT(areNormalizedClasses(norm.classes));
|
||||
LUAU_ASSERT(isNormalizedError(norm.errors));
|
||||
LUAU_ASSERT(isNormalizedNil(norm.nils));
|
||||
LUAU_ASSERT(isNormalizedNumber(norm.numbers));
|
||||
LUAU_ASSERT(isNormalizedString(norm.strings));
|
||||
LUAU_ASSERT(isNormalizedThread(norm.threads));
|
||||
LUAU_ASSERT(areNormalizedFunctions(norm.functions));
|
||||
LUAU_ASSERT(areNormalizedTables(norm.tables));
|
||||
LUAU_ASSERT(isNormalizedTyvar(norm.tyvars));
|
||||
for (auto& [_, child] : norm.tyvars)
|
||||
assertInvariant(*child);
|
||||
#endif
|
||||
LUAU_ASSERT(isNormalizedTop(norm.tops));
|
||||
LUAU_ASSERT(isNormalizedBoolean(norm.booleans));
|
||||
LUAU_ASSERT(areNormalizedClasses(norm.classes));
|
||||
LUAU_ASSERT(isNormalizedError(norm.errors));
|
||||
LUAU_ASSERT(isNormalizedNil(norm.nils));
|
||||
LUAU_ASSERT(isNormalizedNumber(norm.numbers));
|
||||
LUAU_ASSERT(isNormalizedString(norm.strings));
|
||||
LUAU_ASSERT(isNormalizedThread(norm.threads));
|
||||
LUAU_ASSERT(areNormalizedFunctions(norm.functions));
|
||||
LUAU_ASSERT(areNormalizedTables(norm.tables));
|
||||
LUAU_ASSERT(isNormalizedTyvar(norm.tyvars));
|
||||
for (auto& [_, child] : norm.tyvars)
|
||||
assertInvariant(*child);
|
||||
#endif
|
||||
}
|
||||
|
||||
Normalizer::Normalizer(TypeArena* arena, NotNull<SingletonTypes> singletonTypes, NotNull<UnifierSharedState> sharedState)
|
||||
@ -359,7 +351,7 @@ TypeId Normalizer::unionType(TypeId here, TypeId there)
|
||||
return there;
|
||||
if (get<NeverTypeVar>(there) || get<AnyTypeVar>(here))
|
||||
return here;
|
||||
|
||||
|
||||
TypeIds tmps;
|
||||
|
||||
if (const UnionTypeVar* utv = get<UnionTypeVar>(here))
|
||||
@ -405,7 +397,7 @@ TypeId Normalizer::intersectionType(TypeId here, TypeId there)
|
||||
return here;
|
||||
if (get<NeverTypeVar>(there) || get<AnyTypeVar>(here))
|
||||
return there;
|
||||
|
||||
|
||||
TypeIds tmps;
|
||||
|
||||
if (const IntersectionTypeVar* utv = get<IntersectionTypeVar>(here))
|
||||
@ -516,13 +508,13 @@ std::optional<TypePackId> Normalizer::unionOfTypePacks(TypePackId here, TypePack
|
||||
|
||||
std::vector<TypeId> head;
|
||||
std::optional<TypePackId> tail;
|
||||
|
||||
|
||||
bool hereSubThere = true;
|
||||
bool thereSubHere = true;
|
||||
|
||||
TypePackIterator ith = begin(here);
|
||||
TypePackIterator itt = begin(there);
|
||||
|
||||
|
||||
while (ith != end(here) && itt != end(there))
|
||||
{
|
||||
TypeId hty = *ith;
|
||||
@ -537,8 +529,8 @@ std::optional<TypePackId> Normalizer::unionOfTypePacks(TypePackId here, TypePack
|
||||
itt++;
|
||||
}
|
||||
|
||||
auto dealWithDifferentArities = [&](TypePackIterator& ith, TypePackIterator itt, TypePackId here, TypePackId there, bool& hereSubThere, bool& thereSubHere)
|
||||
{
|
||||
auto dealWithDifferentArities = [&](TypePackIterator& ith, TypePackIterator itt, TypePackId here, TypePackId there, bool& hereSubThere,
|
||||
bool& thereSubHere) {
|
||||
if (ith != end(here))
|
||||
{
|
||||
TypeId tty = singletonTypes->nilType;
|
||||
@ -591,13 +583,13 @@ std::optional<TypePackId> Normalizer::unionOfTypePacks(TypePackId here, TypePack
|
||||
if (ty != tvtp->ty)
|
||||
hereSubThere = false;
|
||||
bool hidden = hvtp->hidden & tvtp->hidden;
|
||||
tail = arena->addTypePack(VariadicTypePack{ty,hidden});
|
||||
tail = arena->addTypePack(VariadicTypePack{ty, hidden});
|
||||
}
|
||||
else
|
||||
else
|
||||
// Luau doesn't have unions of type pack variables
|
||||
return std::nullopt;
|
||||
}
|
||||
else
|
||||
else
|
||||
// Luau doesn't have unions of type pack variables
|
||||
return std::nullopt;
|
||||
}
|
||||
@ -627,7 +619,7 @@ std::optional<TypePackId> Normalizer::unionOfTypePacks(TypePackId here, TypePack
|
||||
else if (thereSubHere)
|
||||
return here;
|
||||
if (!head.empty())
|
||||
return arena->addTypePack(TypePack{head,tail});
|
||||
return arena->addTypePack(TypePack{head, tail});
|
||||
else if (tail)
|
||||
return *tail;
|
||||
else
|
||||
@ -639,10 +631,10 @@ std::optional<TypeId> Normalizer::unionOfFunctions(TypeId here, TypeId there)
|
||||
{
|
||||
if (get<ErrorTypeVar>(here))
|
||||
return here;
|
||||
|
||||
|
||||
if (get<ErrorTypeVar>(there))
|
||||
return there;
|
||||
|
||||
|
||||
const FunctionTypeVar* hftv = get<FunctionTypeVar>(here);
|
||||
LUAU_ASSERT(hftv);
|
||||
const FunctionTypeVar* tftv = get<FunctionTypeVar>(there);
|
||||
@ -665,7 +657,7 @@ std::optional<TypeId> Normalizer::unionOfFunctions(TypeId here, TypeId there)
|
||||
return here;
|
||||
if (*argTypes == tftv->argTypes && *retTypes == tftv->retTypes)
|
||||
return there;
|
||||
|
||||
|
||||
FunctionTypeVar result{*argTypes, *retTypes};
|
||||
result.generics = hftv->generics;
|
||||
result.genericPacks = hftv->genericPacks;
|
||||
@ -802,9 +794,9 @@ bool Normalizer::withinResourceLimits()
|
||||
|
||||
// Check the recursion count
|
||||
if (sharedState->counters.recursionLimit > 0)
|
||||
if (sharedState->counters.recursionLimit < sharedState->counters.recursionCount)
|
||||
return false;
|
||||
|
||||
if (sharedState->counters.recursionLimit < sharedState->counters.recursionCount)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1000,13 +992,13 @@ std::optional<TypePackId> Normalizer::intersectionOfTypePacks(TypePackId here, T
|
||||
|
||||
std::vector<TypeId> head;
|
||||
std::optional<TypePackId> tail;
|
||||
|
||||
|
||||
bool hereSubThere = true;
|
||||
bool thereSubHere = true;
|
||||
|
||||
TypePackIterator ith = begin(here);
|
||||
TypePackIterator itt = begin(there);
|
||||
|
||||
|
||||
while (ith != end(here) && itt != end(there))
|
||||
{
|
||||
TypeId hty = *ith;
|
||||
@ -1021,8 +1013,8 @@ std::optional<TypePackId> Normalizer::intersectionOfTypePacks(TypePackId here, T
|
||||
itt++;
|
||||
}
|
||||
|
||||
auto dealWithDifferentArities = [&](TypePackIterator& ith, TypePackIterator itt, TypePackId here, TypePackId there, bool& hereSubThere, bool& thereSubHere)
|
||||
{
|
||||
auto dealWithDifferentArities = [&](TypePackIterator& ith, TypePackIterator itt, TypePackId here, TypePackId there, bool& hereSubThere,
|
||||
bool& thereSubHere) {
|
||||
if (ith != end(here))
|
||||
{
|
||||
TypeId tty = singletonTypes->nilType;
|
||||
@ -1075,13 +1067,13 @@ std::optional<TypePackId> Normalizer::intersectionOfTypePacks(TypePackId here, T
|
||||
if (ty != tvtp->ty)
|
||||
hereSubThere = false;
|
||||
bool hidden = hvtp->hidden & tvtp->hidden;
|
||||
tail = arena->addTypePack(VariadicTypePack{ty,hidden});
|
||||
tail = arena->addTypePack(VariadicTypePack{ty, hidden});
|
||||
}
|
||||
else
|
||||
else
|
||||
// Luau doesn't have unions of type pack variables
|
||||
return std::nullopt;
|
||||
}
|
||||
else
|
||||
else
|
||||
// Luau doesn't have unions of type pack variables
|
||||
return std::nullopt;
|
||||
}
|
||||
@ -1105,7 +1097,7 @@ std::optional<TypePackId> Normalizer::intersectionOfTypePacks(TypePackId here, T
|
||||
else if (thereSubHere)
|
||||
return there;
|
||||
if (!head.empty())
|
||||
return arena->addTypePack(TypePack{head,tail});
|
||||
return arena->addTypePack(TypePack{head, tail});
|
||||
else if (tail)
|
||||
return *tail;
|
||||
else
|
||||
@ -1146,7 +1138,7 @@ std::optional<TypeId> Normalizer::intersectionOfTables(TypeId here, TypeId there
|
||||
return std::nullopt;
|
||||
if (httv->state == TableState::Generic || tttv->state == TableState::Generic)
|
||||
return std::nullopt;
|
||||
|
||||
|
||||
TableState state = httv->state;
|
||||
if (tttv->state == TableState::Unsealed)
|
||||
state = tttv->state;
|
||||
@ -1226,21 +1218,20 @@ std::optional<TypeId> Normalizer::intersectionOfTables(TypeId here, TypeId there
|
||||
}
|
||||
else
|
||||
return std::nullopt;
|
||||
|
||||
}
|
||||
else if (hmtable)
|
||||
{
|
||||
if (table == htable)
|
||||
return here;
|
||||
else
|
||||
return arena->addType(MetatableTypeVar{table, hmtable});
|
||||
return arena->addType(MetatableTypeVar{table, hmtable});
|
||||
}
|
||||
else if (tmtable)
|
||||
{
|
||||
if (table == ttable)
|
||||
return there;
|
||||
else
|
||||
return arena->addType(MetatableTypeVar{table, tmtable});
|
||||
return arena->addType(MetatableTypeVar{table, tmtable});
|
||||
}
|
||||
else
|
||||
return table;
|
||||
@ -1280,7 +1271,7 @@ std::optional<TypeId> Normalizer::intersectionOfFunctions(TypeId here, TypeId th
|
||||
return std::nullopt;
|
||||
if (hftv->retTypes != tftv->retTypes)
|
||||
return std::nullopt;
|
||||
|
||||
|
||||
std::optional<TypePackId> argTypes = unionOfTypePacks(hftv->argTypes, tftv->argTypes);
|
||||
if (!argTypes)
|
||||
return std::nullopt;
|
||||
@ -1289,7 +1280,7 @@ std::optional<TypeId> Normalizer::intersectionOfFunctions(TypeId here, TypeId th
|
||||
return here;
|
||||
if (*argTypes == tftv->argTypes)
|
||||
return there;
|
||||
|
||||
|
||||
FunctionTypeVar result{*argTypes, hftv->retTypes};
|
||||
result.generics = hftv->generics;
|
||||
result.genericPacks = hftv->genericPacks;
|
||||
@ -1299,7 +1290,7 @@ std::optional<TypeId> Normalizer::intersectionOfFunctions(TypeId here, TypeId th
|
||||
std::optional<TypeId> Normalizer::unionSaturatedFunctions(TypeId here, TypeId there)
|
||||
{
|
||||
// Deep breath...
|
||||
//
|
||||
//
|
||||
// When we come to check overloaded functions for subtyping,
|
||||
// we have to compare (F1 & ... & FM) <: (G1 & ... G GN)
|
||||
// where each Fi or Gj is a function type. Now that intersection on the right is no
|
||||
@ -1319,12 +1310,12 @@ std::optional<TypeId> Normalizer::unionSaturatedFunctions(TypeId here, TypeId th
|
||||
//
|
||||
// So subtyping on overloaded functions "just" boils down to defining Apply<F, T>.
|
||||
//
|
||||
// Now for non-overloaded functions, this is easy!
|
||||
// Now for non-overloaded functions, this is easy!
|
||||
// Apply<(R -> S), T> is S if T <: R, and an error type otherwise.
|
||||
//
|
||||
// But for overloaded functions it's not so simple. We'd like Apply<F1 & ... & FM, T>
|
||||
// to just be Apply<F1, T> & ... & Apply<FM, T> but oh dear
|
||||
//
|
||||
//
|
||||
// if f : ((number -> number) & (string -> string))
|
||||
// and x : (number | string)
|
||||
// then f(x) : (number | string)
|
||||
@ -1334,7 +1325,7 @@ std::optional<TypeId> Normalizer::unionSaturatedFunctions(TypeId here, TypeId th
|
||||
// Apply<((number -> number) & (string -> string)), (number | string)> is (number | string)
|
||||
//
|
||||
// but
|
||||
//
|
||||
//
|
||||
// Apply<(number -> number), (number | string)> is an error
|
||||
// Apply<(string -> string), (number | string)> is an error
|
||||
//
|
||||
@ -1382,7 +1373,7 @@ std::optional<TypeId> Normalizer::unionSaturatedFunctions(TypeId here, TypeId th
|
||||
// Covariance and Contravariance, Giuseppe Castagna,
|
||||
// Logical Methods in Computer Science 16(1), 2022
|
||||
// https://arxiv.org/abs/1809.01427
|
||||
//
|
||||
//
|
||||
// A gentle introduction to semantic subtyping, Giuseppe Castagna and Alain Frisch,
|
||||
// Proc. Principles and practice of declarative programming 2005, pp 198–208
|
||||
// https://doi.org/10.1145/1069774.1069793
|
||||
@ -1398,7 +1389,7 @@ std::optional<TypeId> Normalizer::unionSaturatedFunctions(TypeId here, TypeId th
|
||||
return std::nullopt;
|
||||
if (hftv->genericPacks != tftv->genericPacks)
|
||||
return std::nullopt;
|
||||
|
||||
|
||||
std::optional<TypePackId> argTypes = unionOfTypePacks(hftv->argTypes, tftv->argTypes);
|
||||
if (!argTypes)
|
||||
return std::nullopt;
|
||||
@ -1416,7 +1407,7 @@ void Normalizer::intersectFunctionsWithFunction(NormalizedFunctionType& heres, T
|
||||
{
|
||||
if (!heres)
|
||||
return;
|
||||
|
||||
|
||||
for (auto it = heres->begin(); it != heres->end();)
|
||||
{
|
||||
TypeId here = *it;
|
||||
@ -1450,7 +1441,7 @@ void Normalizer::intersectFunctions(NormalizedFunctionType& heres, const Normali
|
||||
{
|
||||
heres = std::nullopt;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (TypeId there : *theres)
|
||||
@ -1530,7 +1521,7 @@ bool Normalizer::intersectNormals(NormalizedType& here, const NormalizedType& th
|
||||
if (isInhabited(inter))
|
||||
it++;
|
||||
else
|
||||
it = here.tyvars.erase(it);
|
||||
it = here.tyvars.erase(it);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -1757,7 +1748,8 @@ bool isSubtype(TypeId subTy, TypeId superTy, NotNull<Scope> scope, NotNull<Singl
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool isSubtype(TypePackId subPack, TypePackId superPack, NotNull<Scope> scope, NotNull<SingletonTypes> singletonTypes, InternalErrorReporter& ice, bool anyIsTop)
|
||||
bool isSubtype(
|
||||
TypePackId subPack, TypePackId superPack, NotNull<Scope> scope, NotNull<SingletonTypes> singletonTypes, InternalErrorReporter& ice, bool anyIsTop)
|
||||
{
|
||||
UnifierSharedState sharedState{&ice};
|
||||
TypeArena arena;
|
||||
@ -2377,4 +2369,3 @@ std::pair<TypePackId, bool> normalize(TypePackId tp, const ModulePtr& module, No
|
||||
}
|
||||
|
||||
} // namespace Luau
|
||||
|
||||
|
@ -9,7 +9,6 @@
|
||||
#include <stdexcept>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauSubstitutionFixMissingFields, false)
|
||||
LUAU_FASTFLAG(LuauLowerBoundsCalculation)
|
||||
LUAU_FASTFLAG(LuauClonePublicInterfaceLess)
|
||||
LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 10000)
|
||||
LUAU_FASTFLAGVARIABLE(LuauClassTypeVarsInSubstitution, false)
|
||||
@ -553,9 +552,6 @@ TypePackId Substitution::replace(TypePackId tp)
|
||||
|
||||
void Substitution::replaceChildren(TypeId ty)
|
||||
{
|
||||
if (BoundTypeVar* btv = log->getMutable<BoundTypeVar>(ty); FFlag::LuauLowerBoundsCalculation && btv)
|
||||
btv->boundTo = replace(btv->boundTo);
|
||||
|
||||
LUAU_ASSERT(ty == log->follow(ty));
|
||||
|
||||
if (ignoreChildren(ty))
|
||||
|
@ -10,11 +10,11 @@
|
||||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
|
||||
LUAU_FASTFLAG(LuauLowerBoundsCalculation)
|
||||
LUAU_FASTFLAG(LuauUnknownAndNeverType)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSpecialTypesAsterisked, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFixNameMaps, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUnseeArrayTtv, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFunctionReturnStringificationFixup, false)
|
||||
|
||||
/*
|
||||
* Prefix generic typenames with gen-
|
||||
@ -524,7 +524,7 @@ struct TypeVarStringifier
|
||||
|
||||
bool plural = true;
|
||||
|
||||
if (FFlag::LuauLowerBoundsCalculation)
|
||||
if (FFlag::LuauFunctionReturnStringificationFixup)
|
||||
{
|
||||
auto retBegin = begin(ftv.retTypes);
|
||||
auto retEnd = end(ftv.retTypes);
|
||||
|
@ -24,7 +24,7 @@ namespace Luau
|
||||
|
||||
// TypeInfer.h
|
||||
// TODO move these
|
||||
using PrintLineProc = void(*)(const std::string&);
|
||||
using PrintLineProc = void (*)(const std::string&);
|
||||
extern PrintLineProc luauPrintLine;
|
||||
|
||||
/* Push a scope onto the end of a stack for the lifetime of the StackPusher instance.
|
||||
@ -127,7 +127,8 @@ struct TypeChecker2
|
||||
if (auto ann = ref->parameters.data[0].type)
|
||||
{
|
||||
TypeId argTy = lookupAnnotation(ref->parameters.data[0].type);
|
||||
luauPrintLine(format("_luau_print (%d, %d): %s\n", annotation->location.begin.line, annotation->location.begin.column, toString(argTy).c_str()));
|
||||
luauPrintLine(format(
|
||||
"_luau_print (%d, %d): %s\n", annotation->location.begin.line, annotation->location.begin.column, toString(argTy).c_str()));
|
||||
return follow(argTy);
|
||||
}
|
||||
}
|
||||
@ -409,8 +410,8 @@ struct TypeChecker2
|
||||
}
|
||||
TypeId iteratorTy = follow(iteratorTypes[0]);
|
||||
|
||||
auto checkFunction = [this, &arena, &scope, &forInStatement, &variableTypes](const FunctionTypeVar* iterFtv, std::vector<TypeId> iterTys, bool isMm)
|
||||
{
|
||||
auto checkFunction = [this, &arena, &scope, &forInStatement, &variableTypes](
|
||||
const FunctionTypeVar* iterFtv, std::vector<TypeId> iterTys, bool isMm) {
|
||||
if (iterTys.size() < 1 || iterTys.size() > 3)
|
||||
{
|
||||
if (isMm)
|
||||
@ -420,20 +421,21 @@ struct TypeChecker2
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// It is okay if there aren't enough iterators, but the iteratee must provide enough.
|
||||
std::vector<TypeId> expectedVariableTypes = flatten(arena, singletonTypes, iterFtv->retTypes, variableTypes.size());
|
||||
if (expectedVariableTypes.size() < variableTypes.size())
|
||||
{
|
||||
if (isMm)
|
||||
reportError(GenericError{"__iter metamethod's next() function does not return enough values"}, getLocation(forInStatement->values));
|
||||
reportError(
|
||||
GenericError{"__iter metamethod's next() function does not return enough values"}, getLocation(forInStatement->values));
|
||||
else
|
||||
reportError(GenericError{"next() does not return enough values"}, forInStatement->values.data[0]->location);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < std::min(expectedVariableTypes.size(), variableTypes.size()); ++i)
|
||||
reportErrors(tryUnify(scope, forInStatement->vars.data[i]->location, variableTypes[i], expectedVariableTypes[i]));
|
||||
|
||||
|
||||
// nextFn is going to be invoked with (arrayTy, startIndexTy)
|
||||
|
||||
// It will be passed two arguments on every iteration save the
|
||||
@ -509,7 +511,8 @@ struct TypeChecker2
|
||||
{
|
||||
// nothing
|
||||
}
|
||||
else if (std::optional<TypeId> iterMmTy = findMetatableEntry(singletonTypes, module->errors, iteratorTy, "__iter", forInStatement->values.data[0]->location))
|
||||
else if (std::optional<TypeId> iterMmTy =
|
||||
findMetatableEntry(singletonTypes, module->errors, iteratorTy, "__iter", forInStatement->values.data[0]->location))
|
||||
{
|
||||
Instantiation instantiation{TxnLog::empty(), &arena, TypeLevel{}, scope};
|
||||
|
||||
@ -554,7 +557,7 @@ struct TypeChecker2
|
||||
// TODO: This will not tell the user that this is because the
|
||||
// metamethod isn't callable. This is not ideal, and we should
|
||||
// improve this error message.
|
||||
|
||||
|
||||
// TODO: This will also not handle intersections of functions or
|
||||
// callable tables (which are supported by the runtime).
|
||||
reportError(CannotCallNonFunction{*iterMmTy}, forInStatement->values.data[0]->location);
|
||||
|
@ -33,15 +33,11 @@ LUAU_FASTINTVARIABLE(LuauVisitRecursionLimit, 500)
|
||||
LUAU_FASTFLAG(LuauKnowsTheDataModel3)
|
||||
LUAU_FASTFLAG(LuauAutocompleteDynamicLimits)
|
||||
LUAU_FASTFLAG(LuauTypeNormalization2)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFunctionArgMismatchDetails, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauLowerBoundsCalculation, false)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauReturnAnyInsteadOfICE, false) // Eventually removed as false.
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauSharedSelf, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauAnyifyModuleReturnGenerics, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUnknownAndNeverType, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCallUnifyPackTails, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCheckGenericHOFTypes, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauBinaryNeedsExpectedTypesToo, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFixVarargExprHeadType, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNeverTypesAndOperatorsInference, false)
|
||||
@ -136,34 +132,6 @@ bool hasBreak(AstStat* node)
|
||||
}
|
||||
}
|
||||
|
||||
static bool hasReturn(const AstStat* node)
|
||||
{
|
||||
struct Searcher : AstVisitor
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
bool visit(AstStat*) override
|
||||
{
|
||||
return !result; // if we've already found a return statement, don't bother to traverse inward anymore
|
||||
}
|
||||
|
||||
bool visit(AstStatReturn*) override
|
||||
{
|
||||
result = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(AstExprFunction*) override
|
||||
{
|
||||
return false; // We don't care if the function uses a lambda that itself returns
|
||||
}
|
||||
};
|
||||
|
||||
Searcher searcher;
|
||||
const_cast<AstStat*>(node)->visit(&searcher);
|
||||
return searcher.result;
|
||||
}
|
||||
|
||||
// returns the last statement before the block exits, or nullptr if the block never exits
|
||||
const AstStat* getFallthrough(const AstStat* node)
|
||||
{
|
||||
@ -550,16 +518,6 @@ void TypeChecker::checkBlockWithoutRecursionCheck(const ScopePtr& scope, const A
|
||||
|
||||
std::unordered_map<AstStat*, std::pair<TypeId, ScopePtr>> functionDecls;
|
||||
|
||||
auto isLocalLambda = [](AstStat* stat) -> AstStatLocal* {
|
||||
AstStatLocal* local = stat->as<AstStatLocal>();
|
||||
|
||||
if (FFlag::LuauLowerBoundsCalculation && local && local->vars.size == 1 && local->values.size == 1 &&
|
||||
local->values.data[0]->is<AstExprFunction>())
|
||||
return local;
|
||||
else
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
auto checkBody = [&](AstStat* stat) {
|
||||
if (auto fun = stat->as<AstStatFunction>())
|
||||
{
|
||||
@ -607,7 +565,7 @@ void TypeChecker::checkBlockWithoutRecursionCheck(const ScopePtr& scope, const A
|
||||
// function f<a>(x:a):a local x: number = g(37) return x end
|
||||
// function g(x:number):number return f(x) end
|
||||
// ```
|
||||
if (containsFunctionCallOrReturn(**protoIter) || (FFlag::LuauLowerBoundsCalculation && isLocalLambda(*protoIter)))
|
||||
if (containsFunctionCallOrReturn(**protoIter))
|
||||
{
|
||||
while (checkIter != protoIter)
|
||||
{
|
||||
@ -906,12 +864,6 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatReturn& return_)
|
||||
|
||||
TypePackId retPack = checkExprList(scope, return_.location, return_.list, false, {}, expectedTypes).type;
|
||||
|
||||
if (useConstrainedIntersections())
|
||||
{
|
||||
unifyLowerBound(retPack, scope->returnType, demoter.demotedLevel(scope->level), scope, return_.location);
|
||||
return;
|
||||
}
|
||||
|
||||
// HACK: Nonstrict mode gets a bit too smart and strict for us when we
|
||||
// start typechecking everything across module boundaries.
|
||||
if (isNonstrictMode() && follow(scope->returnType) == follow(currentModule->getModuleScope()->returnType))
|
||||
@ -1574,11 +1526,7 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias
|
||||
for (auto param : binding->typePackParams)
|
||||
clone.instantiatedTypePackParams.push_back(param.tp);
|
||||
|
||||
bool isNormal = ty->normal;
|
||||
ty = addType(std::move(clone));
|
||||
|
||||
if (FFlag::LuauLowerBoundsCalculation)
|
||||
asMutable(ty)->normal = isNormal;
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -1605,14 +1553,6 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias
|
||||
|
||||
if (unify(ty, bindingType, aliasScope, typealias.location))
|
||||
bindingType = ty;
|
||||
|
||||
if (FFlag::LuauLowerBoundsCalculation)
|
||||
{
|
||||
auto [t, ok] = normalize(bindingType, currentModule, singletonTypes, *iceHandler);
|
||||
bindingType = t;
|
||||
if (!ok)
|
||||
reportError(typealias.location, NormalizationTooComplex{});
|
||||
}
|
||||
}
|
||||
|
||||
void TypeChecker::prototype(const ScopePtr& scope, const AstStatTypeAlias& typealias, int subLevel)
|
||||
@ -1959,9 +1899,8 @@ WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExp
|
||||
}
|
||||
else if (const FreeTypePack* ftp = get<Unifiable::Free>(retPack))
|
||||
{
|
||||
TypeLevel level = FFlag::LuauLowerBoundsCalculation ? ftp->level : scope->level;
|
||||
TypeId head = freshType(level);
|
||||
TypePackId pack = addTypePack(TypePackVar{TypePack{{head}, freshTypePack(level)}});
|
||||
TypeId head = freshType(scope->level);
|
||||
TypePackId pack = addTypePack(TypePackVar{TypePack{{head}, freshTypePack(scope->level)}});
|
||||
unify(pack, retPack, scope, expr.location);
|
||||
return {head, std::move(result.predicates)};
|
||||
}
|
||||
@ -2111,27 +2050,14 @@ std::optional<TypeId> TypeChecker::getIndexTypeFromTypeImpl(
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (FFlag::LuauLowerBoundsCalculation)
|
||||
{
|
||||
// FIXME Inefficient. We craft a UnionTypeVar and immediately throw it away.
|
||||
auto [t, ok] = normalize(addType(UnionTypeVar{std::move(goodOptions)}), currentModule, singletonTypes, *iceHandler);
|
||||
std::vector<TypeId> result = reduceUnion(goodOptions);
|
||||
if (FFlag::LuauUnknownAndNeverType && result.empty())
|
||||
return neverType;
|
||||
|
||||
if (!ok)
|
||||
reportError(location, NormalizationTooComplex{});
|
||||
if (result.size() == 1)
|
||||
return result[0];
|
||||
|
||||
return t;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::vector<TypeId> result = reduceUnion(goodOptions);
|
||||
if (FFlag::LuauUnknownAndNeverType && result.empty())
|
||||
return neverType;
|
||||
|
||||
if (result.size() == 1)
|
||||
return result[0];
|
||||
|
||||
return addType(UnionTypeVar{std::move(result)});
|
||||
}
|
||||
return addType(UnionTypeVar{std::move(result)});
|
||||
}
|
||||
else if (const IntersectionTypeVar* itv = get<IntersectionTypeVar>(type))
|
||||
{
|
||||
@ -3426,13 +3352,6 @@ std::pair<TypeId, ScopePtr> TypeChecker::checkFunctionSignature(const ScopePtr&
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!FFlag::LuauCheckGenericHOFTypes)
|
||||
{
|
||||
// We do not infer type binders, so if a generic function is required we do not propagate
|
||||
if (expectedFunctionType && !(expectedFunctionType->generics.empty() && expectedFunctionType->genericPacks.empty()))
|
||||
expectedFunctionType = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
auto [generics, genericPacks] = createGenericTypes(funScope, std::nullopt, expr, expr.generics, expr.genericPacks);
|
||||
@ -3442,8 +3361,7 @@ std::pair<TypeId, ScopePtr> TypeChecker::checkFunctionSignature(const ScopePtr&
|
||||
retPack = resolveTypePack(funScope, *expr.returnAnnotation);
|
||||
else if (isNonstrictMode())
|
||||
retPack = anyTypePack;
|
||||
else if (expectedFunctionType &&
|
||||
(!FFlag::LuauCheckGenericHOFTypes || (expectedFunctionType->generics.empty() && expectedFunctionType->genericPacks.empty())))
|
||||
else if (expectedFunctionType && expectedFunctionType->generics.empty() && expectedFunctionType->genericPacks.empty())
|
||||
{
|
||||
auto [head, tail] = flatten(expectedFunctionType->retTypes);
|
||||
|
||||
@ -3488,10 +3406,6 @@ std::pair<TypeId, ScopePtr> TypeChecker::checkFunctionSignature(const ScopePtr&
|
||||
funScope->varargPack = anyTypePack;
|
||||
}
|
||||
}
|
||||
else if (FFlag::LuauLowerBoundsCalculation && !isNonstrictMode())
|
||||
{
|
||||
funScope->varargPack = addTypePack(TypePackVar{VariadicTypePack{anyType, /*hidden*/ true}});
|
||||
}
|
||||
|
||||
std::vector<TypeId> argTypes;
|
||||
|
||||
@ -3575,48 +3489,28 @@ std::pair<TypeId, ScopePtr> TypeChecker::checkFunctionSignature(const ScopePtr&
|
||||
|
||||
std::vector<TypeId> genericTys;
|
||||
// if we have a generic expected function type and no generics, we should use the expected ones.
|
||||
if (FFlag::LuauCheckGenericHOFTypes)
|
||||
if (expectedFunctionType && generics.empty())
|
||||
{
|
||||
if (expectedFunctionType && generics.empty())
|
||||
{
|
||||
genericTys = expectedFunctionType->generics;
|
||||
}
|
||||
else
|
||||
{
|
||||
genericTys.reserve(generics.size());
|
||||
for (const GenericTypeDefinition& generic : generics)
|
||||
genericTys.push_back(generic.ty);
|
||||
}
|
||||
genericTys = expectedFunctionType->generics;
|
||||
}
|
||||
else
|
||||
{
|
||||
genericTys.reserve(generics.size());
|
||||
std::transform(generics.begin(), generics.end(), std::back_inserter(genericTys), [](auto&& el) {
|
||||
return el.ty;
|
||||
});
|
||||
for (const GenericTypeDefinition& generic : generics)
|
||||
genericTys.push_back(generic.ty);
|
||||
}
|
||||
|
||||
std::vector<TypePackId> genericTps;
|
||||
// if we have a generic expected function type and no generic typepacks, we should use the expected ones.
|
||||
if (FFlag::LuauCheckGenericHOFTypes)
|
||||
if (expectedFunctionType && genericPacks.empty())
|
||||
{
|
||||
if (expectedFunctionType && genericPacks.empty())
|
||||
{
|
||||
genericTps = expectedFunctionType->genericPacks;
|
||||
}
|
||||
else
|
||||
{
|
||||
genericTps.reserve(genericPacks.size());
|
||||
for (const GenericTypePackDefinition& generic : genericPacks)
|
||||
genericTps.push_back(generic.tp);
|
||||
}
|
||||
genericTps = expectedFunctionType->genericPacks;
|
||||
}
|
||||
else
|
||||
{
|
||||
genericTps.reserve(genericPacks.size());
|
||||
std::transform(genericPacks.begin(), genericPacks.end(), std::back_inserter(genericTps), [](auto&& el) {
|
||||
return el.tp;
|
||||
});
|
||||
for (const GenericTypePackDefinition& generic : genericPacks)
|
||||
genericTps.push_back(generic.tp);
|
||||
}
|
||||
|
||||
TypeId funTy =
|
||||
@ -3674,24 +3568,9 @@ void TypeChecker::checkFunctionBody(const ScopePtr& scope, TypeId ty, const AstE
|
||||
{
|
||||
check(scope, *function.body);
|
||||
|
||||
if (useConstrainedIntersections())
|
||||
{
|
||||
TypePackId retPack = follow(funTy->retTypes);
|
||||
// It is possible for a function to have no annotation and no return statement, and yet still have an ascribed return type
|
||||
// if it is expected to conform to some other interface. (eg the function may be a lambda passed as a callback)
|
||||
if (!hasReturn(function.body) && !function.returnAnnotation.has_value() && get<FreeTypePack>(retPack))
|
||||
{
|
||||
auto level = getLevel(retPack);
|
||||
if (level && scope->level.subsumes(*level))
|
||||
*asMutable(retPack) = TypePack{{}, std::nullopt};
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// We explicitly don't follow here to check if we have a 'true' free type instead of bound one
|
||||
if (get_if<FreeTypePack>(&funTy->retTypes->ty))
|
||||
*asMutable(funTy->retTypes) = TypePack{{}, std::nullopt};
|
||||
}
|
||||
// We explicitly don't follow here to check if we have a 'true' free type instead of bound one
|
||||
if (get_if<FreeTypePack>(&funTy->retTypes->ty))
|
||||
*asMutable(funTy->retTypes) = TypePack{{}, std::nullopt};
|
||||
|
||||
bool reachesImplicitReturn = getFallthrough(function.body) != nullptr;
|
||||
|
||||
@ -3763,21 +3642,13 @@ void TypeChecker::checkArgumentList(const ScopePtr& scope, const AstExpr& funNam
|
||||
if (!argLocations.empty())
|
||||
location = {state.location.begin, argLocations.back().end};
|
||||
|
||||
if (FFlag::LuauFunctionArgMismatchDetails)
|
||||
{
|
||||
std::string namePath;
|
||||
if (std::optional<LValue> lValue = tryGetLValue(funName))
|
||||
namePath = toString(*lValue);
|
||||
std::string namePath;
|
||||
if (std::optional<LValue> lValue = tryGetLValue(funName))
|
||||
namePath = toString(*lValue);
|
||||
|
||||
auto [minParams, optMaxParams] = getParameterExtents(&state.log, paramPack);
|
||||
state.reportError(TypeError{location,
|
||||
CountMismatch{minParams, optMaxParams, std::distance(begin(argPack), end(argPack)), CountMismatch::Context::Arg, false, namePath}});
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t minParams = getParameterExtents(&state.log, paramPack).first;
|
||||
state.reportError(TypeError{location, CountMismatch{minParams, std::nullopt, std::distance(begin(argPack), end(argPack))}});
|
||||
}
|
||||
auto [minParams, optMaxParams] = getParameterExtents(&state.log, paramPack);
|
||||
state.reportError(TypeError{location,
|
||||
CountMismatch{minParams, optMaxParams, std::distance(begin(argPack), end(argPack)), CountMismatch::Context::Arg, false, namePath}});
|
||||
};
|
||||
|
||||
while (true)
|
||||
@ -3801,7 +3672,7 @@ void TypeChecker::checkArgumentList(const ScopePtr& scope, const AstExpr& funNam
|
||||
else
|
||||
state.log.replace(*argTail, TypePackVar(TypePack{{}}));
|
||||
}
|
||||
else if (FFlag::LuauCallUnifyPackTails && paramTail)
|
||||
else if (paramTail)
|
||||
{
|
||||
state.tryUnify(*argTail, *paramTail);
|
||||
}
|
||||
@ -3881,20 +3752,12 @@ void TypeChecker::checkArgumentList(const ScopePtr& scope, const AstExpr& funNam
|
||||
std::optional<TypePackId> tail = flatten(paramPack, state.log).second;
|
||||
bool isVariadic = tail && Luau::isVariadic(*tail);
|
||||
|
||||
if (FFlag::LuauFunctionArgMismatchDetails)
|
||||
{
|
||||
std::string namePath;
|
||||
if (std::optional<LValue> lValue = tryGetLValue(funName))
|
||||
namePath = toString(*lValue);
|
||||
std::string namePath;
|
||||
if (std::optional<LValue> lValue = tryGetLValue(funName))
|
||||
namePath = toString(*lValue);
|
||||
|
||||
state.reportError(TypeError{
|
||||
state.location, CountMismatch{minParams, optMaxParams, paramIndex, CountMismatch::Context::Arg, isVariadic, namePath}});
|
||||
}
|
||||
else
|
||||
{
|
||||
state.reportError(
|
||||
TypeError{state.location, CountMismatch{minParams, std::nullopt, paramIndex, CountMismatch::Context::Arg, isVariadic}});
|
||||
}
|
||||
state.reportError(TypeError{
|
||||
state.location, CountMismatch{minParams, optMaxParams, paramIndex, CountMismatch::Context::Arg, isVariadic, namePath}});
|
||||
return;
|
||||
}
|
||||
++paramIter;
|
||||
@ -3924,21 +3787,6 @@ void TypeChecker::checkArgumentList(const ScopePtr& scope, const AstExpr& funNam
|
||||
}
|
||||
else if (auto vtp = state.log.getMutable<VariadicTypePack>(tail))
|
||||
{
|
||||
if (FFlag::LuauLowerBoundsCalculation && vtp->hidden)
|
||||
{
|
||||
// We know that this function can technically be oversaturated, but we have its definition and we
|
||||
// know that it's useless.
|
||||
|
||||
TypeId e = errorRecoveryType(scope);
|
||||
while (argIter != endIter)
|
||||
{
|
||||
unify(e, *argIter, scope, state.location);
|
||||
++argIter;
|
||||
}
|
||||
|
||||
reportCountMismatchError();
|
||||
return;
|
||||
}
|
||||
// Function is variadic and requires that all subsequent parameters
|
||||
// be compatible with a type.
|
||||
size_t argIndex = paramIndex;
|
||||
@ -4040,21 +3888,14 @@ WithPredicate<TypePackId> TypeChecker::checkExprPackHelper(const ScopePtr& scope
|
||||
}
|
||||
|
||||
TypePackId retPack;
|
||||
if (FFlag::LuauLowerBoundsCalculation)
|
||||
if (auto free = get<FreeTypeVar>(actualFunctionType))
|
||||
{
|
||||
retPack = freshTypePack(scope->level);
|
||||
retPack = freshTypePack(free->level);
|
||||
TypePackId freshArgPack = freshTypePack(free->level);
|
||||
asMutable(actualFunctionType)->ty.emplace<FunctionTypeVar>(free->level, freshArgPack, retPack);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (auto free = get<FreeTypeVar>(actualFunctionType))
|
||||
{
|
||||
retPack = freshTypePack(free->level);
|
||||
TypePackId freshArgPack = freshTypePack(free->level);
|
||||
asMutable(actualFunctionType)->ty.emplace<FunctionTypeVar>(free->level, freshArgPack, retPack);
|
||||
}
|
||||
else
|
||||
retPack = freshTypePack(scope->level);
|
||||
}
|
||||
retPack = freshTypePack(scope->level);
|
||||
|
||||
// checkExpr will log the pre-instantiated type of the function.
|
||||
// That's not nearly as interesting as the instantiated type, which will include details about how
|
||||
@ -4214,39 +4055,13 @@ std::optional<WithPredicate<TypePackId>> TypeChecker::checkCallOverload(const Sc
|
||||
// fn is one of the overloads of actualFunctionType, which
|
||||
// has been instantiated, so is a monotype. We can therefore
|
||||
// unify it with a monomorphic function.
|
||||
if (useConstrainedIntersections())
|
||||
{
|
||||
// This ternary is phrased deliberately. We need ties between sibling scopes to bias toward ftv->level.
|
||||
const TypeLevel level = scope->level.subsumes(ftv->level) ? scope->level : ftv->level;
|
||||
TypeId r = addType(FunctionTypeVar(scope->level, argPack, retPack));
|
||||
|
||||
std::vector<TypeId> adjustedArgTypes;
|
||||
auto it = begin(argPack);
|
||||
auto endIt = end(argPack);
|
||||
Widen widen{¤tModule->internalTypes, singletonTypes};
|
||||
for (; it != endIt; ++it)
|
||||
{
|
||||
adjustedArgTypes.push_back(addType(ConstrainedTypeVar{level, {widen(*it)}}));
|
||||
}
|
||||
UnifierOptions options;
|
||||
options.isFunctionCall = true;
|
||||
unify(r, fn, scope, expr.location, options);
|
||||
|
||||
TypePackId adjustedArgPack = addTypePack(TypePack{std::move(adjustedArgTypes), it.tail()});
|
||||
|
||||
TxnLog log;
|
||||
promoteTypeLevels(log, ¤tModule->internalTypes, level, /*scope*/ nullptr, /*useScope*/ false, retPack);
|
||||
log.commit();
|
||||
|
||||
*asMutable(fn) = FunctionTypeVar{level, adjustedArgPack, retPack};
|
||||
return {{retPack}};
|
||||
}
|
||||
else
|
||||
{
|
||||
TypeId r = addType(FunctionTypeVar(scope->level, argPack, retPack));
|
||||
|
||||
UnifierOptions options;
|
||||
options.isFunctionCall = true;
|
||||
unify(r, fn, scope, expr.location, options);
|
||||
|
||||
return {{retPack}};
|
||||
}
|
||||
return {{retPack}};
|
||||
}
|
||||
|
||||
std::vector<Location> metaArgLocations;
|
||||
@ -4760,14 +4575,6 @@ TypeId TypeChecker::quantify(const ScopePtr& scope, TypeId ty, Location location
|
||||
Luau::quantify(ty, scope->level);
|
||||
else if (auto ttv = getTableType(ty); ttv && ttv->selfTy)
|
||||
Luau::quantify(ty, scope->level);
|
||||
|
||||
if (FFlag::LuauLowerBoundsCalculation)
|
||||
{
|
||||
auto [t, ok] = Luau::normalize(ty, currentModule, singletonTypes, *iceHandler);
|
||||
if (!ok)
|
||||
reportError(location, NormalizationTooComplex{});
|
||||
return t;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -4775,14 +4582,6 @@ TypeId TypeChecker::quantify(const ScopePtr& scope, TypeId ty, Location location
|
||||
|
||||
if (ftv)
|
||||
Luau::quantify(ty, scope->level);
|
||||
|
||||
if (FFlag::LuauLowerBoundsCalculation && ftv)
|
||||
{
|
||||
auto [t, ok] = Luau::normalize(ty, currentModule, singletonTypes, *iceHandler);
|
||||
if (!ok)
|
||||
reportError(location, NormalizationTooComplex{});
|
||||
return t;
|
||||
}
|
||||
}
|
||||
|
||||
return ty;
|
||||
@ -4813,14 +4612,6 @@ TypeId TypeChecker::instantiate(const ScopePtr& scope, TypeId ty, Location locat
|
||||
|
||||
TypeId TypeChecker::anyify(const ScopePtr& scope, TypeId ty, Location location)
|
||||
{
|
||||
if (FFlag::LuauLowerBoundsCalculation)
|
||||
{
|
||||
auto [t, ok] = normalize(ty, currentModule, singletonTypes, *iceHandler);
|
||||
if (!ok)
|
||||
reportError(location, NormalizationTooComplex{});
|
||||
ty = t;
|
||||
}
|
||||
|
||||
Anyification anyification{¤tModule->internalTypes, scope, singletonTypes, iceHandler, anyType, anyTypePack};
|
||||
std::optional<TypeId> any = anyification.substitute(ty);
|
||||
if (anyification.normalizationTooComplex)
|
||||
@ -4836,14 +4627,6 @@ TypeId TypeChecker::anyify(const ScopePtr& scope, TypeId ty, Location location)
|
||||
|
||||
TypePackId TypeChecker::anyify(const ScopePtr& scope, TypePackId ty, Location location)
|
||||
{
|
||||
if (FFlag::LuauLowerBoundsCalculation)
|
||||
{
|
||||
auto [t, ok] = normalize(ty, currentModule, singletonTypes, *iceHandler);
|
||||
if (!ok)
|
||||
reportError(location, NormalizationTooComplex{});
|
||||
ty = t;
|
||||
}
|
||||
|
||||
Anyification anyification{¤tModule->internalTypes, scope, singletonTypes, iceHandler, anyType, anyTypePack};
|
||||
std::optional<TypePackId> any = anyification.substitute(ty);
|
||||
if (any.has_value())
|
||||
@ -6083,11 +5866,6 @@ bool TypeChecker::isNonstrictMode() const
|
||||
return (currentModule->mode == Mode::Nonstrict) || (currentModule->mode == Mode::NoCheck);
|
||||
}
|
||||
|
||||
bool TypeChecker::useConstrainedIntersections() const
|
||||
{
|
||||
return FFlag::LuauLowerBoundsCalculation && !isNonstrictMode();
|
||||
}
|
||||
|
||||
std::vector<TypeId> TypeChecker::unTypePack(const ScopePtr& scope, TypePackId tp, size_t expectedLength, const Location& location)
|
||||
{
|
||||
TypePackId expectedTypePack = addTypePack({});
|
||||
|
@ -6,8 +6,6 @@
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/TypeInfer.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauFunctionArgMismatchDetails)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
@ -218,7 +216,7 @@ std::pair<size_t, std::optional<size_t>> getParameterExtents(const TxnLog* log,
|
||||
++it;
|
||||
}
|
||||
|
||||
if (it.tail() && (!FFlag::LuauFunctionArgMismatchDetails || isVariadicTail(*it.tail(), *log, includeHiddenVariadics)))
|
||||
if (it.tail() && isVariadicTail(*it.tail(), *log, includeHiddenVariadics))
|
||||
return {minCount, std::nullopt};
|
||||
else
|
||||
return {minCount, minCount + optionalCount};
|
||||
|
@ -25,7 +25,6 @@ LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0)
|
||||
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||
LUAU_FASTFLAG(LuauUnknownAndNeverType)
|
||||
LUAU_FASTFLAGVARIABLE(LuauMaybeGenericIntersectionTypes, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauStringFormatArgumentErrorFix, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNoMoreGlobalSingletonTypes, false)
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
|
||||
@ -1166,21 +1165,12 @@ std::optional<WithPredicate<TypePackId>> magicFunctionFormat(
|
||||
}
|
||||
|
||||
// if we know the argument count or if we have too many arguments for sure, we can issue an error
|
||||
if (FFlag::LuauStringFormatArgumentErrorFix)
|
||||
{
|
||||
size_t numActualParams = params.size();
|
||||
size_t numExpectedParams = expected.size() + 1; // + 1 for the format string
|
||||
size_t numActualParams = params.size();
|
||||
size_t numExpectedParams = expected.size() + 1; // + 1 for the format string
|
||||
|
||||
if (numExpectedParams != numActualParams && (!tail || numExpectedParams < numActualParams))
|
||||
typechecker.reportError(TypeError{expr.location, CountMismatch{numExpectedParams, std::nullopt, numActualParams}});
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t actualParamSize = params.size() - paramOffset;
|
||||
if (numExpectedParams != numActualParams && (!tail || numExpectedParams < numActualParams))
|
||||
typechecker.reportError(TypeError{expr.location, CountMismatch{numExpectedParams, std::nullopt, numActualParams}});
|
||||
|
||||
if (expected.size() != actualParamSize && (!tail || expected.size() < actualParamSize))
|
||||
typechecker.reportError(TypeError{expr.location, CountMismatch{expected.size(), std::nullopt, actualParamSize}});
|
||||
}
|
||||
return WithPredicate<TypePackId>{arena.addTypePack({typechecker.stringType})};
|
||||
}
|
||||
|
||||
|
@ -18,14 +18,12 @@ LUAU_FASTINT(LuauTypeInferTypePackLoopLimit);
|
||||
LUAU_FASTINT(LuauTypeInferIterationLimit);
|
||||
LUAU_FASTFLAG(LuauAutocompleteDynamicLimits)
|
||||
LUAU_FASTINTVARIABLE(LuauTypeInferLowerBoundsIterationLimit, 2000);
|
||||
LUAU_FASTFLAG(LuauLowerBoundsCalculation);
|
||||
LUAU_FASTFLAG(LuauErrorRecoveryType);
|
||||
LUAU_FASTFLAG(LuauUnknownAndNeverType)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSubtypeNormalizer, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauScalarShapeSubtyping, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauInstantiateInSubtyping, false)
|
||||
LUAU_FASTFLAG(LuauClassTypeVarsInSubstitution)
|
||||
LUAU_FASTFLAG(LuauCallUnifyPackTails)
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
|
||||
namespace Luau
|
||||
@ -346,8 +344,7 @@ static bool subsumes(bool useScopes, TY_A* left, TY_B* right)
|
||||
return left->level.subsumes(right->level);
|
||||
}
|
||||
|
||||
Unifier::Unifier(NotNull<Normalizer> normalizer, Mode mode, NotNull<Scope> scope, const Location& location,
|
||||
Variance variance, TxnLog* parentLog)
|
||||
Unifier::Unifier(NotNull<Normalizer> normalizer, Mode mode, NotNull<Scope> scope, const Location& location, Variance variance, TxnLog* parentLog)
|
||||
: types(normalizer->arena)
|
||||
, singletonTypes(normalizer->singletonTypes)
|
||||
, normalizer(normalizer)
|
||||
@ -529,7 +526,7 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
|
||||
{
|
||||
tryUnifyUnionWithType(subTy, subUnion, superTy);
|
||||
}
|
||||
else if (const UnionTypeVar* uv = (FFlag::LuauSubtypeNormalizer? nullptr: log.getMutable<UnionTypeVar>(superTy)))
|
||||
else if (const UnionTypeVar* uv = (FFlag::LuauSubtypeNormalizer ? nullptr : log.getMutable<UnionTypeVar>(superTy)))
|
||||
{
|
||||
tryUnifyTypeWithUnion(subTy, superTy, uv, cacheEnabled, isFunctionCall);
|
||||
}
|
||||
@ -865,7 +862,8 @@ void Unifier::tryUnifyIntersectionWithType(TypeId subTy, const IntersectionTypeV
|
||||
}
|
||||
}
|
||||
|
||||
void Unifier::tryUnifyNormalizedTypes(TypeId subTy, TypeId superTy, const NormalizedType& subNorm, const NormalizedType& superNorm, std::string reason, std::optional<TypeError> error)
|
||||
void Unifier::tryUnifyNormalizedTypes(
|
||||
TypeId subTy, TypeId superTy, const NormalizedType& subNorm, const NormalizedType& superNorm, std::string reason, std::optional<TypeError> error)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauSubtypeNormalizer);
|
||||
|
||||
@ -1371,12 +1369,12 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
|
||||
else
|
||||
{
|
||||
// A union type including nil marks an optional argument
|
||||
if ((!FFlag::LuauLowerBoundsCalculation || isNonstrictMode()) && superIter.good() && isOptional(*superIter))
|
||||
if (superIter.good() && isOptional(*superIter))
|
||||
{
|
||||
superIter.advance();
|
||||
continue;
|
||||
}
|
||||
else if ((!FFlag::LuauLowerBoundsCalculation || isNonstrictMode()) && subIter.good() && isOptional(*subIter))
|
||||
else if (subIter.good() && isOptional(*subIter))
|
||||
{
|
||||
subIter.advance();
|
||||
continue;
|
||||
@ -1394,7 +1392,7 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
|
||||
return;
|
||||
}
|
||||
|
||||
if ((!FFlag::LuauLowerBoundsCalculation || isNonstrictMode()) && !isFunctionCall && subIter.good())
|
||||
if (!isFunctionCall && subIter.good())
|
||||
{
|
||||
// Sometimes it is ok to pass too many arguments
|
||||
return;
|
||||
@ -1491,7 +1489,6 @@ void Unifier::tryUnifyFunctions(TypeId subTy, TypeId superTy, bool isFunctionCal
|
||||
|
||||
numGenerics = std::min(superFunction->generics.size(), subFunction->generics.size());
|
||||
numGenericPacks = std::min(superFunction->genericPacks.size(), subFunction->genericPacks.size());
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -2012,7 +2009,8 @@ void Unifier::tryUnifyWithMetatable(TypeId subTy, TypeId superTy, bool reversed)
|
||||
if (auto e = hasUnificationTooComplex(innerState.errors))
|
||||
reportError(*e);
|
||||
else if (!innerState.errors.empty())
|
||||
reportError(TypeError{location, TypeMismatch{reversed ? subTy : superTy, reversed ? superTy : subTy, "", innerState.errors.front()}});
|
||||
reportError(
|
||||
TypeError{location, TypeMismatch{reversed ? subTy : superTy, reversed ? superTy : subTy, "", innerState.errors.front()}});
|
||||
else if (!missingProperty)
|
||||
{
|
||||
log.concat(std::move(innerState.log));
|
||||
@ -2448,8 +2446,7 @@ void Unifier::unifyLowerBound(TypePackId subTy, TypePackId superTy, TypeLevel de
|
||||
for (; superIter != superEndIter; ++superIter)
|
||||
tp->head.push_back(*superIter);
|
||||
}
|
||||
else if (const VariadicTypePack* subVariadic = log.getMutable<VariadicTypePack>(subTailPack);
|
||||
subVariadic && FFlag::LuauCallUnifyPackTails)
|
||||
else if (const VariadicTypePack* subVariadic = log.getMutable<VariadicTypePack>(subTailPack))
|
||||
{
|
||||
while (superIter != superEndIter)
|
||||
{
|
||||
|
@ -1,17 +1,13 @@
|
||||
// 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 <vector>
|
||||
#include <string>
|
||||
|
||||
#include <stdarg.h>
|
||||
|
||||
#if defined(__GNUC__)
|
||||
#define LUAU_PRINTF_ATTR(fmt, arg) __attribute__((format(printf, fmt, arg)))
|
||||
#else
|
||||
#define LUAU_PRINTF_ATTR(fmt, arg)
|
||||
#endif
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
|
71
CLI/Repl.cpp
71
CLI/Repl.cpp
@ -4,6 +4,7 @@
|
||||
#include "lua.h"
|
||||
#include "lualib.h"
|
||||
|
||||
#include "Luau/CodeGen.h"
|
||||
#include "Luau/Compiler.h"
|
||||
#include "Luau/BytecodeBuilder.h"
|
||||
#include "Luau/Parser.h"
|
||||
@ -46,11 +47,15 @@ enum class CompileFormat
|
||||
{
|
||||
Text,
|
||||
Binary,
|
||||
Remarks,
|
||||
Codegen,
|
||||
Null
|
||||
};
|
||||
|
||||
constexpr int MaxTraversalLimit = 50;
|
||||
|
||||
static bool codegen = false;
|
||||
|
||||
// Ctrl-C handling
|
||||
static void sigintCallback(lua_State* L, int gc)
|
||||
{
|
||||
@ -159,6 +164,9 @@ static int lua_require(lua_State* L)
|
||||
std::string bytecode = Luau::compile(*source, copts());
|
||||
if (luau_load(ML, chunkname.c_str(), bytecode.data(), bytecode.size(), 0) == 0)
|
||||
{
|
||||
if (codegen)
|
||||
Luau::CodeGen::compile(ML, -1);
|
||||
|
||||
if (coverageActive())
|
||||
coverageTrack(ML, -1);
|
||||
|
||||
@ -242,6 +250,9 @@ static int lua_callgrind(lua_State* L)
|
||||
|
||||
void setupState(lua_State* L)
|
||||
{
|
||||
if (codegen)
|
||||
Luau::CodeGen::create(L);
|
||||
|
||||
luaL_openlibs(L);
|
||||
|
||||
static const luaL_Reg funcs[] = {
|
||||
@ -276,6 +287,9 @@ std::string runCode(lua_State* L, const std::string& source)
|
||||
return error;
|
||||
}
|
||||
|
||||
if (codegen)
|
||||
Luau::CodeGen::compile(L, -1);
|
||||
|
||||
lua_State* T = lua_newthread(L);
|
||||
|
||||
lua_pushvalue(L, -2);
|
||||
@ -604,6 +618,9 @@ static bool runFile(const char* name, lua_State* GL, bool repl)
|
||||
|
||||
if (luau_load(L, chunkname.c_str(), bytecode.data(), bytecode.size(), 0) == 0)
|
||||
{
|
||||
if (codegen)
|
||||
Luau::CodeGen::compile(L, -1);
|
||||
|
||||
if (coverageActive())
|
||||
coverageTrack(L, -1);
|
||||
|
||||
@ -656,6 +673,20 @@ 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)
|
||||
{
|
||||
std::unique_ptr<lua_State, void (*)(lua_State*)> globalState(luaL_newstate(), lua_close);
|
||||
lua_State* L = globalState.get();
|
||||
|
||||
setupState(L);
|
||||
|
||||
if (luau_load(L, name, bytecode.data(), bytecode.size(), 0) == 0)
|
||||
return Luau::CodeGen::getAssemblyText(L, -1);
|
||||
|
||||
fprintf(stderr, "Error loading bytecode %s\n", name);
|
||||
return "";
|
||||
}
|
||||
|
||||
static bool compileFile(const char* name, CompileFormat format)
|
||||
{
|
||||
std::optional<std::string> source = readFile(name);
|
||||
@ -675,6 +706,11 @@ static bool compileFile(const char* name, CompileFormat format)
|
||||
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);
|
||||
}
|
||||
|
||||
Luau::compileOrThrow(bcb, *source, copts());
|
||||
|
||||
@ -683,9 +719,15 @@ static bool compileFile(const char* name, CompileFormat 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:
|
||||
printf("%s", getCodegenAssembly(name, bcb.getBytecode()).c_str());
|
||||
break;
|
||||
case CompileFormat::Null:
|
||||
break;
|
||||
}
|
||||
@ -713,7 +755,7 @@ static void displayHelp(const char* argv0)
|
||||
printf("\n");
|
||||
printf("Available modes:\n");
|
||||
printf(" omitted: compile and run input files one by one\n");
|
||||
printf(" --compile[=format]: compile input files and output resulting formatted bytecode (binary or text)\n");
|
||||
printf(" --compile[=format]: compile input files and output resulting formatted bytecode (binary, text, remarks, codegen or null)\n");
|
||||
printf("\n");
|
||||
printf("Available options:\n");
|
||||
printf(" --coverage: collect code coverage while running the code and output results to coverage.out\n");
|
||||
@ -723,6 +765,7 @@ static void displayHelp(const char* argv0)
|
||||
printf(" -g<n>: compile with debug level n (default 1, n should be between 0 and 2).\n");
|
||||
printf(" --profile[=N]: profile the code using N Hz sampling (default 10000) and output results to profile.out\n");
|
||||
printf(" --timetrace: record compiler time tracing information into trace.json\n");
|
||||
printf(" --codegen: execute code using native code generation\n");
|
||||
}
|
||||
|
||||
static int assertionHandler(const char* expr, const char* file, int line, const char* function)
|
||||
@ -761,6 +804,14 @@ int replMain(int argc, char** argv)
|
||||
{
|
||||
compileFormat = CompileFormat::Text;
|
||||
}
|
||||
else if (strcmp(argv[1], "--compile=remarks") == 0)
|
||||
{
|
||||
compileFormat = CompileFormat::Remarks;
|
||||
}
|
||||
else if (strcmp(argv[1], "--compile=codegen") == 0)
|
||||
{
|
||||
compileFormat = CompileFormat::Codegen;
|
||||
}
|
||||
else if (strcmp(argv[1], "--compile=null") == 0)
|
||||
{
|
||||
compileFormat = CompileFormat::Null;
|
||||
@ -811,6 +862,10 @@ int replMain(int argc, char** argv)
|
||||
{
|
||||
profile = atoi(argv[i] + 10);
|
||||
}
|
||||
else if (strcmp(argv[i], "--codegen") == 0)
|
||||
{
|
||||
codegen = true;
|
||||
}
|
||||
else if (strcmp(argv[i], "--coverage") == 0)
|
||||
{
|
||||
coverage = true;
|
||||
@ -839,12 +894,26 @@ int replMain(int argc, char** argv)
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !LUA_CUSTOM_EXECUTION
|
||||
if (codegen)
|
||||
{
|
||||
fprintf(stderr, "To run with --codegen, Luau has to be built with LUA_CUSTOM_EXECUTION enabled\n");
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
const std::vector<std::string> files = getSourceFiles(argc, argv);
|
||||
if (mode == CliMode::Unknown)
|
||||
{
|
||||
mode = files.empty() ? CliMode::Repl : CliMode::RunSourceFiles;
|
||||
}
|
||||
|
||||
if (mode != CliMode::Compile && codegen && !Luau::CodeGen::isSupported())
|
||||
{
|
||||
fprintf(stderr, "Cannot enable --codegen, native code generation is not supported in current configuration\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case CliMode::Compile:
|
||||
|
@ -12,6 +12,7 @@ option(LUAU_BUILD_WEB "Build Web module" OFF)
|
||||
option(LUAU_WERROR "Warnings as errors" OFF)
|
||||
option(LUAU_STATIC_CRT "Link with the static CRT (/MT)" OFF)
|
||||
option(LUAU_EXTERN_C "Use extern C for all APIs" OFF)
|
||||
option(LUAU_NATIVE "Enable support for native code generation" OFF)
|
||||
|
||||
if(LUAU_STATIC_CRT)
|
||||
cmake_minimum_required(VERSION 3.15)
|
||||
@ -132,6 +133,10 @@ if(LUAU_EXTERN_C)
|
||||
target_compile_definitions(Luau.Compiler PUBLIC LUACODE_API=extern\"C\")
|
||||
endif()
|
||||
|
||||
if(LUAU_NATIVE)
|
||||
target_compile_definitions(Luau.VM PUBLIC LUA_CUSTOM_EXECUTION=1)
|
||||
endif()
|
||||
|
||||
if (MSVC AND MSVC_VERSION GREATER_EQUAL 1924)
|
||||
# disable partial redundancy elimination which regresses interpreter codegen substantially in VS2022:
|
||||
# https://developercommunity.visualstudio.com/t/performance-regression-on-a-complex-interpreter-lo/1631863
|
||||
@ -167,7 +172,7 @@ if(LUAU_BUILD_CLI)
|
||||
|
||||
target_include_directories(Luau.Repl.CLI PRIVATE extern extern/isocline/include)
|
||||
|
||||
target_link_libraries(Luau.Repl.CLI PRIVATE Luau.Compiler Luau.VM isocline)
|
||||
target_link_libraries(Luau.Repl.CLI PRIVATE Luau.Compiler Luau.CodeGen Luau.VM isocline)
|
||||
|
||||
if(UNIX)
|
||||
find_library(LIBPTHREAD pthread)
|
||||
@ -193,11 +198,11 @@ if(LUAU_BUILD_TESTS)
|
||||
|
||||
target_compile_options(Luau.Conformance PRIVATE ${LUAU_OPTIONS})
|
||||
target_include_directories(Luau.Conformance PRIVATE extern)
|
||||
target_link_libraries(Luau.Conformance PRIVATE Luau.Analysis Luau.Compiler Luau.VM)
|
||||
target_link_libraries(Luau.Conformance PRIVATE Luau.Analysis Luau.Compiler Luau.CodeGen Luau.VM)
|
||||
|
||||
target_compile_options(Luau.CLI.Test PRIVATE ${LUAU_OPTIONS})
|
||||
target_include_directories(Luau.CLI.Test PRIVATE extern CLI)
|
||||
target_link_libraries(Luau.CLI.Test PRIVATE Luau.Compiler Luau.VM isocline)
|
||||
target_link_libraries(Luau.CLI.Test PRIVATE Luau.Compiler Luau.CodeGen Luau.VM isocline)
|
||||
if(UNIX)
|
||||
find_library(LIBPTHREAD pthread)
|
||||
if (LIBPTHREAD)
|
||||
|
@ -15,6 +15,14 @@ namespace Luau
|
||||
namespace CodeGen
|
||||
{
|
||||
|
||||
enum class RoundingModeX64
|
||||
{
|
||||
RoundToNearestEven = 0b00,
|
||||
RoundToNegativeInfinity = 0b01,
|
||||
RoundToPositiveInfinity = 0b10,
|
||||
RoundToZero = 0b11,
|
||||
};
|
||||
|
||||
class AssemblyBuilderX64
|
||||
{
|
||||
public:
|
||||
@ -48,6 +56,8 @@ public:
|
||||
void imul(OperandX64 op);
|
||||
void neg(OperandX64 op);
|
||||
void not_(OperandX64 op);
|
||||
void dec(OperandX64 op);
|
||||
void inc(OperandX64 op);
|
||||
|
||||
// Additional forms of imul
|
||||
void imul(OperandX64 lhs, OperandX64 rhs);
|
||||
@ -82,13 +92,12 @@ public:
|
||||
|
||||
void vxorpd(OperandX64 dst, OperandX64 src1, OperandX64 src2);
|
||||
|
||||
void vcomisd(OperandX64 src1, OperandX64 src2);
|
||||
void vucomisd(OperandX64 src1, OperandX64 src2);
|
||||
|
||||
void vcvttsd2si(OperandX64 dst, OperandX64 src);
|
||||
void vcvtsi2sd(OperandX64 dst, OperandX64 src1, OperandX64 src2);
|
||||
|
||||
void vroundsd(OperandX64 dst, OperandX64 src1, OperandX64 src2, uint8_t mode);
|
||||
void vroundsd(OperandX64 dst, OperandX64 src1, OperandX64 src2, RoundingModeX64 roundingMode); // inexact
|
||||
|
||||
void vsqrtpd(OperandX64 dst, OperandX64 src);
|
||||
void vsqrtps(OperandX64 dst, OperandX64 src);
|
||||
@ -120,6 +129,8 @@ public:
|
||||
OperandX64 f32x4(float x, float y, float z, float w);
|
||||
OperandX64 bytes(const void* ptr, size_t size, size_t align = 8);
|
||||
|
||||
void logAppend(const char* fmt, ...) LUAU_PRINTF_ATTR(2, 3);
|
||||
|
||||
// Resulting data and code that need to be copied over one after the other
|
||||
// The *end* of 'data' has to be aligned to 16 bytes, this will also align 'code'
|
||||
std::vector<uint8_t> data;
|
||||
@ -127,6 +138,8 @@ public:
|
||||
|
||||
std::string text;
|
||||
|
||||
const bool logText = false;
|
||||
|
||||
private:
|
||||
// Instruction archetypes
|
||||
void placeBinary(const char* name, OperandX64 lhs, OperandX64 rhs, uint8_t codeimm8, uint8_t codeimm, uint8_t codeimmImm8, uint8_t code8rev,
|
||||
@ -178,7 +191,6 @@ private:
|
||||
LUAU_NOINLINE void log(Label label);
|
||||
LUAU_NOINLINE void log(const char* opcode, Label label);
|
||||
void log(OperandX64 op);
|
||||
void logAppend(const char* fmt, ...);
|
||||
|
||||
const char* getSizeName(SizeX64 size);
|
||||
const char* getRegisterName(RegisterX64 reg);
|
||||
@ -187,7 +199,6 @@ private:
|
||||
std::vector<Label> pendingLabels;
|
||||
std::vector<uint32_t> labelLocations;
|
||||
|
||||
bool logText = false;
|
||||
bool finalized = false;
|
||||
|
||||
size_t dataPos = 0;
|
||||
|
@ -2,8 +2,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
24
CodeGen/include/Luau/CodeGen.h
Normal file
24
CodeGen/include/Luau/CodeGen.h
Normal file
@ -0,0 +1,24 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
struct lua_State;
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
namespace CodeGen
|
||||
{
|
||||
|
||||
bool isSupported();
|
||||
|
||||
void create(lua_State* L);
|
||||
|
||||
// Builds target function and all inner functions
|
||||
void compile(lua_State* L, int idx);
|
||||
|
||||
// Generates assembly text for target function and all inner functions
|
||||
std::string getAssemblyText(lua_State* L, int idx);
|
||||
|
||||
} // namespace CodeGen
|
||||
} // namespace Luau
|
@ -37,6 +37,9 @@ enum class Condition
|
||||
Zero,
|
||||
NotZero,
|
||||
|
||||
Parity,
|
||||
NotParity,
|
||||
|
||||
Count
|
||||
};
|
||||
|
||||
|
@ -14,9 +14,13 @@ namespace CodeGen
|
||||
// TODO: more assertions on operand sizes
|
||||
|
||||
const uint8_t codeForCondition[] = {
|
||||
0x0, 0x1, 0x2, 0x3, 0x2, 0x6, 0x7, 0x3, 0x4, 0xc, 0xe, 0xf, 0xd, 0x3, 0x7, 0x6, 0x2, 0x5, 0xd, 0xf, 0xe, 0xc, 0x4, 0x5};
|
||||
0x0, 0x1, 0x2, 0x3, 0x2, 0x6, 0x7, 0x3, 0x4, 0xc, 0xe, 0xf, 0xd, 0x3, 0x7, 0x6, 0x2, 0x5, 0xd, 0xf, 0xe, 0xc, 0x4, 0x5, 0xa, 0xb};
|
||||
static_assert(sizeof(codeForCondition) / sizeof(codeForCondition[0]) == size_t(Condition::Count), "all conditions have to be covered");
|
||||
|
||||
const char* textForCondition[] = {"jo", "jno", "jc", "jnc", "jb", "jbe", "ja", "jae", "je", "jl", "jle", "jg", "jge", "jnb", "jnbe", "jna", "jnae",
|
||||
"jne", "jnl", "jnle", "jng", "jnge", "jz", "jnz", "jp", "jnp"};
|
||||
static_assert(sizeof(textForCondition) / sizeof(textForCondition[0]) == size_t(Condition::Count), "all conditions have to be covered");
|
||||
|
||||
#define OP_PLUS_REG(op, reg) ((op) + (reg & 0x7))
|
||||
#define OP_PLUS_CC(op, cc) ((op) + uint8_t(cc))
|
||||
|
||||
@ -48,6 +52,8 @@ const unsigned AVX_F2 = 0b11;
|
||||
|
||||
const unsigned kMaxAlign = 16;
|
||||
|
||||
const uint8_t kRoundingPrecisionInexact = 0b1000;
|
||||
|
||||
AssemblyBuilderX64::AssemblyBuilderX64(bool logText)
|
||||
: logText(logText)
|
||||
{
|
||||
@ -255,6 +261,16 @@ void AssemblyBuilderX64::not_(OperandX64 op)
|
||||
placeUnaryModRegMem("not", op, 0xf6, 0xf7, 2);
|
||||
}
|
||||
|
||||
void AssemblyBuilderX64::dec(OperandX64 op)
|
||||
{
|
||||
placeUnaryModRegMem("dec", op, 0xfe, 0xff, 1);
|
||||
}
|
||||
|
||||
void AssemblyBuilderX64::inc(OperandX64 op)
|
||||
{
|
||||
placeUnaryModRegMem("inc", op, 0xfe, 0xff, 0);
|
||||
}
|
||||
|
||||
void AssemblyBuilderX64::imul(OperandX64 lhs, OperandX64 rhs)
|
||||
{
|
||||
if (logText)
|
||||
@ -338,7 +354,7 @@ void AssemblyBuilderX64::ret()
|
||||
|
||||
void AssemblyBuilderX64::jcc(Condition cond, Label& label)
|
||||
{
|
||||
placeJcc("je", label, codeForCondition[size_t(cond)]);
|
||||
placeJcc(textForCondition[size_t(cond)], label, codeForCondition[size_t(cond)]);
|
||||
}
|
||||
|
||||
void AssemblyBuilderX64::jmp(Label& label)
|
||||
@ -442,11 +458,6 @@ void AssemblyBuilderX64::vxorpd(OperandX64 dst, OperandX64 src1, OperandX64 src2
|
||||
placeAvx("vxorpd", dst, src1, src2, 0x57, false, AVX_0F, AVX_66);
|
||||
}
|
||||
|
||||
void AssemblyBuilderX64::vcomisd(OperandX64 src1, OperandX64 src2)
|
||||
{
|
||||
placeAvx("vcomisd", src1, src2, 0x2f, false, AVX_0F, AVX_66);
|
||||
}
|
||||
|
||||
void AssemblyBuilderX64::vucomisd(OperandX64 src1, OperandX64 src2)
|
||||
{
|
||||
placeAvx("vucomisd", src1, src2, 0x2e, false, AVX_0F, AVX_66);
|
||||
@ -462,9 +473,9 @@ void AssemblyBuilderX64::vcvtsi2sd(OperandX64 dst, OperandX64 src1, OperandX64 s
|
||||
placeAvx("vcvtsi2sd", dst, src1, src2, 0x2a, (src2.cat == CategoryX64::reg ? src2.base.size : src2.memSize) == SizeX64::dword, AVX_0F, AVX_F2);
|
||||
}
|
||||
|
||||
void AssemblyBuilderX64::vroundsd(OperandX64 dst, OperandX64 src1, OperandX64 src2, uint8_t mode)
|
||||
void AssemblyBuilderX64::vroundsd(OperandX64 dst, OperandX64 src1, OperandX64 src2, RoundingModeX64 roundingMode)
|
||||
{
|
||||
placeAvx("vroundsd", dst, src1, src2, mode, 0x0b, false, AVX_0F3A, AVX_66);
|
||||
placeAvx("vroundsd", dst, src1, src2, uint8_t(roundingMode) | kRoundingPrecisionInexact, 0x0b, false, AVX_0F3A, AVX_66);
|
||||
}
|
||||
|
||||
void AssemblyBuilderX64::vsqrtpd(OperandX64 dst, OperandX64 src)
|
||||
@ -534,6 +545,8 @@ void AssemblyBuilderX64::finalize()
|
||||
// Resolve jump targets
|
||||
for (Label fixup : pendingLabels)
|
||||
{
|
||||
// If this assertion fires, a label was used in jmp without calling setLabel
|
||||
LUAU_ASSERT(labelLocations[fixup.id - 1] != ~0u);
|
||||
uint32_t value = labelLocations[fixup.id - 1] - (fixup.location + 4);
|
||||
writeu32(&code[fixup.location], value);
|
||||
}
|
||||
@ -552,7 +565,7 @@ void AssemblyBuilderX64::finalize()
|
||||
Label AssemblyBuilderX64::setLabel()
|
||||
{
|
||||
Label label{nextLabel++, getCodeSize()};
|
||||
labelLocations.push_back(0);
|
||||
labelLocations.push_back(~0u);
|
||||
|
||||
if (logText)
|
||||
log(label);
|
||||
@ -565,7 +578,7 @@ void AssemblyBuilderX64::setLabel(Label& label)
|
||||
if (label.id == 0)
|
||||
{
|
||||
label.id = nextLabel++;
|
||||
labelLocations.push_back(0);
|
||||
labelLocations.push_back(~0u);
|
||||
}
|
||||
|
||||
label.location = getCodeSize();
|
||||
@ -1019,7 +1032,7 @@ void AssemblyBuilderX64::placeLabel(Label& label)
|
||||
if (label.id == 0)
|
||||
{
|
||||
label.id = nextLabel++;
|
||||
labelLocations.push_back(0);
|
||||
labelLocations.push_back(~0u);
|
||||
}
|
||||
|
||||
pendingLabels.push_back({label.id, getCodeSize()});
|
||||
|
449
CodeGen/src/CodeGen.cpp
Normal file
449
CodeGen/src/CodeGen.cpp
Normal file
@ -0,0 +1,449 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/CodeGen.h"
|
||||
|
||||
#include "Luau/AssemblyBuilderX64.h"
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/CodeAllocator.h"
|
||||
#include "Luau/CodeBlockUnwind.h"
|
||||
#include "Luau/UnwindBuilder.h"
|
||||
#include "Luau/UnwindBuilderDwarf2.h"
|
||||
#include "Luau/UnwindBuilderWin.h"
|
||||
|
||||
#include "CustomExecUtils.h"
|
||||
#include "CodeGenX64.h"
|
||||
#include "EmitCommonX64.h"
|
||||
#include "EmitInstructionX64.h"
|
||||
#include "NativeState.h"
|
||||
|
||||
#include "lapi.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#if defined(__x86_64__) || defined(_M_X64)
|
||||
#ifdef _MSC_VER
|
||||
#include <intrin.h> // __cpuid
|
||||
#else
|
||||
#include <cpuid.h> // __cpuid
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
namespace CodeGen
|
||||
{
|
||||
|
||||
static NativeProto* assembleFunction(AssemblyBuilderX64& build, NativeState& data, Proto* proto)
|
||||
{
|
||||
NativeProto* result = new NativeProto();
|
||||
|
||||
result->proto = proto;
|
||||
|
||||
if (build.logText)
|
||||
{
|
||||
if (proto->debugname)
|
||||
build.logAppend("; function %s()", getstr(proto->debugname));
|
||||
else
|
||||
build.logAppend("; function()");
|
||||
|
||||
if (proto->linedefined >= 0)
|
||||
build.logAppend(" line %d\n", proto->linedefined);
|
||||
else
|
||||
build.logAppend("\n");
|
||||
}
|
||||
|
||||
std::vector<Label> instLabels;
|
||||
instLabels.resize(proto->sizecode);
|
||||
|
||||
Label start = build.setLabel();
|
||||
|
||||
for (int i = 0; i < proto->sizecode;)
|
||||
{
|
||||
const Instruction* pc = &proto->code[i];
|
||||
LuauOpcode op = LuauOpcode(LUAU_INSN_OP(*pc));
|
||||
|
||||
build.setLabel(instLabels[i]);
|
||||
|
||||
if (build.logText)
|
||||
build.logAppend("; #%d: %s\n", i, data.names[op]);
|
||||
|
||||
switch (op)
|
||||
{
|
||||
case LOP_NOP:
|
||||
break;
|
||||
case LOP_LOADNIL:
|
||||
emitInstLoadNil(build, data, pc);
|
||||
break;
|
||||
case LOP_LOADB:
|
||||
emitInstLoadB(build, data, pc, i, instLabels.data());
|
||||
break;
|
||||
case LOP_LOADN:
|
||||
emitInstLoadN(build, data, pc);
|
||||
break;
|
||||
case LOP_LOADK:
|
||||
emitInstLoadK(build, data, pc, proto->k);
|
||||
break;
|
||||
case LOP_MOVE:
|
||||
emitInstMove(build, data, pc);
|
||||
break;
|
||||
case LOP_GETTABLE:
|
||||
emitInstGetTable(build, pc, i);
|
||||
break;
|
||||
case LOP_SETTABLE:
|
||||
emitInstSetTable(build, pc, i);
|
||||
break;
|
||||
case LOP_GETTABLEN:
|
||||
emitInstGetTableN(build, pc, i);
|
||||
break;
|
||||
case LOP_SETTABLEN:
|
||||
emitInstSetTableN(build, pc, i);
|
||||
break;
|
||||
case LOP_JUMP:
|
||||
emitInstJump(build, data, pc, i, instLabels.data());
|
||||
break;
|
||||
case LOP_JUMPBACK:
|
||||
emitInstJumpBack(build, data, pc, i, instLabels.data());
|
||||
break;
|
||||
case LOP_JUMPIF:
|
||||
emitInstJumpIf(build, data, pc, i, instLabels.data(), /* not_ */ false);
|
||||
break;
|
||||
case LOP_JUMPIFNOT:
|
||||
emitInstJumpIf(build, data, pc, i, instLabels.data(), /* not_ */ true);
|
||||
break;
|
||||
case LOP_JUMPIFEQ:
|
||||
emitInstJumpIfEq(build, data, pc, i, instLabels.data(), /* not_ */ false);
|
||||
break;
|
||||
case LOP_JUMPIFLE:
|
||||
emitInstJumpIfCond(build, data, pc, i, instLabels.data(), Condition::LessEqual);
|
||||
break;
|
||||
case LOP_JUMPIFLT:
|
||||
emitInstJumpIfCond(build, data, pc, i, instLabels.data(), Condition::Less);
|
||||
break;
|
||||
case LOP_JUMPIFNOTEQ:
|
||||
emitInstJumpIfEq(build, data, pc, i, instLabels.data(), /* not_ */ true);
|
||||
break;
|
||||
case LOP_JUMPIFNOTLE:
|
||||
emitInstJumpIfCond(build, data, pc, i, instLabels.data(), Condition::NotLessEqual);
|
||||
break;
|
||||
case LOP_JUMPIFNOTLT:
|
||||
emitInstJumpIfCond(build, data, pc, i, instLabels.data(), Condition::NotLess);
|
||||
break;
|
||||
case LOP_JUMPX:
|
||||
emitInstJumpX(build, data, pc, i, instLabels.data());
|
||||
break;
|
||||
case LOP_JUMPXEQKNIL:
|
||||
emitInstJumpxEqNil(build, data, pc, proto->k, i, instLabels.data());
|
||||
break;
|
||||
case LOP_JUMPXEQKB:
|
||||
emitInstJumpxEqB(build, data, pc, proto->k, i, instLabels.data());
|
||||
break;
|
||||
case LOP_JUMPXEQKN:
|
||||
emitInstJumpxEqN(build, data, pc, proto->k, i, instLabels.data());
|
||||
break;
|
||||
case LOP_JUMPXEQKS:
|
||||
emitInstJumpxEqS(build, data, pc, proto->k, i, instLabels.data());
|
||||
break;
|
||||
case LOP_ADD:
|
||||
emitInstAdd(build, pc, i);
|
||||
break;
|
||||
case LOP_SUB:
|
||||
emitInstSub(build, pc, i);
|
||||
break;
|
||||
case LOP_MUL:
|
||||
emitInstMul(build, pc, i);
|
||||
break;
|
||||
case LOP_DIV:
|
||||
emitInstDiv(build, pc, i);
|
||||
break;
|
||||
case LOP_MOD:
|
||||
emitInstMod(build, pc, i);
|
||||
break;
|
||||
case LOP_POW:
|
||||
emitInstPow(build, pc, i);
|
||||
break;
|
||||
case LOP_ADDK:
|
||||
emitInstAddK(build, pc, proto->k, i);
|
||||
break;
|
||||
case LOP_SUBK:
|
||||
emitInstSubK(build, pc, proto->k, i);
|
||||
break;
|
||||
case LOP_MULK:
|
||||
emitInstMulK(build, pc, proto->k, i);
|
||||
break;
|
||||
case LOP_DIVK:
|
||||
emitInstDivK(build, pc, proto->k, i);
|
||||
break;
|
||||
case LOP_MODK:
|
||||
emitInstModK(build, pc, proto->k, i);
|
||||
break;
|
||||
case LOP_POWK:
|
||||
emitInstPowK(build, pc, proto->k, i);
|
||||
break;
|
||||
case LOP_NOT:
|
||||
emitInstNot(build, pc);
|
||||
break;
|
||||
case LOP_MINUS:
|
||||
emitInstMinus(build, pc, i);
|
||||
break;
|
||||
case LOP_LENGTH:
|
||||
emitInstLength(build, pc, i);
|
||||
break;
|
||||
case LOP_GETUPVAL:
|
||||
emitInstGetUpval(build, pc, i);
|
||||
break;
|
||||
case LOP_FASTCALL:
|
||||
emitInstFastCall(build, pc, i, instLabels.data());
|
||||
break;
|
||||
case LOP_FASTCALL1:
|
||||
emitInstFastCall1(build, pc, i, instLabels.data());
|
||||
break;
|
||||
case LOP_FASTCALL2:
|
||||
emitInstFastCall2(build, pc, i, instLabels.data());
|
||||
break;
|
||||
case LOP_FASTCALL2K:
|
||||
emitInstFastCall2K(build, pc, proto->k, i, instLabels.data());
|
||||
break;
|
||||
case LOP_FORNPREP:
|
||||
emitInstForNPrep(build, pc, i, instLabels.data());
|
||||
break;
|
||||
case LOP_FORNLOOP:
|
||||
emitInstForNLoop(build, pc, i, instLabels.data());
|
||||
break;
|
||||
case LOP_AND:
|
||||
emitInstAnd(build, pc);
|
||||
break;
|
||||
case LOP_ANDK:
|
||||
emitInstAndK(build, pc);
|
||||
break;
|
||||
case LOP_OR:
|
||||
emitInstOr(build, pc);
|
||||
break;
|
||||
case LOP_ORK:
|
||||
emitInstOrK(build, pc);
|
||||
break;
|
||||
default:
|
||||
emitFallback(build, data, op, i);
|
||||
break;
|
||||
}
|
||||
|
||||
i += getOpLength(op);
|
||||
LUAU_ASSERT(i <= proto->sizecode);
|
||||
}
|
||||
|
||||
result->instTargets = new uintptr_t[proto->sizecode];
|
||||
|
||||
for (int i = 0; i < proto->sizecode; i++)
|
||||
result->instTargets[i] = instLabels[i].location - start.location;
|
||||
|
||||
result->location = start.location;
|
||||
|
||||
if (build.logText)
|
||||
build.logAppend("\n");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void destroyNativeProto(NativeProto* nativeProto)
|
||||
{
|
||||
delete[] nativeProto->instTargets;
|
||||
delete nativeProto;
|
||||
}
|
||||
|
||||
static void onCloseState(lua_State* L)
|
||||
{
|
||||
destroyNativeState(L);
|
||||
}
|
||||
|
||||
static void onDestroyFunction(lua_State* L, Proto* proto)
|
||||
{
|
||||
NativeProto* nativeProto = getProtoExecData(proto);
|
||||
LUAU_ASSERT(nativeProto->proto == proto);
|
||||
|
||||
setProtoExecData(proto, nullptr);
|
||||
destroyNativeProto(nativeProto);
|
||||
}
|
||||
|
||||
static int onEnter(lua_State* L, Proto* proto)
|
||||
{
|
||||
if (L->singlestep)
|
||||
return 1;
|
||||
|
||||
NativeState* data = getNativeState(L);
|
||||
|
||||
if (!L->ci->savedpc)
|
||||
L->ci->savedpc = proto->code;
|
||||
|
||||
// We will jump into native code through a gateway
|
||||
bool (*gate)(lua_State*, Proto*, uintptr_t, NativeContext*) = (bool (*)(lua_State*, Proto*, uintptr_t, NativeContext*))data->context.gateEntry;
|
||||
|
||||
NativeProto* nativeProto = getProtoExecData(proto);
|
||||
uintptr_t target = nativeProto->instTargets[L->ci->savedpc - proto->code];
|
||||
|
||||
// Returns 1 to finish the function in the VM
|
||||
return gate(L, proto, target, &data->context);
|
||||
}
|
||||
|
||||
static void onSetBreakpoint(lua_State* L, Proto* proto, int instruction)
|
||||
{
|
||||
if (!getProtoExecData(proto))
|
||||
return;
|
||||
|
||||
LUAU_ASSERT(!"native breakpoints are not implemented");
|
||||
}
|
||||
|
||||
bool isSupported()
|
||||
{
|
||||
#if !LUA_CUSTOM_EXECUTION
|
||||
return false;
|
||||
#elif defined(__x86_64__) || defined(_M_X64)
|
||||
if (LUA_EXTRA_SIZE != 1)
|
||||
return false;
|
||||
|
||||
if (sizeof(TValue) != 16)
|
||||
return false;
|
||||
|
||||
if (sizeof(LuaNode) != 32)
|
||||
return false;
|
||||
|
||||
int cpuinfo[4] = {};
|
||||
#ifdef _MSC_VER
|
||||
__cpuid(cpuinfo, 1);
|
||||
#else
|
||||
__cpuid(1, cpuinfo[0], cpuinfo[1], cpuinfo[2], cpuinfo[3]);
|
||||
#endif
|
||||
|
||||
// We require AVX1 support for VEX encoded XMM operations
|
||||
// We also requre SSE4.1 support for ROUNDSD but the AVX check below covers it
|
||||
// https://en.wikipedia.org/wiki/CPUID#EAX=1:_Processor_Info_and_Feature_Bits
|
||||
if ((cpuinfo[2] & (1 << 28)) == 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void create(lua_State* L)
|
||||
{
|
||||
LUAU_ASSERT(isSupported());
|
||||
|
||||
NativeState& data = *createNativeState(L);
|
||||
|
||||
#if defined(_WIN32)
|
||||
data.unwindBuilder = std::make_unique<UnwindBuilderWin>();
|
||||
#else
|
||||
data.unwindBuilder = std::make_unique<UnwindBuilderDwarf2>();
|
||||
#endif
|
||||
|
||||
data.codeAllocator.context = data.unwindBuilder.get();
|
||||
data.codeAllocator.createBlockUnwindInfo = createBlockUnwindInfo;
|
||||
data.codeAllocator.destroyBlockUnwindInfo = destroyBlockUnwindInfo;
|
||||
|
||||
initFallbackTable(data);
|
||||
initHelperFunctions(data);
|
||||
|
||||
if (!x64::initEntryFunction(data))
|
||||
{
|
||||
destroyNativeState(L);
|
||||
return;
|
||||
}
|
||||
|
||||
lua_ExecutionCallbacks* ecb = getExecutionCallbacks(L);
|
||||
|
||||
ecb->close = onCloseState;
|
||||
ecb->destroy = onDestroyFunction;
|
||||
ecb->enter = onEnter;
|
||||
ecb->setbreakpoint = onSetBreakpoint;
|
||||
}
|
||||
|
||||
static void gatherFunctions(std::vector<Proto*>& results, Proto* proto)
|
||||
{
|
||||
if (results.size() <= size_t(proto->bytecodeid))
|
||||
results.resize(proto->bytecodeid + 1);
|
||||
|
||||
// Skip protos that we've already compiled in this run: this happens because at -O2, inlined functions get their protos reused
|
||||
if (results[proto->bytecodeid])
|
||||
return;
|
||||
|
||||
results[proto->bytecodeid] = proto;
|
||||
|
||||
for (int i = 0; i < proto->sizep; i++)
|
||||
gatherFunctions(results, proto->p[i]);
|
||||
}
|
||||
|
||||
void compile(lua_State* L, int idx)
|
||||
{
|
||||
LUAU_ASSERT(lua_isLfunction(L, idx));
|
||||
const TValue* func = luaA_toobject(L, idx);
|
||||
|
||||
// If initialization has failed, do not compile any functions
|
||||
if (!getNativeState(L))
|
||||
return;
|
||||
|
||||
AssemblyBuilderX64 build(/* logText= */ false);
|
||||
NativeState* data = getNativeState(L);
|
||||
|
||||
std::vector<Proto*> protos;
|
||||
gatherFunctions(protos, clvalue(func)->l.p);
|
||||
|
||||
std::vector<NativeProto*> results;
|
||||
results.reserve(protos.size());
|
||||
|
||||
// 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, p));
|
||||
|
||||
build.finalize();
|
||||
|
||||
uint8_t* nativeData = nullptr;
|
||||
size_t sizeNativeData = 0;
|
||||
uint8_t* codeStart = nullptr;
|
||||
if (!data->codeAllocator.allocate(
|
||||
build.data.data(), int(build.data.size()), build.code.data(), int(build.code.size()), nativeData, sizeNativeData, codeStart))
|
||||
{
|
||||
for (NativeProto* result : results)
|
||||
destroyNativeProto(result);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Relocate instruction offsets
|
||||
for (NativeProto* result : results)
|
||||
{
|
||||
for (int i = 0; i < result->proto->sizecode; i++)
|
||||
result->instTargets[i] += uintptr_t(codeStart + result->location);
|
||||
}
|
||||
|
||||
// Link native proto objects to Proto; the memory is now managed by VM and will be freed via onDestroyFunction
|
||||
for (NativeProto* result : results)
|
||||
setProtoExecData(result->proto, result);
|
||||
}
|
||||
|
||||
std::string getAssemblyText(lua_State* L, int idx)
|
||||
{
|
||||
LUAU_ASSERT(lua_isLfunction(L, idx));
|
||||
const TValue* func = luaA_toobject(L, idx);
|
||||
|
||||
AssemblyBuilderX64 build(/* logText= */ true);
|
||||
NativeState data;
|
||||
initFallbackTable(data);
|
||||
initInstructionNames(data);
|
||||
|
||||
std::vector<Proto*> protos;
|
||||
gatherFunctions(protos, clvalue(func)->l.p);
|
||||
|
||||
for (Proto* p : protos)
|
||||
if (p)
|
||||
{
|
||||
NativeProto* nativeProto = assembleFunction(build, data, p);
|
||||
destroyNativeProto(nativeProto);
|
||||
}
|
||||
|
||||
build.finalize();
|
||||
|
||||
return build.text;
|
||||
}
|
||||
|
||||
} // namespace CodeGen
|
||||
} // namespace Luau
|
154
CodeGen/src/CodeGenX64.cpp
Normal file
154
CodeGen/src/CodeGenX64.cpp
Normal file
@ -0,0 +1,154 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "CodeGenX64.h"
|
||||
|
||||
#include "Luau/AssemblyBuilderX64.h"
|
||||
#include "Luau/UnwindBuilder.h"
|
||||
|
||||
#include "CustomExecUtils.h"
|
||||
#include "NativeState.h"
|
||||
#include "EmitCommonX64.h"
|
||||
|
||||
#include "lstate.h"
|
||||
|
||||
/* An overview of native environment stack setup that we are making in the entry function:
|
||||
* Each line is 8 bytes, stack grows downwards.
|
||||
*
|
||||
* | ... previous frames ...
|
||||
* | rdx home space | (saved only on windows)
|
||||
* | rcx home space | (saved only on windows)
|
||||
* | return address |
|
||||
* | ... saved non-volatile registers ...
|
||||
* | unused | for 16 byte alignment of the stack
|
||||
* | sCode |
|
||||
* | sClosure | <-- rbp points here
|
||||
* | argument 6 |
|
||||
* | argument 5 |
|
||||
* | r9 home space |
|
||||
* | r8 home space |
|
||||
* | rdx home space |
|
||||
* | rcx home space | <-- rsp points here
|
||||
*
|
||||
* Arguments to our entry function are saved to home space only on Windows.
|
||||
* Space for arguments to function we call is always reserved, but used only on Windows.
|
||||
*
|
||||
* Right now we use a frame pointer, but because of a fixed layout we can omit it in the future
|
||||
*/
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
namespace CodeGen
|
||||
{
|
||||
namespace x64
|
||||
{
|
||||
|
||||
bool initEntryFunction(NativeState& data)
|
||||
{
|
||||
AssemblyBuilderX64 build(/* logText= */ false);
|
||||
UnwindBuilder& unwind = *data.unwindBuilder.get();
|
||||
|
||||
unwind.start();
|
||||
|
||||
if (getCurrentX64ABI() == X64ABI::Windows)
|
||||
{
|
||||
// Place arguments in home space
|
||||
build.mov(qword[rsp + 16], rArg2);
|
||||
unwind.spill(16, rArg2);
|
||||
build.mov(qword[rsp + 8], rArg1);
|
||||
unwind.spill(8, rArg1);
|
||||
|
||||
// Save non-volatile registers that are specific to Windows x64 ABI
|
||||
build.push(rdi);
|
||||
unwind.save(rdi);
|
||||
build.push(rsi);
|
||||
unwind.save(rsi);
|
||||
|
||||
// Once we start using non-volatile SIMD registers, we will save those here
|
||||
}
|
||||
|
||||
// Save common non-volatile registers
|
||||
build.push(rbx);
|
||||
unwind.save(rbx);
|
||||
build.push(rbp);
|
||||
unwind.save(rbp);
|
||||
build.push(r12);
|
||||
unwind.save(r12);
|
||||
build.push(r13);
|
||||
unwind.save(r13);
|
||||
build.push(r14);
|
||||
unwind.save(r14);
|
||||
build.push(r15);
|
||||
unwind.save(r15);
|
||||
|
||||
int stacksize = 32 + 16; // 4 home locations for registers, 16 bytes for additional function call arguments
|
||||
int localssize = 24; // 3 local pointers that also correctly align the stack
|
||||
|
||||
// Allocate stack space (reg home area + local data)
|
||||
build.sub(rsp, stacksize + localssize);
|
||||
unwind.allocStack(stacksize + localssize);
|
||||
|
||||
// Setup frame pointer
|
||||
build.lea(rbp, qword[rsp + stacksize]);
|
||||
unwind.setupFrameReg(rbp, stacksize);
|
||||
|
||||
unwind.finish();
|
||||
|
||||
size_t prologueSize = build.setLabel().location;
|
||||
|
||||
// Setup native execution environment
|
||||
build.mov(rState, rArg1);
|
||||
build.mov(rNativeContext, rArg4);
|
||||
build.mov(rBase, qword[rState + offsetof(lua_State, base)]); // L->base
|
||||
build.mov(rax, qword[rState + offsetof(lua_State, ci)]); // L->ci
|
||||
build.mov(rax, qword[rax + offsetof(CallInfo, func)]); // L->ci->func
|
||||
build.mov(rax, qword[rax + offsetof(TValue, value.gc)]); // L->ci->func->value.gc aka cl
|
||||
build.mov(sClosure, rax);
|
||||
build.mov(rConstants, qword[rArg2 + offsetof(Proto, k)]); // proto->k
|
||||
build.mov(rax, qword[rArg2 + offsetof(Proto, code)]); // proto->code
|
||||
build.mov(sCode, rax);
|
||||
|
||||
// Jump to the specified instruction; further control flow will be handled with custom ABI with register setup from EmitCommonX64.h
|
||||
build.jmp(rArg3);
|
||||
|
||||
// Even though we jumped away, we will return here in the end
|
||||
Label returnOff = build.setLabel();
|
||||
|
||||
// Cleanup and exit
|
||||
build.lea(rsp, qword[rbp + localssize]);
|
||||
build.pop(r15);
|
||||
build.pop(r14);
|
||||
build.pop(r13);
|
||||
build.pop(r12);
|
||||
build.pop(rbp);
|
||||
build.pop(rbx);
|
||||
|
||||
if (getCurrentX64ABI() == X64ABI::Windows)
|
||||
{
|
||||
build.pop(rsi);
|
||||
build.pop(rdi);
|
||||
}
|
||||
|
||||
build.ret();
|
||||
|
||||
build.finalize();
|
||||
|
||||
LUAU_ASSERT(build.data.empty());
|
||||
|
||||
if (!data.codeAllocator.allocate(build.data.data(), int(build.data.size()), build.code.data(), int(build.code.size()), data.gateData,
|
||||
data.gateDataSize, data.context.gateEntry))
|
||||
{
|
||||
LUAU_ASSERT(!"failed to create entry function");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set the offset at the begining so that functions in new blocks will not overlay the locations
|
||||
// specified by the unwind information of the entry function
|
||||
unwind.setBeginOffset(prologueSize);
|
||||
|
||||
data.context.gateExit = data.context.gateEntry + returnOff.location;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace x64
|
||||
} // namespace CodeGen
|
||||
} // namespace Luau
|
18
CodeGen/src/CodeGenX64.h
Normal file
18
CodeGen/src/CodeGenX64.h
Normal file
@ -0,0 +1,18 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
namespace CodeGen
|
||||
{
|
||||
|
||||
struct NativeState;
|
||||
|
||||
namespace x64
|
||||
{
|
||||
|
||||
bool initEntryFunction(NativeState& data);
|
||||
|
||||
} // namespace x64
|
||||
} // namespace CodeGen
|
||||
} // namespace Luau
|
145
CodeGen/src/CustomExecUtils.h
Normal file
145
CodeGen/src/CustomExecUtils.h
Normal file
@ -0,0 +1,145 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "NativeState.h"
|
||||
|
||||
#include "lobject.h"
|
||||
#include "lstate.h"
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
namespace CodeGen
|
||||
{
|
||||
|
||||
// Here we define helper functions to wrap interaction with Luau custom execution API so that it works with or without LUA_CUSTOM_EXECUTION
|
||||
|
||||
#if LUA_CUSTOM_EXECUTION
|
||||
|
||||
inline lua_ExecutionCallbacks* getExecutionCallbacks(lua_State* L)
|
||||
{
|
||||
return &L->global->ecb;
|
||||
}
|
||||
|
||||
inline NativeState* getNativeState(lua_State* L)
|
||||
{
|
||||
lua_ExecutionCallbacks* ecb = getExecutionCallbacks(L);
|
||||
return (NativeState*)ecb->context;
|
||||
}
|
||||
|
||||
inline void setNativeState(lua_State* L, NativeState* nativeState)
|
||||
{
|
||||
lua_ExecutionCallbacks* ecb = getExecutionCallbacks(L);
|
||||
ecb->context = nativeState;
|
||||
}
|
||||
|
||||
inline NativeState* createNativeState(lua_State* L)
|
||||
{
|
||||
NativeState* state = new NativeState();
|
||||
setNativeState(L, state);
|
||||
return state;
|
||||
}
|
||||
|
||||
inline void destroyNativeState(lua_State* L)
|
||||
{
|
||||
NativeState* state = getNativeState(L);
|
||||
setNativeState(L, nullptr);
|
||||
delete state;
|
||||
}
|
||||
|
||||
inline NativeProto* getProtoExecData(Proto* proto)
|
||||
{
|
||||
return (NativeProto*)proto->execdata;
|
||||
}
|
||||
|
||||
inline void setProtoExecData(Proto* proto, NativeProto* nativeProto)
|
||||
{
|
||||
if (nativeProto)
|
||||
LUAU_ASSERT(proto->execdata == nullptr);
|
||||
|
||||
proto->execdata = nativeProto;
|
||||
}
|
||||
|
||||
#define offsetofProtoExecData offsetof(Proto, execdata)
|
||||
|
||||
#else
|
||||
|
||||
inline lua_ExecutionCallbacks* getExecutionCallbacks(lua_State* L)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
inline NativeState* getNativeState(lua_State* L)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
inline void setNativeState(lua_State* L, NativeState* nativeState) {}
|
||||
|
||||
inline NativeState* createNativeState(lua_State* L)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
inline void destroyNativeState(lua_State* L) {}
|
||||
|
||||
inline NativeProto* getProtoExecData(Proto* proto)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
inline void setProtoExecData(Proto* proto, NativeProto* nativeProto) {}
|
||||
|
||||
#define offsetofProtoExecData 0
|
||||
|
||||
#endif
|
||||
|
||||
inline int getOpLength(LuauOpcode op)
|
||||
{
|
||||
switch (op)
|
||||
{
|
||||
case LOP_GETGLOBAL:
|
||||
case LOP_SETGLOBAL:
|
||||
case LOP_GETIMPORT:
|
||||
case LOP_GETTABLEKS:
|
||||
case LOP_SETTABLEKS:
|
||||
case LOP_NAMECALL:
|
||||
case LOP_JUMPIFEQ:
|
||||
case LOP_JUMPIFLE:
|
||||
case LOP_JUMPIFLT:
|
||||
case LOP_JUMPIFNOTEQ:
|
||||
case LOP_JUMPIFNOTLE:
|
||||
case LOP_JUMPIFNOTLT:
|
||||
case LOP_NEWTABLE:
|
||||
case LOP_SETLIST:
|
||||
case LOP_FORGLOOP:
|
||||
case LOP_LOADKX:
|
||||
case LOP_FASTCALL2:
|
||||
case LOP_FASTCALL2K:
|
||||
case LOP_JUMPXEQKNIL:
|
||||
case LOP_JUMPXEQKB:
|
||||
case LOP_JUMPXEQKN:
|
||||
case LOP_JUMPXEQKS:
|
||||
return 2;
|
||||
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
enum class X64ABI
|
||||
{
|
||||
Windows,
|
||||
SystemV,
|
||||
};
|
||||
|
||||
inline X64ABI getCurrentX64ABI()
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
return X64ABI::Windows;
|
||||
#else
|
||||
return X64ABI::SystemV;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace CodeGen
|
||||
} // namespace Luau
|
109
CodeGen/src/EmitBuiltinsX64.cpp
Normal file
109
CodeGen/src/EmitBuiltinsX64.cpp
Normal file
@ -0,0 +1,109 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "EmitBuiltinsX64.h"
|
||||
|
||||
#include "Luau/AssemblyBuilderX64.h"
|
||||
#include "Luau/Bytecode.h"
|
||||
|
||||
#include "EmitCommonX64.h"
|
||||
|
||||
#include "lstate.h"
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
namespace CodeGen
|
||||
{
|
||||
|
||||
BuiltinImplResult emitBuiltinAssert(AssemblyBuilderX64& build, int nparams, int ra, int arg, OperandX64 args, int nresults, Label& fallback)
|
||||
{
|
||||
if (nparams < 1 || nresults != 0)
|
||||
return {BuiltinImplType::None, -1};
|
||||
|
||||
if (build.logText)
|
||||
build.logAppend("; inlined LBF_ASSERT\n");
|
||||
|
||||
Label skip;
|
||||
|
||||
jumpIfFalsy(build, arg, fallback, skip);
|
||||
|
||||
// TODO: use of 'skip' causes a jump to a jump instruction that skips the fallback - can be optimized
|
||||
build.setLabel(skip);
|
||||
|
||||
return {BuiltinImplType::UsesFallback, 0};
|
||||
}
|
||||
|
||||
BuiltinImplResult emitBuiltinMathFloor(AssemblyBuilderX64& build, int nparams, int ra, int arg, OperandX64 args, int nresults, Label& fallback)
|
||||
{
|
||||
if (nparams < 1 || nresults > 1)
|
||||
return {BuiltinImplType::None, -1};
|
||||
|
||||
if (build.logText)
|
||||
build.logAppend("; inlined LBF_MATH_FLOOR\n");
|
||||
|
||||
jumpIfTagIsNot(build, arg, LUA_TNUMBER, fallback);
|
||||
|
||||
build.vroundsd(xmm0, xmm0, luauRegValue(arg), RoundingModeX64::RoundToNegativeInfinity);
|
||||
build.vmovsd(luauRegValue(ra), xmm0);
|
||||
|
||||
if (ra != arg)
|
||||
build.mov(luauRegTag(ra), LUA_TNUMBER);
|
||||
|
||||
return {BuiltinImplType::UsesFallback, 1};
|
||||
}
|
||||
|
||||
BuiltinImplResult emitBuiltinMathCeil(AssemblyBuilderX64& build, int nparams, int ra, int arg, OperandX64 args, int nresults, Label& fallback)
|
||||
{
|
||||
if (nparams < 1 || nresults > 1)
|
||||
return {BuiltinImplType::None, -1};
|
||||
|
||||
if (build.logText)
|
||||
build.logAppend("; inlined LBF_MATH_CEIL\n");
|
||||
|
||||
jumpIfTagIsNot(build, arg, LUA_TNUMBER, fallback);
|
||||
|
||||
build.vroundsd(xmm0, xmm0, luauRegValue(arg), RoundingModeX64::RoundToPositiveInfinity);
|
||||
build.vmovsd(luauRegValue(ra), xmm0);
|
||||
|
||||
if (ra != arg)
|
||||
build.mov(luauRegTag(ra), LUA_TNUMBER);
|
||||
|
||||
return {BuiltinImplType::UsesFallback, 1};
|
||||
}
|
||||
|
||||
BuiltinImplResult emitBuiltinMathSqrt(AssemblyBuilderX64& build, int nparams, int ra, int arg, OperandX64 args, int nresults, Label& fallback)
|
||||
{
|
||||
if (nparams < 1 || nresults > 1)
|
||||
return {BuiltinImplType::None, -1};
|
||||
|
||||
if (build.logText)
|
||||
build.logAppend("; inlined LBF_MATH_SQRT\n");
|
||||
|
||||
jumpIfTagIsNot(build, arg, LUA_TNUMBER, fallback);
|
||||
|
||||
build.vsqrtsd(xmm0, xmm0, luauRegValue(arg));
|
||||
build.vmovsd(luauRegValue(ra), xmm0);
|
||||
|
||||
if (ra != arg)
|
||||
build.mov(luauRegTag(ra), LUA_TNUMBER);
|
||||
|
||||
return {BuiltinImplType::UsesFallback, 1};
|
||||
}
|
||||
|
||||
BuiltinImplResult emitBuiltin(AssemblyBuilderX64& build, int bfid, int nparams, int ra, int arg, OperandX64 args, int nresults, Label& fallback)
|
||||
{
|
||||
switch (bfid)
|
||||
{
|
||||
case LBF_ASSERT:
|
||||
return emitBuiltinAssert(build, nparams, ra, arg, args, nresults, fallback);
|
||||
case LBF_MATH_FLOOR:
|
||||
return emitBuiltinMathFloor(build, nparams, ra, arg, args, nresults, fallback);
|
||||
case LBF_MATH_CEIL:
|
||||
return emitBuiltinMathCeil(build, nparams, ra, arg, args, nresults, fallback);
|
||||
case LBF_MATH_SQRT:
|
||||
return emitBuiltinMathSqrt(build, nparams, ra, arg, args, nresults, fallback);
|
||||
default:
|
||||
return {BuiltinImplType::None, -1};
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace CodeGen
|
||||
} // namespace Luau
|
28
CodeGen/src/EmitBuiltinsX64.h
Normal file
28
CodeGen/src/EmitBuiltinsX64.h
Normal file
@ -0,0 +1,28 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
namespace CodeGen
|
||||
{
|
||||
|
||||
class AssemblyBuilderX64;
|
||||
struct Label;
|
||||
struct OperandX64;
|
||||
|
||||
enum class BuiltinImplType
|
||||
{
|
||||
None,
|
||||
UsesFallback, // Uses fallback for unsupported cases
|
||||
};
|
||||
|
||||
struct BuiltinImplResult
|
||||
{
|
||||
BuiltinImplType type;
|
||||
int actualResultCount;
|
||||
};
|
||||
|
||||
BuiltinImplResult emitBuiltin(AssemblyBuilderX64& build, int bfid, int nparams, int ra, int arg, OperandX64 args, int nresults, Label& fallback);
|
||||
|
||||
} // namespace CodeGen
|
||||
} // namespace Luau
|
345
CodeGen/src/EmitCommonX64.cpp
Normal file
345
CodeGen/src/EmitCommonX64.cpp
Normal file
@ -0,0 +1,345 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "EmitCommonX64.h"
|
||||
|
||||
#include "Luau/AssemblyBuilderX64.h"
|
||||
|
||||
#include "CustomExecUtils.h"
|
||||
#include "NativeState.h"
|
||||
|
||||
#include "lgc.h"
|
||||
#include "lstate.h"
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
namespace CodeGen
|
||||
{
|
||||
|
||||
void jumpOnNumberCmp(AssemblyBuilderX64& build, RegisterX64 tmp, OperandX64 lhs, OperandX64 rhs, Condition cond, Label& label)
|
||||
{
|
||||
// Refresher on comi/ucomi EFLAGS:
|
||||
// CF only: less
|
||||
// ZF only: equal
|
||||
// PF+CF+ZF: unordered (NaN)
|
||||
|
||||
if (rhs.cat == CategoryX64::reg)
|
||||
{
|
||||
build.vucomisd(rhs, lhs);
|
||||
}
|
||||
else
|
||||
{
|
||||
build.vmovsd(tmp, rhs);
|
||||
build.vucomisd(tmp, lhs);
|
||||
}
|
||||
|
||||
// Keep in mind that 'Not' conditions want 'true' for comparisons with NaN
|
||||
// And because of NaN, integer check interchangeability like 'not less or equal' <-> 'greater' does not hold
|
||||
switch (cond)
|
||||
{
|
||||
case Condition::NotLessEqual:
|
||||
// (b < a) is the same as !(a <= b). jnae checks CF=1 which means < or NaN
|
||||
build.jcc(Condition::NotAboveEqual, label);
|
||||
break;
|
||||
case Condition::LessEqual:
|
||||
// (b >= a) is the same as (a <= b). jae checks CF=0 which means >= and not NaN
|
||||
build.jcc(Condition::AboveEqual, label);
|
||||
break;
|
||||
case Condition::NotLess:
|
||||
// (b <= a) is the same as !(a < b). jna checks CF=1 or ZF=1 which means <= or NaN
|
||||
build.jcc(Condition::NotAbove, label);
|
||||
break;
|
||||
case Condition::Less:
|
||||
// (b > a) is the same as (a < b). ja checks CF=0 and ZF=0 which means > and not NaN
|
||||
build.jcc(Condition::Above, label);
|
||||
break;
|
||||
case Condition::NotEqual:
|
||||
// ZF=0 or PF=1 means != or NaN
|
||||
build.jcc(Condition::NotZero, label);
|
||||
build.jcc(Condition::Parity, label);
|
||||
break;
|
||||
default:
|
||||
LUAU_ASSERT(!"Unsupported condition");
|
||||
}
|
||||
}
|
||||
|
||||
void jumpOnAnyCmpFallback(AssemblyBuilderX64& build, int ra, int rb, Condition cond, Label& label, int pcpos)
|
||||
{
|
||||
emitSetSavedPc(build, pcpos + 1);
|
||||
build.mov(rArg1, rState);
|
||||
build.lea(rArg2, luauRegValue(ra));
|
||||
build.lea(rArg3, luauRegValue(rb));
|
||||
|
||||
if (cond == Condition::NotLessEqual || cond == Condition::LessEqual)
|
||||
build.call(qword[rNativeContext + offsetof(NativeContext, luaV_lessequal)]);
|
||||
else if (cond == Condition::NotLess || cond == Condition::Less)
|
||||
build.call(qword[rNativeContext + offsetof(NativeContext, luaV_lessthan)]);
|
||||
else if (cond == Condition::NotEqual || cond == Condition::Equal)
|
||||
build.call(qword[rNativeContext + offsetof(NativeContext, luaV_equalval)]);
|
||||
else
|
||||
LUAU_ASSERT(!"Unsupported condition");
|
||||
|
||||
emitUpdateBase(build);
|
||||
build.test(eax, eax);
|
||||
build.jcc(
|
||||
cond == Condition::NotLessEqual || cond == Condition::NotLess || cond == Condition::NotEqual ? Condition::Zero : Condition::NotZero, label);
|
||||
}
|
||||
|
||||
void convertNumberToIndexOrJump(AssemblyBuilderX64& build, RegisterX64 tmp, RegisterX64 numd, RegisterX64 numi, int ri, Label& label)
|
||||
{
|
||||
LUAU_ASSERT(numi.size == SizeX64::dword);
|
||||
|
||||
build.vmovsd(numd, luauRegValue(ri));
|
||||
|
||||
// Convert to integer, NaN is converted into 0x80000000
|
||||
build.vcvttsd2si(numi, numd);
|
||||
|
||||
// Convert that integer back to double
|
||||
build.vcvtsi2sd(tmp, numd, numi);
|
||||
|
||||
build.vucomisd(tmp, numd); // Sets ZF=1 if equal or NaN
|
||||
// We don't need non-integer values
|
||||
// But to skip the PF=1 check, we proceed with NaN because 0x80000000 index is out of bounds
|
||||
build.jcc(Condition::NotZero, label);
|
||||
}
|
||||
|
||||
void callArithHelper(AssemblyBuilderX64& build, int ra, int rb, OperandX64 c, int pcpos, TMS tm)
|
||||
{
|
||||
emitSetSavedPc(build, pcpos + 1);
|
||||
|
||||
if (getCurrentX64ABI() == X64ABI::Windows)
|
||||
build.mov(sArg5, tm);
|
||||
else
|
||||
build.mov(rArg5, tm);
|
||||
|
||||
build.mov(rArg1, rState);
|
||||
build.lea(rArg2, luauRegValue(ra));
|
||||
build.lea(rArg3, luauRegValue(rb));
|
||||
build.lea(rArg4, c);
|
||||
build.call(qword[rNativeContext + offsetof(NativeContext, luaV_doarith)]);
|
||||
|
||||
emitUpdateBase(build);
|
||||
}
|
||||
|
||||
void callLengthHelper(AssemblyBuilderX64& build, int ra, int rb, int pcpos)
|
||||
{
|
||||
emitSetSavedPc(build, pcpos + 1);
|
||||
|
||||
build.mov(rArg1, rState);
|
||||
build.lea(rArg2, luauRegValue(ra));
|
||||
build.lea(rArg3, luauRegValue(rb));
|
||||
build.call(qword[rNativeContext + offsetof(NativeContext, luaV_dolen)]);
|
||||
|
||||
emitUpdateBase(build);
|
||||
}
|
||||
|
||||
void callPrepareForN(AssemblyBuilderX64& build, int limit, int step, int init, int pcpos)
|
||||
{
|
||||
emitSetSavedPc(build, pcpos + 1);
|
||||
|
||||
build.mov(rArg1, rState);
|
||||
build.lea(rArg2, luauRegValue(limit));
|
||||
build.lea(rArg3, luauRegValue(step));
|
||||
build.lea(rArg4, luauRegValue(init));
|
||||
build.call(qword[rNativeContext + offsetof(NativeContext, luaV_prepareFORN)]);
|
||||
}
|
||||
|
||||
void callGetTable(AssemblyBuilderX64& build, int rb, OperandX64 c, int ra, int pcpos)
|
||||
{
|
||||
emitSetSavedPc(build, pcpos + 1);
|
||||
|
||||
build.mov(rArg1, rState);
|
||||
build.lea(rArg2, luauRegValue(rb));
|
||||
build.lea(rArg3, c);
|
||||
build.lea(rArg4, luauRegValue(ra));
|
||||
build.call(qword[rNativeContext + offsetof(NativeContext, luaV_gettable)]);
|
||||
|
||||
emitUpdateBase(build);
|
||||
}
|
||||
|
||||
void callSetTable(AssemblyBuilderX64& build, int rb, OperandX64 c, int ra, int pcpos)
|
||||
{
|
||||
emitSetSavedPc(build, pcpos + 1);
|
||||
|
||||
build.mov(rArg1, rState);
|
||||
build.lea(rArg2, luauRegValue(rb));
|
||||
build.lea(rArg3, c);
|
||||
build.lea(rArg4, luauRegValue(ra));
|
||||
build.call(qword[rNativeContext + offsetof(NativeContext, luaV_settable)]);
|
||||
|
||||
emitUpdateBase(build);
|
||||
}
|
||||
|
||||
void callBarrierTable(AssemblyBuilderX64& build, RegisterX64 tmp, RegisterX64 table, int ra, Label& skip)
|
||||
{
|
||||
LUAU_ASSERT(tmp != table);
|
||||
|
||||
// iscollectable(ra)
|
||||
build.cmp(luauRegTag(ra), LUA_TSTRING);
|
||||
build.jcc(Condition::Less, skip);
|
||||
|
||||
// isblack(obj2gco(h))
|
||||
build.test(byte[table + offsetof(GCheader, marked)], bitmask(BLACKBIT));
|
||||
build.jcc(Condition::Zero, skip);
|
||||
|
||||
// iswhite(gcvalue(ra))
|
||||
build.mov(tmp, luauRegValue(ra));
|
||||
build.test(byte[tmp + offsetof(GCheader, marked)], bit2mask(WHITE0BIT, WHITE1BIT));
|
||||
build.jcc(Condition::Zero, skip);
|
||||
|
||||
LUAU_ASSERT(table != rArg3);
|
||||
build.mov(rArg3, tmp);
|
||||
build.mov(rArg2, table);
|
||||
build.mov(rArg1, rState);
|
||||
build.call(qword[rNativeContext + offsetof(NativeContext, luaC_barriertable)]);
|
||||
}
|
||||
|
||||
void emitExit(AssemblyBuilderX64& build, bool continueInVm)
|
||||
{
|
||||
if (continueInVm)
|
||||
build.mov(al, 1);
|
||||
else
|
||||
build.xor_(eax, eax);
|
||||
|
||||
build.jmp(qword[rNativeContext + offsetof(NativeContext, gateExit)]);
|
||||
}
|
||||
|
||||
void emitUpdateBase(AssemblyBuilderX64& build)
|
||||
{
|
||||
build.mov(rBase, qword[rState + offsetof(lua_State, base)]);
|
||||
}
|
||||
|
||||
// Note: only uses rax/rdx, the caller may use other registers
|
||||
void emitSetSavedPc(AssemblyBuilderX64& build, int pcpos)
|
||||
{
|
||||
build.mov(rdx, sCode);
|
||||
build.add(rdx, pcpos * sizeof(Instruction));
|
||||
build.mov(rax, qword[rState + offsetof(lua_State, ci)]);
|
||||
build.mov(qword[rax + offsetof(CallInfo, savedpc)], rdx);
|
||||
}
|
||||
|
||||
void emitInterrupt(AssemblyBuilderX64& build, int pcpos)
|
||||
{
|
||||
Label skip;
|
||||
|
||||
// Skip if there is no interrupt set
|
||||
build.mov(r8, qword[rState + offsetof(lua_State, global)]);
|
||||
build.mov(r8, qword[r8 + offsetof(global_State, cb.interrupt)]);
|
||||
build.test(r8, r8);
|
||||
build.jcc(Condition::Zero, skip);
|
||||
|
||||
emitSetSavedPc(build, pcpos + 1); // uses rax/rdx
|
||||
|
||||
// Call interrupt
|
||||
// TODO: This code should move to the end of the function, or even be outlined so that it can be shared by multiple interruptible instructions
|
||||
build.mov(rArg1, rState);
|
||||
build.mov(rArg2d, -1);
|
||||
build.call(r8);
|
||||
|
||||
// Check if we need to exit
|
||||
build.mov(al, byte[rState + offsetof(lua_State, status)]);
|
||||
build.test(al, al);
|
||||
build.jcc(Condition::Zero, skip);
|
||||
|
||||
build.mov(rax, qword[rState + offsetof(lua_State, ci)]);
|
||||
build.sub(qword[rax + offsetof(CallInfo, savedpc)], sizeof(Instruction));
|
||||
emitExit(build, /* continueInVm */ false);
|
||||
|
||||
build.setLabel(skip);
|
||||
}
|
||||
|
||||
void emitFallback(AssemblyBuilderX64& build, NativeState& data, int op, int pcpos)
|
||||
{
|
||||
if (op == LOP_CAPTURE)
|
||||
return;
|
||||
|
||||
NativeFallback& opinfo = data.context.fallback[op];
|
||||
LUAU_ASSERT(opinfo.fallback);
|
||||
|
||||
if (build.logText)
|
||||
build.logAppend("; fallback\n");
|
||||
|
||||
// fallback(L, instruction, base, k)
|
||||
build.mov(rArg1, rState);
|
||||
build.mov(rArg2, sCode);
|
||||
build.add(rArg2, pcpos * sizeof(Instruction));
|
||||
build.mov(rArg3, rBase);
|
||||
build.mov(rArg4, rConstants);
|
||||
build.call(qword[rNativeContext + offsetof(NativeContext, fallback) + op * sizeof(NativeFallback) + offsetof(NativeFallback, fallback)]);
|
||||
|
||||
// Some instructions may interrupt the execution
|
||||
if (opinfo.flags & kFallbackCheckInterrupt)
|
||||
{
|
||||
Label skip;
|
||||
|
||||
build.test(rax, rax);
|
||||
build.jcc(Condition::NotZero, skip);
|
||||
emitExit(build, /* continueInVm */ false);
|
||||
build.setLabel(skip);
|
||||
}
|
||||
|
||||
emitUpdateBase(build);
|
||||
|
||||
// Some instructions may jump to a different instruction or a completely different function
|
||||
if (opinfo.flags & kFallbackUpdatePc)
|
||||
{
|
||||
build.mov(rcx, sClosure);
|
||||
build.mov(rcx, qword[rcx + offsetof(Closure, l.p)]);
|
||||
|
||||
// Get instruction index from returned 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(rax, sCode);
|
||||
|
||||
build.mov(rdx, qword[rcx + offsetofProtoExecData]);
|
||||
|
||||
// Get new instruction location and jump to it
|
||||
build.mov(rcx, qword[rdx + offsetof(NativeProto, instTargets)]);
|
||||
build.jmp(qword[rax * 2 + rcx]);
|
||||
}
|
||||
else if (opinfo.flags & kFallbackUpdateCi)
|
||||
{
|
||||
// Need to update state of the current function before we jump away
|
||||
build.mov(rcx, qword[rState + offsetof(lua_State, ci)]); // L->ci
|
||||
build.mov(rcx, qword[rcx + offsetof(CallInfo, func)]); // L->ci->func
|
||||
build.mov(rcx, qword[rcx + offsetof(TValue, value.gc)]); // L->ci->func->value.gc aka cl
|
||||
build.mov(sClosure, rcx);
|
||||
build.mov(rsi, qword[rcx + offsetof(Closure, l.p)]); // cl->l.p aka proto
|
||||
build.mov(rConstants, qword[rsi + offsetof(Proto, k)]); // proto->k
|
||||
build.mov(rcx, qword[rsi + offsetof(Proto, code)]); // proto->code
|
||||
build.mov(sCode, rcx);
|
||||
|
||||
// We'll need original instruction pointer later to handle return to interpreter
|
||||
if (op == LOP_CALL)
|
||||
build.mov(r9, rax);
|
||||
|
||||
// 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(rax, sCode);
|
||||
|
||||
// We need to check if the new function can be executed natively
|
||||
Label returnToInterpreter;
|
||||
|
||||
build.mov(rdx, qword[rsi + offsetofProtoExecData]);
|
||||
build.test(rdx, rdx);
|
||||
build.jcc(Condition::Zero, returnToInterpreter);
|
||||
|
||||
// Get new instruction location and jump to it
|
||||
build.mov(rcx, qword[rdx + offsetof(NativeProto, instTargets)]);
|
||||
build.jmp(qword[rax * 2 + rcx]);
|
||||
|
||||
build.setLabel(returnToInterpreter);
|
||||
|
||||
// If we are returning to the interpreter to make a call, we need to update the current instruction
|
||||
if (op == LOP_CALL)
|
||||
{
|
||||
build.mov(rax, qword[rState + offsetof(lua_State, ci)]);
|
||||
build.mov(qword[rax + offsetof(CallInfo, savedpc)], r9);
|
||||
}
|
||||
|
||||
// Continue in the interpreter
|
||||
emitExit(build, /* continueInVm */ true);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace CodeGen
|
||||
} // namespace Luau
|
175
CodeGen/src/EmitCommonX64.h
Normal file
175
CodeGen/src/EmitCommonX64.h
Normal file
@ -0,0 +1,175 @@
|
||||
// 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 "lobject.h"
|
||||
#include "ltm.h"
|
||||
|
||||
// MS x64 ABI reminder:
|
||||
// Arguments: rcx, rdx, r8, r9 ('overlapped' with xmm0-xmm3)
|
||||
// Return: rax, xmm0
|
||||
// Nonvolatile: r12-r15, rdi, rsi, rbx, rbp
|
||||
// SIMD: only xmm6-xmm15 are non-volatile, all ymm upper parts are volatile
|
||||
|
||||
// AMD64 ABI reminder:
|
||||
// Arguments: rdi, rsi, rdx, rcx, r8, r9 (xmm0-xmm7)
|
||||
// Return: rax, rdx, xmm0, xmm1
|
||||
// Nonvolatile: r12-r15, rbx, rbp
|
||||
// SIMD: all volatile
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
namespace CodeGen
|
||||
{
|
||||
|
||||
struct NativeState;
|
||||
|
||||
// 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
|
||||
constexpr RegisterX64 rNativeContext = r13; // NativeContext* context
|
||||
constexpr RegisterX64 rConstants = r12; // TValue* k
|
||||
|
||||
// Native code is as stackless as the interpreter, so we can place some data on the stack once and have it accessible at any point
|
||||
constexpr OperandX64 sClosure = qword[rbp + 0]; // Closure* cl
|
||||
constexpr OperandX64 sCode = qword[rbp + 8]; // Instruction* code
|
||||
|
||||
#if defined(_WIN32)
|
||||
|
||||
constexpr RegisterX64 rArg1 = rcx;
|
||||
constexpr RegisterX64 rArg2 = rdx;
|
||||
constexpr RegisterX64 rArg2d = edx;
|
||||
constexpr RegisterX64 rArg3 = r8;
|
||||
constexpr RegisterX64 rArg4 = r9;
|
||||
constexpr RegisterX64 rArg5 = noreg;
|
||||
constexpr RegisterX64 rArg6 = noreg;
|
||||
constexpr OperandX64 sArg5 = qword[rsp + 32];
|
||||
constexpr OperandX64 sArg6 = qword[rsp + 40];
|
||||
|
||||
#else
|
||||
|
||||
constexpr RegisterX64 rArg1 = rdi;
|
||||
constexpr RegisterX64 rArg2 = rsi;
|
||||
constexpr RegisterX64 rArg2d = esi;
|
||||
constexpr RegisterX64 rArg3 = rdx;
|
||||
constexpr RegisterX64 rArg4 = rcx;
|
||||
constexpr RegisterX64 rArg5 = r8;
|
||||
constexpr RegisterX64 rArg6 = r9;
|
||||
constexpr OperandX64 sArg5 = noreg;
|
||||
constexpr OperandX64 sArg6 = noreg;
|
||||
|
||||
#endif
|
||||
|
||||
constexpr unsigned kTValueSizeLog2 = 4;
|
||||
|
||||
inline OperandX64 luauReg(int ri)
|
||||
{
|
||||
return xmmword[rBase + ri * sizeof(TValue)];
|
||||
}
|
||||
|
||||
inline OperandX64 luauRegValue(int ri)
|
||||
{
|
||||
return qword[rBase + ri * sizeof(TValue) + offsetof(TValue, value)];
|
||||
}
|
||||
|
||||
inline OperandX64 luauRegTag(int ri)
|
||||
{
|
||||
return dword[rBase + ri * sizeof(TValue) + offsetof(TValue, tt)];
|
||||
}
|
||||
|
||||
inline OperandX64 luauRegValueBoolean(int ri)
|
||||
{
|
||||
return dword[rBase + ri * sizeof(TValue) + offsetof(TValue, value)];
|
||||
}
|
||||
|
||||
inline OperandX64 luauConstant(int ki)
|
||||
{
|
||||
return xmmword[rConstants + ki * sizeof(TValue)];
|
||||
}
|
||||
|
||||
inline OperandX64 luauConstantValue(int ki)
|
||||
{
|
||||
return qword[rConstants + ki * sizeof(TValue) + offsetof(TValue, value)];
|
||||
}
|
||||
|
||||
inline void setLuauReg(AssemblyBuilderX64& build, RegisterX64 tmp, int ri, OperandX64 op)
|
||||
{
|
||||
LUAU_ASSERT(op.cat == CategoryX64::mem);
|
||||
|
||||
build.vmovups(tmp, op);
|
||||
build.vmovups(luauReg(ri), tmp);
|
||||
}
|
||||
|
||||
inline void jumpIfTagIs(AssemblyBuilderX64& build, int ri, lua_Type tag, Label& label)
|
||||
{
|
||||
build.cmp(luauRegTag(ri), tag);
|
||||
build.jcc(Condition::Equal, label);
|
||||
}
|
||||
|
||||
inline void jumpIfTagIsNot(AssemblyBuilderX64& build, int ri, lua_Type tag, Label& label)
|
||||
{
|
||||
build.cmp(luauRegTag(ri), tag);
|
||||
build.jcc(Condition::NotEqual, label);
|
||||
}
|
||||
|
||||
// Note: fallthrough label should be placed after this condition
|
||||
inline void jumpIfFalsy(AssemblyBuilderX64& build, int ri, Label& target, Label& fallthrough)
|
||||
{
|
||||
jumpIfTagIs(build, ri, LUA_TNIL, target); // false if nil
|
||||
jumpIfTagIsNot(build, ri, LUA_TBOOLEAN, fallthrough); // true if not nil or boolean
|
||||
|
||||
build.cmp(luauRegValueBoolean(ri), 0);
|
||||
build.jcc(Condition::Equal, target); // true if boolean value is 'true'
|
||||
}
|
||||
|
||||
// Note: fallthrough label should be placed after this condition
|
||||
inline void jumpIfTruthy(AssemblyBuilderX64& build, int ri, Label& target, Label& fallthrough)
|
||||
{
|
||||
jumpIfTagIs(build, ri, LUA_TNIL, fallthrough); // false if nil
|
||||
jumpIfTagIsNot(build, ri, LUA_TBOOLEAN, target); // true if not nil or boolean
|
||||
|
||||
build.cmp(luauRegValueBoolean(ri), 0);
|
||||
build.jcc(Condition::NotEqual, target); // true if boolean value is 'true'
|
||||
}
|
||||
|
||||
inline void jumpIfMetatablePresent(AssemblyBuilderX64& build, RegisterX64 table, Label& target)
|
||||
{
|
||||
build.cmp(qword[table + offsetof(Table, metatable)], 0);
|
||||
build.jcc(Condition::NotEqual, target);
|
||||
}
|
||||
|
||||
inline void jumpIfUnsafeEnv(AssemblyBuilderX64& build, RegisterX64 tmp, Label& label)
|
||||
{
|
||||
build.mov(tmp, sClosure);
|
||||
build.mov(tmp, qword[tmp + offsetof(Closure, env)]);
|
||||
build.test(byte[tmp + offsetof(Table, safeenv)], 1);
|
||||
build.jcc(Condition::Zero, label); // Not a safe environment
|
||||
}
|
||||
|
||||
inline void jumpIfTableIsReadOnly(AssemblyBuilderX64& build, RegisterX64 table, Label& label)
|
||||
{
|
||||
build.cmp(byte[table + offsetof(Table, readonly)], 0);
|
||||
build.jcc(Condition::NotEqual, label);
|
||||
}
|
||||
|
||||
void jumpOnNumberCmp(AssemblyBuilderX64& build, RegisterX64 tmp, OperandX64 lhs, OperandX64 rhs, Condition cond, Label& label);
|
||||
void jumpOnAnyCmpFallback(AssemblyBuilderX64& build, int ra, int rb, Condition cond, Label& label, int pcpos);
|
||||
|
||||
void convertNumberToIndexOrJump(AssemblyBuilderX64& build, RegisterX64 tmp, RegisterX64 numd, RegisterX64 numi, int ri, Label& label);
|
||||
|
||||
void callArithHelper(AssemblyBuilderX64& build, int ra, int rb, OperandX64 c, int pcpos, TMS tm);
|
||||
void callLengthHelper(AssemblyBuilderX64& build, int ra, int rb, int pcpos);
|
||||
void callPrepareForN(AssemblyBuilderX64& build, int limit, int step, int init, int pcpos);
|
||||
void callGetTable(AssemblyBuilderX64& build, int rb, OperandX64 c, int ra, int pcpos);
|
||||
void callSetTable(AssemblyBuilderX64& build, int rb, OperandX64 c, int ra, int pcpos);
|
||||
void callBarrierTable(AssemblyBuilderX64& build, RegisterX64 tmp, RegisterX64 table, int ra, Label& skip);
|
||||
|
||||
void emitExit(AssemblyBuilderX64& build, bool continueInVm);
|
||||
void emitUpdateBase(AssemblyBuilderX64& build);
|
||||
void emitSetSavedPc(AssemblyBuilderX64& build, int pcpos); // Note: only uses rax/rdx, the caller may use other registers
|
||||
void emitInterrupt(AssemblyBuilderX64& build, int pcpos);
|
||||
void emitFallback(AssemblyBuilderX64& build, NativeState& data, int op, int pcpos);
|
||||
|
||||
} // namespace CodeGen
|
||||
} // namespace Luau
|
925
CodeGen/src/EmitInstructionX64.cpp
Normal file
925
CodeGen/src/EmitInstructionX64.cpp
Normal file
@ -0,0 +1,925 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "EmitInstructionX64.h"
|
||||
|
||||
#include "Luau/AssemblyBuilderX64.h"
|
||||
|
||||
#include "CustomExecUtils.h"
|
||||
#include "EmitBuiltinsX64.h"
|
||||
#include "EmitCommonX64.h"
|
||||
#include "NativeState.h"
|
||||
|
||||
#include "lobject.h"
|
||||
#include "ltm.h"
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
namespace CodeGen
|
||||
{
|
||||
|
||||
void emitInstLoadNil(AssemblyBuilderX64& build, NativeState& data, const Instruction* pc)
|
||||
{
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
|
||||
build.mov(luauRegTag(ra), LUA_TNIL);
|
||||
}
|
||||
|
||||
void emitInstLoadB(AssemblyBuilderX64& build, NativeState& data, const Instruction* pc, int pcpos, Label* labelarr)
|
||||
{
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
|
||||
build.mov(luauRegValue(ra), LUAU_INSN_B(*pc));
|
||||
build.mov(luauRegTag(ra), LUA_TBOOLEAN);
|
||||
|
||||
if (int target = LUAU_INSN_C(*pc))
|
||||
build.jmp(labelarr[pcpos + target + 1]);
|
||||
}
|
||||
|
||||
void emitInstLoadN(AssemblyBuilderX64& build, NativeState& data, const Instruction* pc)
|
||||
{
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
|
||||
build.vmovsd(xmm0, build.f64(double(LUAU_INSN_D(*pc))));
|
||||
build.vmovsd(luauRegValue(ra), xmm0);
|
||||
build.mov(luauRegTag(ra), LUA_TNUMBER);
|
||||
}
|
||||
|
||||
void emitInstLoadK(AssemblyBuilderX64& build, NativeState& data, const Instruction* pc, const TValue* k)
|
||||
{
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
|
||||
build.vmovups(xmm0, luauConstant(LUAU_INSN_D(*pc)));
|
||||
build.vmovups(luauReg(ra), xmm0);
|
||||
}
|
||||
|
||||
void emitInstMove(AssemblyBuilderX64& build, NativeState& data, const Instruction* pc)
|
||||
{
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
int rb = LUAU_INSN_B(*pc);
|
||||
|
||||
build.vmovups(xmm0, luauReg(rb));
|
||||
build.vmovups(luauReg(ra), xmm0);
|
||||
}
|
||||
|
||||
void emitInstJump(AssemblyBuilderX64& build, NativeState& data, const Instruction* pc, int pcpos, Label* labelarr)
|
||||
{
|
||||
build.jmp(labelarr[pcpos + LUAU_INSN_D(*pc) + 1]);
|
||||
}
|
||||
|
||||
void emitInstJumpBack(AssemblyBuilderX64& build, NativeState& data, const Instruction* pc, int pcpos, Label* labelarr)
|
||||
{
|
||||
emitInterrupt(build, pcpos);
|
||||
|
||||
build.jmp(labelarr[pcpos + LUAU_INSN_D(*pc) + 1]);
|
||||
}
|
||||
|
||||
void emitInstJumpIf(AssemblyBuilderX64& build, NativeState& data, const Instruction* pc, int pcpos, Label* labelarr, bool not_)
|
||||
{
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
|
||||
Label& target = labelarr[pcpos + LUAU_INSN_D(*pc) + 1];
|
||||
Label& exit = labelarr[pcpos + 1];
|
||||
|
||||
if (not_)
|
||||
jumpIfFalsy(build, ra, target, exit);
|
||||
else
|
||||
jumpIfTruthy(build, ra, target, exit);
|
||||
}
|
||||
|
||||
void emitInstJumpIfEq(AssemblyBuilderX64& build, NativeState& data, const Instruction* pc, int pcpos, Label* labelarr, bool not_)
|
||||
{
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
int rb = pc[1];
|
||||
|
||||
Label& target = labelarr[pcpos + LUAU_INSN_D(*pc) + 1];
|
||||
Label& exit = labelarr[pcpos + 2];
|
||||
Label any;
|
||||
|
||||
build.mov(eax, luauRegTag(ra));
|
||||
build.cmp(eax, luauRegTag(rb));
|
||||
build.jcc(Condition::NotEqual, not_ ? target : exit);
|
||||
|
||||
// fast-path: number
|
||||
build.cmp(eax, LUA_TNUMBER);
|
||||
build.jcc(Condition::NotEqual, any);
|
||||
|
||||
jumpOnNumberCmp(build, xmm0, luauRegValue(ra), luauRegValue(rb), Condition::NotEqual, not_ ? target : exit);
|
||||
build.jmp(not_ ? exit : target);
|
||||
|
||||
// slow-path
|
||||
// TODO: move to the end of the function
|
||||
build.setLabel(any);
|
||||
jumpOnAnyCmpFallback(build, ra, rb, not_ ? Condition::NotEqual : Condition::Equal, target, pcpos);
|
||||
}
|
||||
|
||||
void emitInstJumpIfCond(AssemblyBuilderX64& build, NativeState& data, const Instruction* pc, int pcpos, Label* labelarr, Condition cond)
|
||||
{
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
int rb = pc[1];
|
||||
|
||||
Label& target = labelarr[pcpos + LUAU_INSN_D(*pc) + 1];
|
||||
Label& exit = labelarr[pcpos + 2];
|
||||
Label any;
|
||||
|
||||
// fast-path: number
|
||||
jumpIfTagIsNot(build, ra, LUA_TNUMBER, any);
|
||||
jumpIfTagIsNot(build, rb, LUA_TNUMBER, any);
|
||||
|
||||
jumpOnNumberCmp(build, xmm0, luauRegValue(ra), luauRegValue(rb), cond, target);
|
||||
build.jmp(exit);
|
||||
|
||||
// slow-path
|
||||
// TODO: move to the end of the function
|
||||
build.setLabel(any);
|
||||
jumpOnAnyCmpFallback(build, ra, rb, cond, target, pcpos);
|
||||
}
|
||||
|
||||
void emitInstJumpX(AssemblyBuilderX64& build, NativeState& data, const Instruction* pc, int pcpos, Label* labelarr)
|
||||
{
|
||||
emitInterrupt(build, pcpos);
|
||||
|
||||
build.jmp(labelarr[pcpos + LUAU_INSN_E(*pc) + 1]);
|
||||
}
|
||||
|
||||
void emitInstJumpxEqNil(AssemblyBuilderX64& build, NativeState& data, const Instruction* pc, const TValue* k, int pcpos, Label* labelarr)
|
||||
{
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
bool not_ = (pc[1] & 0x80000000) != 0;
|
||||
|
||||
Label& target = labelarr[pcpos + LUAU_INSN_D(*pc) + 1];
|
||||
|
||||
build.cmp(luauRegTag(ra), LUA_TNIL);
|
||||
build.jcc(not_ ? Condition::NotEqual : Condition::Equal, target);
|
||||
}
|
||||
|
||||
void emitInstJumpxEqB(AssemblyBuilderX64& build, NativeState& data, const Instruction* pc, const TValue* k, int pcpos, Label* labelarr)
|
||||
{
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
uint32_t aux = pc[1];
|
||||
bool not_ = (aux & 0x80000000) != 0;
|
||||
|
||||
Label& target = labelarr[pcpos + LUAU_INSN_D(*pc) + 1];
|
||||
Label& exit = labelarr[pcpos + 2];
|
||||
|
||||
jumpIfTagIsNot(build, ra, LUA_TBOOLEAN, not_ ? target : exit);
|
||||
|
||||
build.test(luauRegValueBoolean(ra), 1);
|
||||
build.jcc((aux & 0x1) ^ not_ ? Condition::NotZero : Condition::Zero, target);
|
||||
}
|
||||
|
||||
void emitInstJumpxEqN(AssemblyBuilderX64& build, NativeState& data, const Instruction* pc, const TValue* k, int pcpos, Label* labelarr)
|
||||
{
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
uint32_t aux = pc[1];
|
||||
bool not_ = (aux & 0x80000000) != 0;
|
||||
TValue kv = k[aux & 0xffffff];
|
||||
|
||||
Label& target = labelarr[pcpos + LUAU_INSN_D(*pc) + 1];
|
||||
Label& exit = labelarr[pcpos + 2];
|
||||
|
||||
jumpIfTagIsNot(build, ra, LUA_TNUMBER, not_ ? target : exit);
|
||||
|
||||
if (not_)
|
||||
{
|
||||
jumpOnNumberCmp(build, xmm0, luauRegValue(ra), build.f64(kv.value.n), Condition::NotEqual, target);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Compact equality check requires two labels, so it's not supported in generic 'jumpOnNumberCmp'
|
||||
build.vmovsd(xmm0, luauRegValue(ra));
|
||||
build.vucomisd(xmm0, build.f64(kv.value.n));
|
||||
build.jcc(Condition::Parity, exit); // We first have to check PF=1 for NaN operands, because it also sets ZF=1
|
||||
build.jcc(Condition::Zero, target); // Now that NaN is out of the way, we can check ZF=1 for equality
|
||||
}
|
||||
}
|
||||
|
||||
void emitInstJumpxEqS(AssemblyBuilderX64& build, NativeState& data, const Instruction* pc, const TValue* k, int pcpos, Label* labelarr)
|
||||
{
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
uint32_t aux = pc[1];
|
||||
bool not_ = (aux & 0x80000000) != 0;
|
||||
|
||||
Label& target = labelarr[pcpos + LUAU_INSN_D(*pc) + 1];
|
||||
Label& exit = labelarr[pcpos + 2];
|
||||
|
||||
jumpIfTagIsNot(build, ra, LUA_TSTRING, not_ ? target : exit);
|
||||
|
||||
build.mov(rax, luauRegValue(ra));
|
||||
build.cmp(rax, luauConstantValue(aux & 0xffffff));
|
||||
build.jcc(not_ ? Condition::NotEqual : Condition::Equal, target);
|
||||
}
|
||||
|
||||
static void emitInstBinaryNumeric(AssemblyBuilderX64& build, int ra, int rb, int rc, OperandX64 opc, int pcpos, TMS tm)
|
||||
{
|
||||
Label common, exit;
|
||||
|
||||
jumpIfTagIsNot(build, rb, LUA_TNUMBER, common);
|
||||
|
||||
if (rc != -1 && rc != rb)
|
||||
jumpIfTagIsNot(build, rc, LUA_TNUMBER, common);
|
||||
|
||||
// fast-path: number
|
||||
build.vmovsd(xmm0, luauRegValue(rb));
|
||||
|
||||
switch (tm)
|
||||
{
|
||||
case TM_ADD:
|
||||
build.vaddsd(xmm0, xmm0, opc);
|
||||
break;
|
||||
case TM_SUB:
|
||||
build.vsubsd(xmm0, xmm0, opc);
|
||||
break;
|
||||
case TM_MUL:
|
||||
build.vmulsd(xmm0, xmm0, opc);
|
||||
break;
|
||||
case TM_DIV:
|
||||
build.vdivsd(xmm0, xmm0, opc);
|
||||
break;
|
||||
case TM_MOD:
|
||||
// This follows the implementation of 'luai_nummod' which is less precise than 'fmod' for better performance
|
||||
build.vmovsd(xmm1, opc);
|
||||
build.vdivsd(xmm2, xmm0, xmm1);
|
||||
build.vroundsd(xmm2, xmm2, xmm2, RoundingModeX64::RoundToNegativeInfinity);
|
||||
build.vmulsd(xmm1, xmm2, xmm1);
|
||||
build.vsubsd(xmm0, xmm0, xmm1);
|
||||
break;
|
||||
case TM_POW:
|
||||
build.vmovsd(xmm1, luauRegValue(rc));
|
||||
build.call(qword[rNativeContext + offsetof(NativeContext, libm_pow)]);
|
||||
break;
|
||||
default:
|
||||
LUAU_ASSERT(!"unsupported binary op");
|
||||
}
|
||||
|
||||
build.vmovsd(luauRegValue(ra), xmm0);
|
||||
|
||||
if (ra != rb && ra != rc)
|
||||
build.mov(luauRegTag(ra), LUA_TNUMBER);
|
||||
|
||||
build.jmp(exit);
|
||||
|
||||
// slow-path
|
||||
// TODO: move to the end of the function
|
||||
build.setLabel(common);
|
||||
callArithHelper(build, ra, rb, opc, pcpos, tm);
|
||||
|
||||
build.setLabel(exit);
|
||||
}
|
||||
|
||||
void emitInstAdd(AssemblyBuilderX64& build, const Instruction* pc, int pcpos)
|
||||
{
|
||||
emitInstBinaryNumeric(build, LUAU_INSN_A(*pc), LUAU_INSN_B(*pc), LUAU_INSN_C(*pc), luauRegValue(LUAU_INSN_C(*pc)), pcpos, TM_ADD);
|
||||
}
|
||||
|
||||
void emitInstSub(AssemblyBuilderX64& build, const Instruction* pc, int pcpos)
|
||||
{
|
||||
emitInstBinaryNumeric(build, LUAU_INSN_A(*pc), LUAU_INSN_B(*pc), LUAU_INSN_C(*pc), luauRegValue(LUAU_INSN_C(*pc)), pcpos, TM_SUB);
|
||||
}
|
||||
|
||||
void emitInstMul(AssemblyBuilderX64& build, const Instruction* pc, int pcpos)
|
||||
{
|
||||
emitInstBinaryNumeric(build, LUAU_INSN_A(*pc), LUAU_INSN_B(*pc), LUAU_INSN_C(*pc), luauRegValue(LUAU_INSN_C(*pc)), pcpos, TM_MUL);
|
||||
}
|
||||
|
||||
void emitInstDiv(AssemblyBuilderX64& build, const Instruction* pc, int pcpos)
|
||||
{
|
||||
emitInstBinaryNumeric(build, LUAU_INSN_A(*pc), LUAU_INSN_B(*pc), LUAU_INSN_C(*pc), luauRegValue(LUAU_INSN_C(*pc)), pcpos, TM_DIV);
|
||||
}
|
||||
|
||||
void emitInstMod(AssemblyBuilderX64& build, const Instruction* pc, int pcpos)
|
||||
{
|
||||
emitInstBinaryNumeric(build, LUAU_INSN_A(*pc), LUAU_INSN_B(*pc), LUAU_INSN_C(*pc), luauRegValue(LUAU_INSN_C(*pc)), pcpos, TM_MOD);
|
||||
}
|
||||
|
||||
void emitInstPow(AssemblyBuilderX64& build, const Instruction* pc, int pcpos)
|
||||
{
|
||||
emitInstBinaryNumeric(build, LUAU_INSN_A(*pc), LUAU_INSN_B(*pc), LUAU_INSN_C(*pc), luauRegValue(LUAU_INSN_C(*pc)), pcpos, TM_POW);
|
||||
}
|
||||
|
||||
void emitInstAddK(AssemblyBuilderX64& build, const Instruction* pc, const TValue* k, int pcpos)
|
||||
{
|
||||
emitInstBinaryNumeric(build, LUAU_INSN_A(*pc), LUAU_INSN_B(*pc), -1, luauConstantValue(LUAU_INSN_C(*pc)), pcpos, TM_ADD);
|
||||
}
|
||||
|
||||
void emitInstSubK(AssemblyBuilderX64& build, const Instruction* pc, const TValue* k, int pcpos)
|
||||
{
|
||||
emitInstBinaryNumeric(build, LUAU_INSN_A(*pc), LUAU_INSN_B(*pc), -1, luauConstantValue(LUAU_INSN_C(*pc)), pcpos, TM_SUB);
|
||||
}
|
||||
|
||||
void emitInstMulK(AssemblyBuilderX64& build, const Instruction* pc, const TValue* k, int pcpos)
|
||||
{
|
||||
emitInstBinaryNumeric(build, LUAU_INSN_A(*pc), LUAU_INSN_B(*pc), -1, luauConstantValue(LUAU_INSN_C(*pc)), pcpos, TM_MUL);
|
||||
}
|
||||
|
||||
void emitInstDivK(AssemblyBuilderX64& build, const Instruction* pc, const TValue* k, int pcpos)
|
||||
{
|
||||
emitInstBinaryNumeric(build, LUAU_INSN_A(*pc), LUAU_INSN_B(*pc), -1, luauConstantValue(LUAU_INSN_C(*pc)), pcpos, TM_DIV);
|
||||
}
|
||||
|
||||
void emitInstModK(AssemblyBuilderX64& build, const Instruction* pc, const TValue* k, int pcpos)
|
||||
{
|
||||
emitInstBinaryNumeric(build, LUAU_INSN_A(*pc), LUAU_INSN_B(*pc), -1, luauConstantValue(LUAU_INSN_C(*pc)), pcpos, TM_MOD);
|
||||
}
|
||||
|
||||
void emitInstPowK(AssemblyBuilderX64& build, const Instruction* pc, const TValue* k, int pcpos)
|
||||
{
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
int rb = LUAU_INSN_B(*pc);
|
||||
double kv = nvalue(&k[LUAU_INSN_C(*pc)]);
|
||||
|
||||
Label common, exit;
|
||||
|
||||
jumpIfTagIsNot(build, rb, LUA_TNUMBER, common);
|
||||
|
||||
// fast-path: number
|
||||
build.vmovsd(xmm0, luauRegValue(rb));
|
||||
|
||||
// Specialize for a few constants, similar to how it's done in the VM
|
||||
if (kv == 2.0)
|
||||
{
|
||||
build.vmulsd(xmm0, xmm0, xmm0);
|
||||
}
|
||||
else if (kv == 0.5)
|
||||
{
|
||||
build.vsqrtsd(xmm0, xmm0, xmm0);
|
||||
}
|
||||
else if (kv == 3.0)
|
||||
{
|
||||
build.vmulsd(xmm1, xmm0, xmm0);
|
||||
build.vmulsd(xmm0, xmm0, xmm1);
|
||||
}
|
||||
else
|
||||
{
|
||||
build.vmovsd(xmm1, build.f64(kv));
|
||||
build.call(qword[rNativeContext + offsetof(NativeContext, libm_pow)]);
|
||||
}
|
||||
|
||||
build.vmovsd(luauRegValue(ra), xmm0);
|
||||
|
||||
if (ra != rb)
|
||||
build.mov(luauRegTag(ra), LUA_TNUMBER);
|
||||
|
||||
build.jmp(exit);
|
||||
|
||||
// slow-path
|
||||
// TODO: move to the end of the function
|
||||
build.setLabel(common);
|
||||
callArithHelper(build, ra, rb, luauConstantValue(LUAU_INSN_C(*pc)), pcpos, TM_POW);
|
||||
|
||||
build.setLabel(exit);
|
||||
}
|
||||
|
||||
void emitInstNot(AssemblyBuilderX64& build, const Instruction* pc)
|
||||
{
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
int rb = LUAU_INSN_B(*pc);
|
||||
|
||||
Label saveone, savezero, exit;
|
||||
|
||||
jumpIfFalsy(build, rb, saveone, savezero);
|
||||
|
||||
build.setLabel(savezero);
|
||||
build.mov(luauRegValueBoolean(ra), 0);
|
||||
build.jmp(exit);
|
||||
|
||||
build.setLabel(saveone);
|
||||
build.mov(luauRegValueBoolean(ra), 1);
|
||||
|
||||
build.setLabel(exit);
|
||||
build.mov(luauRegTag(ra), LUA_TBOOLEAN);
|
||||
}
|
||||
|
||||
void emitInstMinus(AssemblyBuilderX64& build, const Instruction* pc, int pcpos)
|
||||
{
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
int rb = LUAU_INSN_B(*pc);
|
||||
|
||||
Label any, exit;
|
||||
|
||||
jumpIfTagIsNot(build, rb, LUA_TNUMBER, any);
|
||||
|
||||
// fast-path: number
|
||||
build.vxorpd(xmm0, xmm0, xmm0);
|
||||
build.vsubsd(xmm0, xmm0, luauRegValue(rb));
|
||||
build.vmovsd(luauRegValue(ra), xmm0);
|
||||
|
||||
if (ra != rb)
|
||||
build.mov(luauRegTag(ra), LUA_TNUMBER);
|
||||
|
||||
build.jmp(exit);
|
||||
|
||||
// slow-path
|
||||
// TODO: move to the end of the function
|
||||
build.setLabel(any);
|
||||
callArithHelper(build, ra, rb, luauRegValue(rb), pcpos, TM_UNM);
|
||||
|
||||
build.setLabel(exit);
|
||||
}
|
||||
|
||||
void emitInstLength(AssemblyBuilderX64& build, const Instruction* pc, int pcpos)
|
||||
{
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
int rb = LUAU_INSN_B(*pc);
|
||||
|
||||
Label any, exit;
|
||||
|
||||
jumpIfTagIsNot(build, rb, LUA_TTABLE, any);
|
||||
|
||||
// fast-path: table without __len
|
||||
build.mov(rArg1, luauRegValue(rb));
|
||||
jumpIfMetatablePresent(build, rArg1, any);
|
||||
|
||||
// First argument (Table*) is already in rArg1
|
||||
build.call(qword[rNativeContext + offsetof(NativeContext, luaH_getn)]);
|
||||
|
||||
build.vcvtsi2sd(xmm0, xmm0, eax);
|
||||
build.vmovsd(luauRegValue(ra), xmm0);
|
||||
build.mov(luauRegTag(ra), LUA_TNUMBER);
|
||||
build.jmp(exit);
|
||||
|
||||
// slow-path
|
||||
// TODO: move to the end of the function
|
||||
build.setLabel(any);
|
||||
callLengthHelper(build, ra, rb, pcpos);
|
||||
|
||||
build.setLabel(exit);
|
||||
}
|
||||
|
||||
void emitInstGetUpval(AssemblyBuilderX64& build, const Instruction* pc, int pcpos)
|
||||
{
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
int up = LUAU_INSN_B(*pc);
|
||||
|
||||
build.mov(rax, sClosure);
|
||||
build.add(rax, offsetof(Closure, l.uprefs) + sizeof(TValue) * up);
|
||||
|
||||
// uprefs[] is either an actual value, or it points to UpVal object which has a pointer to value
|
||||
Label skip;
|
||||
// TODO: jumpIfTagIsNot can be generalized to take OperandX64 and then we can use it here; let's wait until we see this more though
|
||||
build.cmp(dword[rax + offsetof(TValue, tt)], LUA_TUPVAL);
|
||||
build.jcc(Condition::NotEqual, skip);
|
||||
|
||||
// UpVal.v points to the value (either on stack, or on heap inside each UpVal, but we can deref it unconditionally)
|
||||
build.mov(rax, qword[rax + offsetof(TValue, value.gc)]);
|
||||
build.mov(rax, qword[rax + offsetof(UpVal, v)]);
|
||||
|
||||
build.setLabel(skip);
|
||||
|
||||
build.vmovups(xmm0, xmmword[rax]);
|
||||
build.vmovups(luauReg(ra), xmm0);
|
||||
}
|
||||
|
||||
static void emitInstFastCallN(
|
||||
AssemblyBuilderX64& build, const Instruction* pc, bool customParams, int customParamCount, OperandX64 customArgs, int pcpos, Label* labelarr)
|
||||
{
|
||||
int bfid = LUAU_INSN_A(*pc);
|
||||
int skip = LUAU_INSN_C(*pc);
|
||||
|
||||
Instruction call = pc[skip + 1];
|
||||
LUAU_ASSERT(LUAU_INSN_OP(call) == LOP_CALL);
|
||||
int ra = LUAU_INSN_A(call);
|
||||
|
||||
int nparams = customParams ? customParamCount : LUAU_INSN_B(call) - 1;
|
||||
int nresults = LUAU_INSN_C(call) - 1;
|
||||
int arg = customParams ? LUAU_INSN_B(*pc) : ra + 1;
|
||||
OperandX64 args = customParams ? customArgs : luauRegValue(ra + 2);
|
||||
|
||||
Label exit;
|
||||
|
||||
jumpIfUnsafeEnv(build, rax, exit);
|
||||
|
||||
BuiltinImplResult br = emitBuiltin(build, LuauBuiltinFunction(bfid), nparams, ra, arg, args, nresults, exit);
|
||||
|
||||
if (br.type == BuiltinImplType::UsesFallback)
|
||||
{
|
||||
if (nresults == LUA_MULTRET)
|
||||
{
|
||||
// L->top = ra + n;
|
||||
build.lea(rax, qword[rBase + (ra + br.actualResultCount) * sizeof(TValue)]);
|
||||
build.mov(qword[rState + offsetof(lua_State, top)], rax);
|
||||
}
|
||||
else if (nparams == LUA_MULTRET)
|
||||
{
|
||||
// L->top = L->ci->top;
|
||||
build.mov(rax, qword[rState + offsetof(lua_State, ci)]);
|
||||
build.mov(rax, qword[rax + offsetof(CallInfo, top)]);
|
||||
build.mov(qword[rState + offsetof(lua_State, top)], rax);
|
||||
}
|
||||
|
||||
// TODO: once we start outlining the fallback, we will be able to fallthrough to the next instruction
|
||||
build.jmp(labelarr[pcpos + skip + 2]);
|
||||
build.setLabel(exit);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: we can skip saving pc for some well-behaved builtins which we didn't inline
|
||||
emitSetSavedPc(build, pcpos); // uses rax/rdx
|
||||
|
||||
build.mov(rax, qword[rNativeContext + offsetof(NativeContext, luauF_table) + bfid * sizeof(luau_FastFunction)]);
|
||||
|
||||
// 5th parameter (args) is left unset for LOP_FASTCALL1
|
||||
if (args.cat == CategoryX64::mem)
|
||||
{
|
||||
if (getCurrentX64ABI() == X64ABI::Windows)
|
||||
{
|
||||
build.lea(rcx, args);
|
||||
build.mov(sArg5, rcx);
|
||||
}
|
||||
else
|
||||
{
|
||||
build.lea(rArg5, args);
|
||||
}
|
||||
}
|
||||
|
||||
if (nparams == LUA_MULTRET)
|
||||
{
|
||||
// TODO: for SystemV ABI we can compute the result directly into rArg6
|
||||
// L->top - (ra + 1)
|
||||
build.mov(rcx, qword[rState + offsetof(lua_State, top)]);
|
||||
build.lea(rdx, qword[rBase + (ra + 1) * sizeof(TValue)]);
|
||||
build.sub(rcx, rdx);
|
||||
build.shr(rcx, kTValueSizeLog2);
|
||||
|
||||
if (getCurrentX64ABI() == X64ABI::Windows)
|
||||
build.mov(sArg6, rcx);
|
||||
else
|
||||
build.mov(rArg6, rcx);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (getCurrentX64ABI() == X64ABI::Windows)
|
||||
build.mov(sArg6, nparams);
|
||||
else
|
||||
build.mov(rArg6, nparams);
|
||||
}
|
||||
|
||||
build.mov(rArg1, rState);
|
||||
build.lea(rArg2, luauRegValue(ra));
|
||||
build.lea(rArg3, luauRegValue(arg));
|
||||
build.mov(rArg4, nresults);
|
||||
|
||||
build.call(rax);
|
||||
|
||||
build.test(eax, eax); // test here will set SF=1 for a negative number and it always sets OF to 0
|
||||
build.jcc(Condition::Less, exit); // jl jumps if SF != OF
|
||||
|
||||
if (nresults == LUA_MULTRET)
|
||||
{
|
||||
// L->top = ra + n;
|
||||
build.shl(rax, kTValueSizeLog2);
|
||||
build.lea(rax, qword[rBase + rax + ra * sizeof(TValue)]);
|
||||
build.mov(qword[rState + offsetof(lua_State, top)], rax);
|
||||
}
|
||||
else if (nparams == LUA_MULTRET)
|
||||
{
|
||||
// L->top = L->ci->top;
|
||||
build.mov(rax, qword[rState + offsetof(lua_State, ci)]);
|
||||
build.mov(rax, qword[rax + offsetof(CallInfo, top)]);
|
||||
build.mov(qword[rState + offsetof(lua_State, top)], rax);
|
||||
}
|
||||
|
||||
build.jmp(labelarr[pcpos + skip + 2]);
|
||||
|
||||
build.setLabel(exit);
|
||||
|
||||
// TODO: fallback to LOP_CALL after a fast call should be outlined
|
||||
}
|
||||
|
||||
void emitInstFastCall1(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr)
|
||||
{
|
||||
emitInstFastCallN(build, pc, /* customParams */ true, /* customParamCount */ 1, /* customArgs */ 0, pcpos, labelarr);
|
||||
}
|
||||
|
||||
void emitInstFastCall2(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr)
|
||||
{
|
||||
emitInstFastCallN(build, pc, /* customParams */ true, /* customParamCount */ 2, /* customArgs */ luauRegValue(pc[1]), pcpos, labelarr);
|
||||
}
|
||||
|
||||
void emitInstFastCall2K(AssemblyBuilderX64& build, const Instruction* pc, const TValue* k, int pcpos, Label* labelarr)
|
||||
{
|
||||
emitInstFastCallN(build, pc, /* customParams */ true, /* customParamCount */ 2, /* customArgs */ luauConstantValue(pc[1]), pcpos, labelarr);
|
||||
}
|
||||
|
||||
void emitInstFastCall(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr)
|
||||
{
|
||||
emitInstFastCallN(build, pc, /* customParams */ false, /* customParamCount */ 0, /* customArgs */ 0, pcpos, labelarr);
|
||||
}
|
||||
|
||||
void emitInstForNPrep(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr)
|
||||
{
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
Label& loopExit = labelarr[pcpos + LUAU_INSN_D(*pc) + 1];
|
||||
|
||||
Label tryConvert, exit;
|
||||
|
||||
jumpIfTagIsNot(build, ra + 0, LUA_TNUMBER, tryConvert);
|
||||
jumpIfTagIsNot(build, ra + 1, LUA_TNUMBER, tryConvert);
|
||||
jumpIfTagIsNot(build, ra + 2, LUA_TNUMBER, tryConvert);
|
||||
|
||||
// After successful conversion of arguments to number, we return here
|
||||
Label retry = build.setLabel();
|
||||
|
||||
RegisterX64 limit = xmm0;
|
||||
RegisterX64 step = xmm1;
|
||||
RegisterX64 idx = xmm2;
|
||||
RegisterX64 zero = xmm3;
|
||||
|
||||
build.vxorpd(zero, xmm0, xmm0);
|
||||
build.vmovsd(limit, luauRegValue(ra + 0));
|
||||
build.vmovsd(step, luauRegValue(ra + 1));
|
||||
build.vmovsd(idx, luauRegValue(ra + 2));
|
||||
|
||||
Label reverse;
|
||||
|
||||
// step <= 0
|
||||
jumpOnNumberCmp(build, noreg, step, zero, Condition::LessEqual, reverse);
|
||||
|
||||
// TODO: target branches can probably be arranged better, but we need tests for NaN behavior preservation
|
||||
// false: idx <= limit
|
||||
jumpOnNumberCmp(build, noreg, idx, limit, Condition::LessEqual, exit);
|
||||
build.jmp(loopExit);
|
||||
|
||||
// true: limit <= idx
|
||||
build.setLabel(reverse);
|
||||
jumpOnNumberCmp(build, noreg, limit, idx, Condition::LessEqual, exit);
|
||||
build.jmp(loopExit);
|
||||
|
||||
// TOOD: place at the end of the function
|
||||
build.setLabel(tryConvert);
|
||||
callPrepareForN(build, ra + 0, ra + 1, ra + 2, pcpos);
|
||||
build.jmp(retry);
|
||||
|
||||
build.setLabel(exit);
|
||||
}
|
||||
|
||||
void emitInstForNLoop(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr)
|
||||
{
|
||||
emitInterrupt(build, pcpos);
|
||||
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
Label& loopRepeat = labelarr[pcpos + LUAU_INSN_D(*pc) + 1];
|
||||
|
||||
RegisterX64 limit = xmm0;
|
||||
RegisterX64 step = xmm1;
|
||||
RegisterX64 idx = xmm2;
|
||||
RegisterX64 zero = xmm3;
|
||||
|
||||
build.vxorpd(zero, xmm0, xmm0);
|
||||
build.vmovsd(limit, luauRegValue(ra + 0));
|
||||
build.vmovsd(step, luauRegValue(ra + 1));
|
||||
build.vmovsd(idx, luauRegValue(ra + 2));
|
||||
build.vaddsd(idx, idx, step);
|
||||
build.vmovsd(luauRegValue(ra + 2), idx);
|
||||
|
||||
Label reverse, exit;
|
||||
|
||||
// step <= 0
|
||||
jumpOnNumberCmp(build, noreg, step, zero, Condition::LessEqual, reverse);
|
||||
|
||||
// false: idx <= limit
|
||||
jumpOnNumberCmp(build, noreg, idx, limit, Condition::LessEqual, loopRepeat);
|
||||
build.jmp(exit);
|
||||
|
||||
// true: limit <= idx
|
||||
build.setLabel(reverse);
|
||||
jumpOnNumberCmp(build, noreg, limit, idx, Condition::LessEqual, loopRepeat);
|
||||
|
||||
build.setLabel(exit);
|
||||
}
|
||||
|
||||
static void emitInstAndX(AssemblyBuilderX64& build, int ra, int rb, OperandX64 c)
|
||||
{
|
||||
Label target, fallthrough;
|
||||
jumpIfFalsy(build, rb, target, fallthrough);
|
||||
|
||||
build.setLabel(fallthrough);
|
||||
|
||||
build.vmovups(xmm0, c);
|
||||
build.vmovups(luauReg(ra), xmm0);
|
||||
|
||||
if (ra == rb)
|
||||
{
|
||||
build.setLabel(target);
|
||||
}
|
||||
else
|
||||
{
|
||||
Label exit;
|
||||
build.jmp(exit);
|
||||
|
||||
build.setLabel(target);
|
||||
|
||||
build.vmovups(xmm0, luauReg(rb));
|
||||
build.vmovups(luauReg(ra), xmm0);
|
||||
|
||||
build.setLabel(exit);
|
||||
}
|
||||
}
|
||||
|
||||
void emitInstAnd(AssemblyBuilderX64& build, const Instruction* pc)
|
||||
{
|
||||
emitInstAndX(build, LUAU_INSN_A(*pc), LUAU_INSN_B(*pc), luauReg(LUAU_INSN_C(*pc)));
|
||||
}
|
||||
|
||||
void emitInstAndK(AssemblyBuilderX64& build, const Instruction* pc)
|
||||
{
|
||||
emitInstAndX(build, LUAU_INSN_A(*pc), LUAU_INSN_B(*pc), luauConstant(LUAU_INSN_C(*pc)));
|
||||
}
|
||||
|
||||
static void emitInstOrX(AssemblyBuilderX64& build, int ra, int rb, OperandX64 c)
|
||||
{
|
||||
Label target, fallthrough;
|
||||
jumpIfTruthy(build, rb, target, fallthrough);
|
||||
|
||||
build.setLabel(fallthrough);
|
||||
|
||||
build.vmovups(xmm0, c);
|
||||
build.vmovups(luauReg(ra), xmm0);
|
||||
|
||||
if (ra == rb)
|
||||
{
|
||||
build.setLabel(target);
|
||||
}
|
||||
else
|
||||
{
|
||||
Label exit;
|
||||
build.jmp(exit);
|
||||
|
||||
build.setLabel(target);
|
||||
|
||||
build.vmovups(xmm0, luauReg(rb));
|
||||
build.vmovups(luauReg(ra), xmm0);
|
||||
|
||||
build.setLabel(exit);
|
||||
}
|
||||
}
|
||||
|
||||
void emitInstOr(AssemblyBuilderX64& build, const Instruction* pc)
|
||||
{
|
||||
emitInstOrX(build, LUAU_INSN_A(*pc), LUAU_INSN_B(*pc), luauReg(LUAU_INSN_C(*pc)));
|
||||
}
|
||||
|
||||
void emitInstOrK(AssemblyBuilderX64& build, const Instruction* pc)
|
||||
{
|
||||
emitInstOrX(build, LUAU_INSN_A(*pc), LUAU_INSN_B(*pc), luauConstant(LUAU_INSN_C(*pc)));
|
||||
}
|
||||
|
||||
void emitInstGetTableN(AssemblyBuilderX64& build, const Instruction* pc, int pcpos)
|
||||
{
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
int rb = LUAU_INSN_B(*pc);
|
||||
int c = LUAU_INSN_C(*pc);
|
||||
|
||||
Label fallback, exit;
|
||||
|
||||
jumpIfTagIsNot(build, rb, LUA_TTABLE, fallback);
|
||||
|
||||
RegisterX64 table = rcx;
|
||||
build.mov(table, luauRegValue(rb));
|
||||
|
||||
// unsigned(c) < unsigned(h->sizearray)
|
||||
build.cmp(dword[table + offsetof(Table, sizearray)], c);
|
||||
build.jcc(Condition::BelowEqual, fallback);
|
||||
|
||||
jumpIfMetatablePresent(build, table, fallback);
|
||||
|
||||
build.mov(rax, qword[table + offsetof(Table, array)]);
|
||||
setLuauReg(build, xmm0, ra, xmmword[rax + c * sizeof(TValue)]);
|
||||
|
||||
build.jmp(exit);
|
||||
|
||||
// slow-path
|
||||
// TODO: move to the end of the function
|
||||
build.setLabel(fallback);
|
||||
TValue n;
|
||||
setnvalue(&n, c + 1);
|
||||
callGetTable(build, rb, build.bytes(&n, sizeof(n)), ra, pcpos);
|
||||
|
||||
build.setLabel(exit);
|
||||
}
|
||||
|
||||
void emitInstSetTableN(AssemblyBuilderX64& build, const Instruction* pc, int pcpos)
|
||||
{
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
int rb = LUAU_INSN_B(*pc);
|
||||
int c = LUAU_INSN_C(*pc);
|
||||
|
||||
Label fallback, exit;
|
||||
|
||||
jumpIfTagIsNot(build, rb, LUA_TTABLE, fallback);
|
||||
|
||||
RegisterX64 table = rcx;
|
||||
build.mov(table, luauRegValue(rb));
|
||||
|
||||
// unsigned(c) < unsigned(h->sizearray)
|
||||
build.cmp(dword[table + offsetof(Table, sizearray)], c);
|
||||
build.jcc(Condition::BelowEqual, fallback);
|
||||
|
||||
jumpIfMetatablePresent(build, table, fallback);
|
||||
jumpIfTableIsReadOnly(build, table, fallback);
|
||||
|
||||
// setobj2t(L, &h->array[c], ra);
|
||||
build.mov(rax, qword[table + offsetof(Table, array)]);
|
||||
build.vmovups(xmm0, luauReg(ra));
|
||||
build.vmovups(xmmword[rax + c * sizeof(TValue)], xmm0);
|
||||
|
||||
callBarrierTable(build, rax, table, ra, exit);
|
||||
build.jmp(exit);
|
||||
|
||||
// slow-path
|
||||
// TODO: move to the end of the function
|
||||
build.setLabel(fallback);
|
||||
TValue n;
|
||||
setnvalue(&n, c + 1);
|
||||
callSetTable(build, rb, build.bytes(&n, sizeof(n)), ra, pcpos);
|
||||
|
||||
build.setLabel(exit);
|
||||
}
|
||||
|
||||
void emitInstGetTable(AssemblyBuilderX64& build, const Instruction* pc, int pcpos)
|
||||
{
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
int rb = LUAU_INSN_B(*pc);
|
||||
int rc = LUAU_INSN_C(*pc);
|
||||
|
||||
Label fallback, exit;
|
||||
|
||||
jumpIfTagIsNot(build, rb, LUA_TTABLE, fallback);
|
||||
jumpIfTagIsNot(build, rc, LUA_TNUMBER, fallback);
|
||||
|
||||
// fast-path: table with a number index
|
||||
RegisterX64 table = rcx;
|
||||
build.mov(table, luauRegValue(rb));
|
||||
|
||||
convertNumberToIndexOrJump(build, xmm1, xmm0, eax, rc, fallback);
|
||||
|
||||
// index - 1
|
||||
build.dec(eax);
|
||||
|
||||
// unsigned(index - 1) < unsigned(h->sizearray)
|
||||
build.cmp(dword[table + offsetof(Table, sizearray)], eax);
|
||||
build.jcc(Condition::BelowEqual, fallback);
|
||||
|
||||
jumpIfMetatablePresent(build, table, fallback);
|
||||
|
||||
// setobj2s(L, ra, &h->array[unsigned(index - 1)]);
|
||||
build.mov(rdx, qword[table + offsetof(Table, array)]);
|
||||
build.shl(eax, kTValueSizeLog2);
|
||||
setLuauReg(build, xmm0, ra, xmmword[rdx + rax]);
|
||||
|
||||
build.jmp(exit);
|
||||
|
||||
build.setLabel(fallback);
|
||||
|
||||
// slow-path
|
||||
// TODO: move to the end of the function
|
||||
callGetTable(build, rb, luauRegValue(rc), ra, pcpos);
|
||||
|
||||
build.setLabel(exit);
|
||||
}
|
||||
|
||||
void emitInstSetTable(AssemblyBuilderX64& build, const Instruction* pc, int pcpos)
|
||||
{
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
int rb = LUAU_INSN_B(*pc);
|
||||
int rc = LUAU_INSN_C(*pc);
|
||||
|
||||
Label fallback, exit;
|
||||
|
||||
jumpIfTagIsNot(build, rb, LUA_TTABLE, fallback);
|
||||
jumpIfTagIsNot(build, rc, LUA_TNUMBER, fallback);
|
||||
|
||||
// fast-path: table with a number index
|
||||
RegisterX64 table = rcx;
|
||||
build.mov(table, luauRegValue(rb));
|
||||
|
||||
convertNumberToIndexOrJump(build, xmm1, xmm0, eax, rc, fallback);
|
||||
|
||||
// index - 1
|
||||
build.dec(eax);
|
||||
|
||||
// unsigned(index - 1) < unsigned(h->sizearray)
|
||||
build.cmp(dword[table + offsetof(Table, sizearray)], eax);
|
||||
build.jcc(Condition::BelowEqual, fallback);
|
||||
|
||||
jumpIfMetatablePresent(build, table, fallback);
|
||||
jumpIfTableIsReadOnly(build, table, fallback);
|
||||
|
||||
// setobj2t(L, &h->array[unsigned(index - 1)], ra);
|
||||
build.mov(rdx, qword[table + offsetof(Table, array)]);
|
||||
build.shl(eax, kTValueSizeLog2);
|
||||
build.vmovups(xmm0, luauReg(ra));
|
||||
build.vmovups(xmmword[rdx + rax], xmm0);
|
||||
|
||||
callBarrierTable(build, rdx, table, ra, exit);
|
||||
build.jmp(exit);
|
||||
|
||||
build.setLabel(fallback);
|
||||
|
||||
// slow-path
|
||||
// TODO: move to the end of the function
|
||||
callSetTable(build, rb, luauRegValue(rc), ra, pcpos);
|
||||
|
||||
build.setLabel(exit);
|
||||
}
|
||||
|
||||
} // namespace CodeGen
|
||||
} // namespace Luau
|
66
CodeGen/src/EmitInstructionX64.h
Normal file
66
CodeGen/src/EmitInstructionX64.h
Normal file
@ -0,0 +1,66 @@
|
||||
// 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>
|
||||
|
||||
typedef uint32_t Instruction;
|
||||
typedef struct lua_TValue TValue;
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
namespace CodeGen
|
||||
{
|
||||
|
||||
class AssemblyBuilderX64;
|
||||
enum class Condition;
|
||||
struct Label;
|
||||
struct NativeState;
|
||||
|
||||
void emitInstLoadNil(AssemblyBuilderX64& build, NativeState& data, const Instruction* pc);
|
||||
void emitInstLoadB(AssemblyBuilderX64& build, NativeState& data, const Instruction* pc, int pcpos, Label* labelarr);
|
||||
void emitInstLoadN(AssemblyBuilderX64& build, NativeState& data, const Instruction* pc);
|
||||
void emitInstLoadK(AssemblyBuilderX64& build, NativeState& data, const Instruction* pc, const TValue* k);
|
||||
void emitInstMove(AssemblyBuilderX64& build, NativeState& data, const Instruction* pc);
|
||||
void emitInstJump(AssemblyBuilderX64& build, NativeState& data, const Instruction* pc, int pcpos, Label* labelarr);
|
||||
void emitInstJumpBack(AssemblyBuilderX64& build, NativeState& data, const Instruction* pc, int pcpos, Label* labelarr);
|
||||
void emitInstJumpIf(AssemblyBuilderX64& build, NativeState& data, const Instruction* pc, int pcpos, Label* labelarr, bool not_);
|
||||
void emitInstJumpIfEq(AssemblyBuilderX64& build, NativeState& data, const Instruction* pc, int pcpos, Label* labelarr, bool not_);
|
||||
void emitInstJumpIfCond(AssemblyBuilderX64& build, NativeState& data, const Instruction* pc, int pcpos, Label* labelarr, Condition cond);
|
||||
void emitInstJumpX(AssemblyBuilderX64& build, NativeState& data, const Instruction* pc, int pcpos, Label* labelarr);
|
||||
void emitInstJumpxEqNil(AssemblyBuilderX64& build, NativeState& data, const Instruction* pc, const TValue* k, int pcpos, Label* labelarr);
|
||||
void emitInstJumpxEqB(AssemblyBuilderX64& build, NativeState& data, const Instruction* pc, const TValue* k, int pcpos, Label* labelarr);
|
||||
void emitInstJumpxEqN(AssemblyBuilderX64& build, NativeState& data, const Instruction* pc, const TValue* k, int pcpos, Label* labelarr);
|
||||
void emitInstJumpxEqS(AssemblyBuilderX64& build, NativeState& data, const Instruction* pc, const TValue* k, int pcpos, Label* labelarr);
|
||||
void emitInstAdd(AssemblyBuilderX64& build, const Instruction* pc, int pcpos);
|
||||
void emitInstSub(AssemblyBuilderX64& build, const Instruction* pc, int pcpos);
|
||||
void emitInstMul(AssemblyBuilderX64& build, const Instruction* pc, int pcpos);
|
||||
void emitInstDiv(AssemblyBuilderX64& build, const Instruction* pc, int pcpos);
|
||||
void emitInstMod(AssemblyBuilderX64& build, const Instruction* pc, int pcpos);
|
||||
void emitInstPow(AssemblyBuilderX64& build, const Instruction* pc, int pcpos);
|
||||
void emitInstAddK(AssemblyBuilderX64& build, const Instruction* pc, const TValue* k, int pcpos);
|
||||
void emitInstSubK(AssemblyBuilderX64& build, const Instruction* pc, const TValue* k, int pcpos);
|
||||
void emitInstMulK(AssemblyBuilderX64& build, const Instruction* pc, const TValue* k, int pcpos);
|
||||
void emitInstDivK(AssemblyBuilderX64& build, const Instruction* pc, const TValue* k, int pcpos);
|
||||
void emitInstModK(AssemblyBuilderX64& build, const Instruction* pc, const TValue* k, int pcpos);
|
||||
void emitInstPowK(AssemblyBuilderX64& build, const Instruction* pc, const TValue* k, int pcpos);
|
||||
void emitInstNot(AssemblyBuilderX64& build, const Instruction* pc);
|
||||
void emitInstMinus(AssemblyBuilderX64& build, const Instruction* pc, int pcpos);
|
||||
void emitInstLength(AssemblyBuilderX64& build, const Instruction* pc, int pcpos);
|
||||
void emitInstGetUpval(AssemblyBuilderX64& build, const Instruction* pc, int pcpos);
|
||||
void emitInstFastCall1(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr);
|
||||
void emitInstFastCall2(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr);
|
||||
void emitInstFastCall2K(AssemblyBuilderX64& build, const Instruction* pc, const TValue* k, int pcpos, Label* labelarr);
|
||||
void emitInstFastCall(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr);
|
||||
void emitInstForNPrep(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr);
|
||||
void emitInstForNLoop(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr);
|
||||
void emitInstAnd(AssemblyBuilderX64& build, const Instruction* pc);
|
||||
void emitInstAndK(AssemblyBuilderX64& build, const Instruction* pc);
|
||||
void emitInstOr(AssemblyBuilderX64& build, const Instruction* pc);
|
||||
void emitInstOrK(AssemblyBuilderX64& build, const Instruction* pc);
|
||||
void emitInstGetTableN(AssemblyBuilderX64& build, const Instruction* pc, int pcpos);
|
||||
void emitInstSetTableN(AssemblyBuilderX64& build, const Instruction* pc, int pcpos);
|
||||
void emitInstGetTable(AssemblyBuilderX64& build, const Instruction* pc, int pcpos);
|
||||
void emitInstSetTable(AssemblyBuilderX64& build, const Instruction* pc, int pcpos);
|
||||
|
||||
} // namespace CodeGen
|
||||
} // namespace Luau
|
@ -4,15 +4,17 @@
|
||||
#include "Fallbacks.h"
|
||||
#include "FallbacksProlog.h"
|
||||
|
||||
const Instruction* execute_LOP_NOP(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_NOP(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
LUAU_ASSERT(insn == 0);
|
||||
return pc;
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_LOADNIL(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_LOADNIL(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
|
||||
@ -20,8 +22,9 @@ const Instruction* execute_LOP_LOADNIL(lua_State* L, const Instruction* pc, Clos
|
||||
return pc;
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_LOADB(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_LOADB(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
|
||||
@ -32,8 +35,9 @@ const Instruction* execute_LOP_LOADB(lua_State* L, const Instruction* pc, Closur
|
||||
return pc;
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_LOADN(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_LOADN(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
|
||||
@ -41,8 +45,9 @@ const Instruction* execute_LOP_LOADN(lua_State* L, const Instruction* pc, Closur
|
||||
return pc;
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_LOADK(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_LOADK(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
TValue* kv = VM_KV(LUAU_INSN_D(insn));
|
||||
@ -51,8 +56,9 @@ const Instruction* execute_LOP_LOADK(lua_State* L, const Instruction* pc, Closur
|
||||
return pc;
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_MOVE(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_MOVE(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
StkId rb = VM_REG(LUAU_INSN_B(insn));
|
||||
@ -61,8 +67,9 @@ const Instruction* execute_LOP_MOVE(lua_State* L, const Instruction* pc, Closure
|
||||
return pc;
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_GETGLOBAL(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_GETGLOBAL(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
uint32_t aux = *pc++;
|
||||
@ -92,8 +99,9 @@ const Instruction* execute_LOP_GETGLOBAL(lua_State* L, const Instruction* pc, Cl
|
||||
}
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_SETGLOBAL(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_SETGLOBAL(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
uint32_t aux = *pc++;
|
||||
@ -124,8 +132,9 @@ const Instruction* execute_LOP_SETGLOBAL(lua_State* L, const Instruction* pc, Cl
|
||||
}
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_GETUPVAL(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_GETUPVAL(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
TValue* ur = VM_UV(LUAU_INSN_B(insn));
|
||||
@ -135,8 +144,9 @@ const Instruction* execute_LOP_GETUPVAL(lua_State* L, const Instruction* pc, Clo
|
||||
return pc;
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_SETUPVAL(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_SETUPVAL(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
TValue* ur = VM_UV(LUAU_INSN_B(insn));
|
||||
@ -147,8 +157,9 @@ const Instruction* execute_LOP_SETUPVAL(lua_State* L, const Instruction* pc, Clo
|
||||
return pc;
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_CLOSEUPVALS(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_CLOSEUPVALS(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
|
||||
@ -157,8 +168,9 @@ const Instruction* execute_LOP_CLOSEUPVALS(lua_State* L, const Instruction* pc,
|
||||
return pc;
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_GETIMPORT(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_GETIMPORT(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
TValue* kv = VM_KV(LUAU_INSN_D(insn));
|
||||
@ -183,8 +195,9 @@ const Instruction* execute_LOP_GETIMPORT(lua_State* L, const Instruction* pc, Cl
|
||||
}
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_GETTABLEKS(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_GETTABLEKS(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
StkId rb = VM_REG(LUAU_INSN_B(insn));
|
||||
@ -300,8 +313,9 @@ const Instruction* execute_LOP_GETTABLEKS(lua_State* L, const Instruction* pc, C
|
||||
return pc;
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_SETTABLEKS(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_SETTABLEKS(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
StkId rb = VM_REG(LUAU_INSN_B(insn));
|
||||
@ -376,8 +390,9 @@ const Instruction* execute_LOP_SETTABLEKS(lua_State* L, const Instruction* pc, C
|
||||
}
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_GETTABLE(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_GETTABLE(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
StkId rb = VM_REG(LUAU_INSN_B(insn));
|
||||
@ -406,8 +421,9 @@ const Instruction* execute_LOP_GETTABLE(lua_State* L, const Instruction* pc, Clo
|
||||
return pc;
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_SETTABLE(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_SETTABLE(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
StkId rb = VM_REG(LUAU_INSN_B(insn));
|
||||
@ -437,8 +453,9 @@ const Instruction* execute_LOP_SETTABLE(lua_State* L, const Instruction* pc, Clo
|
||||
return pc;
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_GETTABLEN(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_GETTABLEN(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
StkId rb = VM_REG(LUAU_INSN_B(insn));
|
||||
@ -465,8 +482,9 @@ const Instruction* execute_LOP_GETTABLEN(lua_State* L, const Instruction* pc, Cl
|
||||
return pc;
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_SETTABLEN(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_SETTABLEN(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
StkId rb = VM_REG(LUAU_INSN_B(insn));
|
||||
@ -494,8 +512,9 @@ const Instruction* execute_LOP_SETTABLEN(lua_State* L, const Instruction* pc, Cl
|
||||
return pc;
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_NEWCLOSURE(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_NEWCLOSURE(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
|
||||
@ -527,6 +546,7 @@ const Instruction* execute_LOP_NEWCLOSURE(lua_State* L, const Instruction* pc, C
|
||||
|
||||
default:
|
||||
LUAU_ASSERT(!"Unknown upvalue capture type");
|
||||
LUAU_UNREACHABLE(); // improves switch() codegen by eliding opcode bounds checks
|
||||
}
|
||||
}
|
||||
|
||||
@ -534,8 +554,9 @@ const Instruction* execute_LOP_NEWCLOSURE(lua_State* L, const Instruction* pc, C
|
||||
return pc;
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_NAMECALL(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_NAMECALL(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
StkId rb = VM_REG(LUAU_INSN_B(insn));
|
||||
@ -641,8 +662,9 @@ const Instruction* execute_LOP_NAMECALL(lua_State* L, const Instruction* pc, Clo
|
||||
return pc;
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_CALL(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_CALL(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
VM_INTERRUPT();
|
||||
Instruction insn = *pc++;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
@ -734,8 +756,9 @@ const Instruction* execute_LOP_CALL(lua_State* L, const Instruction* pc, Closure
|
||||
}
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_RETURN(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_RETURN(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
VM_INTERRUPT();
|
||||
Instruction insn = *pc++;
|
||||
StkId ra = &base[LUAU_INSN_A(insn)]; // note: this can point to L->top if b == LUA_MULTRET making VM_REG unsafe to use
|
||||
@ -785,8 +808,9 @@ const Instruction* execute_LOP_RETURN(lua_State* L, const Instruction* pc, Closu
|
||||
return pc;
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_JUMP(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_JUMP(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
|
||||
pc += LUAU_INSN_D(insn);
|
||||
@ -794,8 +818,9 @@ const Instruction* execute_LOP_JUMP(lua_State* L, const Instruction* pc, Closure
|
||||
return pc;
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_JUMPIF(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_JUMPIF(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
|
||||
@ -804,8 +829,9 @@ const Instruction* execute_LOP_JUMPIF(lua_State* L, const Instruction* pc, Closu
|
||||
return pc;
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_JUMPIFNOT(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_JUMPIFNOT(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
|
||||
@ -814,8 +840,9 @@ const Instruction* execute_LOP_JUMPIFNOT(lua_State* L, const Instruction* pc, Cl
|
||||
return pc;
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_JUMPIFEQ(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_JUMPIFEQ(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
uint32_t aux = *pc;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
@ -906,7 +933,9 @@ const Instruction* execute_LOP_JUMPIFEQ(lua_State* L, const Instruction* pc, Clo
|
||||
// slow path after switch()
|
||||
break;
|
||||
|
||||
default:;
|
||||
default:
|
||||
LUAU_ASSERT(!"Unknown value type");
|
||||
LUAU_UNREACHABLE(); // improves switch() codegen by eliding opcode bounds checks
|
||||
}
|
||||
|
||||
// slow-path: tables with metatables and userdata values
|
||||
@ -926,8 +955,9 @@ const Instruction* execute_LOP_JUMPIFEQ(lua_State* L, const Instruction* pc, Clo
|
||||
}
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_JUMPIFNOTEQ(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_JUMPIFNOTEQ(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
uint32_t aux = *pc;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
@ -1018,7 +1048,9 @@ const Instruction* execute_LOP_JUMPIFNOTEQ(lua_State* L, const Instruction* pc,
|
||||
// slow path after switch()
|
||||
break;
|
||||
|
||||
default:;
|
||||
default:
|
||||
LUAU_ASSERT(!"Unknown value type");
|
||||
LUAU_UNREACHABLE(); // improves switch() codegen by eliding opcode bounds checks
|
||||
}
|
||||
|
||||
// slow-path: tables with metatables and userdata values
|
||||
@ -1038,8 +1070,9 @@ const Instruction* execute_LOP_JUMPIFNOTEQ(lua_State* L, const Instruction* pc,
|
||||
}
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_JUMPIFLE(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_JUMPIFLE(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
uint32_t aux = *pc;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
@ -1071,8 +1104,9 @@ const Instruction* execute_LOP_JUMPIFLE(lua_State* L, const Instruction* pc, Clo
|
||||
}
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_JUMPIFNOTLE(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_JUMPIFNOTLE(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
uint32_t aux = *pc;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
@ -1104,8 +1138,9 @@ const Instruction* execute_LOP_JUMPIFNOTLE(lua_State* L, const Instruction* pc,
|
||||
}
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_JUMPIFLT(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_JUMPIFLT(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
uint32_t aux = *pc;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
@ -1137,8 +1172,9 @@ const Instruction* execute_LOP_JUMPIFLT(lua_State* L, const Instruction* pc, Clo
|
||||
}
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_JUMPIFNOTLT(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_JUMPIFNOTLT(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
uint32_t aux = *pc;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
@ -1170,8 +1206,9 @@ const Instruction* execute_LOP_JUMPIFNOTLT(lua_State* L, const Instruction* pc,
|
||||
}
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_ADD(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_ADD(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
StkId rb = VM_REG(LUAU_INSN_B(insn));
|
||||
@ -1216,8 +1253,9 @@ const Instruction* execute_LOP_ADD(lua_State* L, const Instruction* pc, Closure*
|
||||
}
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_SUB(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_SUB(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
StkId rb = VM_REG(LUAU_INSN_B(insn));
|
||||
@ -1262,8 +1300,9 @@ const Instruction* execute_LOP_SUB(lua_State* L, const Instruction* pc, Closure*
|
||||
}
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_MUL(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_MUL(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
StkId rb = VM_REG(LUAU_INSN_B(insn));
|
||||
@ -1323,8 +1362,9 @@ const Instruction* execute_LOP_MUL(lua_State* L, const Instruction* pc, Closure*
|
||||
}
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_DIV(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_DIV(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
StkId rb = VM_REG(LUAU_INSN_B(insn));
|
||||
@ -1384,8 +1424,9 @@ const Instruction* execute_LOP_DIV(lua_State* L, const Instruction* pc, Closure*
|
||||
}
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_MOD(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_MOD(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
StkId rb = VM_REG(LUAU_INSN_B(insn));
|
||||
@ -1407,8 +1448,9 @@ const Instruction* execute_LOP_MOD(lua_State* L, const Instruction* pc, Closure*
|
||||
}
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_POW(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_POW(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
StkId rb = VM_REG(LUAU_INSN_B(insn));
|
||||
@ -1428,8 +1470,9 @@ const Instruction* execute_LOP_POW(lua_State* L, const Instruction* pc, Closure*
|
||||
}
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_ADDK(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_ADDK(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
StkId rb = VM_REG(LUAU_INSN_B(insn));
|
||||
@ -1449,8 +1492,9 @@ const Instruction* execute_LOP_ADDK(lua_State* L, const Instruction* pc, Closure
|
||||
}
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_SUBK(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_SUBK(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
StkId rb = VM_REG(LUAU_INSN_B(insn));
|
||||
@ -1470,8 +1514,9 @@ const Instruction* execute_LOP_SUBK(lua_State* L, const Instruction* pc, Closure
|
||||
}
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_MULK(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_MULK(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
StkId rb = VM_REG(LUAU_INSN_B(insn));
|
||||
@ -1516,8 +1561,9 @@ const Instruction* execute_LOP_MULK(lua_State* L, const Instruction* pc, Closure
|
||||
}
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_DIVK(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_DIVK(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
StkId rb = VM_REG(LUAU_INSN_B(insn));
|
||||
@ -1562,8 +1608,9 @@ const Instruction* execute_LOP_DIVK(lua_State* L, const Instruction* pc, Closure
|
||||
}
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_MODK(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_MODK(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
StkId rb = VM_REG(LUAU_INSN_B(insn));
|
||||
@ -1585,8 +1632,9 @@ const Instruction* execute_LOP_MODK(lua_State* L, const Instruction* pc, Closure
|
||||
}
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_POWK(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_POWK(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
StkId rb = VM_REG(LUAU_INSN_B(insn));
|
||||
@ -1612,8 +1660,9 @@ const Instruction* execute_LOP_POWK(lua_State* L, const Instruction* pc, Closure
|
||||
}
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_AND(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_AND(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
StkId rb = VM_REG(LUAU_INSN_B(insn));
|
||||
@ -1623,8 +1672,9 @@ const Instruction* execute_LOP_AND(lua_State* L, const Instruction* pc, Closure*
|
||||
return pc;
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_OR(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_OR(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
StkId rb = VM_REG(LUAU_INSN_B(insn));
|
||||
@ -1634,8 +1684,9 @@ const Instruction* execute_LOP_OR(lua_State* L, const Instruction* pc, Closure*
|
||||
return pc;
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_ANDK(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_ANDK(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
StkId rb = VM_REG(LUAU_INSN_B(insn));
|
||||
@ -1645,8 +1696,9 @@ const Instruction* execute_LOP_ANDK(lua_State* L, const Instruction* pc, Closure
|
||||
return pc;
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_ORK(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_ORK(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
StkId rb = VM_REG(LUAU_INSN_B(insn));
|
||||
@ -1656,8 +1708,9 @@ const Instruction* execute_LOP_ORK(lua_State* L, const Instruction* pc, Closure*
|
||||
return pc;
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_CONCAT(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_CONCAT(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
int b = LUAU_INSN_B(insn);
|
||||
int c = LUAU_INSN_C(insn);
|
||||
@ -1672,8 +1725,9 @@ const Instruction* execute_LOP_CONCAT(lua_State* L, const Instruction* pc, Closu
|
||||
return pc;
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_NOT(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_NOT(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
StkId rb = VM_REG(LUAU_INSN_B(insn));
|
||||
@ -1683,8 +1737,9 @@ const Instruction* execute_LOP_NOT(lua_State* L, const Instruction* pc, Closure*
|
||||
return pc;
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_MINUS(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_MINUS(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
StkId rb = VM_REG(LUAU_INSN_B(insn));
|
||||
@ -1726,8 +1781,9 @@ const Instruction* execute_LOP_MINUS(lua_State* L, const Instruction* pc, Closur
|
||||
}
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_LENGTH(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_LENGTH(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
StkId rb = VM_REG(LUAU_INSN_B(insn));
|
||||
@ -1764,8 +1820,9 @@ const Instruction* execute_LOP_LENGTH(lua_State* L, const Instruction* pc, Closu
|
||||
}
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_NEWTABLE(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_NEWTABLE(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
int b = LUAU_INSN_B(insn);
|
||||
@ -1776,8 +1833,9 @@ const Instruction* execute_LOP_NEWTABLE(lua_State* L, const Instruction* pc, Clo
|
||||
return pc;
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_DUPTABLE(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_DUPTABLE(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
TValue* kv = VM_KV(LUAU_INSN_D(insn));
|
||||
@ -1787,8 +1845,9 @@ const Instruction* execute_LOP_DUPTABLE(lua_State* L, const Instruction* pc, Clo
|
||||
return pc;
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_SETLIST(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_SETLIST(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
StkId rb = &base[LUAU_INSN_B(insn)]; // note: this can point to L->top if c == LUA_MULTRET making VM_REG unsafe to use
|
||||
@ -1819,8 +1878,9 @@ const Instruction* execute_LOP_SETLIST(lua_State* L, const Instruction* pc, Clos
|
||||
return pc;
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_FORNPREP(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_FORNPREP(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
|
||||
@ -1843,8 +1903,9 @@ const Instruction* execute_LOP_FORNPREP(lua_State* L, const Instruction* pc, Clo
|
||||
return pc;
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_FORNLOOP(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_FORNLOOP(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
VM_INTERRUPT();
|
||||
Instruction insn = *pc++;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
@ -1870,8 +1931,9 @@ const Instruction* execute_LOP_FORNLOOP(lua_State* L, const Instruction* pc, Clo
|
||||
}
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_FORGPREP(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_FORGPREP(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
|
||||
@ -1926,8 +1988,9 @@ const Instruction* execute_LOP_FORGPREP(lua_State* L, const Instruction* pc, Clo
|
||||
return pc;
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_FORGLOOP(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_FORGLOOP(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
VM_INTERRUPT();
|
||||
Instruction insn = *pc++;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
@ -2026,8 +2089,9 @@ const Instruction* execute_LOP_FORGLOOP(lua_State* L, const Instruction* pc, Clo
|
||||
}
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_FORGPREP_INEXT(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_FORGPREP_INEXT(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
|
||||
@ -2048,14 +2112,16 @@ const Instruction* execute_LOP_FORGPREP_INEXT(lua_State* L, const Instruction* p
|
||||
return pc;
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_DEP_FORGLOOP_INEXT(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_DEP_FORGLOOP_INEXT(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
LUAU_ASSERT(!"Unsupported deprecated opcode");
|
||||
LUAU_UNREACHABLE();
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_FORGPREP_NEXT(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_FORGPREP_NEXT(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
|
||||
@ -2076,14 +2142,16 @@ const Instruction* execute_LOP_FORGPREP_NEXT(lua_State* L, const Instruction* pc
|
||||
return pc;
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_DEP_FORGLOOP_NEXT(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_DEP_FORGLOOP_NEXT(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
LUAU_ASSERT(!"Unsupported deprecated opcode");
|
||||
LUAU_UNREACHABLE();
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_GETVARARGS(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_GETVARARGS(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
int b = LUAU_INSN_B(insn) - 1;
|
||||
int n = cast_int(base - L->ci->func) - cl->l.p->numparams - 1;
|
||||
@ -2111,8 +2179,9 @@ const Instruction* execute_LOP_GETVARARGS(lua_State* L, const Instruction* pc, C
|
||||
}
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_DUPCLOSURE(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_DUPCLOSURE(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
TValue* kv = VM_KV(LUAU_INSN_D(insn));
|
||||
@ -2166,8 +2235,9 @@ const Instruction* execute_LOP_DUPCLOSURE(lua_State* L, const Instruction* pc, C
|
||||
return pc;
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_PREPVARARGS(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_PREPVARARGS(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
int numparams = LUAU_INSN_A(insn);
|
||||
|
||||
@ -2196,8 +2266,9 @@ const Instruction* execute_LOP_PREPVARARGS(lua_State* L, const Instruction* pc,
|
||||
return pc;
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_JUMPBACK(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_JUMPBACK(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
VM_INTERRUPT();
|
||||
Instruction insn = *pc++;
|
||||
|
||||
@ -2206,8 +2277,9 @@ const Instruction* execute_LOP_JUMPBACK(lua_State* L, const Instruction* pc, Clo
|
||||
return pc;
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_LOADKX(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_LOADKX(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
uint32_t aux = *pc++;
|
||||
@ -2217,8 +2289,9 @@ const Instruction* execute_LOP_LOADKX(lua_State* L, const Instruction* pc, Closu
|
||||
return pc;
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_JUMPX(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_JUMPX(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
VM_INTERRUPT();
|
||||
Instruction insn = *pc++;
|
||||
|
||||
@ -2227,8 +2300,9 @@ const Instruction* execute_LOP_JUMPX(lua_State* L, const Instruction* pc, Closur
|
||||
return pc;
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_FASTCALL(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_FASTCALL(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
int bfid = LUAU_INSN_A(insn);
|
||||
int skip = LUAU_INSN_C(insn);
|
||||
@ -2273,8 +2347,9 @@ const Instruction* execute_LOP_FASTCALL(lua_State* L, const Instruction* pc, Clo
|
||||
}
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_COVERAGE(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_COVERAGE(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
int hits = LUAU_INSN_E(insn);
|
||||
|
||||
@ -2285,26 +2360,30 @@ const Instruction* execute_LOP_COVERAGE(lua_State* L, const Instruction* pc, Clo
|
||||
return pc;
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_CAPTURE(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_CAPTURE(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
LUAU_ASSERT(!"CAPTURE is a pseudo-opcode and must be executed as part of NEWCLOSURE");
|
||||
LUAU_UNREACHABLE();
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_DEP_JUMPIFEQK(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_DEP_JUMPIFEQK(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
LUAU_ASSERT(!"Unsupported deprecated opcode");
|
||||
LUAU_UNREACHABLE();
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_DEP_JUMPIFNOTEQK(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_DEP_JUMPIFNOTEQK(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
LUAU_ASSERT(!"Unsupported deprecated opcode");
|
||||
LUAU_UNREACHABLE();
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_FASTCALL1(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_FASTCALL1(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
int bfid = LUAU_INSN_A(insn);
|
||||
TValue* arg = VM_REG(LUAU_INSN_B(insn));
|
||||
@ -2349,8 +2428,9 @@ const Instruction* execute_LOP_FASTCALL1(lua_State* L, const Instruction* pc, Cl
|
||||
}
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_FASTCALL2(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_FASTCALL2(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
int bfid = LUAU_INSN_A(insn);
|
||||
int skip = LUAU_INSN_C(insn) - 1;
|
||||
@ -2397,8 +2477,9 @@ const Instruction* execute_LOP_FASTCALL2(lua_State* L, const Instruction* pc, Cl
|
||||
}
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_FASTCALL2K(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_FASTCALL2K(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
int bfid = LUAU_INSN_A(insn);
|
||||
int skip = LUAU_INSN_C(insn) - 1;
|
||||
@ -2445,14 +2526,15 @@ const Instruction* execute_LOP_FASTCALL2K(lua_State* L, const Instruction* pc, C
|
||||
}
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_BREAK(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_BREAK(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
LUAU_ASSERT(!"Unsupported deprecated opcode");
|
||||
LUAU_UNREACHABLE();
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_JUMPXEQKNIL(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_JUMPXEQKNIL(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
uint32_t aux = *pc;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
@ -2464,8 +2546,9 @@ const Instruction* execute_LOP_JUMPXEQKNIL(lua_State* L, const Instruction* pc,
|
||||
return pc;
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_JUMPXEQKB(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_JUMPXEQKB(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
uint32_t aux = *pc;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
@ -2475,8 +2558,9 @@ const Instruction* execute_LOP_JUMPXEQKB(lua_State* L, const Instruction* pc, Cl
|
||||
return pc;
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_JUMPXEQKN(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_JUMPXEQKN(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
uint32_t aux = *pc;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
@ -2497,8 +2581,9 @@ const Instruction* execute_LOP_JUMPXEQKN(lua_State* L, const Instruction* pc, Cl
|
||||
return pc;
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_JUMPXEQKS(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)
|
||||
const Instruction* execute_LOP_JUMPXEQKS(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
uint32_t aux = *pc;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
|
@ -10,84 +10,84 @@ typedef uint32_t Instruction;
|
||||
typedef struct lua_TValue TValue;
|
||||
typedef TValue* StkId;
|
||||
|
||||
const Instruction* execute_LOP_NOP(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_LOADNIL(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_LOADB(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_LOADN(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_LOADK(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_MOVE(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_GETGLOBAL(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_SETGLOBAL(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_GETUPVAL(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_SETUPVAL(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_CLOSEUPVALS(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_GETIMPORT(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_GETTABLEKS(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_SETTABLEKS(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_GETTABLE(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_SETTABLE(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_GETTABLEN(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_SETTABLEN(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_NEWCLOSURE(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_NAMECALL(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_CALL(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_RETURN(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_JUMP(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_JUMPIF(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_JUMPIFNOT(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_JUMPIFEQ(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_JUMPIFNOTEQ(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_JUMPIFLE(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_JUMPIFNOTLE(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_JUMPIFLT(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_JUMPIFNOTLT(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_ADD(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_SUB(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_MUL(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_DIV(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_MOD(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_POW(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_ADDK(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_SUBK(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_MULK(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_DIVK(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_MODK(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_POWK(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_AND(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_OR(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_ANDK(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_ORK(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_CONCAT(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_NOT(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_MINUS(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_LENGTH(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_NEWTABLE(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_DUPTABLE(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_SETLIST(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_FORNPREP(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_FORNLOOP(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_FORGPREP(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_FORGLOOP(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_FORGPREP_INEXT(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_DEP_FORGLOOP_INEXT(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_FORGPREP_NEXT(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_DEP_FORGLOOP_NEXT(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_GETVARARGS(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_DUPCLOSURE(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_PREPVARARGS(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_JUMPBACK(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_LOADKX(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_JUMPX(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_FASTCALL(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_COVERAGE(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_CAPTURE(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_DEP_JUMPIFEQK(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_DEP_JUMPIFNOTEQK(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_FASTCALL1(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_FASTCALL2(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_FASTCALL2K(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_BREAK(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_JUMPXEQKNIL(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_JUMPXEQKB(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_JUMPXEQKN(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_JUMPXEQKS(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_NOP(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_LOADNIL(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_LOADB(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_LOADN(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_LOADK(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_MOVE(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_GETGLOBAL(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_SETGLOBAL(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_GETUPVAL(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_SETUPVAL(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_CLOSEUPVALS(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_GETIMPORT(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_GETTABLEKS(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_SETTABLEKS(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_GETTABLE(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_SETTABLE(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_GETTABLEN(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_SETTABLEN(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_NEWCLOSURE(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_NAMECALL(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_CALL(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_RETURN(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_JUMP(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_JUMPIF(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_JUMPIFNOT(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_JUMPIFEQ(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_JUMPIFNOTEQ(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_JUMPIFLE(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_JUMPIFNOTLE(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_JUMPIFLT(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_JUMPIFNOTLT(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_ADD(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_SUB(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_MUL(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_DIV(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_MOD(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_POW(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_ADDK(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_SUBK(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_MULK(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_DIVK(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_MODK(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_POWK(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_AND(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_OR(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_ANDK(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_ORK(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_CONCAT(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_NOT(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_MINUS(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_LENGTH(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_NEWTABLE(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_DUPTABLE(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_SETLIST(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_FORNPREP(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_FORNLOOP(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_FORGPREP(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_FORGLOOP(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_FORGPREP_INEXT(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_DEP_FORGLOOP_INEXT(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_FORGPREP_NEXT(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_DEP_FORGLOOP_NEXT(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_GETVARARGS(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_DUPCLOSURE(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_PREPVARARGS(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_JUMPBACK(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_LOADKX(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_JUMPX(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_FASTCALL(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_COVERAGE(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_CAPTURE(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_DEP_JUMPIFEQK(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_DEP_JUMPIFNOTEQK(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_FASTCALL1(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_FASTCALL2(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_FASTCALL2K(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_BREAK(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_JUMPXEQKNIL(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_JUMPXEQKB(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_JUMPXEQKN(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_JUMPXEQKS(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
|
122
CodeGen/src/NativeState.cpp
Normal file
122
CodeGen/src/NativeState.cpp
Normal file
@ -0,0 +1,122 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "NativeState.h"
|
||||
|
||||
#include "Luau/UnwindBuilder.h"
|
||||
|
||||
#include "CustomExecUtils.h"
|
||||
#include "Fallbacks.h"
|
||||
|
||||
#include "lbuiltins.h"
|
||||
#include "lgc.h"
|
||||
#include "ltable.h"
|
||||
#include "lvm.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#define CODEGEN_SET_FALLBACK(op, flags) data.context.fallback[op] = {execute_##op, flags}
|
||||
#define CODEGEN_SET_NAME(op) data.names[op] = #op
|
||||
|
||||
// Similar to a dispatch table in lvmexecute.cpp
|
||||
#define CODEGEN_SET_NAMES() \
|
||||
CODEGEN_SET_NAME(LOP_NOP), CODEGEN_SET_NAME(LOP_BREAK), CODEGEN_SET_NAME(LOP_LOADNIL), CODEGEN_SET_NAME(LOP_LOADB), CODEGEN_SET_NAME(LOP_LOADN), \
|
||||
CODEGEN_SET_NAME(LOP_LOADK), CODEGEN_SET_NAME(LOP_MOVE), CODEGEN_SET_NAME(LOP_GETGLOBAL), CODEGEN_SET_NAME(LOP_SETGLOBAL), \
|
||||
CODEGEN_SET_NAME(LOP_GETUPVAL), CODEGEN_SET_NAME(LOP_SETUPVAL), CODEGEN_SET_NAME(LOP_CLOSEUPVALS), CODEGEN_SET_NAME(LOP_GETIMPORT), \
|
||||
CODEGEN_SET_NAME(LOP_GETTABLE), CODEGEN_SET_NAME(LOP_SETTABLE), CODEGEN_SET_NAME(LOP_GETTABLEKS), CODEGEN_SET_NAME(LOP_SETTABLEKS), \
|
||||
CODEGEN_SET_NAME(LOP_GETTABLEN), CODEGEN_SET_NAME(LOP_SETTABLEN), CODEGEN_SET_NAME(LOP_NEWCLOSURE), CODEGEN_SET_NAME(LOP_NAMECALL), \
|
||||
CODEGEN_SET_NAME(LOP_CALL), CODEGEN_SET_NAME(LOP_RETURN), CODEGEN_SET_NAME(LOP_JUMP), CODEGEN_SET_NAME(LOP_JUMPBACK), \
|
||||
CODEGEN_SET_NAME(LOP_JUMPIF), CODEGEN_SET_NAME(LOP_JUMPIFNOT), CODEGEN_SET_NAME(LOP_JUMPIFEQ), CODEGEN_SET_NAME(LOP_JUMPIFLE), \
|
||||
CODEGEN_SET_NAME(LOP_JUMPIFLT), CODEGEN_SET_NAME(LOP_JUMPIFNOTEQ), CODEGEN_SET_NAME(LOP_JUMPIFNOTLE), CODEGEN_SET_NAME(LOP_JUMPIFNOTLT), \
|
||||
CODEGEN_SET_NAME(LOP_ADD), CODEGEN_SET_NAME(LOP_SUB), CODEGEN_SET_NAME(LOP_MUL), CODEGEN_SET_NAME(LOP_DIV), CODEGEN_SET_NAME(LOP_MOD), \
|
||||
CODEGEN_SET_NAME(LOP_POW), CODEGEN_SET_NAME(LOP_ADDK), CODEGEN_SET_NAME(LOP_SUBK), CODEGEN_SET_NAME(LOP_MULK), CODEGEN_SET_NAME(LOP_DIVK), \
|
||||
CODEGEN_SET_NAME(LOP_MODK), CODEGEN_SET_NAME(LOP_POWK), CODEGEN_SET_NAME(LOP_AND), CODEGEN_SET_NAME(LOP_OR), CODEGEN_SET_NAME(LOP_ANDK), \
|
||||
CODEGEN_SET_NAME(LOP_ORK), CODEGEN_SET_NAME(LOP_CONCAT), CODEGEN_SET_NAME(LOP_NOT), CODEGEN_SET_NAME(LOP_MINUS), \
|
||||
CODEGEN_SET_NAME(LOP_LENGTH), CODEGEN_SET_NAME(LOP_NEWTABLE), CODEGEN_SET_NAME(LOP_DUPTABLE), CODEGEN_SET_NAME(LOP_SETLIST), \
|
||||
CODEGEN_SET_NAME(LOP_FORNPREP), CODEGEN_SET_NAME(LOP_FORNLOOP), CODEGEN_SET_NAME(LOP_FORGLOOP), CODEGEN_SET_NAME(LOP_FORGPREP_INEXT), \
|
||||
CODEGEN_SET_NAME(LOP_DEP_FORGLOOP_INEXT), CODEGEN_SET_NAME(LOP_FORGPREP_NEXT), CODEGEN_SET_NAME(LOP_DEP_FORGLOOP_NEXT), \
|
||||
CODEGEN_SET_NAME(LOP_GETVARARGS), CODEGEN_SET_NAME(LOP_DUPCLOSURE), CODEGEN_SET_NAME(LOP_PREPVARARGS), CODEGEN_SET_NAME(LOP_LOADKX), \
|
||||
CODEGEN_SET_NAME(LOP_JUMPX), CODEGEN_SET_NAME(LOP_FASTCALL), CODEGEN_SET_NAME(LOP_COVERAGE), CODEGEN_SET_NAME(LOP_CAPTURE), \
|
||||
CODEGEN_SET_NAME(LOP_DEP_JUMPIFEQK), CODEGEN_SET_NAME(LOP_DEP_JUMPIFNOTEQK), CODEGEN_SET_NAME(LOP_FASTCALL1), \
|
||||
CODEGEN_SET_NAME(LOP_FASTCALL2), CODEGEN_SET_NAME(LOP_FASTCALL2K), CODEGEN_SET_NAME(LOP_FORGPREP), CODEGEN_SET_NAME(LOP_JUMPXEQKNIL), \
|
||||
CODEGEN_SET_NAME(LOP_JUMPXEQKB), CODEGEN_SET_NAME(LOP_JUMPXEQKN), CODEGEN_SET_NAME(LOP_JUMPXEQKS)
|
||||
|
||||
static int luauF_missing(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
namespace CodeGen
|
||||
{
|
||||
|
||||
constexpr unsigned kBlockSize = 4 * 1024 * 1024;
|
||||
constexpr unsigned kMaxTotalSize = 256 * 1024 * 1024;
|
||||
|
||||
NativeState::NativeState()
|
||||
: codeAllocator(kBlockSize, kMaxTotalSize)
|
||||
{
|
||||
}
|
||||
|
||||
NativeState::~NativeState() = default;
|
||||
|
||||
void initFallbackTable(NativeState& data)
|
||||
{
|
||||
CODEGEN_SET_FALLBACK(LOP_GETGLOBAL, 0);
|
||||
CODEGEN_SET_FALLBACK(LOP_SETGLOBAL, 0);
|
||||
CODEGEN_SET_FALLBACK(LOP_SETUPVAL, 0);
|
||||
CODEGEN_SET_FALLBACK(LOP_CLOSEUPVALS, 0);
|
||||
CODEGEN_SET_FALLBACK(LOP_GETIMPORT, 0);
|
||||
CODEGEN_SET_FALLBACK(LOP_GETTABLEKS, 0);
|
||||
CODEGEN_SET_FALLBACK(LOP_SETTABLEKS, 0);
|
||||
CODEGEN_SET_FALLBACK(LOP_NEWCLOSURE, kFallbackUpdatePc);
|
||||
CODEGEN_SET_FALLBACK(LOP_NAMECALL, 0);
|
||||
CODEGEN_SET_FALLBACK(LOP_CALL, kFallbackUpdateCi | kFallbackCheckInterrupt);
|
||||
CODEGEN_SET_FALLBACK(LOP_RETURN, kFallbackUpdateCi | kFallbackCheckInterrupt);
|
||||
CODEGEN_SET_FALLBACK(LOP_CONCAT, 0);
|
||||
CODEGEN_SET_FALLBACK(LOP_NEWTABLE, 0);
|
||||
CODEGEN_SET_FALLBACK(LOP_DUPTABLE, 0);
|
||||
CODEGEN_SET_FALLBACK(LOP_SETLIST, kFallbackCheckInterrupt);
|
||||
CODEGEN_SET_FALLBACK(LOP_FORGPREP, kFallbackUpdatePc);
|
||||
CODEGEN_SET_FALLBACK(LOP_FORGLOOP, kFallbackUpdatePc | kFallbackCheckInterrupt);
|
||||
CODEGEN_SET_FALLBACK(LOP_FORGPREP_INEXT, kFallbackUpdatePc);
|
||||
CODEGEN_SET_FALLBACK(LOP_FORGPREP_NEXT, kFallbackUpdatePc);
|
||||
CODEGEN_SET_FALLBACK(LOP_GETVARARGS, 0);
|
||||
CODEGEN_SET_FALLBACK(LOP_DUPCLOSURE, 0);
|
||||
CODEGEN_SET_FALLBACK(LOP_PREPVARARGS, 0);
|
||||
CODEGEN_SET_FALLBACK(LOP_LOADKX, 0);
|
||||
CODEGEN_SET_FALLBACK(LOP_COVERAGE, 0);
|
||||
CODEGEN_SET_FALLBACK(LOP_BREAK, 0);
|
||||
}
|
||||
|
||||
void initHelperFunctions(NativeState& data)
|
||||
{
|
||||
static_assert(sizeof(data.context.luauF_table) / sizeof(data.context.luauF_table[0]) == sizeof(luauF_table) / sizeof(luauF_table[0]),
|
||||
"fast call tables are not of the same length");
|
||||
|
||||
// Replace missing fast call functions with an empty placeholder that forces LOP_CALL fallback
|
||||
for (int i = 0; i < sizeof(data.context.luauF_table) / sizeof(data.context.luauF_table[0]); i++)
|
||||
data.context.luauF_table[i] = luauF_table[i] ? luauF_table[i] : luauF_missing;
|
||||
|
||||
data.context.luaV_lessthan = luaV_lessthan;
|
||||
data.context.luaV_lessequal = luaV_lessequal;
|
||||
data.context.luaV_equalval = luaV_equalval;
|
||||
data.context.luaV_doarith = luaV_doarith;
|
||||
data.context.luaV_dolen = luaV_dolen;
|
||||
data.context.luaV_prepareFORN = luaV_prepareFORN;
|
||||
data.context.luaV_gettable = luaV_gettable;
|
||||
data.context.luaV_settable = luaV_settable;
|
||||
|
||||
data.context.luaH_getn = luaH_getn;
|
||||
|
||||
data.context.luaC_barriertable = luaC_barriertable;
|
||||
|
||||
data.context.libm_pow = pow;
|
||||
}
|
||||
|
||||
void initInstructionNames(NativeState& data)
|
||||
{
|
||||
CODEGEN_SET_NAMES();
|
||||
}
|
||||
|
||||
} // namespace CodeGen
|
||||
} // namespace Luau
|
94
CodeGen/src/NativeState.h
Normal file
94
CodeGen/src/NativeState.h
Normal file
@ -0,0 +1,94 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Bytecode.h"
|
||||
#include "Luau/CodeAllocator.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "lobject.h"
|
||||
#include "ltm.h"
|
||||
|
||||
typedef int (*luau_FastFunction)(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams);
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
namespace CodeGen
|
||||
{
|
||||
|
||||
class UnwindBuilder;
|
||||
|
||||
using FallbackFn = const Instruction*(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
|
||||
constexpr uint8_t kFallbackUpdatePc = 1 << 0;
|
||||
constexpr uint8_t kFallbackUpdateCi = 1 << 1;
|
||||
constexpr uint8_t kFallbackCheckInterrupt = 1 << 2;
|
||||
|
||||
struct NativeFallback
|
||||
{
|
||||
FallbackFn* fallback;
|
||||
uint8_t flags;
|
||||
};
|
||||
|
||||
struct NativeProto
|
||||
{
|
||||
uintptr_t* instTargets = nullptr;
|
||||
|
||||
Proto* proto = nullptr;
|
||||
uint32_t location = 0;
|
||||
};
|
||||
|
||||
struct NativeContext
|
||||
{
|
||||
// Gateway (C => native transition) entry & exit, compiled at runtime
|
||||
uint8_t* gateEntry = nullptr;
|
||||
uint8_t* gateExit = nullptr;
|
||||
|
||||
// Opcode fallbacks, implemented in C
|
||||
NativeFallback fallback[LOP__COUNT] = {};
|
||||
|
||||
// Fast call methods, implemented in C
|
||||
luau_FastFunction luauF_table[256] = {};
|
||||
|
||||
// Helper functions, implemented in C
|
||||
int (*luaV_lessthan)(lua_State* L, const TValue* l, const TValue* r) = nullptr;
|
||||
int (*luaV_lessequal)(lua_State* L, const TValue* l, const TValue* r) = nullptr;
|
||||
int (*luaV_equalval)(lua_State* L, const TValue* t1, const TValue* t2) = nullptr;
|
||||
void (*luaV_doarith)(lua_State* L, StkId ra, const TValue* rb, const TValue* rc, TMS op) = nullptr;
|
||||
void (*luaV_dolen)(lua_State* L, StkId ra, const TValue* rb) = nullptr;
|
||||
void (*luaV_prepareFORN)(lua_State* L, StkId plimit, StkId pstep, StkId pinit) = nullptr;
|
||||
void (*luaV_gettable)(lua_State* L, const TValue* t, TValue* key, StkId val) = nullptr;
|
||||
void (*luaV_settable)(lua_State* L, const TValue* t, TValue* key, StkId val) = nullptr;
|
||||
|
||||
int (*luaH_getn)(Table* t) = nullptr;
|
||||
|
||||
void (*luaC_barriertable)(lua_State* L, Table* t, GCObject* v) = nullptr;
|
||||
|
||||
double (*libm_pow)(double, double) = nullptr;
|
||||
};
|
||||
|
||||
struct NativeState
|
||||
{
|
||||
NativeState();
|
||||
~NativeState();
|
||||
|
||||
CodeAllocator codeAllocator;
|
||||
std::unique_ptr<UnwindBuilder> unwindBuilder;
|
||||
|
||||
// For annotations in assembly text generation
|
||||
const char* names[LOP__COUNT] = {};
|
||||
|
||||
uint8_t* gateData = nullptr;
|
||||
size_t gateDataSize = 0;
|
||||
|
||||
NativeContext context;
|
||||
};
|
||||
|
||||
void initFallbackTable(NativeState& data);
|
||||
void initHelperFunctions(NativeState& data);
|
||||
void initInstructionNames(NativeState& data);
|
||||
|
||||
} // namespace CodeGen
|
||||
} // namespace Luau
|
@ -122,3 +122,9 @@ FValue<T>* FValue<T>::list = nullptr;
|
||||
{ \
|
||||
Luau::FValue<int> flag(#flag, def, true); \
|
||||
}
|
||||
|
||||
#if defined(__GNUC__)
|
||||
#define LUAU_PRINTF_ATTR(fmt, arg) __attribute__((format(printf, fmt, arg)))
|
||||
#else
|
||||
#define LUAU_PRINTF_ATTR(fmt, arg)
|
||||
#endif
|
||||
|
@ -11,7 +11,6 @@ inline bool isFlagExperimental(const char* flag)
|
||||
// Flags in this list are disabled by default in various command-line tools. They may have behavior that is not fully final,
|
||||
// or critical bugs that are found after the code has been submitted.
|
||||
static const char* kList[] = {
|
||||
"LuauLowerBoundsCalculation",
|
||||
"LuauInterpolatedStringBaseSupport",
|
||||
"LuauInstantiateInSubtyping", // requires some fixes to lua-apps code
|
||||
// makes sure we always have at least one entry
|
||||
|
@ -102,6 +102,11 @@ public:
|
||||
|
||||
void setDumpSource(const std::string& source);
|
||||
|
||||
bool needsDebugRemarks() const
|
||||
{
|
||||
return (dumpFlags & Dump_Remarks) != 0;
|
||||
}
|
||||
|
||||
const std::string& getBytecode() const
|
||||
{
|
||||
LUAU_ASSERT(!bytecode.empty()); // did you forget to call finalize?
|
||||
@ -110,6 +115,7 @@ public:
|
||||
|
||||
std::string dumpFunction(uint32_t id) const;
|
||||
std::string dumpEverything() const;
|
||||
std::string dumpSourceRemarks() const;
|
||||
|
||||
static uint32_t getImportId(int32_t id0);
|
||||
static uint32_t getImportId(int32_t id0, int32_t id1);
|
||||
@ -243,6 +249,7 @@ private:
|
||||
|
||||
uint32_t dumpFlags = 0;
|
||||
std::vector<std::string> dumpSource;
|
||||
std::vector<std::pair<int, std::string>> dumpRemarks;
|
||||
|
||||
std::string (BytecodeBuilder::*dumpFunctionPtr)() const = nullptr;
|
||||
|
||||
|
@ -572,6 +572,7 @@ void BytecodeBuilder::addDebugRemark(const char* format, ...)
|
||||
debugRemarkBuffer += '\0';
|
||||
|
||||
debugRemarks.emplace_back(uint32_t(insns.size()), uint32_t(offset));
|
||||
dumpRemarks.emplace_back(debugLine, debugRemarkBuffer.c_str() + offset);
|
||||
}
|
||||
|
||||
void BytecodeBuilder::finalize()
|
||||
@ -1949,4 +1950,40 @@ std::string BytecodeBuilder::dumpEverything() const
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string BytecodeBuilder::dumpSourceRemarks() const
|
||||
{
|
||||
std::string result;
|
||||
|
||||
size_t nextRemark = 0;
|
||||
|
||||
std::vector<std::pair<int, std::string>> remarks = dumpRemarks;
|
||||
std::sort(remarks.begin(), remarks.end());
|
||||
|
||||
for (size_t i = 0; i < dumpSource.size(); ++i)
|
||||
{
|
||||
const std::string& line = dumpSource[i];
|
||||
|
||||
size_t indent = 0;
|
||||
while (indent < line.length() && (line[indent] == ' ' || line[indent] == '\t'))
|
||||
indent++;
|
||||
|
||||
while (nextRemark < remarks.size() && remarks[nextRemark].first == int(i + 1))
|
||||
{
|
||||
formatAppend(result, "%.*s-- remark: %s\n", int(indent), line.c_str(), remarks[nextRemark].second.c_str());
|
||||
nextRemark++;
|
||||
|
||||
// skip duplicate remarks (due to inlining/unrolling)
|
||||
while (nextRemark < remarks.size() && remarks[nextRemark] == remarks[nextRemark - 1])
|
||||
nextRemark++;
|
||||
}
|
||||
|
||||
result += line;
|
||||
|
||||
if (i + 1 < dumpSource.size())
|
||||
result += '\n';
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace Luau
|
||||
|
@ -709,6 +709,17 @@ struct Compiler
|
||||
if (const int* id = builtins.find(expr))
|
||||
bfid = *id;
|
||||
|
||||
if (bfid >= 0 && bytecode.needsDebugRemarks())
|
||||
{
|
||||
Builtin builtin = getBuiltin(expr->func, globals, variables);
|
||||
bool lastMult = expr->args.size > 0 && isExprMultRet(expr->args.data[expr->args.size - 1]);
|
||||
|
||||
if (builtin.object.value)
|
||||
bytecode.addDebugRemark("builtin %s.%s/%d%s", builtin.object.value, builtin.method.value, int(expr->args.size), lastMult ? "+" : "");
|
||||
else if (builtin.method.value)
|
||||
bytecode.addDebugRemark("builtin %s/%d%s", builtin.method.value, int(expr->args.size), lastMult ? "+" : "");
|
||||
}
|
||||
|
||||
if (bfid == LBF_SELECT_VARARG)
|
||||
{
|
||||
// Optimization: compile select(_, ...) as FASTCALL1; the builtin will read variadic arguments directly
|
||||
@ -918,6 +929,9 @@ struct Compiler
|
||||
shared = int16_t(cid);
|
||||
}
|
||||
|
||||
if (shared < 0)
|
||||
bytecode.addDebugRemark("allocation: closure with %d upvalues", int(captures.size()));
|
||||
|
||||
if (shared >= 0)
|
||||
bytecode.emitAD(LOP_DUPCLOSURE, target, shared);
|
||||
else
|
||||
@ -1599,6 +1613,8 @@ struct Compiler
|
||||
{
|
||||
TableShape shape = tableShapes[expr];
|
||||
|
||||
bytecode.addDebugRemark("allocation: table hash %d", shape.hashSize);
|
||||
|
||||
bytecode.emitABC(LOP_NEWTABLE, target, encodeHashSize(shape.hashSize), 0);
|
||||
bytecode.emitAux(shape.arraySize);
|
||||
return;
|
||||
@ -1671,6 +1687,8 @@ struct Compiler
|
||||
if (tid < 0)
|
||||
CompileError::raise(expr->location, "Exceeded constant limit; simplify the code to compile");
|
||||
|
||||
bytecode.addDebugRemark("allocation: table template %d", hashSize);
|
||||
|
||||
if (tid < 32768)
|
||||
{
|
||||
bytecode.emitAD(LOP_DUPTABLE, reg, int16_t(tid));
|
||||
@ -1690,8 +1708,17 @@ struct Compiler
|
||||
bool trailingVarargs = last && last->kind == AstExprTable::Item::List && last->value->is<AstExprVarargs>();
|
||||
LUAU_ASSERT(!trailingVarargs || arraySize > 0);
|
||||
|
||||
unsigned int arrayAllocation = arraySize - trailingVarargs + indexSize;
|
||||
|
||||
if (hashSize == 0)
|
||||
bytecode.addDebugRemark("allocation: table array %d", arrayAllocation);
|
||||
else if (arrayAllocation == 0)
|
||||
bytecode.addDebugRemark("allocation: table hash %d", hashSize);
|
||||
else
|
||||
bytecode.addDebugRemark("allocation: table hash %d array %d", hashSize, arrayAllocation);
|
||||
|
||||
bytecode.emitABC(LOP_NEWTABLE, reg, uint8_t(encodedHashSize), 0);
|
||||
bytecode.emitAux(arraySize - trailingVarargs + indexSize);
|
||||
bytecode.emitAux(arrayAllocation);
|
||||
}
|
||||
|
||||
unsigned int arrayChunkSize = std::min(16u, arraySize);
|
||||
|
9
Makefile
9
Makefile
@ -112,6 +112,11 @@ ifeq ($(protobuf),download)
|
||||
EPROTOC=../build/libprotobuf-mutator/external.protobuf/bin/protoc
|
||||
endif
|
||||
|
||||
ifneq ($(native),)
|
||||
CXXFLAGS+=-DLUA_CUSTOM_EXECUTION=1
|
||||
TESTS_ARGS+=--codegen
|
||||
endif
|
||||
|
||||
# target-specific flags
|
||||
$(AST_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include
|
||||
$(COMPILER_OBJECTS): CXXFLAGS+=-std=c++17 -ICompiler/include -ICommon/include -IAst/include
|
||||
@ -120,7 +125,7 @@ $(CODEGEN_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -ICodeGen/include -IVM
|
||||
$(VM_OBJECTS): CXXFLAGS+=-std=c++11 -ICommon/include -IVM/include
|
||||
$(ISOCLINE_OBJECTS): CXXFLAGS+=-Wno-unused-function -Iextern/isocline/include
|
||||
$(TESTS_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IAnalysis/include -ICodeGen/include -IVM/include -ICLI -Iextern -DDOCTEST_CONFIG_DOUBLE_STRINGIFY
|
||||
$(REPL_CLI_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IVM/include -Iextern -Iextern/isocline/include
|
||||
$(REPL_CLI_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IVM/include -ICodeGen/include -Iextern -Iextern/isocline/include
|
||||
$(ANALYZE_CLI_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -IAnalysis/include -Iextern
|
||||
$(FUZZ_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IAnalysis/include -IVM/include
|
||||
|
||||
@ -175,7 +180,7 @@ luau-tests: $(TESTS_TARGET)
|
||||
|
||||
# executable targets
|
||||
$(TESTS_TARGET): $(TESTS_OBJECTS) $(ANALYSIS_TARGET) $(COMPILER_TARGET) $(AST_TARGET) $(CODEGEN_TARGET) $(VM_TARGET) $(ISOCLINE_TARGET)
|
||||
$(REPL_CLI_TARGET): $(REPL_CLI_OBJECTS) $(COMPILER_TARGET) $(AST_TARGET) $(VM_TARGET) $(ISOCLINE_TARGET)
|
||||
$(REPL_CLI_TARGET): $(REPL_CLI_OBJECTS) $(COMPILER_TARGET) $(AST_TARGET) $(CODEGEN_TARGET) $(VM_TARGET) $(ISOCLINE_TARGET)
|
||||
$(ANALYZE_CLI_TARGET): $(ANALYZE_CLI_OBJECTS) $(ANALYSIS_TARGET) $(AST_TARGET)
|
||||
|
||||
$(TESTS_TARGET) $(REPL_CLI_TARGET) $(ANALYZE_CLI_TARGET):
|
||||
|
@ -58,6 +58,7 @@ target_sources(Luau.CodeGen PRIVATE
|
||||
CodeGen/include/Luau/AssemblyBuilderX64.h
|
||||
CodeGen/include/Luau/CodeAllocator.h
|
||||
CodeGen/include/Luau/CodeBlockUnwind.h
|
||||
CodeGen/include/Luau/CodeGen.h
|
||||
CodeGen/include/Luau/Condition.h
|
||||
CodeGen/include/Luau/Label.h
|
||||
CodeGen/include/Luau/OperandX64.h
|
||||
@ -69,13 +70,25 @@ target_sources(Luau.CodeGen PRIVATE
|
||||
CodeGen/src/AssemblyBuilderX64.cpp
|
||||
CodeGen/src/CodeAllocator.cpp
|
||||
CodeGen/src/CodeBlockUnwind.cpp
|
||||
CodeGen/src/CodeGen.cpp
|
||||
CodeGen/src/CodeGenX64.cpp
|
||||
CodeGen/src/EmitBuiltinsX64.cpp
|
||||
CodeGen/src/EmitCommonX64.cpp
|
||||
CodeGen/src/EmitInstructionX64.cpp
|
||||
CodeGen/src/Fallbacks.cpp
|
||||
CodeGen/src/NativeState.cpp
|
||||
CodeGen/src/UnwindBuilderDwarf2.cpp
|
||||
CodeGen/src/UnwindBuilderWin.cpp
|
||||
|
||||
CodeGen/src/ByteUtils.h
|
||||
CodeGen/src/CustomExecUtils.h
|
||||
CodeGen/src/CodeGenX64.h
|
||||
CodeGen/src/EmitBuiltinsX64.h
|
||||
CodeGen/src/EmitCommonX64.h
|
||||
CodeGen/src/EmitInstructionX64.h
|
||||
CodeGen/src/Fallbacks.h
|
||||
CodeGen/src/FallbacksProlog.h
|
||||
CodeGen/src/NativeState.h
|
||||
)
|
||||
|
||||
# Luau.Analysis Sources
|
||||
@ -210,6 +223,7 @@ target_sources(Luau.VM PRIVATE
|
||||
VM/src/lvmexecute.cpp
|
||||
VM/src/lvmload.cpp
|
||||
VM/src/lvmutils.cpp
|
||||
|
||||
VM/src/lapi.h
|
||||
VM/src/lbuiltins.h
|
||||
VM/src/lbytecode.h
|
||||
|
@ -38,10 +38,10 @@ enum lua_Status
|
||||
enum lua_CoStatus
|
||||
{
|
||||
LUA_CORUN = 0, // running
|
||||
LUA_COSUS, // suspended
|
||||
LUA_CONOR, // 'normal' (it resumed another coroutine)
|
||||
LUA_COFIN, // finished
|
||||
LUA_COERR, // finished with error
|
||||
LUA_COSUS, // suspended
|
||||
LUA_CONOR, // 'normal' (it resumed another coroutine)
|
||||
LUA_COFIN, // finished
|
||||
LUA_COERR, // finished with error
|
||||
};
|
||||
|
||||
typedef struct lua_State lua_State;
|
||||
@ -398,16 +398,16 @@ LUA_API const char* lua_debugtrace(lua_State* L);
|
||||
|
||||
struct lua_Debug
|
||||
{
|
||||
const char* name; // (n)
|
||||
const char* what; // (s) `Lua', `C', `main', `tail'
|
||||
const char* source; // (s)
|
||||
const char* short_src; // (s)
|
||||
int linedefined; // (s)
|
||||
int currentline; // (l)
|
||||
unsigned char nupvals; // (u) number of upvalues
|
||||
unsigned char nparams; // (a) number of parameters
|
||||
char isvararg; // (a)
|
||||
void* userdata; // only valid in luau_callhook
|
||||
const char* name; // (n)
|
||||
const char* what; // (s) `Lua', `C', `main', `tail'
|
||||
const char* source; // (s)
|
||||
const char* short_src; // (s)
|
||||
int linedefined; // (s)
|
||||
int currentline; // (l)
|
||||
unsigned char nupvals; // (u) number of upvalues
|
||||
unsigned char nparams; // (a) number of parameters
|
||||
char isvararg; // (a)
|
||||
void* userdata; // only valid in luau_callhook
|
||||
|
||||
char ssbuf[LUA_IDSIZE];
|
||||
};
|
||||
|
@ -133,4 +133,4 @@
|
||||
|
||||
#define LUA_VECTOR_SIZE 3 // must be 3 or 4
|
||||
|
||||
#define LUA_EXTRA_SIZE LUA_VECTOR_SIZE - 2
|
||||
#define LUA_EXTRA_SIZE (LUA_VECTOR_SIZE - 2)
|
||||
|
@ -263,11 +263,14 @@ static int luauF_log(lua_State* L, StkId res, TValue* arg0, int nresults, StkId
|
||||
|
||||
static int luauF_max(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams)
|
||||
{
|
||||
if (nparams >= 1 && nresults <= 1 && ttisnumber(arg0))
|
||||
if (nparams >= 2 && nresults <= 1 && ttisnumber(arg0) && ttisnumber(args))
|
||||
{
|
||||
double r = nvalue(arg0);
|
||||
double a1 = nvalue(arg0);
|
||||
double a2 = nvalue(args);
|
||||
|
||||
for (int i = 2; i <= nparams; ++i)
|
||||
double r = (a2 > a1) ? a2 : a1;
|
||||
|
||||
for (int i = 3; i <= nparams; ++i)
|
||||
{
|
||||
if (!ttisnumber(args + (i - 2)))
|
||||
return -1;
|
||||
@ -286,11 +289,14 @@ static int luauF_max(lua_State* L, StkId res, TValue* arg0, int nresults, StkId
|
||||
|
||||
static int luauF_min(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams)
|
||||
{
|
||||
if (nparams >= 1 && nresults <= 1 && ttisnumber(arg0))
|
||||
if (nparams >= 2 && nresults <= 1 && ttisnumber(arg0) && ttisnumber(args))
|
||||
{
|
||||
double r = nvalue(arg0);
|
||||
double a1 = nvalue(arg0);
|
||||
double a2 = nvalue(args);
|
||||
|
||||
for (int i = 2; i <= nparams; ++i)
|
||||
double r = (a2 < a1) ? a2 : a1;
|
||||
|
||||
for (int i = 3; i <= nparams; ++i)
|
||||
{
|
||||
if (!ttisnumber(args + (i - 2)))
|
||||
return -1;
|
||||
@ -439,22 +445,18 @@ static int luauF_arshift(lua_State* L, StkId res, TValue* arg0, int nresults, St
|
||||
|
||||
static int luauF_band(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams)
|
||||
{
|
||||
if (nparams >= 1 && nresults <= 1)
|
||||
if (nparams >= 2 && nresults <= 1 && ttisnumber(arg0) && ttisnumber(args))
|
||||
{
|
||||
uint32_t r = ~0u;
|
||||
double a1 = nvalue(arg0);
|
||||
double a2 = nvalue(args);
|
||||
|
||||
if (!ttisnumber(arg0))
|
||||
return -1;
|
||||
unsigned u1, u2;
|
||||
luai_num2unsigned(u1, a1);
|
||||
luai_num2unsigned(u2, a2);
|
||||
|
||||
{
|
||||
double a1 = nvalue(arg0);
|
||||
unsigned u;
|
||||
luai_num2unsigned(u, a1);
|
||||
uint32_t r = u1 & u2;
|
||||
|
||||
r &= u;
|
||||
}
|
||||
|
||||
for (int i = 2; i <= nparams; ++i)
|
||||
for (int i = 3; i <= nparams; ++i)
|
||||
{
|
||||
if (!ttisnumber(args + (i - 2)))
|
||||
return -1;
|
||||
@ -492,22 +494,18 @@ static int luauF_bnot(lua_State* L, StkId res, TValue* arg0, int nresults, StkId
|
||||
|
||||
static int luauF_bor(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams)
|
||||
{
|
||||
if (nparams >= 1 && nresults <= 1)
|
||||
if (nparams >= 2 && nresults <= 1 && ttisnumber(arg0) && ttisnumber(args))
|
||||
{
|
||||
uint32_t r = 0;
|
||||
double a1 = nvalue(arg0);
|
||||
double a2 = nvalue(args);
|
||||
|
||||
if (!ttisnumber(arg0))
|
||||
return -1;
|
||||
unsigned u1, u2;
|
||||
luai_num2unsigned(u1, a1);
|
||||
luai_num2unsigned(u2, a2);
|
||||
|
||||
{
|
||||
double a1 = nvalue(arg0);
|
||||
unsigned u;
|
||||
luai_num2unsigned(u, a1);
|
||||
uint32_t r = u1 | u2;
|
||||
|
||||
r |= u;
|
||||
}
|
||||
|
||||
for (int i = 2; i <= nparams; ++i)
|
||||
for (int i = 3; i <= nparams; ++i)
|
||||
{
|
||||
if (!ttisnumber(args + (i - 2)))
|
||||
return -1;
|
||||
@ -528,22 +526,18 @@ static int luauF_bor(lua_State* L, StkId res, TValue* arg0, int nresults, StkId
|
||||
|
||||
static int luauF_bxor(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams)
|
||||
{
|
||||
if (nparams >= 1 && nresults <= 1)
|
||||
if (nparams >= 2 && nresults <= 1 && ttisnumber(arg0) && ttisnumber(args))
|
||||
{
|
||||
uint32_t r = 0;
|
||||
double a1 = nvalue(arg0);
|
||||
double a2 = nvalue(args);
|
||||
|
||||
if (!ttisnumber(arg0))
|
||||
return -1;
|
||||
unsigned u1, u2;
|
||||
luai_num2unsigned(u1, a1);
|
||||
luai_num2unsigned(u2, a2);
|
||||
|
||||
{
|
||||
double a1 = nvalue(arg0);
|
||||
unsigned u;
|
||||
luai_num2unsigned(u, a1);
|
||||
uint32_t r = u1 ^ u2;
|
||||
|
||||
r ^= u;
|
||||
}
|
||||
|
||||
for (int i = 2; i <= nparams; ++i)
|
||||
for (int i = 3; i <= nparams; ++i)
|
||||
{
|
||||
if (!ttisnumber(args + (i - 2)))
|
||||
return -1;
|
||||
@ -564,22 +558,18 @@ static int luauF_bxor(lua_State* L, StkId res, TValue* arg0, int nresults, StkId
|
||||
|
||||
static int luauF_btest(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams)
|
||||
{
|
||||
if (nparams >= 1 && nresults <= 1)
|
||||
if (nparams >= 2 && nresults <= 1 && ttisnumber(arg0) && ttisnumber(args))
|
||||
{
|
||||
uint32_t r = ~0u;
|
||||
double a1 = nvalue(arg0);
|
||||
double a2 = nvalue(args);
|
||||
|
||||
if (!ttisnumber(arg0))
|
||||
return -1;
|
||||
unsigned u1, u2;
|
||||
luai_num2unsigned(u1, a1);
|
||||
luai_num2unsigned(u2, a2);
|
||||
|
||||
{
|
||||
double a1 = nvalue(arg0);
|
||||
unsigned u;
|
||||
luai_num2unsigned(u, a1);
|
||||
uint32_t r = u1 & u2;
|
||||
|
||||
r &= u;
|
||||
}
|
||||
|
||||
for (int i = 2; i <= nparams; ++i)
|
||||
for (int i = 3; i <= nparams; ++i)
|
||||
{
|
||||
if (!ttisnumber(args + (i - 2)))
|
||||
return -1;
|
||||
|
@ -108,7 +108,7 @@ UpVal* luaF_findupval(lua_State* L, StkId level)
|
||||
|
||||
void luaF_freeupval(lua_State* L, UpVal* uv, lua_Page* page)
|
||||
{
|
||||
luaM_freegco(L, uv, sizeof(UpVal), uv->memcat, page); // free upvalue
|
||||
luaM_freegco(L, uv, sizeof(UpVal), uv->memcat, page); // free upvalue
|
||||
}
|
||||
|
||||
void luaF_close(lua_State* L, StkId level)
|
||||
|
@ -13,8 +13,6 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
|
||||
|
||||
LUAU_FASTFLAG(LuauFasterGetInfo)
|
||||
|
||||
const TValue luaO_nilobject_ = {{NULL}, {0}, LUA_TNIL};
|
||||
|
@ -290,6 +290,7 @@ typedef struct Proto
|
||||
int sizelineinfo;
|
||||
int linegaplog2;
|
||||
int linedefined;
|
||||
int bytecodeid;
|
||||
|
||||
|
||||
uint8_t nups; // number of upvalues
|
||||
|
@ -100,6 +100,12 @@ static void close_state(lua_State* L)
|
||||
LUAU_ASSERT(g->memcatbytes[0] == sizeof(LG));
|
||||
for (int i = 1; i < LUA_MEMORY_CATEGORIES; i++)
|
||||
LUAU_ASSERT(g->memcatbytes[i] == 0);
|
||||
|
||||
#if LUA_CUSTOM_EXECUTION
|
||||
if (L->global->ecb.close)
|
||||
L->global->ecb.close(L);
|
||||
#endif
|
||||
|
||||
(*g->frealloc)(g->ud, L, sizeof(LG), 0);
|
||||
}
|
||||
|
||||
|
@ -146,18 +146,15 @@ struct GCMetrics
|
||||
};
|
||||
#endif
|
||||
|
||||
#if LUA_CUSTOM_EXECUTION
|
||||
|
||||
// Callbacks that can be used to to redirect code execution from Luau bytecode VM to a custom implementation (AoT/JiT/sandboxing/...)
|
||||
typedef struct lua_ExecutionCallbacks
|
||||
struct lua_ExecutionCallbacks
|
||||
{
|
||||
void* context;
|
||||
void (*close)(lua_State* L); // called when global VM state is closed
|
||||
void (*destroy)(lua_State* L, Proto* proto); // called when function is destroyed
|
||||
int (*enter)(lua_State* L, Proto* proto); // called when function is about to start/resume (when execdata is present), return 0 to exit VM
|
||||
void (*setbreakpoint)(lua_State* L, Proto* proto, int line); // called when a breakpoint is set in a function
|
||||
} lua_ExecutionCallbacks;
|
||||
|
||||
#endif
|
||||
};
|
||||
|
||||
/*
|
||||
** `global state', shared by all threads of this state
|
||||
|
@ -16,6 +16,8 @@
|
||||
|
||||
#include <string.h>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauNoTopRestoreInFastCall, false)
|
||||
|
||||
// Disable c99-designator to avoid the warning in CGOTO dispatch table
|
||||
#ifdef __clang__
|
||||
#if __has_warning("-Wc99-designator")
|
||||
@ -2537,6 +2539,8 @@ reentry:
|
||||
|
||||
if (n >= 0)
|
||||
{
|
||||
// when nresults != MULTRET, L->top might be pointing to the middle of stack frame if nparams is equal to MULTRET
|
||||
// instead of restoring L->top to L->ci->top if nparams is MULTRET, we do it unconditionally to skip an extra check
|
||||
L->top = (nresults == LUA_MULTRET) ? ra + n : L->ci->top;
|
||||
|
||||
pc += skip + 1; // skip instructions that compute function as well as CALL
|
||||
@ -2613,7 +2617,15 @@ reentry:
|
||||
|
||||
if (n >= 0)
|
||||
{
|
||||
L->top = (nresults == LUA_MULTRET) ? ra + n : L->ci->top;
|
||||
if (FFlag::LuauNoTopRestoreInFastCall)
|
||||
{
|
||||
if (nresults == LUA_MULTRET)
|
||||
L->top = ra + n;
|
||||
}
|
||||
else
|
||||
{
|
||||
L->top = (nresults == LUA_MULTRET) ? ra + n : L->ci->top;
|
||||
}
|
||||
|
||||
pc += skip + 1; // skip instructions that compute function as well as CALL
|
||||
LUAU_ASSERT(unsigned(pc - cl->l.p->code) < unsigned(cl->l.p->sizecode));
|
||||
@ -2661,7 +2673,15 @@ reentry:
|
||||
|
||||
if (n >= 0)
|
||||
{
|
||||
L->top = (nresults == LUA_MULTRET) ? ra + n : L->ci->top;
|
||||
if (FFlag::LuauNoTopRestoreInFastCall)
|
||||
{
|
||||
if (nresults == LUA_MULTRET)
|
||||
L->top = ra + n;
|
||||
}
|
||||
else
|
||||
{
|
||||
L->top = (nresults == LUA_MULTRET) ? ra + n : L->ci->top;
|
||||
}
|
||||
|
||||
pc += skip + 1; // skip instructions that compute function as well as CALL
|
||||
LUAU_ASSERT(unsigned(pc - cl->l.p->code) < unsigned(cl->l.p->sizecode));
|
||||
@ -2709,7 +2729,15 @@ reentry:
|
||||
|
||||
if (n >= 0)
|
||||
{
|
||||
L->top = (nresults == LUA_MULTRET) ? ra + n : L->ci->top;
|
||||
if (FFlag::LuauNoTopRestoreInFastCall)
|
||||
{
|
||||
if (nresults == LUA_MULTRET)
|
||||
L->top = ra + n;
|
||||
}
|
||||
else
|
||||
{
|
||||
L->top = (nresults == LUA_MULTRET) ? ra + n : L->ci->top;
|
||||
}
|
||||
|
||||
pc += skip + 1; // skip instructions that compute function as well as CALL
|
||||
LUAU_ASSERT(unsigned(pc - cl->l.p->code) < unsigned(cl->l.p->sizecode));
|
||||
|
@ -192,6 +192,7 @@ int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size
|
||||
{
|
||||
Proto* p = luaF_newproto(L);
|
||||
p->source = source;
|
||||
p->bytecodeid = int(i);
|
||||
|
||||
p->maxstacksize = read<uint8_t>(data, size, offset);
|
||||
p->numparams = read<uint8_t>(data, size, offset);
|
||||
|
@ -5,6 +5,7 @@ function test()
|
||||
local band = bit32.band
|
||||
local bnot = bit32.bnot
|
||||
local bxor = bit32.bxor
|
||||
local bor = bit32.bor
|
||||
|
||||
local rrotate = bit32.rrotate
|
||||
local rshift = bit32.rshift
|
||||
@ -79,14 +80,14 @@ function test()
|
||||
h, g, f, e, d, c, b, a = g, f, e, d + t1, c, b, a, t1 + t2
|
||||
end
|
||||
|
||||
hash[1] = band(hash[1] + a)
|
||||
hash[2] = band(hash[2] + b)
|
||||
hash[3] = band(hash[3] + c)
|
||||
hash[4] = band(hash[4] + d)
|
||||
hash[5] = band(hash[5] + e)
|
||||
hash[6] = band(hash[6] + f)
|
||||
hash[7] = band(hash[7] + g)
|
||||
hash[8] = band(hash[8] + h)
|
||||
hash[1] = bor(hash[1] + a, 0)
|
||||
hash[2] = bor(hash[2] + b, 0)
|
||||
hash[3] = bor(hash[3] + c, 0)
|
||||
hash[4] = bor(hash[4] + d, 0)
|
||||
hash[5] = bor(hash[5] + e, 0)
|
||||
hash[6] = bor(hash[6] + f, 0)
|
||||
hash[7] = bor(hash[7] + g, 0)
|
||||
hash[8] = bor(hash[8] + h, 0)
|
||||
end
|
||||
|
||||
local function sha256(msg)
|
||||
@ -139,4 +140,4 @@ function test()
|
||||
return ts1 - ts0
|
||||
end
|
||||
|
||||
bench.runCode(test, "sha256")
|
||||
bench.runCode(test, "sha256")
|
||||
|
@ -177,6 +177,9 @@ TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "BaseUnaryInstructionForms")
|
||||
SINGLE_COMPARE(imul(r9), 0x49, 0xf7, 0xe9);
|
||||
SINGLE_COMPARE(neg(r9), 0x49, 0xf7, 0xd9);
|
||||
SINGLE_COMPARE(not_(r12), 0x49, 0xf7, 0xd4);
|
||||
SINGLE_COMPARE(inc(r12), 0x49, 0xff, 0xc4);
|
||||
SINGLE_COMPARE(dec(ecx), 0xff, 0xc9);
|
||||
SINGLE_COMPARE(dec(byte[rdx]), 0xfe, 0x0a);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "FormsOfMov")
|
||||
@ -365,7 +368,6 @@ TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "AVXUnaryMergeInstructionForms")
|
||||
SINGLE_COMPARE(vsqrtss(xmm8, xmm10, dword[r9]), 0xc4, 0x41, 0xaa, 0x51, 0x01);
|
||||
|
||||
// Coverage for other instructions that follow the same pattern
|
||||
SINGLE_COMPARE(vcomisd(xmm8, xmm10), 0xc4, 0x41, 0xf9, 0x2f, 0xc2);
|
||||
SINGLE_COMPARE(vucomisd(xmm1, xmm4), 0xc4, 0xe1, 0xf9, 0x2e, 0xcc);
|
||||
}
|
||||
|
||||
@ -405,9 +407,10 @@ TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "AVXConversionInstructionForms")
|
||||
|
||||
TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "AVXTernaryInstructionForms")
|
||||
{
|
||||
SINGLE_COMPARE(vroundsd(xmm7, xmm12, xmm3, 9), 0xc4, 0xe3, 0x99, 0x0b, 0xfb, 0x09);
|
||||
SINGLE_COMPARE(vroundsd(xmm8, xmm13, xmmword[r13 + rdx], 9), 0xc4, 0x43, 0x91, 0x0b, 0x44, 0x15, 0x00, 0x09);
|
||||
SINGLE_COMPARE(vroundsd(xmm9, xmm14, xmmword[rcx + r10], 1), 0xc4, 0x23, 0x89, 0x0b, 0x0c, 0x11, 0x01);
|
||||
SINGLE_COMPARE(vroundsd(xmm7, xmm12, xmm3, RoundingModeX64::RoundToNegativeInfinity), 0xc4, 0xe3, 0x99, 0x0b, 0xfb, 0x09);
|
||||
SINGLE_COMPARE(
|
||||
vroundsd(xmm8, xmm13, xmmword[r13 + rdx], RoundingModeX64::RoundToPositiveInfinity), 0xc4, 0x43, 0x91, 0x0b, 0x44, 0x15, 0x00, 0x0a);
|
||||
SINGLE_COMPARE(vroundsd(xmm9, xmm14, xmmword[rcx + r10], RoundingModeX64::RoundToZero), 0xc4, 0x23, 0x89, 0x0b, 0x0c, 0x11, 0x0b);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "MiscInstructions")
|
||||
@ -441,7 +444,7 @@ TEST_CASE("LogTest")
|
||||
build.movsx(rsi, word[r12]);
|
||||
build.imul(rcx, rdx);
|
||||
build.imul(rcx, rdx, 8);
|
||||
build.vroundsd(xmm1, xmm2, xmm3, 5);
|
||||
build.vroundsd(xmm1, xmm2, xmm3, RoundingModeX64::RoundToNearestEven);
|
||||
build.pop(r12);
|
||||
build.ret();
|
||||
build.int3();
|
||||
@ -469,7 +472,7 @@ TEST_CASE("LogTest")
|
||||
movsx rsi,word ptr [r12]
|
||||
imul rcx,rdx
|
||||
imul rcx,rdx,8
|
||||
vroundsd xmm1,xmm2,xmm3,5
|
||||
vroundsd xmm1,xmm2,xmm3,8
|
||||
pop r12
|
||||
ret
|
||||
int3
|
||||
|
@ -42,4 +42,4 @@ bool FindNthOccurenceOf::visit(AstTypePack* t)
|
||||
return checkIt(t);
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace Luau
|
||||
|
@ -80,4 +80,4 @@ T* query(AstNode* node, const std::vector<Nth>& nths = {nth<T>(N)})
|
||||
return node ? node->as<T>() : nullptr;
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace Luau
|
||||
|
@ -2873,6 +2873,43 @@ RETURN R0 0
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE("SourceRemarks")
|
||||
{
|
||||
const char* source = R"(
|
||||
local a, b = ...
|
||||
|
||||
local function foo(x)
|
||||
return(math.abs(x))
|
||||
end
|
||||
|
||||
return foo(a) + foo(assert(b))
|
||||
)";
|
||||
|
||||
Luau::BytecodeBuilder bcb;
|
||||
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Source | Luau::BytecodeBuilder::Dump_Remarks);
|
||||
bcb.setDumpSource(source);
|
||||
|
||||
Luau::CompileOptions options;
|
||||
options.optimizationLevel = 2;
|
||||
|
||||
Luau::compileOrThrow(bcb, source, options);
|
||||
|
||||
std::string remarks = bcb.dumpSourceRemarks();
|
||||
|
||||
CHECK_EQ(remarks, R"(
|
||||
local a, b = ...
|
||||
|
||||
local function foo(x)
|
||||
-- remark: builtin math.abs/1
|
||||
return(math.abs(x))
|
||||
end
|
||||
|
||||
-- remark: builtin assert/1
|
||||
-- remark: inlining succeeded (cost 2, profit 2.50x, depth 0)
|
||||
return foo(a) + foo(assert(b))
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE("AssignmentConflict")
|
||||
{
|
||||
// assignments are left to right
|
||||
@ -5042,7 +5079,6 @@ GETIMPORT R2 2
|
||||
CALL R2 0 0
|
||||
RETURN R1 1
|
||||
)");
|
||||
|
||||
}
|
||||
|
||||
TEST_CASE("InlineNestedLoops")
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "Luau/TypeInfer.h"
|
||||
#include "Luau/StringUtils.h"
|
||||
#include "Luau/BytecodeBuilder.h"
|
||||
#include "Luau/CodeGen.h"
|
||||
|
||||
#include "doctest.h"
|
||||
#include "ScopedFlags.h"
|
||||
@ -17,6 +18,7 @@
|
||||
#include <math.h>
|
||||
|
||||
extern bool verbose;
|
||||
extern bool codegen;
|
||||
extern int optimizationLevel;
|
||||
|
||||
static lua_CompileOptions defaultOptions()
|
||||
@ -137,7 +139,7 @@ int lua_silence(lua_State* L)
|
||||
using StateRef = std::unique_ptr<lua_State, void (*)(lua_State*)>;
|
||||
|
||||
static StateRef runConformance(const char* name, void (*setup)(lua_State* L) = nullptr, void (*yield)(lua_State* L) = nullptr,
|
||||
lua_State* initialLuaState = nullptr, lua_CompileOptions* options = nullptr)
|
||||
lua_State* initialLuaState = nullptr, lua_CompileOptions* options = nullptr, bool skipCodegen = false)
|
||||
{
|
||||
std::string path = __FILE__;
|
||||
path.erase(path.find_last_of("\\/"));
|
||||
@ -156,6 +158,9 @@ static StateRef runConformance(const char* name, void (*setup)(lua_State* L) = n
|
||||
StateRef globalState(initialLuaState, lua_close);
|
||||
lua_State* L = globalState.get();
|
||||
|
||||
if (codegen && !skipCodegen && Luau::CodeGen::isSupported())
|
||||
Luau::CodeGen::create(L);
|
||||
|
||||
luaL_openlibs(L);
|
||||
|
||||
// Register a few global functions for conformance tests
|
||||
@ -207,6 +212,9 @@ static StateRef runConformance(const char* name, void (*setup)(lua_State* L) = n
|
||||
int result = luau_load(L, chunkname.c_str(), bytecode, bytecodeSize, 0);
|
||||
free(bytecode);
|
||||
|
||||
if (result == 0 && codegen && !skipCodegen && Luau::CodeGen::isSupported())
|
||||
Luau::CodeGen::compile(L, -1);
|
||||
|
||||
int status = (result == 0) ? lua_resume(L, nullptr, 0) : LUA_ERRSYNTAX;
|
||||
|
||||
while (yield && (status == LUA_YIELD || status == LUA_BREAK))
|
||||
@ -563,19 +571,19 @@ TEST_CASE("Debugger")
|
||||
};
|
||||
|
||||
// add breakpoint() function
|
||||
lua_pushcfunction(
|
||||
lua_pushcclosurek(
|
||||
L,
|
||||
[](lua_State* L) -> int {
|
||||
int line = luaL_checkinteger(L, 1);
|
||||
bool enabled = luaL_optboolean(L, 2, true);
|
||||
|
||||
lua_Debug ar = {};
|
||||
lua_getinfo(L, 1, "f", &ar);
|
||||
lua_getinfo(L, lua_stackdepth(L) - 1, "f", &ar);
|
||||
|
||||
lua_breakpoint(L, -1, line, enabled);
|
||||
return 0;
|
||||
},
|
||||
"breakpoint");
|
||||
"breakpoint", 0, nullptr);
|
||||
lua_setglobal(L, "breakpoint");
|
||||
},
|
||||
[](lua_State* L) {
|
||||
@ -656,9 +664,9 @@ TEST_CASE("Debugger")
|
||||
interruptedthread = nullptr;
|
||||
}
|
||||
},
|
||||
nullptr, &copts);
|
||||
nullptr, &copts, /* skipCodegen */ true); // Native code doesn't support debugging yet
|
||||
|
||||
CHECK(breakhits == 10); // 2 hits per breakpoint
|
||||
CHECK(breakhits == 12); // 2 hits per breakpoint
|
||||
}
|
||||
|
||||
TEST_CASE("SameHash")
|
||||
@ -806,17 +814,17 @@ TEST_CASE("ApiIter")
|
||||
{
|
||||
sum1 += lua_tonumber(L, -2); // key
|
||||
sum1 += lua_tonumber(L, -1); // value
|
||||
lua_pop(L, 1); // pop value, key is used by lua_next
|
||||
lua_pop(L, 1); // pop value, key is used by lua_next
|
||||
}
|
||||
CHECK(sum1 == 580);
|
||||
|
||||
// Luau iteration interface: lua_rawiter (faster and preferable to lua_next)
|
||||
double sum2 = 0;
|
||||
for (int index = 0; index = lua_rawiter(L, -1, index), index >= 0; )
|
||||
for (int index = 0; index = lua_rawiter(L, -1, index), index >= 0;)
|
||||
{
|
||||
sum2 += lua_tonumber(L, -2); // key
|
||||
sum2 += lua_tonumber(L, -1); // value
|
||||
lua_pop(L, 2); // pop both key and value
|
||||
lua_pop(L, 2); // pop both key and value
|
||||
}
|
||||
CHECK(sum2 == 580);
|
||||
|
||||
@ -1188,7 +1196,13 @@ TEST_CASE("Interrupt")
|
||||
5,
|
||||
5,
|
||||
6,
|
||||
11,
|
||||
18,
|
||||
13,
|
||||
13,
|
||||
13,
|
||||
13,
|
||||
16,
|
||||
20,
|
||||
};
|
||||
static int index;
|
||||
|
||||
@ -1283,6 +1297,21 @@ TEST_CASE("UserdataApi")
|
||||
*(int*)ud3 = 43;
|
||||
*(char*)ud4 = 3;
|
||||
|
||||
// user data with named metatable
|
||||
luaL_newmetatable(L, "udata1");
|
||||
luaL_newmetatable(L, "udata2");
|
||||
|
||||
void* ud5 = lua_newuserdata(L, 0);
|
||||
lua_getfield(L, LUA_REGISTRYINDEX, "udata1");
|
||||
lua_setmetatable(L, -2);
|
||||
|
||||
void* ud6 = lua_newuserdata(L, 0);
|
||||
lua_getfield(L, LUA_REGISTRYINDEX, "udata2");
|
||||
lua_setmetatable(L, -2);
|
||||
|
||||
CHECK(luaL_checkudata(L, -2, "udata1") == ud5);
|
||||
CHECK(luaL_checkudata(L, -1, "udata2") == ud6);
|
||||
|
||||
globalState.reset();
|
||||
|
||||
CHECK(dtorhits == 42);
|
||||
@ -1494,4 +1523,9 @@ TEST_CASE("Userdata")
|
||||
});
|
||||
}
|
||||
|
||||
TEST_CASE("SafeEnv")
|
||||
{
|
||||
runConformance("safeenv.lua");
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -14,4 +14,4 @@ ConstraintGraphBuilderFixture::ConstraintGraphBuilderFixture()
|
||||
BlockedTypePack::nextIndex = 0;
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace Luau
|
||||
|
@ -24,4 +24,4 @@ struct ConstraintGraphBuilderFixture : Fixture
|
||||
ConstraintGraphBuilderFixture();
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace Luau
|
||||
|
@ -92,7 +92,8 @@ std::optional<std::string> TestFileResolver::getEnvironmentForModule(const Modul
|
||||
|
||||
Fixture::Fixture(bool freeze, bool prepareAutocomplete)
|
||||
: sff_DebugLuauFreezeArena("DebugLuauFreezeArena", freeze)
|
||||
, frontend(&fileResolver, &configResolver, {/* retainFullTypeGraphs= */ true, /* forAutocomplete */ false, /* randomConstraintResolutionSeed */ randomSeed})
|
||||
, frontend(&fileResolver, &configResolver,
|
||||
{/* retainFullTypeGraphs= */ true, /* forAutocomplete */ false, /* randomConstraintResolutionSeed */ randomSeed})
|
||||
, typeChecker(frontend.typeChecker)
|
||||
, singletonTypes(frontend.singletonTypes)
|
||||
{
|
||||
|
@ -1029,7 +1029,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "reexport_cyclic_type")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{"LuauForceExportSurfacesToBeNormal", true},
|
||||
{"LuauLowerBoundsCalculation", true},
|
||||
};
|
||||
|
||||
fileResolver.source["Module/A"] = R"(
|
||||
@ -1065,7 +1064,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "reexport_type_alias")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{"LuauForceExportSurfacesToBeNormal", true},
|
||||
{"LuauLowerBoundsCalculation", true},
|
||||
};
|
||||
|
||||
fileResolver.source["Module/A"] = R"(
|
||||
|
@ -45,8 +45,6 @@ TEST_CASE_FIXTURE(Fixture, "DeprecatedGlobal")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "DeprecatedGlobalNoReplacement")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauLintFixDeprecationMessage", true};
|
||||
|
||||
// Normally this would be defined externally, so hack it in for testing
|
||||
const char* deprecationReplacementString = "";
|
||||
addGlobalBinding(frontend, "Version", Binding{typeChecker.anyType, {}, true, deprecationReplacementString});
|
||||
|
@ -11,7 +11,6 @@
|
||||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
LUAU_FASTFLAG(LuauLowerBoundsCalculation);
|
||||
|
||||
TEST_SUITE_BEGIN("ModuleTests");
|
||||
|
||||
@ -107,11 +106,8 @@ TEST_CASE_FIXTURE(Fixture, "deepClone_cyclic_table")
|
||||
REQUIRE(methodReturnType);
|
||||
|
||||
CHECK_EQ(methodReturnType, counterCopy);
|
||||
if (FFlag::LuauLowerBoundsCalculation)
|
||||
CHECK_EQ(3, dest.typePacks.size()); // function args, its return type, and the hidden any... pack
|
||||
else
|
||||
CHECK_EQ(2, dest.typePacks.size()); // one for the function args, and another for its return type
|
||||
CHECK_EQ(2, dest.typeVars.size()); // One table and one function
|
||||
CHECK_EQ(2, dest.typePacks.size()); // one for the function args, and another for its return type
|
||||
CHECK_EQ(2, dest.typeVars.size()); // One table and one function
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "builtin_types_point_into_globalTypes_arena")
|
||||
|
@ -12,8 +12,6 @@ using namespace Luau;
|
||||
|
||||
struct NormalizeFixture : Fixture
|
||||
{
|
||||
ScopedFastFlag sff1{"LuauLowerBoundsCalculation", true};
|
||||
|
||||
bool isSubtype(TypeId a, TypeId b)
|
||||
{
|
||||
return ::Luau::isSubtype(a, b, NotNull{getMainModule()->getModuleScope().get()}, singletonTypes, ice);
|
||||
@ -116,159 +114,6 @@ TEST_CASE_FIXTURE(NormalizeFixture, "functions_and_any")
|
||||
CHECK(!isSubtype(a, b));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "intersection_of_functions_of_different_arities")
|
||||
{
|
||||
check(R"(
|
||||
type A = (any) -> ()
|
||||
type B = (any, any) -> ()
|
||||
type T = A & B
|
||||
|
||||
local a: A
|
||||
local b: B
|
||||
local t: T
|
||||
)");
|
||||
|
||||
TypeId a = requireType("a");
|
||||
TypeId b = requireType("b");
|
||||
|
||||
CHECK(!isSubtype(a, b)); // !!
|
||||
CHECK(!isSubtype(b, a));
|
||||
|
||||
CHECK("((any) -> ()) & ((any, any) -> ())" == toString(requireType("t")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "functions_with_mismatching_arity")
|
||||
{
|
||||
check(R"(
|
||||
local a: (number) -> ()
|
||||
local b: () -> ()
|
||||
|
||||
local c: () -> number
|
||||
)");
|
||||
|
||||
TypeId a = requireType("a");
|
||||
TypeId b = requireType("b");
|
||||
TypeId c = requireType("c");
|
||||
|
||||
CHECK(!isSubtype(b, a));
|
||||
CHECK(!isSubtype(c, a));
|
||||
|
||||
CHECK(!isSubtype(a, b));
|
||||
CHECK(!isSubtype(c, b));
|
||||
|
||||
CHECK(!isSubtype(a, c));
|
||||
CHECK(!isSubtype(b, c));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "functions_with_mismatching_arity_but_optional_parameters")
|
||||
{
|
||||
/*
|
||||
* (T0..TN) <: (T0..TN, A?)
|
||||
* (T0..TN) <: (T0..TN, any)
|
||||
* (T0..TN, A?) </: (T0..TN) We don't technically need to spell this out, but it's quite important.
|
||||
* T <: T
|
||||
* if A <: B and B <: C then A <: C
|
||||
* T -> R <: U -> S if U <: T and R <: S
|
||||
* A | B <: T if A <: T and B <: T
|
||||
* T <: A | B if T <: A or T <: B
|
||||
*/
|
||||
check(R"(
|
||||
local a: (number?) -> ()
|
||||
local b: (number) -> ()
|
||||
local c: (number, number?) -> ()
|
||||
)");
|
||||
|
||||
TypeId a = requireType("a");
|
||||
TypeId b = requireType("b");
|
||||
TypeId c = requireType("c");
|
||||
|
||||
/*
|
||||
* (number) -> () </: (number?) -> ()
|
||||
* because number? </: number (because number <: number, but nil </: number)
|
||||
*/
|
||||
CHECK(!isSubtype(b, a));
|
||||
|
||||
/*
|
||||
* (number, number?) </: (number?) -> ()
|
||||
* because number? </: number (as above)
|
||||
*/
|
||||
CHECK(!isSubtype(c, a));
|
||||
|
||||
/*
|
||||
* (number?) -> () <: (number) -> ()
|
||||
* because number <: number? (because number <: number)
|
||||
*/
|
||||
CHECK(isSubtype(a, b));
|
||||
|
||||
/*
|
||||
* (number, number?) -> () <: (number) -> (number)
|
||||
* The packs have inequal lengths, but (number) <: (number, number?)
|
||||
* and number <: number
|
||||
*/
|
||||
CHECK(!isSubtype(c, b));
|
||||
|
||||
/*
|
||||
* (number?) -> () </: (number, number?) -> ()
|
||||
* because (number, number?) </: (number)
|
||||
*/
|
||||
CHECK(!isSubtype(a, c));
|
||||
|
||||
/*
|
||||
* (number) -> () </: (number, number?) -> ()
|
||||
* because (number, number?) </: (number)
|
||||
*/
|
||||
CHECK(!isSubtype(b, c));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "functions_with_mismatching_arity_but_any_is_an_optional_param")
|
||||
{
|
||||
check(R"(
|
||||
local a: (number?) -> ()
|
||||
local b: (number) -> ()
|
||||
local c: (number, any) -> ()
|
||||
)");
|
||||
|
||||
TypeId a = requireType("a");
|
||||
TypeId b = requireType("b");
|
||||
TypeId c = requireType("c");
|
||||
|
||||
/*
|
||||
* (number) -> () </: (number?) -> ()
|
||||
* because number? </: number (because number <: number, but nil </: number)
|
||||
*/
|
||||
CHECK(!isSubtype(b, a));
|
||||
|
||||
/*
|
||||
* (number, any) </: (number?) -> ()
|
||||
* because number? </: number (as above)
|
||||
*/
|
||||
CHECK(!isSubtype(c, a));
|
||||
|
||||
/*
|
||||
* (number?) -> () <: (number) -> ()
|
||||
* because number <: number? (because number <: number)
|
||||
*/
|
||||
CHECK(isSubtype(a, b));
|
||||
|
||||
/*
|
||||
* (number, any) -> () </: (number) -> (number)
|
||||
* The packs have inequal lengths
|
||||
*/
|
||||
CHECK(!isSubtype(c, b));
|
||||
|
||||
/*
|
||||
* (number?) -> () </: (number, any) -> ()
|
||||
* The packs have inequal lengths
|
||||
*/
|
||||
CHECK(!isSubtype(a, c));
|
||||
|
||||
/*
|
||||
* (number) -> () </: (number, any) -> ()
|
||||
* The packs have inequal lengths
|
||||
*/
|
||||
CHECK(!isSubtype(b, c));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "variadic_functions_with_no_head")
|
||||
{
|
||||
check(R"(
|
||||
@ -356,7 +201,7 @@ TEST_CASE_FIXTURE(NormalizeFixture, "table_with_any_prop")
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "intersection")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
ScopedFastFlag sffs[]{
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
@ -398,16 +243,6 @@ TEST_CASE_FIXTURE(NormalizeFixture, "union_and_intersection")
|
||||
CHECK(isSubtype(a, b));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "table_with_table_prop")
|
||||
{
|
||||
check(R"(
|
||||
type T = {x: {y: number}} & {x: {y: string}}
|
||||
local a: T
|
||||
)");
|
||||
|
||||
CHECK_EQ("{| x: {| y: number & string |} |}", toString(requireType("a")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "tables")
|
||||
{
|
||||
check(R"(
|
||||
@ -552,50 +387,10 @@ TEST_CASE_FIXTURE(NormalizeFixture, "metatable" * doctest::expected_failures{1})
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "intersection_of_tables")
|
||||
{
|
||||
check(R"(
|
||||
type T = {x: number} & ({x: number} & {y: string?})
|
||||
local t: T
|
||||
)");
|
||||
|
||||
CHECK("{| x: number, y: string? |}" == toString(requireType("t")));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
||||
TEST_SUITE_BEGIN("Normalize");
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "intersection_of_disjoint_tables")
|
||||
{
|
||||
check(R"(
|
||||
type T = {a: number} & {b: number}
|
||||
local t: T
|
||||
)");
|
||||
|
||||
CHECK_EQ("{| a: number, b: number |}", toString(requireType("t")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "intersection_of_overlapping_tables")
|
||||
{
|
||||
check(R"(
|
||||
type T = {a: number, b: string} & {b: number, c: string}
|
||||
local t: T
|
||||
)");
|
||||
|
||||
CHECK_EQ("{| a: number, b: number & string, c: string |}", toString(requireType("t")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "intersection_of_confluent_overlapping_tables")
|
||||
{
|
||||
check(R"(
|
||||
type T = {a: number, b: string} & {b: string, c: string}
|
||||
local t: T
|
||||
)");
|
||||
|
||||
CHECK_EQ("{| a: number, b: string, c: string |}", toString(requireType("t")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "union_with_overlapping_field_that_has_a_subtype_relationship")
|
||||
{
|
||||
check(R"(
|
||||
@ -616,54 +411,6 @@ TEST_CASE_FIXTURE(NormalizeFixture, "union_with_overlapping_field_that_has_a_sub
|
||||
CHECK_EQ("{| x: number? |}", toString(tType, {true}));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "intersection_of_functions")
|
||||
{
|
||||
check(R"(
|
||||
type T = ((any) -> string) & ((number) -> string)
|
||||
local t: T
|
||||
)");
|
||||
|
||||
CHECK_EQ("(any) -> string", toString(requireType("t")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "normalize_module_return_type")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{"LuauLowerBoundsCalculation", true},
|
||||
};
|
||||
|
||||
check(R"(
|
||||
--!nonstrict
|
||||
|
||||
if Math.random() then
|
||||
return function(initialState, handlers)
|
||||
return function(state, action)
|
||||
return state
|
||||
end
|
||||
end
|
||||
else
|
||||
return function(initialState, handlers)
|
||||
return function(state, action)
|
||||
return state
|
||||
end
|
||||
end
|
||||
end
|
||||
)");
|
||||
|
||||
CHECK_EQ("(any, any) -> (...any)", toString(getMainModule()->getModuleScope()->returnType));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "return_type_is_not_a_constrained_intersection")
|
||||
{
|
||||
check(R"(
|
||||
function foo(x:number, y:number)
|
||||
return x + y
|
||||
end
|
||||
)");
|
||||
|
||||
CHECK_EQ("(number, number) -> number", toString(requireType("foo")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "higher_order_function")
|
||||
{
|
||||
check(R"(
|
||||
@ -689,52 +436,6 @@ TEST_CASE_FIXTURE(Fixture, "higher_order_function_with_annotation")
|
||||
CHECK_EQ("<a, b>((a) -> b, a) -> b", toString(requireType("apply")));
|
||||
}
|
||||
|
||||
// Unfortunately, getting this right in the general case is difficult.
|
||||
TEST_CASE_FIXTURE(Fixture, "cyclic_table_is_not_marked_normal")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{"LuauLowerBoundsCalculation", true},
|
||||
};
|
||||
|
||||
check(R"(
|
||||
type Fiber = {
|
||||
return_: Fiber?
|
||||
}
|
||||
|
||||
local f: Fiber
|
||||
)");
|
||||
|
||||
TypeId t = requireType("f");
|
||||
CHECK(!t->normal);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "variadic_tail_is_marked_normal")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{"LuauLowerBoundsCalculation", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type Weirdo = (...{x: number}) -> ()
|
||||
|
||||
local w: Weirdo
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
TypeId t = requireType("w");
|
||||
auto ftv = get<FunctionTypeVar>(t);
|
||||
REQUIRE(ftv);
|
||||
|
||||
auto [argHead, argTail] = flatten(ftv->argTypes);
|
||||
CHECK(argHead.empty());
|
||||
REQUIRE(argTail.has_value());
|
||||
|
||||
auto vtp = get<VariadicTypePack>(*argTail);
|
||||
REQUIRE(vtp);
|
||||
CHECK(vtp->ty->normal);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "cyclic_table_normalizes_sensibly")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
@ -750,343 +451,6 @@ TEST_CASE_FIXTURE(Fixture, "cyclic_table_normalizes_sensibly")
|
||||
CHECK_EQ("t1 where t1 = { get: () -> t1 }", toString(ty, {true}));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "cyclic_union")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{"LuauLowerBoundsCalculation", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type T = {T?}?
|
||||
|
||||
local a: T
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK("t1? where t1 = {t1?}" == toString(requireType("a")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "cyclic_intersection")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{"LuauLowerBoundsCalculation", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type T = {T & {}}
|
||||
|
||||
local a: T
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
// FIXME: We are not properly normalizing this type, but we are at least not improperly discarding information
|
||||
CHECK("t1 where t1 = {{t1 & {| |}}}" == toString(requireType("a"), {true}));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_indexers")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{"LuauLowerBoundsCalculation", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type A = {number}
|
||||
type B = {string}
|
||||
|
||||
type C = A & B
|
||||
|
||||
local a: C
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
// FIXME: We are not properly normalizing this type, but we are at least not improperly discarding information
|
||||
CHECK("{number & string}" == toString(requireType("a"), {true}));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "union_of_distinct_free_types")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{"LuauLowerBoundsCalculation", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function fussy(a, b)
|
||||
if math.random() > 0.5 then
|
||||
return a
|
||||
else
|
||||
return b
|
||||
end
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK("<a, b>(a, b) -> a | b" == toString(requireType("fussy")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "constrained_intersection_of_intersections")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{"LuauLowerBoundsCalculation", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local f : (() -> number) | ((number) -> number)
|
||||
local g : (() -> number) | ((string) -> number)
|
||||
|
||||
function h()
|
||||
if math.random() then
|
||||
return f
|
||||
else
|
||||
return g
|
||||
end
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
TypeId h = requireType("h");
|
||||
|
||||
CHECK("() -> (() -> number) | ((number) -> number) | ((string) -> number)" == toString(h));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "intersection_inside_a_table_inside_another_intersection")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{"LuauLowerBoundsCalculation", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type X = {}
|
||||
type Y = {y: number}
|
||||
type Z = {z: string}
|
||||
type W = {w: boolean}
|
||||
type T = {x: Y & X} & {x:Z & W}
|
||||
|
||||
local x: X
|
||||
local y: Y
|
||||
local z: Z
|
||||
local w: W
|
||||
local t: T
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK("{| |}" == toString(requireType("x"), {true}));
|
||||
CHECK("{| y: number |}" == toString(requireType("y"), {true}));
|
||||
CHECK("{| z: string |}" == toString(requireType("z"), {true}));
|
||||
CHECK("{| w: boolean |}" == toString(requireType("w"), {true}));
|
||||
CHECK("{| x: {| w: boolean, y: number, z: string |} |}" == toString(requireType("t"), {true}));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "intersection_inside_a_table_inside_another_intersection_2")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{"LuauLowerBoundsCalculation", true},
|
||||
};
|
||||
|
||||
// We use a function and inferred parameter types to prevent intermediate normalizations from being performed.
|
||||
// This exposes a bug where the type of y is mutated.
|
||||
CheckResult result = check(R"(
|
||||
function strange(w, x, y, z)
|
||||
y.y = 5
|
||||
z.z = "five"
|
||||
w.w = true
|
||||
|
||||
type Z = {x: typeof(x) & typeof(y)} & {x: typeof(w) & typeof(z)}
|
||||
|
||||
return ((nil :: any) :: Z)
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
TypeId t = requireType("strange");
|
||||
auto ftv = get<FunctionTypeVar>(t);
|
||||
REQUIRE(ftv != nullptr);
|
||||
|
||||
std::vector<TypeId> args = flatten(ftv->argTypes).first;
|
||||
|
||||
REQUIRE(4 == args.size());
|
||||
CHECK("{+ w: boolean +}" == toString(args[0]));
|
||||
CHECK("a" == toString(args[1]));
|
||||
CHECK("{+ y: number +}" == toString(args[2]));
|
||||
CHECK("{+ z: string +}" == toString(args[3]));
|
||||
|
||||
std::vector<TypeId> ret = flatten(ftv->retTypes).first;
|
||||
|
||||
REQUIRE(1 == ret.size());
|
||||
CHECK("{| x: a & {+ w: boolean, y: number, z: string +} |}" == toString(ret[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "intersection_inside_a_table_inside_another_intersection_3")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{"LuauLowerBoundsCalculation", true},
|
||||
};
|
||||
|
||||
// We use a function and inferred parameter types to prevent intermediate normalizations from being performed.
|
||||
// This exposes a bug where the type of y is mutated.
|
||||
CheckResult result = check(R"(
|
||||
function strange(x, y, z)
|
||||
x.x = true
|
||||
y.y = y
|
||||
z.z = "five"
|
||||
|
||||
type Z = {x: typeof(y)} & {x: typeof(x) & typeof(z)}
|
||||
|
||||
return ((nil :: any) :: Z)
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
TypeId t = requireType("strange");
|
||||
auto ftv = get<FunctionTypeVar>(t);
|
||||
REQUIRE(ftv != nullptr);
|
||||
|
||||
std::vector<TypeId> args = flatten(ftv->argTypes).first;
|
||||
|
||||
REQUIRE(3 == args.size());
|
||||
CHECK("{+ x: boolean +}" == toString(args[0]));
|
||||
CHECK("t1 where t1 = {+ y: t1 +}" == toString(args[1]));
|
||||
CHECK("{+ z: string +}" == toString(args[2]));
|
||||
|
||||
std::vector<TypeId> ret = flatten(ftv->retTypes).first;
|
||||
|
||||
REQUIRE(1 == ret.size());
|
||||
CHECK("{| x: {+ x: boolean, y: t1, z: string +} |} where t1 = {+ y: t1 +}" == toString(ret[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "intersection_inside_a_table_inside_another_intersection_4")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{"LuauLowerBoundsCalculation", true},
|
||||
};
|
||||
|
||||
// We use a function and inferred parameter types to prevent intermediate normalizations from being performed.
|
||||
// This exposes a bug where the type of y is mutated.
|
||||
CheckResult result = check(R"(
|
||||
function strange(x, y, z)
|
||||
x.x = true
|
||||
z.z = "five"
|
||||
|
||||
type R = {x: typeof(y)} & {x: typeof(x) & typeof(z)}
|
||||
local r: R
|
||||
|
||||
y.y = r
|
||||
|
||||
return r
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
TypeId t = requireType("strange");
|
||||
auto ftv = get<FunctionTypeVar>(t);
|
||||
REQUIRE(ftv != nullptr);
|
||||
|
||||
std::vector<TypeId> args = flatten(ftv->argTypes).first;
|
||||
|
||||
REQUIRE(3 == args.size());
|
||||
CHECK("{+ x: boolean +}" == toString(args[0]));
|
||||
CHECK("{+ y: t1 +} where t1 = {| x: {+ x: boolean, y: t1, z: string +} |}" == toString(args[1]));
|
||||
CHECK("{+ z: string +}" == toString(args[2]));
|
||||
|
||||
std::vector<TypeId> ret = flatten(ftv->retTypes).first;
|
||||
|
||||
REQUIRE(1 == ret.size());
|
||||
CHECK("t1 where t1 = {| x: {+ x: boolean, y: t1, z: string +} |}" == toString(ret[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "nested_table_normalization_with_non_table__no_ice")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{"LuauLowerBoundsCalculation", true},
|
||||
{"LuauNormalizeCombineTableFix", true},
|
||||
};
|
||||
// CLI-52787
|
||||
// ends up combining {_:any} with any, recursively
|
||||
// which used to ICE because this combines a table with a non-table.
|
||||
CheckResult result = check(R"(
|
||||
export type t0 = any & { _: {_:any} } & { _:any }
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "visiting_a_type_twice_is_not_considered_normal")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauLowerBoundsCalculation", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
function f(a, b)
|
||||
local function g()
|
||||
if math.random() > 0.5 then
|
||||
return a()
|
||||
else
|
||||
return b
|
||||
end
|
||||
end
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK_EQ("<a>(() -> a, a) -> ()", toString(requireType("f")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "fuzz_failure_instersection_combine_must_follow")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{"LuauLowerBoundsCalculation", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
export type t0 = {_:{_:any} & {_:any|string}} & {_:{_:{}}}
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "fuzz_failure_bound_type_is_normal_but_not_its_bounded_to")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauLowerBoundsCalculation", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type t252 = ((t0<t252...>)|(any))|(any)
|
||||
type t0 = t252<t0<any,t24...>,t24...>
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
}
|
||||
|
||||
// We had an issue where a normal BoundTypeVar might point at a non-normal BoundTypeVar if it in turn pointed to a
|
||||
// normal TypeVar because we were calling follow() in an improper place.
|
||||
TEST_CASE_FIXTURE(Fixture, "bound_typevars_should_only_be_marked_normal_if_their_pointee_is_normal")
|
||||
{
|
||||
ScopedFastFlag sff[]{
|
||||
{"LuauLowerBoundsCalculation", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local T = {}
|
||||
|
||||
function T:M()
|
||||
local function f(a)
|
||||
print(self.prop)
|
||||
self:g(a)
|
||||
self.prop = a
|
||||
end
|
||||
end
|
||||
|
||||
return T
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "skip_force_normal_on_external_types")
|
||||
{
|
||||
createSomeClasses(frontend);
|
||||
@ -1108,68 +472,4 @@ export type t0 = (((any)&({_:l0.t0,n0:t0,_G:any,}))&({_:any,}))&(((any)&({_:l0.t
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "normalize_unions_containing_never")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauLowerBoundsCalculation", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type Foo = string | never
|
||||
local foo: Foo
|
||||
)");
|
||||
|
||||
CHECK_EQ("string", toString(requireType("foo")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "normalize_unions_containing_unknown")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauLowerBoundsCalculation", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type Foo = string | unknown
|
||||
local foo: Foo
|
||||
)");
|
||||
|
||||
CHECK_EQ("unknown", toString(requireType("foo")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "any_wins_the_battle_over_unknown_in_unions")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauLowerBoundsCalculation", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type Foo = unknown | any
|
||||
local foo: Foo
|
||||
|
||||
type Bar = any | unknown
|
||||
local bar: Bar
|
||||
)");
|
||||
|
||||
CHECK_EQ("any", toString(requireType("foo")));
|
||||
CHECK_EQ("any", toString(requireType("bar")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "normalization_does_not_convert_ever")
|
||||
{
|
||||
ScopedFastFlag sff[]{
|
||||
{"LuauLowerBoundsCalculation", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
local function f()
|
||||
if math.random() > 0.5 then
|
||||
return true
|
||||
end
|
||||
type Ret = typeof(f())
|
||||
if math.random() > 0.5 then
|
||||
return "something"
|
||||
end
|
||||
return "something" :: Ret
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK_EQ("() -> boolean | string", toString(requireType("f")));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -15,8 +15,6 @@
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauLowerBoundsCalculation);
|
||||
|
||||
struct LimitFixture : BuiltinsFixture
|
||||
{
|
||||
#if defined(_NOOPT) || defined(_DEBUG)
|
||||
@ -267,10 +265,7 @@ TEST_CASE_FIXTURE(LimitFixture, "typescript_port_of_Result_type")
|
||||
CheckResult result = check(src);
|
||||
CodeTooComplex ctc;
|
||||
|
||||
if (FFlag::LuauLowerBoundsCalculation)
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
else
|
||||
CHECK(hasError(result, &ctc));
|
||||
CHECK(hasError(result, &ctc));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -7,8 +7,6 @@
|
||||
|
||||
#include "doctest.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauLowerBoundsCalculation)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
struct ToDotClassFixture : Fixture
|
||||
@ -111,29 +109,7 @@ local function f(a, ...: string) return a end
|
||||
ToDotOptions opts;
|
||||
opts.showPointers = false;
|
||||
|
||||
if (FFlag::LuauLowerBoundsCalculation)
|
||||
{
|
||||
CHECK_EQ(R"(digraph graphname {
|
||||
n1 [label="FunctionTypeVar 1"];
|
||||
n1 -> n2 [label="arg"];
|
||||
n2 [label="TypePack 2"];
|
||||
n2 -> n3;
|
||||
n3 [label="GenericTypeVar 3"];
|
||||
n2 -> n4 [label="tail"];
|
||||
n4 [label="VariadicTypePack 4"];
|
||||
n4 -> n5;
|
||||
n5 [label="string"];
|
||||
n1 -> n6 [label="ret"];
|
||||
n6 [label="TypePack 6"];
|
||||
n6 -> n7;
|
||||
n7 [label="BoundTypeVar 7"];
|
||||
n7 -> n3;
|
||||
})",
|
||||
toDot(requireType("f"), opts));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ(R"(digraph graphname {
|
||||
CHECK_EQ(R"(digraph graphname {
|
||||
n1 [label="FunctionTypeVar 1"];
|
||||
n1 -> n2 [label="arg"];
|
||||
n2 [label="TypePack 2"];
|
||||
@ -149,8 +125,7 @@ n6 -> n7;
|
||||
n7 [label="TypePack 7"];
|
||||
n7 -> n3;
|
||||
})",
|
||||
toDot(requireType("f"), opts));
|
||||
}
|
||||
toDot(requireType("f"), opts));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "union")
|
||||
|
@ -12,6 +12,7 @@ using namespace Luau;
|
||||
LUAU_FASTFLAG(LuauRecursiveTypeParameterRestriction);
|
||||
LUAU_FASTFLAG(LuauSpecialTypesAsterisked);
|
||||
LUAU_FASTFLAG(LuauFixNameMaps);
|
||||
LUAU_FASTFLAG(LuauFunctionReturnStringificationFixup);
|
||||
|
||||
TEST_SUITE_BEGIN("ToString");
|
||||
|
||||
@ -570,6 +571,22 @@ TEST_CASE_FIXTURE(Fixture, "toString_the_boundTo_table_type_contained_within_a_T
|
||||
CHECK_EQ("{| hello: number, world: number |}", toString(&tpv2));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "no_parentheses_around_return_type_if_pack_has_an_empty_head_link")
|
||||
{
|
||||
TypeArena arena;
|
||||
TypePackId realTail = arena.addTypePack({singletonTypes->stringType});
|
||||
TypePackId emptyTail = arena.addTypePack({}, realTail);
|
||||
|
||||
TypePackId argList = arena.addTypePack({singletonTypes->stringType});
|
||||
|
||||
TypeId functionType = arena.addType(FunctionTypeVar{argList, emptyTail});
|
||||
|
||||
if (FFlag::LuauFunctionReturnStringificationFixup)
|
||||
CHECK("(string) -> string" == toString(functionType));
|
||||
else
|
||||
CHECK("(string) -> (string)" == toString(functionType));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "no_parentheses_around_cyclic_function_type_in_union")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
|
@ -657,50 +657,9 @@ struct AssertionCatcher
|
||||
int AssertionCatcher::tripped;
|
||||
} // namespace
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "luau_ice_triggers_an_ice")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{"DebugLuauMagicTypes", true},
|
||||
{"LuauUseInternalCompilerErrorException", false},
|
||||
};
|
||||
|
||||
AssertionCatcher ac;
|
||||
|
||||
CHECK_THROWS_AS(check(R"(
|
||||
local a: _luau_ice = 55
|
||||
)"),
|
||||
std::runtime_error);
|
||||
|
||||
LUAU_ASSERT(1 == AssertionCatcher::tripped);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "luau_ice_triggers_an_ice_handler")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{"DebugLuauMagicTypes", true},
|
||||
{"LuauUseInternalCompilerErrorException", false},
|
||||
};
|
||||
|
||||
bool caught = false;
|
||||
|
||||
frontend.iceHandler.onInternalError = [&](const char*) {
|
||||
caught = true;
|
||||
};
|
||||
|
||||
CHECK_THROWS_AS(check(R"(
|
||||
local a: _luau_ice = 55
|
||||
)"),
|
||||
std::runtime_error);
|
||||
|
||||
CHECK_EQ(true, caught);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "luau_ice_triggers_an_ice_exception_with_flag")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{"DebugLuauMagicTypes", true},
|
||||
{"LuauUseInternalCompilerErrorException", true},
|
||||
};
|
||||
ScopedFastFlag sffs{"DebugLuauMagicTypes", true};
|
||||
|
||||
AssertionCatcher ac;
|
||||
|
||||
@ -714,10 +673,7 @@ TEST_CASE_FIXTURE(Fixture, "luau_ice_triggers_an_ice_exception_with_flag")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "luau_ice_triggers_an_ice_exception_with_flag_handler")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{"DebugLuauMagicTypes", true},
|
||||
{"LuauUseInternalCompilerErrorException", true},
|
||||
};
|
||||
ScopedFastFlag sffs{"DebugLuauMagicTypes", true};
|
||||
|
||||
bool caught = false;
|
||||
|
||||
|
@ -8,9 +8,7 @@
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauLowerBoundsCalculation);
|
||||
LUAU_FASTFLAG(LuauSpecialTypesAsterisked);
|
||||
LUAU_FASTFLAG(LuauStringFormatArgumentErrorFix)
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
|
||||
TEST_SUITE_BEGIN("BuiltinTests");
|
||||
@ -637,8 +635,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "select_with_decimal_argument_is_rounded_down
|
||||
// Could be flaky if the fix has regressed.
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "bad_select_should_not_crash")
|
||||
{
|
||||
ScopedFastFlag luauFunctionArgMismatchDetails{"LuauFunctionArgMismatchDetails", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
do end
|
||||
local _ = function(l0,...)
|
||||
@ -754,14 +750,7 @@ TEST_CASE_FIXTURE(Fixture, "string_format_use_correct_argument")
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
if (FFlag::LuauStringFormatArgumentErrorFix)
|
||||
{
|
||||
CHECK_EQ("Argument count mismatch. Function expects 2 arguments, but 3 are specified", toString(result.errors[0]));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("Argument count mismatch. Function expects 1 argument, but 2 are specified", toString(result.errors[0]));
|
||||
}
|
||||
CHECK_EQ("Argument count mismatch. Function expects 2 arguments, but 3 are specified", toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "string_format_use_correct_argument2")
|
||||
@ -778,8 +767,6 @@ TEST_CASE_FIXTURE(Fixture, "string_format_use_correct_argument2")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "string_format_use_correct_argument3")
|
||||
{
|
||||
ScopedFastFlag LuauStringFormatArgumentErrorFix{"LuauStringFormatArgumentErrorFix", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local s1 = string.format("%d")
|
||||
local s2 = string.format("%d", 1)
|
||||
@ -966,10 +953,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "assert_removes_falsy_types")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
if (FFlag::LuauLowerBoundsCalculation)
|
||||
CHECK_EQ("((boolean | number)?) -> number | true", toString(requireType("f")));
|
||||
else
|
||||
CHECK_EQ("((boolean | number)?) -> boolean | number", toString(requireType("f")));
|
||||
CHECK_EQ("((boolean | number)?) -> boolean | number", toString(requireType("f")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "assert_removes_falsy_types2")
|
||||
@ -1040,8 +1024,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_freeze_is_generic")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "set_metatable_needs_arguments")
|
||||
{
|
||||
ScopedFastFlag luauFunctionArgMismatchDetails{"LuauFunctionArgMismatchDetails", true};
|
||||
|
||||
ScopedFastFlag sff{"LuauSetMetaTableArgsCheck", true};
|
||||
CheckResult result = check(R"(
|
||||
local a = {b=setmetatable}
|
||||
|
@ -482,7 +482,7 @@ local a: ChildClass = i
|
||||
|
||||
TEST_CASE_FIXTURE(ClassFixture, "intersections_of_unions_of_classes")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
ScopedFastFlag sffs[]{
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
@ -499,7 +499,7 @@ TEST_CASE_FIXTURE(ClassFixture, "intersections_of_unions_of_classes")
|
||||
|
||||
TEST_CASE_FIXTURE(ClassFixture, "unions_of_intersections_of_classes")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
ScopedFastFlag sffs[]{
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
|
@ -14,7 +14,6 @@
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauLowerBoundsCalculation);
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping);
|
||||
LUAU_FASTFLAG(LuauSpecialTypesAsterisked);
|
||||
|
||||
@ -299,22 +298,6 @@ TEST_CASE_FIXTURE(Fixture, "cyclic_function_type_in_rets")
|
||||
CHECK_EQ("t1 where t1 = () -> t1", toString(requireType("f")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "cyclic_function_type_in_args")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{"LuauLowerBoundsCalculation", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function f(g)
|
||||
return f(f)
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK_EQ("t1 where t1 = <a...>(t1) -> (a...)", toString(requireType("f")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "another_higher_order_function")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
@ -1132,16 +1115,13 @@ f(function(x) return x * 2 end)
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ("Type 'number' could not be converted into 'Table'", toString(result.errors[0]));
|
||||
|
||||
if (!FFlag::LuauLowerBoundsCalculation)
|
||||
{
|
||||
// Return type doesn't inference 'nil'
|
||||
result = check(R"(
|
||||
function f(a: (number) -> nil) return a(4) end
|
||||
f(function(x) print(x) end)
|
||||
)");
|
||||
// Return type doesn't inference 'nil'
|
||||
result = check(R"(
|
||||
function f(a: (number) -> nil) return a(4) end
|
||||
f(function(x) print(x) end)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "infer_anonymous_function_arguments")
|
||||
@ -1244,16 +1224,13 @@ f(function(x) return x * 2 end)
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ("Type 'number' could not be converted into 'Table'", toString(result.errors[0]));
|
||||
|
||||
if (!FFlag::LuauLowerBoundsCalculation)
|
||||
{
|
||||
// Return type doesn't inference 'nil'
|
||||
result = check(R"(
|
||||
function f(a: (number) -> nil) return a(4) end
|
||||
f(function(x) print(x) end)
|
||||
)");
|
||||
// Return type doesn't inference 'nil'
|
||||
result = check(R"(
|
||||
function f(a: (number) -> nil) return a(4) end
|
||||
f(function(x) print(x) end)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "infer_anonymous_function_arguments_outside_call")
|
||||
@ -1436,87 +1413,6 @@ end
|
||||
CHECK_EQ(toString(result.errors[1]), R"(Type 'string' could not be converted into 'number')");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "inconsistent_return_types")
|
||||
{
|
||||
const ScopedFastFlag flags[] = {
|
||||
{"LuauLowerBoundsCalculation", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function foo(a: boolean, b: number)
|
||||
if a then
|
||||
return nil
|
||||
else
|
||||
return b
|
||||
end
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK_EQ("(boolean, number) -> number?", toString(requireType("foo")));
|
||||
|
||||
// TODO: Test multiple returns
|
||||
// Think of various cases where typepacks need to grow. maybe consult other tests
|
||||
// Basic normalization of ConstrainedTypeVars during quantification
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "inconsistent_higher_order_function")
|
||||
{
|
||||
const ScopedFastFlag flags[] = {
|
||||
{"LuauLowerBoundsCalculation", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function foo(f)
|
||||
f(5)
|
||||
f("six")
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("<a...>((number | string) -> (a...)) -> ()", toString(requireType("foo")));
|
||||
}
|
||||
|
||||
|
||||
/* The bug here is that we are using the same level 2.0 for both the body of resolveDispatcher and the
|
||||
* lambda useCallback.
|
||||
*
|
||||
* I think what we want to do is, at each scope level, never reuse the same sublevel.
|
||||
*
|
||||
* We also adjust checkBlock to consider the syntax `local x = function() ... end` to be sortable
|
||||
* in the same way as `local function x() ... end`. This causes the function `resolveDispatcher` to be
|
||||
* checked before the lambda.
|
||||
*/
|
||||
TEST_CASE_FIXTURE(Fixture, "inferred_higher_order_functions_are_quantified_at_the_right_time")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{"LuauLowerBoundsCalculation", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
|
||||
local function resolveDispatcher()
|
||||
return (nil :: any) :: {useCallback: (any) -> any}
|
||||
end
|
||||
|
||||
local useCallback = function(deps: any)
|
||||
return resolveDispatcher().useCallback(deps)
|
||||
end
|
||||
)");
|
||||
|
||||
// LUAU_REQUIRE_NO_ERRORS is particularly unhelpful when this test is broken.
|
||||
// You get a TypeMismatch error where both types stringify the same.
|
||||
|
||||
CHECK(result.errors.empty());
|
||||
if (!result.errors.empty())
|
||||
{
|
||||
for (const auto& e : result.errors)
|
||||
printf("%s: %s\n", toString(e.location).c_str(), toString(e).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "inferred_higher_order_functions_are_quantified_at_the_right_time2")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
@ -1700,56 +1596,6 @@ TEST_CASE_FIXTURE(Fixture, "occurs_check_failure_in_function_return_type")
|
||||
CHECK(nullptr != get<OccursCheckFailed>(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "quantify_constrained_types")
|
||||
{
|
||||
ScopedFastFlag sff[]{
|
||||
{"LuauLowerBoundsCalculation", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
local function foo(f)
|
||||
f(5)
|
||||
f("hi")
|
||||
local function g()
|
||||
return f
|
||||
end
|
||||
local h = g()
|
||||
h(true)
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("<a...>((boolean | number | string) -> (a...)) -> ()", toString(requireType("foo")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "call_o_with_another_argument_after_foo_was_quantified")
|
||||
{
|
||||
ScopedFastFlag sff[]{
|
||||
{"LuauLowerBoundsCalculation", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(o)
|
||||
local t = {}
|
||||
t[o] = true
|
||||
|
||||
local function foo(o)
|
||||
o.m1(5)
|
||||
t[o] = nil
|
||||
end
|
||||
|
||||
o.m1("hi")
|
||||
|
||||
return t
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
// TODO: check the normalized type of f
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "free_is_not_bound_to_unknown")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
@ -1800,8 +1646,6 @@ TEST_CASE_FIXTURE(Fixture, "dont_mutate_the_underlying_head_of_typepack_when_cal
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "improved_function_arg_mismatch_errors")
|
||||
{
|
||||
ScopedFastFlag luauFunctionArgMismatchDetails{"LuauFunctionArgMismatchDetails", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function foo1(a: number) end
|
||||
foo1()
|
||||
@ -1838,8 +1682,6 @@ u.a.foo()
|
||||
// This might be surprising, but since 'any' became optional, unannotated functions in non-strict 'expect' 0 arguments
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "improved_function_arg_mismatch_error_nonstrict")
|
||||
{
|
||||
ScopedFastFlag luauFunctionArgMismatchDetails{"LuauFunctionArgMismatchDetails", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
--!nonstrict
|
||||
local function foo(a, b) end
|
||||
|
@ -9,7 +9,6 @@
|
||||
|
||||
#include "doctest.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauCheckGenericHOFTypes)
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
LUAU_FASTFLAG(LuauSpecialTypesAsterisked)
|
||||
|
||||
@ -783,8 +782,6 @@ local TheDispatcher: Dispatcher = {
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "generic_argument_count_too_few")
|
||||
{
|
||||
ScopedFastFlag luauFunctionArgMismatchDetails{"LuauFunctionArgMismatchDetails", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function test(a: number)
|
||||
return 1
|
||||
@ -802,8 +799,6 @@ wrapper(test)
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "generic_argument_count_too_many")
|
||||
{
|
||||
ScopedFastFlag luauFunctionArgMismatchDetails{"LuauFunctionArgMismatchDetails", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function test2(a: number, b: string)
|
||||
return 1
|
||||
@ -965,7 +960,6 @@ TEST_CASE_FIXTURE(Fixture, "instantiate_generic_function_in_assignments")
|
||||
CHECK_EQ("<a, b...>((a) -> (b...), a) -> (b...)", toString(tm->givenType));
|
||||
else
|
||||
CHECK_EQ("((number) -> number, number) -> number", toString(tm->givenType));
|
||||
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "instantiate_generic_function_in_assignments2")
|
||||
@ -1114,27 +1108,7 @@ local b = sumrec(sum) -- ok
|
||||
local c = sumrec(function(x, y, f) return f(x, y) end) -- type binders are not inferred
|
||||
)");
|
||||
|
||||
if (FFlag::LuauCheckGenericHOFTypes)
|
||||
{
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
else if (FFlag::LuauInstantiateInSubtyping)
|
||||
{
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
CHECK_EQ(
|
||||
R"(Type '<a, b, c...>(a, b, (a, b) -> (c...)) -> (c...)' could not be converted into '<a>(a, a, (a, a) -> a) -> a'
|
||||
caused by:
|
||||
Argument #1 type is not compatible. Generic subtype escaping scope)",
|
||||
toString(result.errors[0]));
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
CHECK_EQ(
|
||||
"Type '(a, b, (a, b) -> (c...)) -> (c...)' could not be converted into '<a>(a, a, (a, a) -> a) -> a'; different number of generic type "
|
||||
"parameters",
|
||||
toString(result.errors[0]));
|
||||
}
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "substitution_with_bound_table")
|
||||
@ -1258,7 +1232,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "higher_rank_polymorphism_should_not_accept_i
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{"LuauInstantiateInSubtyping", true},
|
||||
{"LuauCheckGenericHOFTypes", true}, // necessary because of interactions with the test
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
|
@ -8,7 +8,6 @@
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauLowerBoundsCalculation);
|
||||
|
||||
TEST_SUITE_BEGIN("IntersectionTypes");
|
||||
|
||||
@ -306,10 +305,7 @@ TEST_CASE_FIXTURE(Fixture, "table_intersection_write_sealed")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
if (FFlag::LuauLowerBoundsCalculation)
|
||||
CHECK_EQ(toString(result.errors[0]), "Cannot add property 'z' to table '{| x: number, y: number |}'");
|
||||
else
|
||||
CHECK_EQ(toString(result.errors[0]), "Cannot add property 'z' to table 'X & Y'");
|
||||
CHECK_EQ(toString(result.errors[0]), "Cannot add property 'z' to table 'X & Y'");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "table_intersection_write_sealed_indirect")
|
||||
@ -333,16 +329,9 @@ TEST_CASE_FIXTURE(Fixture, "table_intersection_write_sealed_indirect")
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Type '(string, number) -> string' could not be converted into '(string) -> string'
|
||||
caused by:
|
||||
Argument count mismatch. Function expects 2 arguments, but only 1 is specified)");
|
||||
if (FFlag::LuauLowerBoundsCalculation)
|
||||
CHECK_EQ(toString(result.errors[1]), "Cannot add property 'z' to table '{| x: (number) -> number, y: (string) -> string |}'");
|
||||
else
|
||||
CHECK_EQ(toString(result.errors[1]), "Cannot add property 'z' to table 'X & Y'");
|
||||
CHECK_EQ(toString(result.errors[1]), "Cannot add property 'z' to table 'X & Y'");
|
||||
CHECK_EQ(toString(result.errors[2]), "Type 'number' could not be converted into 'string'");
|
||||
|
||||
if (FFlag::LuauLowerBoundsCalculation)
|
||||
CHECK_EQ(toString(result.errors[3]), "Cannot add property 'w' to table '{| x: (number) -> number, y: (string) -> string |}'");
|
||||
else
|
||||
CHECK_EQ(toString(result.errors[3]), "Cannot add property 'w' to table 'X & Y'");
|
||||
CHECK_EQ(toString(result.errors[3]), "Cannot add property 'w' to table 'X & Y'");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "table_write_sealed_indirect")
|
||||
@ -381,15 +370,11 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_intersection_setmetatable")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "error_detailed_intersection_part")
|
||||
{
|
||||
ScopedFastFlag flags[] = {{"LuauLowerBoundsCalculation", false}};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type X = { x: number }
|
||||
type Y = { y: number }
|
||||
type Z = { z: number }
|
||||
|
||||
type XYZ = X & Y & Z
|
||||
|
||||
local a: XYZ = 3
|
||||
)");
|
||||
|
||||
@ -401,15 +386,11 @@ caused by:
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "error_detailed_intersection_all")
|
||||
{
|
||||
ScopedFastFlag flags[] = {{"LuauLowerBoundsCalculation", false}};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type X = { x: number }
|
||||
type Y = { y: number }
|
||||
type Z = { z: number }
|
||||
|
||||
type XYZ = X & Y & Z
|
||||
|
||||
local a: XYZ
|
||||
local b: number = a
|
||||
)");
|
||||
@ -468,12 +449,13 @@ TEST_CASE_FIXTURE(Fixture, "intersect_false_and_bool_and_false")
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
// TODO: odd stringification of `false & (boolean & false)`.)
|
||||
CHECK_EQ(toString(result.errors[0]), "Type 'boolean & false & false' could not be converted into 'true'; none of the intersection parts are compatible");
|
||||
CHECK_EQ(toString(result.errors[0]),
|
||||
"Type 'boolean & false & false' could not be converted into 'true'; none of the intersection parts are compatible");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "intersect_saturate_overloaded_functions")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
ScopedFastFlag sffs[]{
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
@ -485,12 +467,13 @@ TEST_CASE_FIXTURE(Fixture, "intersect_saturate_overloaded_functions")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '((number?) -> number?) & ((string?) -> string?)' could not be converted into '(number) -> number'; none of the intersection parts are compatible");
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '((number?) -> number?) & ((string?) -> string?)' could not be converted into '(number) -> number'; "
|
||||
"none of the intersection parts are compatible");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "union_saturate_overloaded_functions")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
ScopedFastFlag sffs[]{
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
@ -502,12 +485,13 @@ TEST_CASE_FIXTURE(Fixture, "union_saturate_overloaded_functions")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '((number) -> number) & ((string) -> string)' could not be converted into '(boolean | number) -> boolean | number'; none of the intersection parts are compatible");
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '((number) -> number) & ((string) -> string)' could not be converted into '(boolean | number) -> "
|
||||
"boolean | number'; none of the intersection parts are compatible");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "intersection_of_tables")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
ScopedFastFlag sffs[]{
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
@ -519,7 +503,8 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '{| p: number?, q: number?, r: number? |} & {| p: number?, q: string? |}' could not be converted into '{| p: nil |}'; none of the intersection parts are compatible");
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '{| p: number?, q: number?, r: number? |} & {| p: number?, q: string? |}' could not be converted into "
|
||||
"'{| p: nil |}'; none of the intersection parts are compatible");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_top_properties")
|
||||
@ -531,12 +516,13 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_top_properties")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '{| p: number?, q: any |} & {| p: unknown, q: string? |}' could not be converted into '{| p: string?, q: number? |}'; none of the intersection parts are compatible");
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '{| p: number?, q: any |} & {| p: unknown, q: string? |}' could not be converted into '{| p: string?, "
|
||||
"q: number? |}'; none of the intersection parts are compatible");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_never_properties")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
ScopedFastFlag sffs[]{
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
@ -549,12 +535,13 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_never_properties")
|
||||
|
||||
// TODO: this should not produce type errors, since never <: { p : never }
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '{| p: never, q: string? |} & {| p: number?, q: never |}' could not be converted into 'never'; none of the intersection parts are compatible");
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '{| p: never, q: string? |} & {| p: number?, q: never |}' could not be converted into 'never'; none "
|
||||
"of the intersection parts are compatible");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "overloaded_functions_returning_intersections")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
ScopedFastFlag sffs[]{
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
@ -566,12 +553,14 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_returning_intersections")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '((number?) -> {| p: number |} & {| q: number |}) & ((string?) -> {| p: number |} & {| r: number |})' could not be converted into '(number?) -> {| p: number, q: number, r: number |}'; none of the intersection parts are compatible");
|
||||
CHECK_EQ(toString(result.errors[0]),
|
||||
"Type '((number?) -> {| p: number |} & {| q: number |}) & ((string?) -> {| p: number |} & {| r: number |})' could not be converted into "
|
||||
"'(number?) -> {| p: number, q: number, r: number |}'; none of the intersection parts are compatible");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
ScopedFastFlag sffs[]{
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
@ -585,12 +574,13 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '((number?) -> a | number) & ((string?) -> a | string)' could not be converted into '(number?) -> a'; none of the intersection parts are compatible");
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '((number?) -> a | number) & ((string?) -> a | string)' could not be converted into '(number?) -> a'; "
|
||||
"none of the intersection parts are compatible");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generics")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
ScopedFastFlag sffs[]{
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
@ -604,12 +594,13 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generics")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '((a?) -> a | b) & ((c?) -> b | c)' could not be converted into '(a?) -> (a & c) | b'; none of the intersection parts are compatible");
|
||||
CHECK_EQ(toString(result.errors[0]),
|
||||
"Type '((a?) -> a | b) & ((c?) -> b | c)' could not be converted into '(a?) -> (a & c) | b'; none of the intersection parts are compatible");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic_packs")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
ScopedFastFlag sffs[]{
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
@ -623,12 +614,13 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic_packs")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...))' could not be converted into '(nil, b...) -> (nil, a...)'; none of the intersection parts are compatible");
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...))' could not be converted "
|
||||
"into '(nil, b...) -> (nil, a...)'; none of the intersection parts are compatible");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_result")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
ScopedFastFlag sffs[]{
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
@ -642,12 +634,13 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_result")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '((nil) -> unknown) & ((number) -> number)' could not be converted into '(number?) -> number?'; none of the intersection parts are compatible");
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '((nil) -> unknown) & ((number) -> number)' could not be converted into '(number?) -> number?'; none "
|
||||
"of the intersection parts are compatible");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_arguments")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
ScopedFastFlag sffs[]{
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
@ -661,12 +654,13 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_arguments")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '((number) -> number?) & ((unknown) -> string?)' could not be converted into '(number?) -> nil'; none of the intersection parts are compatible");
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '((number) -> number?) & ((unknown) -> string?)' could not be converted into '(number?) -> nil'; none "
|
||||
"of the intersection parts are compatible");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_result")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
ScopedFastFlag sffs[]{
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
@ -680,12 +674,13 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_result")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '((nil) -> never) & ((number) -> number)' could not be converted into '(number?) -> never'; none of the intersection parts are compatible");
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '((nil) -> never) & ((number) -> number)' could not be converted into '(number?) -> never'; none of "
|
||||
"the intersection parts are compatible");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_arguments")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
ScopedFastFlag sffs[]{
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
@ -699,7 +694,8 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_arguments")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '((never) -> string?) & ((number) -> number?)' could not be converted into '(number?) -> nil'; none of the intersection parts are compatible");
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '((never) -> string?) & ((number) -> number?)' could not be converted into '(number?) -> nil'; none "
|
||||
"of the intersection parts are compatible");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_overlapping_results_and_variadics")
|
||||
@ -711,7 +707,8 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_overlapping_results_and_
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '((number?) -> (...number)) & ((string?) -> number | string)' could not be converted into '(number | string) -> (number, number?)'; none of the intersection parts are compatible");
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '((number?) -> (...number)) & ((string?) -> number | string)' could not be converted into '(number | "
|
||||
"string) -> (number, number?)'; none of the intersection parts are compatible");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_1")
|
||||
@ -725,7 +722,8 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_1")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '(() -> (a...)) & (() -> (b...))' could not be converted into '() -> ()'; none of the intersection parts are compatible");
|
||||
CHECK_EQ(toString(result.errors[0]),
|
||||
"Type '(() -> (a...)) & (() -> (b...))' could not be converted into '() -> ()'; none of the intersection parts are compatible");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_2")
|
||||
@ -739,7 +737,8 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_2")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '((a...) -> ()) & ((b...) -> ())' could not be converted into '() -> ()'; none of the intersection parts are compatible");
|
||||
CHECK_EQ(toString(result.errors[0]),
|
||||
"Type '((a...) -> ()) & ((b...) -> ())' could not be converted into '() -> ()'; none of the intersection parts are compatible");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_3")
|
||||
@ -753,7 +752,8 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_3")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '(() -> (a...)) & (() -> (number?, a...))' could not be converted into '() -> number'; none of the intersection parts are compatible");
|
||||
CHECK_EQ(toString(result.errors[0]),
|
||||
"Type '(() -> (a...)) & (() -> (number?, a...))' could not be converted into '() -> number'; none of the intersection parts are compatible");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_4")
|
||||
@ -767,12 +767,13 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_4")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '((a...) -> ()) & ((number, a...) -> number)' could not be converted into '(number?) -> ()'; none of the intersection parts are compatible");
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '((a...) -> ()) & ((number, a...) -> number)' could not be converted into '(number?) -> ()'; none of "
|
||||
"the intersection parts are compatible");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatables")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
ScopedFastFlag sffs[]{
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
@ -800,7 +801,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatables")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatable_subtypes")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
ScopedFastFlag sffs[]{
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
@ -826,7 +827,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatable_subtypes")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatables_with_properties")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
ScopedFastFlag sffs[]{
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
@ -849,7 +850,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatables_with_properties")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatable_with table")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
ScopedFastFlag sffs[]{
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
@ -874,7 +875,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatable_with table")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "CLI-44817")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
ScopedFastFlag sffs[]{
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
|
@ -622,9 +622,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "loop_iter_metamethod_not_enough_returns")
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK(result.errors[0] == TypeError{
|
||||
Location{{2, 36}, {2, 37}},
|
||||
GenericError{"__iter must return at least one value"},
|
||||
});
|
||||
Location{{2, 36}, {2, 37}},
|
||||
GenericError{"__iter must return at least one value"},
|
||||
});
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "loop_iter_metamethod_ok")
|
||||
|
@ -254,20 +254,20 @@ return m
|
||||
if (FFlag::LuauInstantiateInSubtyping)
|
||||
{
|
||||
// though this didn't error before the flag, it seems as though it should error since fields of a table are invariant.
|
||||
// the user's intent would likely be that these "method" fields would be read-only, but without an annotation, accepting this should be unsound.
|
||||
// the user's intent would likely be that these "method" fields would be read-only, but without an annotation, accepting this should be
|
||||
// unsound.
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
CHECK_EQ(R"(Type 'n' could not be converted into 't1 where t1 = {- Clone: (t1) -> (a...) -}'
|
||||
caused by:
|
||||
Property 'Clone' is not compatible. Type '<a>(a) -> ()' could not be converted into 't1 where t1 = ({- Clone: t1 -}) -> (a...)'; different number of generic type parameters)",
|
||||
toString(result.errors[0]));
|
||||
toString(result.errors[0]));
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "custom_require_global")
|
||||
|
@ -7,8 +7,6 @@
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
LUAU_FASTFLAG(LuauLowerBoundsCalculation)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
TEST_SUITE_BEGIN("ProvisionalTests");
|
||||
@ -301,19 +299,10 @@ TEST_CASE_FIXTURE(Fixture, "do_not_ice_when_trying_to_pick_first_of_generic_type
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::LuauLowerBoundsCalculation)
|
||||
{
|
||||
CHECK_EQ("() -> ()", toString(requireType("f")));
|
||||
CHECK_EQ("() -> ()", toString(requireType("g")));
|
||||
CHECK_EQ("nil", toString(requireType("x")));
|
||||
}
|
||||
else
|
||||
{
|
||||
// f and g should have the type () -> ()
|
||||
CHECK_EQ("() -> (a...)", toString(requireType("f")));
|
||||
CHECK_EQ("<a...>() -> (a...)", toString(requireType("g")));
|
||||
CHECK_EQ("any", toString(requireType("x"))); // any is returned instead of ICE for now
|
||||
}
|
||||
// f and g should have the type () -> ()
|
||||
CHECK_EQ("() -> (a...)", toString(requireType("f")));
|
||||
CHECK_EQ("<a...>() -> (a...)", toString(requireType("g")));
|
||||
CHECK_EQ("any", toString(requireType("x"))); // any is returned instead of ICE for now
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "specialization_binds_with_prototypes_too_early")
|
||||
@ -330,7 +319,6 @@ TEST_CASE_FIXTURE(Fixture, "specialization_binds_with_prototypes_too_early")
|
||||
TEST_CASE_FIXTURE(Fixture, "weird_fail_to_unify_type_pack")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{"LuauLowerBoundsCalculation", false},
|
||||
// I'm not sure why this is broken without DCR, but it seems to be fixed
|
||||
// when DCR is enabled.
|
||||
{"DebugLuauDeferredConstraintResolution", false},
|
||||
@ -347,7 +335,6 @@ TEST_CASE_FIXTURE(Fixture, "weird_fail_to_unify_type_pack")
|
||||
TEST_CASE_FIXTURE(Fixture, "weird_fail_to_unify_variadic_pack")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{"LuauLowerBoundsCalculation", false},
|
||||
// I'm not sure why this is broken without DCR, but it seems to be fixed
|
||||
// when DCR is enabled.
|
||||
{"DebugLuauDeferredConstraintResolution", false},
|
||||
@ -362,56 +349,6 @@ TEST_CASE_FIXTURE(Fixture, "weird_fail_to_unify_variadic_pack")
|
||||
LUAU_REQUIRE_ERRORS(result); // Should not have any errors.
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "lower_bounds_calculation_is_too_permissive_with_overloaded_higher_order_functions")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{"LuauLowerBoundsCalculation", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function foo(f)
|
||||
f(5, 'a')
|
||||
f('b', 6)
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
// We incorrectly infer that the argument to foo could be called with (number, number) or (string, string)
|
||||
// even though that is strictly more permissive than the actual source text shows.
|
||||
CHECK("<a...>((number | string, number | string) -> (a...)) -> ()" == toString(requireType("foo")));
|
||||
}
|
||||
|
||||
// Once fixed, move this to Normalize.test.cpp
|
||||
TEST_CASE_FIXTURE(Fixture, "normalization_fails_on_certain_kinds_of_cyclic_tables")
|
||||
{
|
||||
#if defined(_DEBUG) || defined(_NOOPT)
|
||||
ScopedFastInt sfi("LuauNormalizeIterationLimit", 500);
|
||||
#endif
|
||||
|
||||
ScopedFastFlag flags[] = {
|
||||
{"LuauLowerBoundsCalculation", true},
|
||||
};
|
||||
|
||||
// We use a function and inferred parameter types to prevent intermediate normalizations from being performed.
|
||||
// This exposes a bug where the type of y is mutated.
|
||||
CheckResult result = check(R"(
|
||||
function strange(x, y)
|
||||
x.x = y
|
||||
y.x = x
|
||||
|
||||
type R = {x: typeof(x)} & {x: typeof(y)}
|
||||
local r: R
|
||||
|
||||
return r
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
CHECK(nullptr != get<NormalizationTooComplex>(result.errors[0]));
|
||||
}
|
||||
|
||||
// Belongs in TypeInfer.builtins.test.cpp.
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "pcall_returns_at_least_two_value_but_function_returns_nothing")
|
||||
{
|
||||
@ -473,36 +410,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "function_returns_many_things_but_first_of_it
|
||||
CHECK_EQ("boolean", toString(requireType("b")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "constrained_is_level_dependent")
|
||||
{
|
||||
ScopedFastFlag sff[]{
|
||||
{"LuauLowerBoundsCalculation", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(o)
|
||||
local t = {}
|
||||
t[o] = true
|
||||
|
||||
local function foo(o)
|
||||
o:m1()
|
||||
t[o] = nil
|
||||
end
|
||||
|
||||
local function bar(o)
|
||||
o:m2()
|
||||
t[o] = true
|
||||
end
|
||||
|
||||
return t
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
// TODO: We're missing generics b...
|
||||
CHECK_EQ("<a...>(t1) -> {| [t1]: boolean |} where t1 = t2 ; t2 = {+ m1: (t1) -> (a...), m2: (t2) -> (b...) +}", toString(requireType("f")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "free_is_not_bound_to_any")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
@ -695,4 +602,187 @@ return wrapStrictTable(Constants, "Constants")
|
||||
CHECK(get<AnyTypeVar>(*result));
|
||||
}
|
||||
|
||||
// We need a simplification step to make this do the right thing. ("normalization-lite")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_with_a_singleton_argument")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local function foo(t, x)
|
||||
if x == "hi" or x == "bye" then
|
||||
table.insert(t, x)
|
||||
end
|
||||
|
||||
return t
|
||||
end
|
||||
|
||||
local t = foo({}, "hi")
|
||||
table.insert(t, "totally_unrelated_type" :: "totally_unrelated_type")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
// We'd really like for this to be {string}
|
||||
CHECK_EQ("{string | string}", toString(requireType("t")));
|
||||
}
|
||||
|
||||
struct NormalizeFixture : Fixture
|
||||
{
|
||||
bool isSubtype(TypeId a, TypeId b)
|
||||
{
|
||||
return ::Luau::isSubtype(a, b, NotNull{getMainModule()->getModuleScope().get()}, singletonTypes, ice);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "intersection_of_functions_of_different_arities")
|
||||
{
|
||||
check(R"(
|
||||
type A = (any) -> ()
|
||||
type B = (any, any) -> ()
|
||||
type T = A & B
|
||||
|
||||
local a: A
|
||||
local b: B
|
||||
local t: T
|
||||
)");
|
||||
|
||||
[[maybe_unused]] TypeId a = requireType("a");
|
||||
[[maybe_unused]] TypeId b = requireType("b");
|
||||
|
||||
// CHECK(!isSubtype(a, b)); // !!
|
||||
// CHECK(!isSubtype(b, a));
|
||||
|
||||
CHECK("((any) -> ()) & ((any, any) -> ())" == toString(requireType("t")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "functions_with_mismatching_arity")
|
||||
{
|
||||
check(R"(
|
||||
local a: (number) -> ()
|
||||
local b: () -> ()
|
||||
|
||||
local c: () -> number
|
||||
)");
|
||||
|
||||
TypeId a = requireType("a");
|
||||
TypeId b = requireType("b");
|
||||
TypeId c = requireType("c");
|
||||
|
||||
// CHECK(!isSubtype(b, a));
|
||||
// CHECK(!isSubtype(c, a));
|
||||
|
||||
CHECK(!isSubtype(a, b));
|
||||
// CHECK(!isSubtype(c, b));
|
||||
|
||||
CHECK(!isSubtype(a, c));
|
||||
CHECK(!isSubtype(b, c));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "functions_with_mismatching_arity_but_optional_parameters")
|
||||
{
|
||||
/*
|
||||
* (T0..TN) <: (T0..TN, A?)
|
||||
* (T0..TN) <: (T0..TN, any)
|
||||
* (T0..TN, A?) </: (T0..TN) We don't technically need to spell this out, but it's quite important.
|
||||
* T <: T
|
||||
* if A <: B and B <: C then A <: C
|
||||
* T -> R <: U -> S if U <: T and R <: S
|
||||
* A | B <: T if A <: T and B <: T
|
||||
* T <: A | B if T <: A or T <: B
|
||||
*/
|
||||
check(R"(
|
||||
local a: (number?) -> ()
|
||||
local b: (number) -> ()
|
||||
local c: (number, number?) -> ()
|
||||
)");
|
||||
|
||||
TypeId a = requireType("a");
|
||||
TypeId b = requireType("b");
|
||||
TypeId c = requireType("c");
|
||||
|
||||
/*
|
||||
* (number) -> () </: (number?) -> ()
|
||||
* because number? </: number (because number <: number, but nil </: number)
|
||||
*/
|
||||
CHECK(!isSubtype(b, a));
|
||||
|
||||
/*
|
||||
* (number, number?) </: (number?) -> ()
|
||||
* because number? </: number (as above)
|
||||
*/
|
||||
CHECK(!isSubtype(c, a));
|
||||
|
||||
/*
|
||||
* (number?) -> () <: (number) -> ()
|
||||
* because number <: number? (because number <: number)
|
||||
*/
|
||||
CHECK(isSubtype(a, b));
|
||||
|
||||
/*
|
||||
* (number, number?) -> () <: (number) -> (number)
|
||||
* The packs have inequal lengths, but (number) <: (number, number?)
|
||||
* and number <: number
|
||||
*/
|
||||
// CHECK(!isSubtype(c, b));
|
||||
|
||||
/*
|
||||
* (number?) -> () </: (number, number?) -> ()
|
||||
* because (number, number?) </: (number)
|
||||
*/
|
||||
// CHECK(!isSubtype(a, c));
|
||||
|
||||
/*
|
||||
* (number) -> () </: (number, number?) -> ()
|
||||
* because (number, number?) </: (number)
|
||||
*/
|
||||
// CHECK(!isSubtype(b, c));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "functions_with_mismatching_arity_but_any_is_an_optional_param")
|
||||
{
|
||||
check(R"(
|
||||
local a: (number?) -> ()
|
||||
local b: (number) -> ()
|
||||
local c: (number, any) -> ()
|
||||
)");
|
||||
|
||||
TypeId a = requireType("a");
|
||||
TypeId b = requireType("b");
|
||||
TypeId c = requireType("c");
|
||||
|
||||
/*
|
||||
* (number) -> () </: (number?) -> ()
|
||||
* because number? </: number (because number <: number, but nil </: number)
|
||||
*/
|
||||
CHECK(!isSubtype(b, a));
|
||||
|
||||
/*
|
||||
* (number, any) </: (number?) -> ()
|
||||
* because number? </: number (as above)
|
||||
*/
|
||||
CHECK(!isSubtype(c, a));
|
||||
|
||||
/*
|
||||
* (number?) -> () <: (number) -> ()
|
||||
* because number <: number? (because number <: number)
|
||||
*/
|
||||
CHECK(isSubtype(a, b));
|
||||
|
||||
/*
|
||||
* (number, any) -> () </: (number) -> (number)
|
||||
* The packs have inequal lengths
|
||||
*/
|
||||
// CHECK(!isSubtype(c, b));
|
||||
|
||||
/*
|
||||
* (number?) -> () </: (number, any) -> ()
|
||||
* The packs have inequal lengths
|
||||
*/
|
||||
// CHECK(!isSubtype(a, c));
|
||||
|
||||
/*
|
||||
* (number) -> () </: (number, any) -> ()
|
||||
* The packs have inequal lengths
|
||||
*/
|
||||
// CHECK(!isSubtype(b, c));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -7,7 +7,6 @@
|
||||
|
||||
#include "doctest.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauLowerBoundsCalculation)
|
||||
LUAU_FASTFLAG(LuauSpecialTypesAsterisked)
|
||||
|
||||
using namespace Luau;
|
||||
@ -608,10 +607,7 @@ TEST_CASE_FIXTURE(Fixture, "type_guard_can_filter_for_intersection_of_tables")
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::LuauLowerBoundsCalculation)
|
||||
CHECK_EQ("{| x: number, y: number |}", toString(requireTypeAtPosition({4, 28})));
|
||||
else
|
||||
CHECK_EQ("{| x: number |} & {| y: number |}", toString(requireTypeAtPosition({4, 28})));
|
||||
CHECK_EQ("{| x: number |} & {| y: number |}", toString(requireTypeAtPosition({4, 28})));
|
||||
CHECK_EQ("nil", toString(requireTypeAtPosition({6, 28})));
|
||||
}
|
||||
|
||||
|
@ -421,28 +421,6 @@ TEST_CASE_FIXTURE(Fixture, "widening_happens_almost_everywhere_except_for_tables
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_with_a_singleton_argument")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauLowerBoundsCalculation", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function foo(t, x)
|
||||
if x == "hi" or x == "bye" then
|
||||
table.insert(t, x)
|
||||
end
|
||||
|
||||
return t
|
||||
end
|
||||
|
||||
local t = foo({}, "hi")
|
||||
table.insert(t, "totally_unrelated_type" :: "totally_unrelated_type")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("{string}", toString(requireType("t")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "functions_are_not_to_be_widened")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
|
@ -11,7 +11,6 @@
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauLowerBoundsCalculation)
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
|
||||
TEST_SUITE_BEGIN("TableTests");
|
||||
@ -1196,10 +1195,7 @@ TEST_CASE_FIXTURE(Fixture, "pass_incompatible_union_to_a_generic_table_without_c
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
if (FFlag::LuauLowerBoundsCalculation)
|
||||
CHECK(get<MissingProperties>(result.errors[0]));
|
||||
else
|
||||
CHECK(get<TypeMismatch>(result.errors[0]));
|
||||
CHECK(get<TypeMismatch>(result.errors[0]));
|
||||
}
|
||||
|
||||
// This unit test could be flaky if the fix has regressed.
|
||||
@ -2627,8 +2623,6 @@ do end
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "dont_crash_when_setmetatable_does_not_produce_a_metatabletypevar")
|
||||
{
|
||||
ScopedFastFlag luauFunctionArgMismatchDetails{"LuauFunctionArgMismatchDetails", true};
|
||||
|
||||
CheckResult result = check("local x = setmetatable({})");
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ("Argument count mismatch. Function 'setmetatable' expects 2 arguments, but only 1 is specified", toString(result.errors[0]));
|
||||
@ -2709,8 +2703,6 @@ local baz = foo[bar]
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "table_simple_call")
|
||||
{
|
||||
ScopedFastFlag luauFunctionArgMismatchDetails{"LuauFunctionArgMismatchDetails", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local a = setmetatable({ x = 2 }, {
|
||||
__call = function(self)
|
||||
@ -2887,7 +2879,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "dont_leak_free_table_props")
|
||||
TEST_CASE_FIXTURE(Fixture, "inferred_return_type_of_free_table")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{"LuauLowerBoundsCalculation", true},
|
||||
// {"LuauLowerBoundsCalculation", true},
|
||||
{"DebugLuauSharedSelf", true},
|
||||
};
|
||||
|
||||
|
@ -14,12 +14,10 @@
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
LUAU_FASTFLAG(LuauLowerBoundsCalculation);
|
||||
LUAU_FASTFLAG(LuauFixLocationSpanTableIndexExpr);
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping);
|
||||
LUAU_FASTFLAG(LuauSpecialTypesAsterisked);
|
||||
LUAU_FASTFLAG(LuauCheckGenericHOFTypes);
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
@ -89,7 +87,6 @@ TEST_CASE_FIXTURE(Fixture, "infer_in_nocheck_mode")
|
||||
{
|
||||
ScopedFastFlag sff[]{
|
||||
{"DebugLuauDeferredConstraintResolution", false},
|
||||
{"LuauLowerBoundsCalculation", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
@ -1001,21 +998,23 @@ TEST_CASE_FIXTURE(Fixture, "cli_50041_committing_txnlog_in_apollo_client_error")
|
||||
end
|
||||
)");
|
||||
|
||||
if (FFlag::LuauInstantiateInSubtyping && !FFlag::LuauCheckGenericHOFTypes)
|
||||
if (FFlag::LuauInstantiateInSubtyping)
|
||||
{
|
||||
// though this didn't error before the flag, it seems as though it should error since fields of a table are invariant.
|
||||
// the user's intent would likely be that these "method" fields would be read-only, but without an annotation, accepting this should be unsound.
|
||||
// the user's intent would likely be that these "method" fields would be read-only, but without an annotation, accepting this should be
|
||||
// unsound.
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
CHECK_EQ(R"(Type 't1 where t1 = {+ getStoreFieldName: (t1, {| fieldName: string |} & {| from: number? |}) -> (a, b...) +}' could not be converted into 'Policies'
|
||||
CHECK_EQ(
|
||||
R"(Type 't1 where t1 = {+ getStoreFieldName: (t1, {| fieldName: string |} & {| from: number? |}) -> (a, b...) +}' could not be converted into 'Policies'
|
||||
caused by:
|
||||
Property 'getStoreFieldName' is not compatible. Type 't1 where t1 = ({+ getStoreFieldName: t1 +}, {| fieldName: string |} & {| from: number? |}) -> (a, b...)' could not be converted into '(Policies, FieldSpecifier) -> string'
|
||||
caused by:
|
||||
Argument #2 type is not compatible. Type 'FieldSpecifier' could not be converted into 'FieldSpecifier & {| from: number? |}'
|
||||
caused by:
|
||||
Not all intersection parts are compatible. Table type 'FieldSpecifier' not compatible with type '{| from: number? |}' because the former has extra field 'fieldName')",
|
||||
toString(result.errors[0]));
|
||||
toString(result.errors[0]));
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -1044,7 +1043,7 @@ TEST_CASE_FIXTURE(Fixture, "type_infer_recursion_limit_no_ice")
|
||||
TEST_CASE_FIXTURE(Fixture, "type_infer_recursion_limit_normalizer")
|
||||
{
|
||||
ScopedFastInt sfi("LuauTypeInferRecursionLimit", 10);
|
||||
ScopedFastFlag sffs[] {
|
||||
ScopedFastFlag sffs[]{
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
{"LuauAutocompleteDynamicLimits", true},
|
||||
@ -1057,14 +1056,14 @@ TEST_CASE_FIXTURE(Fixture, "type_infer_recursion_limit_normalizer")
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ("Internal error: Code is too complex to typecheck! Consider adding type annotations around this area", toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "type_infer_cache_limit_normalizer")
|
||||
{
|
||||
ScopedFastInt sfi("LuauNormalizeCacheLimit", 10);
|
||||
ScopedFastFlag sffs[] {
|
||||
ScopedFastFlag sffs[]{
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
@ -1101,45 +1100,6 @@ TEST_CASE_FIXTURE(Fixture, "follow_on_new_types_in_substitution")
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* The problem we had here was that the type of q in B.h was initially inferring to {} | {prop: free} before we bound
|
||||
* that second table to the enclosing union.
|
||||
*/
|
||||
TEST_CASE_FIXTURE(Fixture, "do_not_bind_a_free_table_to_a_union_containing_that_table")
|
||||
{
|
||||
ScopedFastFlag flag[] = {
|
||||
{"LuauLowerBoundsCalculation", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
|
||||
local A = {}
|
||||
|
||||
function A:f()
|
||||
local t = {}
|
||||
|
||||
for key, value in pairs(self) do
|
||||
t[key] = value
|
||||
end
|
||||
|
||||
return t
|
||||
end
|
||||
|
||||
local B = A:f()
|
||||
|
||||
function B.g(t)
|
||||
assert(type(t) == "table")
|
||||
assert(t.prop ~= nil)
|
||||
end
|
||||
|
||||
function B.h(q)
|
||||
q = q or {}
|
||||
return q or {}
|
||||
end
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "types_stored_in_astResolvedTypes")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
|
@ -302,8 +302,8 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "metatables_unify_against_shape_of_free_table
|
||||
REQUIRE_EQ(state.errors.size(), 1);
|
||||
|
||||
std::string expected = "Type '{ @metatable {| __index: {| foo: string |} |}, { } }' could not be converted into '{- foo: number -}'\n"
|
||||
"caused by:\n"
|
||||
" Type 'number' could not be converted into 'string'";
|
||||
"caused by:\n"
|
||||
" Type 'number' could not be converted into 'string'";
|
||||
CHECK_EQ(toString(state.errors[0]), expected);
|
||||
}
|
||||
|
||||
|
@ -7,9 +7,9 @@
|
||||
|
||||
#include "doctest.h"
|
||||
|
||||
using namespace Luau;
|
||||
LUAU_FASTFLAG(LuauFunctionReturnStringificationFixup);
|
||||
|
||||
LUAU_FASTFLAG(LuauLowerBoundsCalculation);
|
||||
using namespace Luau;
|
||||
|
||||
TEST_SUITE_BEGIN("TypePackTests");
|
||||
|
||||
@ -311,7 +311,7 @@ local c: Packed<string, number, boolean>
|
||||
auto ttvA = get<TableTypeVar>(requireType("a"));
|
||||
REQUIRE(ttvA);
|
||||
CHECK_EQ(toString(requireType("a")), "Packed<number>");
|
||||
if (FFlag::LuauLowerBoundsCalculation)
|
||||
if (FFlag::LuauFunctionReturnStringificationFixup)
|
||||
CHECK_EQ(toString(requireType("a"), {true}), "{| f: (number) -> number |}");
|
||||
else
|
||||
CHECK_EQ(toString(requireType("a"), {true}), "{| f: (number) -> (number) |}");
|
||||
@ -966,8 +966,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "detect_cyclic_typepacks2")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "unify_variadic_tails_in_arguments")
|
||||
{
|
||||
ScopedFastFlag luauCallUnifyPackTails{"LuauCallUnifyPackTails", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function foo(...: string): number
|
||||
return 1
|
||||
@ -984,8 +982,6 @@ TEST_CASE_FIXTURE(Fixture, "unify_variadic_tails_in_arguments")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "unify_variadic_tails_in_arguments_free")
|
||||
{
|
||||
ScopedFastFlag luauCallUnifyPackTails{"LuauCallUnifyPackTails", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function foo<T...>(...: T...): T...
|
||||
return ...
|
||||
|
@ -6,7 +6,6 @@
|
||||
|
||||
#include "doctest.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauLowerBoundsCalculation)
|
||||
LUAU_FASTFLAG(LuauSpecialTypesAsterisked)
|
||||
|
||||
using namespace Luau;
|
||||
@ -360,10 +359,7 @@ a.x = 2
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
if (FFlag::LuauLowerBoundsCalculation)
|
||||
CHECK_EQ("Value of type '{| x: number, y: number |}?' could be nil", toString(result.errors[0]));
|
||||
else
|
||||
CHECK_EQ("Value of type '({| x: number |} & {| y: number |})?' could be nil", toString(result.errors[0]));
|
||||
CHECK_EQ("Value of type '({| x: number |} & {| y: number |})?' could be nil", toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "optional_length_error")
|
||||
@ -532,18 +528,13 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_union_write_indirect")
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
// NOTE: union normalization will improve this message
|
||||
if (FFlag::LuauLowerBoundsCalculation)
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '(string) -> number' could not be converted into '(number) -> string'\n"
|
||||
"caused by:\n"
|
||||
" Argument #1 type is not compatible. Type 'number' could not be converted into 'string'");
|
||||
else
|
||||
CHECK_EQ(toString(result.errors[0]),
|
||||
R"(Type '(string) -> number' could not be converted into '((number) -> string) | ((number) -> string)'; none of the union options are compatible)");
|
||||
CHECK_EQ(toString(result.errors[0]),
|
||||
R"(Type '(string) -> number' could not be converted into '((number) -> string) | ((number) -> string)'; none of the union options are compatible)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "union_true_and_false")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
ScopedFastFlag sffs[]{
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
@ -561,7 +552,7 @@ TEST_CASE_FIXTURE(Fixture, "union_true_and_false")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "union_of_functions")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
ScopedFastFlag sffs[]{
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
@ -598,7 +589,7 @@ TEST_CASE_FIXTURE(Fixture, "union_of_generic_typepack_functions")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "union_of_functions_mentioning_generics")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
ScopedFastFlag sffs[]{
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
@ -612,12 +603,13 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_mentioning_generics")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '(a) -> a?' could not be converted into '((b) -> b) | ((b?) -> nil)'; none of the union options are compatible");
|
||||
CHECK_EQ(toString(result.errors[0]),
|
||||
"Type '(a) -> a?' could not be converted into '((b) -> b) | ((b?) -> nil)'; none of the union options are compatible");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "union_of_functions_mentioning_generic_typepacks")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
ScopedFastFlag sffs[]{
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
@ -631,12 +623,13 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_mentioning_generic_typepacks")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '(number, a...) -> (number?, a...)' could not be converted into '((number) -> number) | ((number?, a...) -> (number?, a...))'; none of the union options are compatible");
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '(number, a...) -> (number?, a...)' could not be converted into '((number) -> number) | ((number?, "
|
||||
"a...) -> (number?, a...))'; none of the union options are compatible");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_arities")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
ScopedFastFlag sffs[]{
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
@ -648,12 +641,13 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_arities")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '(number) -> number?' could not be converted into '((number) -> nil) | ((number, string?) -> number)'; none of the union options are compatible");
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '(number) -> number?' could not be converted into '((number) -> nil) | ((number, string?) -> "
|
||||
"number)'; none of the union options are compatible");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_arities")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
ScopedFastFlag sffs[]{
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
@ -665,12 +659,13 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_arities")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '() -> number | string' could not be converted into '(() -> (string, string)) | (() -> number)'; none of the union options are compatible");
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '() -> number | string' could not be converted into '(() -> (string, string)) | (() -> number)'; none "
|
||||
"of the union options are compatible");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_variadics")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
ScopedFastFlag sffs[]{
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
@ -682,12 +677,13 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_variadics")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '(...nil) -> (...number?)' could not be converted into '((...string?) -> (...number)) | ((...string?) -> nil)'; none of the union options are compatible");
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '(...nil) -> (...number?)' could not be converted into '((...string?) -> (...number)) | ((...string?) "
|
||||
"-> nil)'; none of the union options are compatible");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_variadics")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
ScopedFastFlag sffs[]{
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
@ -699,12 +695,13 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_variadics")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '(number) -> ()' could not be converted into '((...number?) -> ()) | ((number?) -> ())'; none of the union options are compatible");
|
||||
CHECK_EQ(toString(result.errors[0]),
|
||||
"Type '(number) -> ()' could not be converted into '((...number?) -> ()) | ((number?) -> ())'; none of the union options are compatible");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_variadics")
|
||||
{
|
||||
ScopedFastFlag sffs[] {
|
||||
ScopedFastFlag sffs[]{
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
@ -716,7 +713,8 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_variadics
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '() -> (number?, ...number)' could not be converted into '(() -> (...number)) | (() -> number)'; none of the union options are compatible");
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '() -> (number?, ...number)' could not be converted into '(() -> (...number)) | (() -> number)'; none "
|
||||
"of the union options are compatible");
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -93,6 +93,7 @@ assert((function() local a = 1 a = a * 2 return a end)() == 2)
|
||||
assert((function() local a = 1 a = a / 2 return a end)() == 0.5)
|
||||
assert((function() local a = 5 a = a % 2 return a end)() == 1)
|
||||
assert((function() local a = 3 a = a ^ 2 return a end)() == 9)
|
||||
assert((function() local a = 9 a = a ^ 0.5 return a end)() == 3)
|
||||
|
||||
assert((function() local a = '1' a = a .. '2' return a end)() == "12")
|
||||
assert((function() local a = '1' a = a .. '2' .. '3' return a end)() == "123")
|
||||
@ -475,6 +476,12 @@ assert(rawequal("a", "a") == true)
|
||||
assert(rawequal("a", "b") == false)
|
||||
assert((function() a = {} b = {} mt = { __eq = function(l, r) return #l == #r end } setmetatable(a, mt) setmetatable(b, mt) return concat(a == b, rawequal(a, b)) end)() == "true,false")
|
||||
|
||||
-- rawequal fallback
|
||||
assert(concat(pcall(rawequal, "a", "a")) == "true,true")
|
||||
assert(concat(pcall(rawequal, "a", "b")) == "true,false")
|
||||
assert(concat(pcall(rawequal, "a", nil)) == "true,false")
|
||||
assert(pcall(rawequal, "a") == false)
|
||||
|
||||
-- metatable ops
|
||||
local function vec3t(x, y, z)
|
||||
return setmetatable({x=x, y=y, z=z}, {
|
||||
|
@ -71,6 +71,7 @@ for _, b in pairs(c) do
|
||||
assert(bit32.bxor(b) == b)
|
||||
assert(bit32.bxor(b, b) == 0)
|
||||
assert(bit32.bxor(b, 0) == b)
|
||||
assert(bit32.bxor(b, b, b) == b)
|
||||
assert(bit32.bnot(b) ~= b)
|
||||
assert(bit32.bnot(bit32.bnot(b)) == b)
|
||||
assert(bit32.bnot(b) == 2^32 - 1 - b)
|
||||
@ -104,6 +105,9 @@ assert(bit32.extract(0xa0001111, 16) == 0)
|
||||
assert(bit32.extract(0xa0001111, 31) == 1)
|
||||
assert(bit32.extract(42, 1, 3) == 5)
|
||||
|
||||
local pos pos = 1
|
||||
assert(bit32.extract(42, pos, 3) == 5) -- test bit32.extract builtin instead of bit32.extractk
|
||||
|
||||
assert(not pcall(bit32.extract, 0, -1))
|
||||
assert(not pcall(bit32.extract, 0, 32))
|
||||
assert(not pcall(bit32.extract, 0, 0, 33))
|
||||
@ -144,13 +148,17 @@ assert(bit32.lrotate("0x12345678", 4) == 0x23456781)
|
||||
assert(bit32.rrotate("0x12345678", -4) == 0x23456781)
|
||||
assert(bit32.arshift("0x12345678", 1) == 0x12345678 / 2)
|
||||
assert(bit32.arshift("-1", 32) == 0xffffffff)
|
||||
assert(bit32.arshift("-1", 1) == 0xffffffff)
|
||||
assert(bit32.bnot("1") == 0xfffffffe)
|
||||
assert(bit32.band("1", 3) == 1)
|
||||
assert(bit32.band(1, "3") == 1)
|
||||
assert(bit32.band(1, 3, "5") == 1)
|
||||
assert(bit32.bor("1", 2) == 3)
|
||||
assert(bit32.bor(1, "2") == 3)
|
||||
assert(bit32.bor(1, 3, "5") == 7)
|
||||
assert(bit32.bxor("1", 3) == 2)
|
||||
assert(bit32.bxor(1, "3") == 2)
|
||||
assert(bit32.bxor(1, 3, "5") == 7)
|
||||
assert(bit32.btest(1, "3") == true)
|
||||
assert(bit32.btest("1", 3) == true)
|
||||
assert(bit32.countlz("42") == 26)
|
||||
|
@ -54,4 +54,19 @@ breakpoint(49, false) -- validate that disabling breakpoints works
|
||||
|
||||
bar()
|
||||
|
||||
local function breakpointSetFromMetamethod()
|
||||
local a = setmetatable({}, {
|
||||
__index = function()
|
||||
breakpoint(67)
|
||||
return 2
|
||||
end
|
||||
})
|
||||
|
||||
local b = a.x
|
||||
|
||||
assert(b == 2)
|
||||
end
|
||||
|
||||
breakpointSetFromMetamethod()
|
||||
|
||||
return 'OK'
|
||||
|
@ -4,20 +4,6 @@ print('testing metatables')
|
||||
|
||||
local unpack = table.unpack
|
||||
|
||||
X = 20; B = 30
|
||||
|
||||
local _G = getfenv()
|
||||
setfenv(1, setmetatable({}, {__index=_G}))
|
||||
|
||||
collectgarbage()
|
||||
|
||||
X = X+10
|
||||
assert(X == 30 and _G.X == 20)
|
||||
B = false
|
||||
assert(B == false)
|
||||
B = nil
|
||||
assert(B == 30)
|
||||
|
||||
assert(getmetatable{} == nil)
|
||||
assert(getmetatable(4) == nil)
|
||||
assert(getmetatable(nil) == nil)
|
||||
@ -299,14 +285,8 @@ x = c(3,4,5)
|
||||
assert(i == 3 and x[1] == 3 and x[3] == 5)
|
||||
|
||||
|
||||
assert(_G.X == 20)
|
||||
assert(_G == getfenv(0))
|
||||
|
||||
print'+'
|
||||
|
||||
local _g = _G
|
||||
setfenv(1, setmetatable({}, {__index=function (_,k) return _g[k] end}))
|
||||
|
||||
-- testing proxies
|
||||
assert(getmetatable(newproxy()) == nil)
|
||||
assert(getmetatable(newproxy(false)) == nil)
|
||||
@ -480,4 +460,23 @@ do
|
||||
end
|
||||
end
|
||||
|
||||
function testfenv()
|
||||
X = 20; B = 30
|
||||
|
||||
local _G = getfenv()
|
||||
setfenv(1, setmetatable({}, {__index=_G}))
|
||||
|
||||
X = X+10
|
||||
assert(X == 30 and _G.X == 20)
|
||||
B = false
|
||||
assert(B == false)
|
||||
B = nil
|
||||
assert(B == 30)
|
||||
|
||||
assert(_G.X == 20)
|
||||
assert(_G == getfenv(0))
|
||||
end
|
||||
|
||||
testfenv() -- DONT MOVE THIS LINE
|
||||
|
||||
return 'OK'
|
||||
|
@ -8,4 +8,13 @@ end
|
||||
|
||||
foo()
|
||||
|
||||
function bar()
|
||||
local i = 0
|
||||
while i < 10 do
|
||||
i += i + 1
|
||||
end
|
||||
end
|
||||
|
||||
bar()
|
||||
|
||||
return "OK"
|
||||
|
@ -152,14 +152,34 @@ assert(eq(a[1000][3], 1000/3, 0.001))
|
||||
print('+')
|
||||
|
||||
do -- testing NaN
|
||||
local NaN = 10e500 - 10e400
|
||||
local NaN -- to avoid constant folding
|
||||
NaN = 10e500 - 10e400
|
||||
|
||||
assert(NaN ~= NaN)
|
||||
assert(not (NaN == NaN))
|
||||
|
||||
assert(not (NaN < NaN))
|
||||
assert(not (NaN <= NaN))
|
||||
assert(not (NaN > NaN))
|
||||
assert(not (NaN >= NaN))
|
||||
|
||||
assert(not (0 == NaN))
|
||||
assert(not (0 < NaN))
|
||||
assert(not (0 <= NaN))
|
||||
assert(not (0 > NaN))
|
||||
assert(not (0 >= NaN))
|
||||
|
||||
assert(not (NaN == 0))
|
||||
assert(not (NaN < 0))
|
||||
assert(not (NaN <= 0))
|
||||
assert(not (NaN > 0))
|
||||
assert(not (NaN >= 0))
|
||||
|
||||
assert(if NaN < 0 then false else true)
|
||||
assert(if NaN <= 0 then false else true)
|
||||
assert(if NaN > 0 then false else true)
|
||||
assert(if NaN >= 0 then false else true)
|
||||
|
||||
local a = {}
|
||||
assert(not pcall(function () a[NaN] = 1 end))
|
||||
assert(a[NaN] == nil)
|
||||
@ -215,6 +235,16 @@ assert(flag);
|
||||
|
||||
assert(select(2, pcall(math.random, 1, 2, 3)):match("wrong number of arguments"))
|
||||
|
||||
-- min/max
|
||||
assert(math.min(1) == 1)
|
||||
assert(math.min(1, 2) == 1)
|
||||
assert(math.min(1, 2, -1) == -1)
|
||||
assert(math.min(1, -1, 2) == -1)
|
||||
assert(math.max(1) == 1)
|
||||
assert(math.max(1, 2) == 2)
|
||||
assert(math.max(1, 2, -1) == 2)
|
||||
assert(math.max(1, -1, 2) == 2)
|
||||
|
||||
-- noise
|
||||
assert(math.noise(0.5) == 0)
|
||||
assert(math.noise(0.5, 0.5) == -0.25)
|
||||
@ -277,8 +307,10 @@ assert(math.log("10", 10) == 1)
|
||||
assert(math.log("9", 3) == 2)
|
||||
assert(math.max("1", 2) == 2)
|
||||
assert(math.max(2, "1") == 2)
|
||||
assert(math.max(1, 2, "3") == 3)
|
||||
assert(math.min("1", 2) == 1)
|
||||
assert(math.min(2, "1") == 1)
|
||||
assert(math.min(1, 2, "3") == 1)
|
||||
local v,f = math.modf("1.5")
|
||||
assert(v == 1 and f == 0.5)
|
||||
assert(math.pow("2", 2) == 4)
|
||||
@ -295,4 +327,9 @@ assert(math.sign("-2") == -1)
|
||||
assert(math.sign("0") == 0)
|
||||
assert(math.round("1.8") == 2)
|
||||
|
||||
-- test that fastcalls return correct number of results
|
||||
assert(select('#', math.floor(1.4)) == 1)
|
||||
assert(select('#', math.ceil(1.6)) == 1)
|
||||
assert(select('#', math.sqrt(9)) == 1)
|
||||
|
||||
return('OK')
|
||||
|
23
tests/conformance/safeenv.lua
Normal file
23
tests/conformance/safeenv.lua
Normal file
@ -0,0 +1,23 @@
|
||||
-- This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
print("safeenv reset")
|
||||
|
||||
local function envChangeInMetamethod()
|
||||
-- declare constant so that at O2 this test doesn't interfere with constant folding which we can't deoptimize
|
||||
local ten
|
||||
ten = 10
|
||||
|
||||
local a = setmetatable({}, {
|
||||
__index = function()
|
||||
getfenv().math = { abs = function(n) return n*n end }
|
||||
return 2
|
||||
end
|
||||
})
|
||||
|
||||
local b = a.x
|
||||
|
||||
assert(math.abs(ten) == 100)
|
||||
end
|
||||
|
||||
envChangeInMetamethod()
|
||||
|
||||
return"OK"
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user