Sync to upstream/release/549 (#707)

* Reoptimized math.min/max/bit32 builtins assuming at least 2 arguments are used (1-2% lift on some benchmarks)
* Type errors that mention function types no longer have redundant parenthesis around return type
* Luau REPL now supports --compile=remarks which displays the source code with optimization remarks embedded as comments
* Builtin calls are slightly faster when called with 1-2 arguments (~1% improvement in some benchmarks)
This commit is contained in:
vegorov-rbx 2022-10-14 12:48:41 -07:00 committed by GitHub
parent ff736fd3e4
commit 76070f8da2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
104 changed files with 4099 additions and 2223 deletions

View file

@ -76,8 +76,8 @@ struct ConstraintSolver
DcrLogger* logger; DcrLogger* logger;
explicit ConstraintSolver(NotNull<Normalizer> normalizer, NotNull<Scope> rootScope, ModuleName moduleName, explicit ConstraintSolver(NotNull<Normalizer> normalizer, NotNull<Scope> rootScope, ModuleName moduleName, NotNull<ModuleResolver> moduleResolver,
NotNull<ModuleResolver> moduleResolver, std::vector<RequireCycle> requireCycles, DcrLogger* logger); std::vector<RequireCycle> requireCycles, DcrLogger* logger);
// Randomize the order in which to dispatch constraints // Randomize the order in which to dispatch constraints
void randomize(unsigned seed); void randomize(unsigned seed);
@ -88,7 +88,9 @@ struct ConstraintSolver
**/ **/
void run(); void run();
bool done(); bool isDone();
void finalizeModule();
/** Attempt to dispatch a constraint. Returns true if it was successful. If /** Attempt to dispatch a constraint. Returns true if it was successful. If
* tryDispatch() returns false, the constraint remains in the unsolved set * tryDispatch() returns false, the constraint remains in the unsolved set

View file

@ -112,11 +112,13 @@ struct DcrLogger
void popBlock(NotNull<const Constraint> block); void popBlock(NotNull<const Constraint> block);
void captureInitialSolverState(const Scope* rootScope, const std::vector<NotNull<const Constraint>>& unsolvedConstraints); 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 commitStepSnapshot(StepSnapshot snapshot);
void captureFinalSolverState(const Scope* rootScope, const std::vector<NotNull<const Constraint>>& unsolvedConstraints); void captureFinalSolverState(const Scope* rootScope, const std::vector<NotNull<const Constraint>>& unsolvedConstraints);
void captureTypeCheckError(const TypeError& error); void captureTypeCheckError(const TypeError& error);
private: private:
ConstraintGenerationLog generationLog; ConstraintGenerationLog generationLog;
std::unordered_map<NotNull<const Constraint>, std::vector<ConstraintBlockTarget>> constraintBlocks; std::unordered_map<NotNull<const Constraint>, std::vector<ConstraintBlockTarget>> constraintBlocks;

View file

@ -33,7 +33,6 @@ struct UnknownSymbol
{ {
Binding, Binding,
Type, Type,
Generic
}; };
Name name; Name name;
Context context; Context context;

View file

@ -240,7 +240,7 @@ void write(JsonEmitter& emitter, const std::unordered_map<std::string, T>& map)
for (const auto& [k, v] : map) for (const auto& [k, v] : map)
o.writePair(k, v); o.writePair(k, v);
o.finish(); o.finish();
} }

View file

@ -17,8 +17,10 @@ struct SingletonTypes;
using ModulePtr = std::shared_ptr<Module>; 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(
bool isSubtype(TypePackId subTy, TypePackId superTy, NotNull<Scope> scope, NotNull<SingletonTypes> singletonTypes, InternalErrorReporter& ice, bool anyIsTop = true); 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( std::pair<TypeId, bool> normalize(
TypeId ty, NotNull<Scope> scope, TypeArena& arena, NotNull<SingletonTypes> singletonTypes, InternalErrorReporter& ice); TypeId ty, NotNull<Scope> scope, TypeArena& arena, NotNull<SingletonTypes> singletonTypes, InternalErrorReporter& ice);
@ -68,13 +70,14 @@ public:
insert(*it); insert(*it);
} }
bool operator ==(const TypeIds& there) const; bool operator==(const TypeIds& there) const;
size_t getHash() const; size_t getHash() const;
}; };
} // namespace Luau } // namespace Luau
template<> struct std::hash<Luau::TypeIds> template<>
struct std::hash<Luau::TypeIds>
{ {
std::size_t operator()(const Luau::TypeIds& tys) const 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 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 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 bool operator()(const Luau::TypeIds* here, const Luau::TypeIds* there) const
{ {
@ -160,7 +166,7 @@ struct NormalizedType
// The string part of the type. // The string part of the type.
// This may be the `string` type, or a union of singletons. // 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. // The thread part of the type.
// This type is either never or thread. // This type is either never or thread.

View file

@ -397,7 +397,7 @@ private:
std::vector<std::pair<TypeId, ScopePtr>> deferredQuantification; std::vector<std::pair<TypeId, ScopePtr>> deferredQuantification;
}; };
using PrintLineProc = void(*)(const std::string&); using PrintLineProc = void (*)(const std::string&);
extern PrintLineProc luauPrintLine; extern PrintLineProc luauPrintLine;

View file

@ -61,15 +61,15 @@ struct Unifier
ErrorVec errors; ErrorVec errors;
Location location; Location location;
Variance variance = Covariant; 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 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 normalize; // Normalize unions and intersections if necessary
bool useScopes = false; // If true, we use the scope hierarchy rather than TypeLevels bool useScopes = false; // If true, we use the scope hierarchy rather than TypeLevels
CountMismatch::Context ctx = CountMismatch::Arg; CountMismatch::Context ctx = CountMismatch::Arg;
UnifierSharedState& sharedState; UnifierSharedState& sharedState;
Unifier(NotNull<Normalizer> normalizer, Mode mode, NotNull<Scope> scope, const Location& location, Variance variance, Unifier(
TxnLog* parentLog = nullptr); 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. // Test whether the two type vars unify. Never commits the result.
ErrorVec canUnify(TypeId subTy, TypeId superTy); 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 tryUnifyTypeWithUnion(TypeId subTy, TypeId superTy, const UnionTypeVar* uv, bool cacheEnabled, bool isFunctionCall);
void tryUnifyTypeWithIntersection(TypeId subTy, TypeId superTy, const IntersectionTypeVar* uv); void tryUnifyTypeWithIntersection(TypeId subTy, TypeId superTy, const IntersectionTypeVar* uv);
void tryUnifyIntersectionWithType(TypeId subTy, const IntersectionTypeVar* uv, TypeId superTy, bool cacheEnabled, bool isFunctionCall); 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 tryUnifyPrimitives(TypeId subTy, TypeId superTy);
void tryUnifySingletons(TypeId subTy, TypeId superTy); void tryUnifySingletons(TypeId subTy, TypeId superTy);
void tryUnifyFunctions(TypeId subTy, TypeId superTy, bool isFunctionCall = false); void tryUnifyFunctions(TypeId subTy, TypeId superTy, bool isFunctionCall = false);

View file

@ -497,7 +497,7 @@ static bool dcrMagicFunctionSelect(MagicFunctionCallContext context)
asMutable(context.result)->ty.emplace<BoundTypePack>(resTypePack); asMutable(context.result)->ty.emplace<BoundTypePack>(resTypePack);
} }
else if (tail) else if (tail)
asMutable(context.result)->ty.emplace<BoundTypePack>(*tail); asMutable(context.result)->ty.emplace<BoundTypePack>(*tail);
return true; return true;
} }
@ -507,7 +507,8 @@ static bool dcrMagicFunctionSelect(MagicFunctionCallContext context)
if (AstExprConstantString* str = arg1->as<AstExprConstantString>()) 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}); TypePackId numberTypePack = context.solver->arena->addTypePack({context.solver->singletonTypes->numberType});
asMutable(context.result)->ty.emplace<BoundTypePack>(numberTypePack); asMutable(context.result)->ty.emplace<BoundTypePack>(numberTypePack);
return true; return true;

View file

@ -43,7 +43,7 @@ static bool matchSetmetatable(const AstExprCall& call)
if (call.args.size != 2) if (call.args.size != 2)
return false; return false;
const AstExprGlobal* funcAsGlobal = call.func->as<AstExprGlobal>(); const AstExprGlobal* funcAsGlobal = call.func->as<AstExprGlobal>();
if (!funcAsGlobal || funcAsGlobal->name != smt) if (!funcAsGlobal || funcAsGlobal->name != smt)
return false; return false;
@ -52,7 +52,8 @@ static bool matchSetmetatable(const AstExprCall& call)
} }
ConstraintGraphBuilder::ConstraintGraphBuilder(const ModuleName& moduleName, ModulePtr module, TypeArena* arena, 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) : moduleName(moduleName)
, module(module) , module(module)
, singletonTypes(singletonTypes) , singletonTypes(singletonTypes)

View file

@ -14,8 +14,6 @@
#include "Luau/VisitTypeVar.h" #include "Luau/VisitTypeVar.h"
#include "Luau/TypeUtils.h" #include "Luau/TypeUtils.h"
#include <random>
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolver, false); LUAU_FASTFLAGVARIABLE(DebugLuauLogSolver, false);
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson, false); LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson, false);
LUAU_FASTFLAG(LuauFixNameMaps) LUAU_FASTFLAG(LuauFixNameMaps)
@ -283,13 +281,27 @@ ConstraintSolver::ConstraintSolver(NotNull<Normalizer> normalizer, NotNull<Scope
void ConstraintSolver::randomize(unsigned seed) void ConstraintSolver::randomize(unsigned seed)
{ {
std::mt19937 g(seed); if (unsolvedConstraints.empty())
std::shuffle(begin(unsolvedConstraints), end(unsolvedConstraints), g); 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() void ConstraintSolver::run()
{ {
if (done()) if (isDone())
return; return;
if (FFlag::DebugLuauLogSolver) if (FFlag::DebugLuauLogSolver)
@ -364,6 +376,8 @@ void ConstraintSolver::run()
progress |= runSolverPass(true); progress |= runSolverPass(true);
} while (progress); } while (progress);
finalizeModule();
if (FFlag::DebugLuauLogSolver) if (FFlag::DebugLuauLogSolver)
{ {
dumpBindings(rootScope, opts); dumpBindings(rootScope, opts);
@ -375,11 +389,24 @@ void ConstraintSolver::run()
} }
} }
bool ConstraintSolver::done() bool ConstraintSolver::isDone()
{ {
return unsolvedConstraints.empty(); 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) bool ConstraintSolver::tryDispatch(NotNull<const Constraint> constraint, bool force)
{ {
if (!force && isBlocked(constraint)) if (!force && isBlocked(constraint))
@ -506,25 +533,25 @@ bool ConstraintSolver::tryDispatch(const UnaryConstraint& c, NotNull<const Const
switch (c.op) 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; return true;
} }
case AstExprUnary::Len: break;
{ }
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;
}
} }
LUAU_ASSERT(false); // TODO metatable handling LUAU_ASSERT(false); // TODO metatable handling

View file

@ -57,7 +57,7 @@ void write(JsonEmitter& emitter, const ConstraintGenerationLog& log)
emitter.writeRaw(":"); emitter.writeRaw(":");
ObjectEmitter locationEmitter = emitter.writeObject(); ObjectEmitter locationEmitter = emitter.writeObject();
for (const auto& [id, location] : log.constraintLocations) for (const auto& [id, location] : log.constraintLocations)
{ {
locationEmitter.writePair(id, location); locationEmitter.writePair(id, location);
@ -232,7 +232,7 @@ void DcrLogger::captureSource(std::string source)
void DcrLogger::captureGenerationError(const TypeError& error) void DcrLogger::captureGenerationError(const TypeError& error)
{ {
std::string stringifiedError = toString(error); std::string stringifiedError = toString(error);
generationLog.errors.push_back(ErrorSnapshot { generationLog.errors.push_back(ErrorSnapshot{
/* message */ stringifiedError, /* message */ stringifiedError,
/* location */ error.location, /* 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); ScopeSnapshot scopeSnapshot = snapshotScope(rootScope, opts);
std::string currentId = toPointerId(current); std::string currentId = toPointerId(current);
@ -344,7 +345,7 @@ void DcrLogger::captureFinalSolverState(const Scope* rootScope, const std::vecto
void DcrLogger::captureTypeCheckError(const TypeError& error) void DcrLogger::captureTypeCheckError(const TypeError& error)
{ {
std::string stringifiedError = toString(error); std::string stringifiedError = toString(error);
checkLog.errors.push_back(ErrorSnapshot { checkLog.errors.push_back(ErrorSnapshot{
/* message */ stringifiedError, /* message */ stringifiedError,
/* location */ error.location, /* location */ error.location,
}); });
@ -359,7 +360,7 @@ std::vector<ConstraintBlock> DcrLogger::snapshotBlocks(NotNull<const Constraint>
} }
std::vector<ConstraintBlock> snapshot; std::vector<ConstraintBlock> snapshot;
for (const ConstraintBlockTarget& target : it->second) for (const ConstraintBlockTarget& target : it->second)
{ {
if (const TypeId* ty = get_if<TypeId>(&target)) if (const TypeId* ty = get_if<TypeId>(&target))

View file

@ -8,7 +8,6 @@
#include <stdexcept> #include <stdexcept>
LUAU_FASTFLAGVARIABLE(LuauTypeMismatchModuleNameResolution, false) LUAU_FASTFLAGVARIABLE(LuauTypeMismatchModuleNameResolution, false)
LUAU_FASTFLAGVARIABLE(LuauUseInternalCompilerErrorException, false)
static std::string wrongNumberOfArgsString( static std::string wrongNumberOfArgsString(
size_t expectedCount, std::optional<size_t> maximumCount, size_t actualCount, const char* argPrefix = nullptr, bool isVariadic = false) 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 + "'"; return "Unknown global '" + e.name + "'";
case UnknownSymbol::Type: case UnknownSymbol::Type:
return "Unknown type '" + e.name + "'"; return "Unknown type '" + e.name + "'";
case UnknownSymbol::Generic:
return "Unknown generic '" + e.name + "'";
} }
LUAU_ASSERT(!"Unexpected context for UnknownSymbol"); 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) 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) if (onInternalError)
onInternalError(error.what()); onInternalError(error.what());
throw error; throw error;
}
else
{
std::runtime_error error("Internal error in " + moduleName + " at " + toString(location) + ": " + message);
if (onInternalError)
onInternalError(error.what());
throw error;
}
} }
void InternalErrorReporter::ice(const std::string& message) void InternalErrorReporter::ice(const std::string& message)
{ {
if (FFlag::LuauUseInternalCompilerErrorException) InternalCompilerError error(message, moduleName);
{
InternalCompilerError error(message, moduleName);
if (onInternalError) if (onInternalError)
onInternalError(error.what()); onInternalError(error.what());
throw error; throw error;
}
else
{
std::runtime_error error("Internal error in " + moduleName + ": " + message);
if (onInternalError)
onInternalError(error.what());
throw error;
}
} }
const char* InternalCompilerError::what() const throw() const char* InternalCompilerError::what() const throw()

View file

@ -14,7 +14,6 @@
LUAU_FASTINTVARIABLE(LuauSuggestionDistance, 4) LUAU_FASTINTVARIABLE(LuauSuggestionDistance, 4)
LUAU_FASTFLAGVARIABLE(LuauLintGlobalNeverReadBeforeWritten, false) LUAU_FASTFLAGVARIABLE(LuauLintGlobalNeverReadBeforeWritten, false)
LUAU_FASTFLAGVARIABLE(LuauLintFixDeprecationMessage, false)
namespace Luau namespace Luau
{ {
@ -307,22 +306,11 @@ private:
emitWarning(*context, LintWarning::Code_UnknownGlobal, gv->location, "Unknown global '%s'", gv->name.value); emitWarning(*context, LintWarning::Code_UnknownGlobal, gv->location, "Unknown global '%s'", gv->name.value);
else if (g->deprecated) 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",
if (const char* replacement = *g->deprecated; replacement && strlen(replacement)) gv->name.value, 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);
}
else else
{ emitWarning(*context, LintWarning::Code_DeprecatedGlobal, gv->location, "Global '%s' is deprecated", gv->name.value);
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);
}
} }
} }

View file

@ -15,7 +15,6 @@
#include <algorithm> #include <algorithm>
LUAU_FASTFLAG(LuauAnyifyModuleReturnGenerics) LUAU_FASTFLAG(LuauAnyifyModuleReturnGenerics)
LUAU_FASTFLAG(LuauLowerBoundsCalculation);
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution); LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
LUAU_FASTFLAGVARIABLE(LuauForceExportSurfacesToBeNormal, false); LUAU_FASTFLAGVARIABLE(LuauForceExportSurfacesToBeNormal, false);
LUAU_FASTFLAGVARIABLE(LuauClonePublicInterfaceLess, false); LUAU_FASTFLAGVARIABLE(LuauClonePublicInterfaceLess, false);
@ -244,19 +243,6 @@ void Module::clonePublicInterface(NotNull<SingletonTypes> singletonTypes, Intern
ForceNormal forceNormal{&interfaceTypes}; 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) if (exportedTypeBindings)
{ {
for (auto& [name, tf] : *exportedTypeBindings) for (auto& [name, tf] : *exportedTypeBindings)
@ -265,24 +251,6 @@ void Module::clonePublicInterface(NotNull<SingletonTypes> singletonTypes, Intern
tf = clonePublicInterface.cloneTypeFun(tf); tf = clonePublicInterface.cloneTypeFun(tf);
else else
tf = clone(tf, interfaceTypes, cloneState); 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); ty = clonePublicInterface.cloneType(ty);
else else
ty = clone(ty, interfaceTypes, cloneState); ty = clone(ty, interfaceTypes, cloneState);
if (FFlag::LuauLowerBoundsCalculation)
{
normalize(ty, NotNull{this}, singletonTypes, ice);
if (FFlag::LuauForceExportSurfacesToBeNormal)
forceNormal.traverse(ty);
}
} }
freeze(internalTypes); freeze(internalTypes);

View file

@ -119,17 +119,9 @@ NormalizedType::NormalizedType(NotNull<SingletonTypes> singletonTypes)
static bool isInhabited(const NormalizedType& norm) static bool isInhabited(const NormalizedType& norm)
{ {
return !get<NeverTypeVar>(norm.tops) return !get<NeverTypeVar>(norm.tops) || !get<NeverTypeVar>(norm.booleans) || !norm.classes.empty() || !get<NeverTypeVar>(norm.errors) ||
|| !get<NeverTypeVar>(norm.booleans) !get<NeverTypeVar>(norm.nils) || !get<NeverTypeVar>(norm.numbers) || !norm.strings || !norm.strings->empty() ||
|| !norm.classes.empty() !get<NeverTypeVar>(norm.threads) || norm.functions || !norm.tables.empty() || !norm.tyvars.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) static int tyvarIndex(TypeId ty)
@ -139,7 +131,7 @@ static int tyvarIndex(TypeId ty)
else if (const FreeTypeVar* ftv = get<FreeTypeVar>(ty)) else if (const FreeTypeVar* ftv = get<FreeTypeVar>(ty))
return ftv->index; return ftv->index;
else else
return 0; return 0;
} }
#ifdef LUAU_ASSERTENABLED #ifdef LUAU_ASSERTENABLED
@ -193,7 +185,7 @@ static bool isNormalizedString(const NormalizedStringType& ty)
{ {
if (!ty) if (!ty)
return true; return true;
for (auto& [str, ty] : *ty) for (auto& [str, ty] : *ty)
{ {
if (const SingletonTypeVar* stv = get<SingletonTypeVar>(ty)) if (const SingletonTypeVar* stv = get<SingletonTypeVar>(ty))
@ -272,24 +264,24 @@ static bool isNormalizedTyvar(const NormalizedTyvars& tyvars)
static void assertInvariant(const NormalizedType& norm) static void assertInvariant(const NormalizedType& norm)
{ {
#ifdef LUAU_ASSERTENABLED #ifdef LUAU_ASSERTENABLED
if (!FFlag::DebugLuauCheckNormalizeInvariant) if (!FFlag::DebugLuauCheckNormalizeInvariant)
return; return;
LUAU_ASSERT(isNormalizedTop(norm.tops)); LUAU_ASSERT(isNormalizedTop(norm.tops));
LUAU_ASSERT(isNormalizedBoolean(norm.booleans)); LUAU_ASSERT(isNormalizedBoolean(norm.booleans));
LUAU_ASSERT(areNormalizedClasses(norm.classes)); LUAU_ASSERT(areNormalizedClasses(norm.classes));
LUAU_ASSERT(isNormalizedError(norm.errors)); LUAU_ASSERT(isNormalizedError(norm.errors));
LUAU_ASSERT(isNormalizedNil(norm.nils)); LUAU_ASSERT(isNormalizedNil(norm.nils));
LUAU_ASSERT(isNormalizedNumber(norm.numbers)); LUAU_ASSERT(isNormalizedNumber(norm.numbers));
LUAU_ASSERT(isNormalizedString(norm.strings)); LUAU_ASSERT(isNormalizedString(norm.strings));
LUAU_ASSERT(isNormalizedThread(norm.threads)); LUAU_ASSERT(isNormalizedThread(norm.threads));
LUAU_ASSERT(areNormalizedFunctions(norm.functions)); LUAU_ASSERT(areNormalizedFunctions(norm.functions));
LUAU_ASSERT(areNormalizedTables(norm.tables)); LUAU_ASSERT(areNormalizedTables(norm.tables));
LUAU_ASSERT(isNormalizedTyvar(norm.tyvars)); LUAU_ASSERT(isNormalizedTyvar(norm.tyvars));
for (auto& [_, child] : norm.tyvars) for (auto& [_, child] : norm.tyvars)
assertInvariant(*child); assertInvariant(*child);
#endif #endif
} }
Normalizer::Normalizer(TypeArena* arena, NotNull<SingletonTypes> singletonTypes, NotNull<UnifierSharedState> sharedState) Normalizer::Normalizer(TypeArena* arena, NotNull<SingletonTypes> singletonTypes, NotNull<UnifierSharedState> sharedState)
@ -359,7 +351,7 @@ TypeId Normalizer::unionType(TypeId here, TypeId there)
return there; return there;
if (get<NeverTypeVar>(there) || get<AnyTypeVar>(here)) if (get<NeverTypeVar>(there) || get<AnyTypeVar>(here))
return here; return here;
TypeIds tmps; TypeIds tmps;
if (const UnionTypeVar* utv = get<UnionTypeVar>(here)) if (const UnionTypeVar* utv = get<UnionTypeVar>(here))
@ -405,7 +397,7 @@ TypeId Normalizer::intersectionType(TypeId here, TypeId there)
return here; return here;
if (get<NeverTypeVar>(there) || get<AnyTypeVar>(here)) if (get<NeverTypeVar>(there) || get<AnyTypeVar>(here))
return there; return there;
TypeIds tmps; TypeIds tmps;
if (const IntersectionTypeVar* utv = get<IntersectionTypeVar>(here)) if (const IntersectionTypeVar* utv = get<IntersectionTypeVar>(here))
@ -516,13 +508,13 @@ std::optional<TypePackId> Normalizer::unionOfTypePacks(TypePackId here, TypePack
std::vector<TypeId> head; std::vector<TypeId> head;
std::optional<TypePackId> tail; std::optional<TypePackId> tail;
bool hereSubThere = true; bool hereSubThere = true;
bool thereSubHere = true; bool thereSubHere = true;
TypePackIterator ith = begin(here); TypePackIterator ith = begin(here);
TypePackIterator itt = begin(there); TypePackIterator itt = begin(there);
while (ith != end(here) && itt != end(there)) while (ith != end(here) && itt != end(there))
{ {
TypeId hty = *ith; TypeId hty = *ith;
@ -537,8 +529,8 @@ std::optional<TypePackId> Normalizer::unionOfTypePacks(TypePackId here, TypePack
itt++; 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)) if (ith != end(here))
{ {
TypeId tty = singletonTypes->nilType; TypeId tty = singletonTypes->nilType;
@ -591,13 +583,13 @@ std::optional<TypePackId> Normalizer::unionOfTypePacks(TypePackId here, TypePack
if (ty != tvtp->ty) if (ty != tvtp->ty)
hereSubThere = false; hereSubThere = false;
bool hidden = hvtp->hidden & tvtp->hidden; 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 // Luau doesn't have unions of type pack variables
return std::nullopt; return std::nullopt;
} }
else else
// Luau doesn't have unions of type pack variables // Luau doesn't have unions of type pack variables
return std::nullopt; return std::nullopt;
} }
@ -627,7 +619,7 @@ std::optional<TypePackId> Normalizer::unionOfTypePacks(TypePackId here, TypePack
else if (thereSubHere) else if (thereSubHere)
return here; return here;
if (!head.empty()) if (!head.empty())
return arena->addTypePack(TypePack{head,tail}); return arena->addTypePack(TypePack{head, tail});
else if (tail) else if (tail)
return *tail; return *tail;
else else
@ -639,10 +631,10 @@ std::optional<TypeId> Normalizer::unionOfFunctions(TypeId here, TypeId there)
{ {
if (get<ErrorTypeVar>(here)) if (get<ErrorTypeVar>(here))
return here; return here;
if (get<ErrorTypeVar>(there)) if (get<ErrorTypeVar>(there))
return there; return there;
const FunctionTypeVar* hftv = get<FunctionTypeVar>(here); const FunctionTypeVar* hftv = get<FunctionTypeVar>(here);
LUAU_ASSERT(hftv); LUAU_ASSERT(hftv);
const FunctionTypeVar* tftv = get<FunctionTypeVar>(there); const FunctionTypeVar* tftv = get<FunctionTypeVar>(there);
@ -665,7 +657,7 @@ std::optional<TypeId> Normalizer::unionOfFunctions(TypeId here, TypeId there)
return here; return here;
if (*argTypes == tftv->argTypes && *retTypes == tftv->retTypes) if (*argTypes == tftv->argTypes && *retTypes == tftv->retTypes)
return there; return there;
FunctionTypeVar result{*argTypes, *retTypes}; FunctionTypeVar result{*argTypes, *retTypes};
result.generics = hftv->generics; result.generics = hftv->generics;
result.genericPacks = hftv->genericPacks; result.genericPacks = hftv->genericPacks;
@ -802,9 +794,9 @@ bool Normalizer::withinResourceLimits()
// Check the recursion count // Check the recursion count
if (sharedState->counters.recursionLimit > 0) if (sharedState->counters.recursionLimit > 0)
if (sharedState->counters.recursionLimit < sharedState->counters.recursionCount) if (sharedState->counters.recursionLimit < sharedState->counters.recursionCount)
return false; return false;
return true; return true;
} }
@ -1000,13 +992,13 @@ std::optional<TypePackId> Normalizer::intersectionOfTypePacks(TypePackId here, T
std::vector<TypeId> head; std::vector<TypeId> head;
std::optional<TypePackId> tail; std::optional<TypePackId> tail;
bool hereSubThere = true; bool hereSubThere = true;
bool thereSubHere = true; bool thereSubHere = true;
TypePackIterator ith = begin(here); TypePackIterator ith = begin(here);
TypePackIterator itt = begin(there); TypePackIterator itt = begin(there);
while (ith != end(here) && itt != end(there)) while (ith != end(here) && itt != end(there))
{ {
TypeId hty = *ith; TypeId hty = *ith;
@ -1021,8 +1013,8 @@ std::optional<TypePackId> Normalizer::intersectionOfTypePacks(TypePackId here, T
itt++; 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)) if (ith != end(here))
{ {
TypeId tty = singletonTypes->nilType; TypeId tty = singletonTypes->nilType;
@ -1075,13 +1067,13 @@ std::optional<TypePackId> Normalizer::intersectionOfTypePacks(TypePackId here, T
if (ty != tvtp->ty) if (ty != tvtp->ty)
hereSubThere = false; hereSubThere = false;
bool hidden = hvtp->hidden & tvtp->hidden; 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 // Luau doesn't have unions of type pack variables
return std::nullopt; return std::nullopt;
} }
else else
// Luau doesn't have unions of type pack variables // Luau doesn't have unions of type pack variables
return std::nullopt; return std::nullopt;
} }
@ -1105,7 +1097,7 @@ std::optional<TypePackId> Normalizer::intersectionOfTypePacks(TypePackId here, T
else if (thereSubHere) else if (thereSubHere)
return there; return there;
if (!head.empty()) if (!head.empty())
return arena->addTypePack(TypePack{head,tail}); return arena->addTypePack(TypePack{head, tail});
else if (tail) else if (tail)
return *tail; return *tail;
else else
@ -1146,7 +1138,7 @@ std::optional<TypeId> Normalizer::intersectionOfTables(TypeId here, TypeId there
return std::nullopt; return std::nullopt;
if (httv->state == TableState::Generic || tttv->state == TableState::Generic) if (httv->state == TableState::Generic || tttv->state == TableState::Generic)
return std::nullopt; return std::nullopt;
TableState state = httv->state; TableState state = httv->state;
if (tttv->state == TableState::Unsealed) if (tttv->state == TableState::Unsealed)
state = tttv->state; state = tttv->state;
@ -1226,21 +1218,20 @@ std::optional<TypeId> Normalizer::intersectionOfTables(TypeId here, TypeId there
} }
else else
return std::nullopt; return std::nullopt;
} }
else if (hmtable) else if (hmtable)
{ {
if (table == htable) if (table == htable)
return here; return here;
else else
return arena->addType(MetatableTypeVar{table, hmtable}); return arena->addType(MetatableTypeVar{table, hmtable});
} }
else if (tmtable) else if (tmtable)
{ {
if (table == ttable) if (table == ttable)
return there; return there;
else else
return arena->addType(MetatableTypeVar{table, tmtable}); return arena->addType(MetatableTypeVar{table, tmtable});
} }
else else
return table; return table;
@ -1280,7 +1271,7 @@ std::optional<TypeId> Normalizer::intersectionOfFunctions(TypeId here, TypeId th
return std::nullopt; return std::nullopt;
if (hftv->retTypes != tftv->retTypes) if (hftv->retTypes != tftv->retTypes)
return std::nullopt; return std::nullopt;
std::optional<TypePackId> argTypes = unionOfTypePacks(hftv->argTypes, tftv->argTypes); std::optional<TypePackId> argTypes = unionOfTypePacks(hftv->argTypes, tftv->argTypes);
if (!argTypes) if (!argTypes)
return std::nullopt; return std::nullopt;
@ -1289,7 +1280,7 @@ std::optional<TypeId> Normalizer::intersectionOfFunctions(TypeId here, TypeId th
return here; return here;
if (*argTypes == tftv->argTypes) if (*argTypes == tftv->argTypes)
return there; return there;
FunctionTypeVar result{*argTypes, hftv->retTypes}; FunctionTypeVar result{*argTypes, hftv->retTypes};
result.generics = hftv->generics; result.generics = hftv->generics;
result.genericPacks = hftv->genericPacks; 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) std::optional<TypeId> Normalizer::unionSaturatedFunctions(TypeId here, TypeId there)
{ {
// Deep breath... // Deep breath...
// //
// When we come to check overloaded functions for subtyping, // When we come to check overloaded functions for subtyping,
// we have to compare (F1 & ... & FM) <: (G1 & ... G GN) // 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 // 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>. // 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. // 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> // 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 // to just be Apply<F1, T> & ... & Apply<FM, T> but oh dear
// //
// if f : ((number -> number) & (string -> string)) // if f : ((number -> number) & (string -> string))
// and x : (number | string) // and x : (number | string)
// then f(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) // Apply<((number -> number) & (string -> string)), (number | string)> is (number | string)
// //
// but // but
// //
// Apply<(number -> number), (number | string)> is an error // Apply<(number -> number), (number | string)> is an error
// Apply<(string -> string), (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, // Covariance and Contravariance, Giuseppe Castagna,
// Logical Methods in Computer Science 16(1), 2022 // Logical Methods in Computer Science 16(1), 2022
// https://arxiv.org/abs/1809.01427 // https://arxiv.org/abs/1809.01427
// //
// A gentle introduction to semantic subtyping, Giuseppe Castagna and Alain Frisch, // A gentle introduction to semantic subtyping, Giuseppe Castagna and Alain Frisch,
// Proc. Principles and practice of declarative programming 2005, pp 198208 // Proc. Principles and practice of declarative programming 2005, pp 198208
// https://doi.org/10.1145/1069774.1069793 // https://doi.org/10.1145/1069774.1069793
@ -1398,7 +1389,7 @@ std::optional<TypeId> Normalizer::unionSaturatedFunctions(TypeId here, TypeId th
return std::nullopt; return std::nullopt;
if (hftv->genericPacks != tftv->genericPacks) if (hftv->genericPacks != tftv->genericPacks)
return std::nullopt; return std::nullopt;
std::optional<TypePackId> argTypes = unionOfTypePacks(hftv->argTypes, tftv->argTypes); std::optional<TypePackId> argTypes = unionOfTypePacks(hftv->argTypes, tftv->argTypes);
if (!argTypes) if (!argTypes)
return std::nullopt; return std::nullopt;
@ -1416,7 +1407,7 @@ void Normalizer::intersectFunctionsWithFunction(NormalizedFunctionType& heres, T
{ {
if (!heres) if (!heres)
return; return;
for (auto it = heres->begin(); it != heres->end();) for (auto it = heres->begin(); it != heres->end();)
{ {
TypeId here = *it; TypeId here = *it;
@ -1450,7 +1441,7 @@ void Normalizer::intersectFunctions(NormalizedFunctionType& heres, const Normali
{ {
heres = std::nullopt; heres = std::nullopt;
return; return;
} }
else else
{ {
for (TypeId there : *theres) for (TypeId there : *theres)
@ -1530,7 +1521,7 @@ bool Normalizer::intersectNormals(NormalizedType& here, const NormalizedType& th
if (isInhabited(inter)) if (isInhabited(inter))
it++; it++;
else else
it = here.tyvars.erase(it); it = here.tyvars.erase(it);
} }
return true; return true;
} }
@ -1757,7 +1748,8 @@ bool isSubtype(TypeId subTy, TypeId superTy, NotNull<Scope> scope, NotNull<Singl
return ok; 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}; UnifierSharedState sharedState{&ice};
TypeArena arena; TypeArena arena;
@ -2377,4 +2369,3 @@ std::pair<TypePackId, bool> normalize(TypePackId tp, const ModulePtr& module, No
} }
} // namespace Luau } // namespace Luau

View file

@ -9,7 +9,6 @@
#include <stdexcept> #include <stdexcept>
LUAU_FASTFLAGVARIABLE(LuauSubstitutionFixMissingFields, false) LUAU_FASTFLAGVARIABLE(LuauSubstitutionFixMissingFields, false)
LUAU_FASTFLAG(LuauLowerBoundsCalculation)
LUAU_FASTFLAG(LuauClonePublicInterfaceLess) LUAU_FASTFLAG(LuauClonePublicInterfaceLess)
LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 10000) LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 10000)
LUAU_FASTFLAGVARIABLE(LuauClassTypeVarsInSubstitution, false) LUAU_FASTFLAGVARIABLE(LuauClassTypeVarsInSubstitution, false)
@ -553,9 +552,6 @@ TypePackId Substitution::replace(TypePackId tp)
void Substitution::replaceChildren(TypeId ty) 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)); LUAU_ASSERT(ty == log->follow(ty));
if (ignoreChildren(ty)) if (ignoreChildren(ty))

View file

@ -10,11 +10,11 @@
#include <algorithm> #include <algorithm>
#include <stdexcept> #include <stdexcept>
LUAU_FASTFLAG(LuauLowerBoundsCalculation)
LUAU_FASTFLAG(LuauUnknownAndNeverType) LUAU_FASTFLAG(LuauUnknownAndNeverType)
LUAU_FASTFLAGVARIABLE(LuauSpecialTypesAsterisked, false) LUAU_FASTFLAGVARIABLE(LuauSpecialTypesAsterisked, false)
LUAU_FASTFLAGVARIABLE(LuauFixNameMaps, false) LUAU_FASTFLAGVARIABLE(LuauFixNameMaps, false)
LUAU_FASTFLAGVARIABLE(LuauUnseeArrayTtv, false) LUAU_FASTFLAGVARIABLE(LuauUnseeArrayTtv, false)
LUAU_FASTFLAGVARIABLE(LuauFunctionReturnStringificationFixup, false)
/* /*
* Prefix generic typenames with gen- * Prefix generic typenames with gen-
@ -524,7 +524,7 @@ struct TypeVarStringifier
bool plural = true; bool plural = true;
if (FFlag::LuauLowerBoundsCalculation) if (FFlag::LuauFunctionReturnStringificationFixup)
{ {
auto retBegin = begin(ftv.retTypes); auto retBegin = begin(ftv.retTypes);
auto retEnd = end(ftv.retTypes); auto retEnd = end(ftv.retTypes);

View file

@ -24,7 +24,7 @@ namespace Luau
// TypeInfer.h // TypeInfer.h
// TODO move these // TODO move these
using PrintLineProc = void(*)(const std::string&); using PrintLineProc = void (*)(const std::string&);
extern PrintLineProc luauPrintLine; extern PrintLineProc luauPrintLine;
/* Push a scope onto the end of a stack for the lifetime of the StackPusher instance. /* 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) if (auto ann = ref->parameters.data[0].type)
{ {
TypeId argTy = lookupAnnotation(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); return follow(argTy);
} }
} }
@ -409,8 +410,8 @@ struct TypeChecker2
} }
TypeId iteratorTy = follow(iteratorTypes[0]); 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 (iterTys.size() < 1 || iterTys.size() > 3)
{ {
if (isMm) if (isMm)
@ -420,20 +421,21 @@ struct TypeChecker2
return; return;
} }
// It is okay if there aren't enough iterators, but the iteratee must provide enough. // 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()); std::vector<TypeId> expectedVariableTypes = flatten(arena, singletonTypes, iterFtv->retTypes, variableTypes.size());
if (expectedVariableTypes.size() < variableTypes.size()) if (expectedVariableTypes.size() < variableTypes.size())
{ {
if (isMm) 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 else
reportError(GenericError{"next() does not return enough values"}, forInStatement->values.data[0]->location); 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) 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])); reportErrors(tryUnify(scope, forInStatement->vars.data[i]->location, variableTypes[i], expectedVariableTypes[i]));
// nextFn is going to be invoked with (arrayTy, startIndexTy) // nextFn is going to be invoked with (arrayTy, startIndexTy)
// It will be passed two arguments on every iteration save the // It will be passed two arguments on every iteration save the
@ -509,7 +511,8 @@ struct TypeChecker2
{ {
// nothing // 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}; 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 // TODO: This will not tell the user that this is because the
// metamethod isn't callable. This is not ideal, and we should // metamethod isn't callable. This is not ideal, and we should
// improve this error message. // improve this error message.
// TODO: This will also not handle intersections of functions or // TODO: This will also not handle intersections of functions or
// callable tables (which are supported by the runtime). // callable tables (which are supported by the runtime).
reportError(CannotCallNonFunction{*iterMmTy}, forInStatement->values.data[0]->location); reportError(CannotCallNonFunction{*iterMmTy}, forInStatement->values.data[0]->location);

View file

@ -33,15 +33,11 @@ LUAU_FASTINTVARIABLE(LuauVisitRecursionLimit, 500)
LUAU_FASTFLAG(LuauKnowsTheDataModel3) LUAU_FASTFLAG(LuauKnowsTheDataModel3)
LUAU_FASTFLAG(LuauAutocompleteDynamicLimits) LUAU_FASTFLAG(LuauAutocompleteDynamicLimits)
LUAU_FASTFLAG(LuauTypeNormalization2) LUAU_FASTFLAG(LuauTypeNormalization2)
LUAU_FASTFLAGVARIABLE(LuauFunctionArgMismatchDetails, false)
LUAU_FASTFLAGVARIABLE(LuauLowerBoundsCalculation, false)
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false) LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false)
LUAU_FASTFLAGVARIABLE(LuauReturnAnyInsteadOfICE, false) // Eventually removed as false. LUAU_FASTFLAGVARIABLE(LuauReturnAnyInsteadOfICE, false) // Eventually removed as false.
LUAU_FASTFLAGVARIABLE(DebugLuauSharedSelf, false) LUAU_FASTFLAGVARIABLE(DebugLuauSharedSelf, false)
LUAU_FASTFLAGVARIABLE(LuauAnyifyModuleReturnGenerics, false) LUAU_FASTFLAGVARIABLE(LuauAnyifyModuleReturnGenerics, false)
LUAU_FASTFLAGVARIABLE(LuauUnknownAndNeverType, false) LUAU_FASTFLAGVARIABLE(LuauUnknownAndNeverType, false)
LUAU_FASTFLAGVARIABLE(LuauCallUnifyPackTails, false)
LUAU_FASTFLAGVARIABLE(LuauCheckGenericHOFTypes, false)
LUAU_FASTFLAGVARIABLE(LuauBinaryNeedsExpectedTypesToo, false) LUAU_FASTFLAGVARIABLE(LuauBinaryNeedsExpectedTypesToo, false)
LUAU_FASTFLAGVARIABLE(LuauFixVarargExprHeadType, false) LUAU_FASTFLAGVARIABLE(LuauFixVarargExprHeadType, false)
LUAU_FASTFLAGVARIABLE(LuauNeverTypesAndOperatorsInference, 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 // returns the last statement before the block exits, or nullptr if the block never exits
const AstStat* getFallthrough(const AstStat* node) 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; 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) { auto checkBody = [&](AstStat* stat) {
if (auto fun = stat->as<AstStatFunction>()) 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 f<a>(x:a):a local x: number = g(37) return x end
// function g(x:number):number return f(x) end // function g(x:number):number return f(x) end
// ``` // ```
if (containsFunctionCallOrReturn(**protoIter) || (FFlag::LuauLowerBoundsCalculation && isLocalLambda(*protoIter))) if (containsFunctionCallOrReturn(**protoIter))
{ {
while (checkIter != 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; 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 // HACK: Nonstrict mode gets a bit too smart and strict for us when we
// start typechecking everything across module boundaries. // start typechecking everything across module boundaries.
if (isNonstrictMode() && follow(scope->returnType) == follow(currentModule->getModuleScope()->returnType)) 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) for (auto param : binding->typePackParams)
clone.instantiatedTypePackParams.push_back(param.tp); clone.instantiatedTypePackParams.push_back(param.tp);
bool isNormal = ty->normal;
ty = addType(std::move(clone)); ty = addType(std::move(clone));
if (FFlag::LuauLowerBoundsCalculation)
asMutable(ty)->normal = isNormal;
} }
} }
else else
@ -1605,14 +1553,6 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias
if (unify(ty, bindingType, aliasScope, typealias.location)) if (unify(ty, bindingType, aliasScope, typealias.location))
bindingType = ty; 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) 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)) else if (const FreeTypePack* ftp = get<Unifiable::Free>(retPack))
{ {
TypeLevel level = FFlag::LuauLowerBoundsCalculation ? ftp->level : scope->level; TypeId head = freshType(scope->level);
TypeId head = freshType(level); TypePackId pack = addTypePack(TypePackVar{TypePack{{head}, freshTypePack(scope->level)}});
TypePackId pack = addTypePack(TypePackVar{TypePack{{head}, freshTypePack(level)}});
unify(pack, retPack, scope, expr.location); unify(pack, retPack, scope, expr.location);
return {head, std::move(result.predicates)}; return {head, std::move(result.predicates)};
} }
@ -2111,27 +2050,14 @@ std::optional<TypeId> TypeChecker::getIndexTypeFromTypeImpl(
return std::nullopt; return std::nullopt;
} }
if (FFlag::LuauLowerBoundsCalculation) std::vector<TypeId> result = reduceUnion(goodOptions);
{ if (FFlag::LuauUnknownAndNeverType && result.empty())
// FIXME Inefficient. We craft a UnionTypeVar and immediately throw it away. return neverType;
auto [t, ok] = normalize(addType(UnionTypeVar{std::move(goodOptions)}), currentModule, singletonTypes, *iceHandler);
if (!ok) if (result.size() == 1)
reportError(location, NormalizationTooComplex{}); return result[0];
return t; return addType(UnionTypeVar{std::move(result)});
}
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)});
}
} }
else if (const IntersectionTypeVar* itv = get<IntersectionTypeVar>(type)) 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); 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); retPack = resolveTypePack(funScope, *expr.returnAnnotation);
else if (isNonstrictMode()) else if (isNonstrictMode())
retPack = anyTypePack; retPack = anyTypePack;
else if (expectedFunctionType && else if (expectedFunctionType && expectedFunctionType->generics.empty() && expectedFunctionType->genericPacks.empty())
(!FFlag::LuauCheckGenericHOFTypes || (expectedFunctionType->generics.empty() && expectedFunctionType->genericPacks.empty())))
{ {
auto [head, tail] = flatten(expectedFunctionType->retTypes); auto [head, tail] = flatten(expectedFunctionType->retTypes);
@ -3488,10 +3406,6 @@ std::pair<TypeId, ScopePtr> TypeChecker::checkFunctionSignature(const ScopePtr&
funScope->varargPack = anyTypePack; funScope->varargPack = anyTypePack;
} }
} }
else if (FFlag::LuauLowerBoundsCalculation && !isNonstrictMode())
{
funScope->varargPack = addTypePack(TypePackVar{VariadicTypePack{anyType, /*hidden*/ true}});
}
std::vector<TypeId> argTypes; std::vector<TypeId> argTypes;
@ -3575,48 +3489,28 @@ std::pair<TypeId, ScopePtr> TypeChecker::checkFunctionSignature(const ScopePtr&
std::vector<TypeId> genericTys; std::vector<TypeId> genericTys;
// if we have a generic expected function type and no generics, we should use the expected ones. // 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;
{
genericTys = expectedFunctionType->generics;
}
else
{
genericTys.reserve(generics.size());
for (const GenericTypeDefinition& generic : generics)
genericTys.push_back(generic.ty);
}
} }
else else
{ {
genericTys.reserve(generics.size()); genericTys.reserve(generics.size());
std::transform(generics.begin(), generics.end(), std::back_inserter(genericTys), [](auto&& el) { for (const GenericTypeDefinition& generic : generics)
return el.ty; genericTys.push_back(generic.ty);
});
} }
std::vector<TypePackId> genericTps; std::vector<TypePackId> genericTps;
// if we have a generic expected function type and no generic typepacks, we should use the expected ones. // 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;
{
genericTps = expectedFunctionType->genericPacks;
}
else
{
genericTps.reserve(genericPacks.size());
for (const GenericTypePackDefinition& generic : genericPacks)
genericTps.push_back(generic.tp);
}
} }
else else
{ {
genericTps.reserve(genericPacks.size()); genericTps.reserve(genericPacks.size());
std::transform(genericPacks.begin(), genericPacks.end(), std::back_inserter(genericTps), [](auto&& el) { for (const GenericTypePackDefinition& generic : genericPacks)
return el.tp; genericTps.push_back(generic.tp);
});
} }
TypeId funTy = TypeId funTy =
@ -3674,24 +3568,9 @@ void TypeChecker::checkFunctionBody(const ScopePtr& scope, TypeId ty, const AstE
{ {
check(scope, *function.body); check(scope, *function.body);
if (useConstrainedIntersections()) // 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))
TypePackId retPack = follow(funTy->retTypes); *asMutable(funTy->retTypes) = TypePack{{}, std::nullopt};
// 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};
}
bool reachesImplicitReturn = getFallthrough(function.body) != nullptr; bool reachesImplicitReturn = getFallthrough(function.body) != nullptr;
@ -3763,21 +3642,13 @@ void TypeChecker::checkArgumentList(const ScopePtr& scope, const AstExpr& funNam
if (!argLocations.empty()) if (!argLocations.empty())
location = {state.location.begin, argLocations.back().end}; location = {state.location.begin, argLocations.back().end};
if (FFlag::LuauFunctionArgMismatchDetails) std::string namePath;
{ if (std::optional<LValue> lValue = tryGetLValue(funName))
std::string namePath; namePath = toString(*lValue);
if (std::optional<LValue> lValue = tryGetLValue(funName))
namePath = toString(*lValue);
auto [minParams, optMaxParams] = getParameterExtents(&state.log, paramPack); auto [minParams, optMaxParams] = getParameterExtents(&state.log, paramPack);
state.reportError(TypeError{location, state.reportError(TypeError{location,
CountMismatch{minParams, optMaxParams, std::distance(begin(argPack), end(argPack)), CountMismatch::Context::Arg, false, namePath}}); 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))}});
}
}; };
while (true) while (true)
@ -3801,7 +3672,7 @@ void TypeChecker::checkArgumentList(const ScopePtr& scope, const AstExpr& funNam
else else
state.log.replace(*argTail, TypePackVar(TypePack{{}})); state.log.replace(*argTail, TypePackVar(TypePack{{}}));
} }
else if (FFlag::LuauCallUnifyPackTails && paramTail) else if (paramTail)
{ {
state.tryUnify(*argTail, *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; std::optional<TypePackId> tail = flatten(paramPack, state.log).second;
bool isVariadic = tail && Luau::isVariadic(*tail); bool isVariadic = tail && Luau::isVariadic(*tail);
if (FFlag::LuauFunctionArgMismatchDetails) std::string namePath;
{ if (std::optional<LValue> lValue = tryGetLValue(funName))
std::string namePath; namePath = toString(*lValue);
if (std::optional<LValue> lValue = tryGetLValue(funName))
namePath = toString(*lValue);
state.reportError(TypeError{ state.reportError(TypeError{
state.location, CountMismatch{minParams, optMaxParams, paramIndex, CountMismatch::Context::Arg, isVariadic, namePath}}); 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}});
}
return; return;
} }
++paramIter; ++paramIter;
@ -3924,21 +3787,6 @@ void TypeChecker::checkArgumentList(const ScopePtr& scope, const AstExpr& funNam
} }
else if (auto vtp = state.log.getMutable<VariadicTypePack>(tail)) 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 // Function is variadic and requires that all subsequent parameters
// be compatible with a type. // be compatible with a type.
size_t argIndex = paramIndex; size_t argIndex = paramIndex;
@ -4040,21 +3888,14 @@ WithPredicate<TypePackId> TypeChecker::checkExprPackHelper(const ScopePtr& scope
} }
TypePackId retPack; 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 else
{ retPack = freshTypePack(scope->level);
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);
}
// checkExpr will log the pre-instantiated type of the function. // 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 // 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 // fn is one of the overloads of actualFunctionType, which
// has been instantiated, so is a monotype. We can therefore // has been instantiated, so is a monotype. We can therefore
// unify it with a monomorphic function. // unify it with a monomorphic function.
if (useConstrainedIntersections()) TypeId r = addType(FunctionTypeVar(scope->level, argPack, retPack));
{
// 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;
std::vector<TypeId> adjustedArgTypes; UnifierOptions options;
auto it = begin(argPack); options.isFunctionCall = true;
auto endIt = end(argPack); unify(r, fn, scope, expr.location, options);
Widen widen{&currentModule->internalTypes, singletonTypes};
for (; it != endIt; ++it)
{
adjustedArgTypes.push_back(addType(ConstrainedTypeVar{level, {widen(*it)}}));
}
TypePackId adjustedArgPack = addTypePack(TypePack{std::move(adjustedArgTypes), it.tail()}); return {{retPack}};
TxnLog log;
promoteTypeLevels(log, &currentModule->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}};
}
} }
std::vector<Location> metaArgLocations; std::vector<Location> metaArgLocations;
@ -4760,14 +4575,6 @@ TypeId TypeChecker::quantify(const ScopePtr& scope, TypeId ty, Location location
Luau::quantify(ty, scope->level); Luau::quantify(ty, scope->level);
else if (auto ttv = getTableType(ty); ttv && ttv->selfTy) else if (auto ttv = getTableType(ty); ttv && ttv->selfTy)
Luau::quantify(ty, scope->level); 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 else
{ {
@ -4775,14 +4582,6 @@ TypeId TypeChecker::quantify(const ScopePtr& scope, TypeId ty, Location location
if (ftv) if (ftv)
Luau::quantify(ty, scope->level); 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; 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) 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{&currentModule->internalTypes, scope, singletonTypes, iceHandler, anyType, anyTypePack}; Anyification anyification{&currentModule->internalTypes, scope, singletonTypes, iceHandler, anyType, anyTypePack};
std::optional<TypeId> any = anyification.substitute(ty); std::optional<TypeId> any = anyification.substitute(ty);
if (anyification.normalizationTooComplex) 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) 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{&currentModule->internalTypes, scope, singletonTypes, iceHandler, anyType, anyTypePack}; Anyification anyification{&currentModule->internalTypes, scope, singletonTypes, iceHandler, anyType, anyTypePack};
std::optional<TypePackId> any = anyification.substitute(ty); std::optional<TypePackId> any = anyification.substitute(ty);
if (any.has_value()) if (any.has_value())
@ -6083,11 +5866,6 @@ bool TypeChecker::isNonstrictMode() const
return (currentModule->mode == Mode::Nonstrict) || (currentModule->mode == Mode::NoCheck); 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) std::vector<TypeId> TypeChecker::unTypePack(const ScopePtr& scope, TypePackId tp, size_t expectedLength, const Location& location)
{ {
TypePackId expectedTypePack = addTypePack({}); TypePackId expectedTypePack = addTypePack({});

View file

@ -6,8 +6,6 @@
#include "Luau/ToString.h" #include "Luau/ToString.h"
#include "Luau/TypeInfer.h" #include "Luau/TypeInfer.h"
LUAU_FASTFLAG(LuauFunctionArgMismatchDetails)
namespace Luau namespace Luau
{ {
@ -218,7 +216,7 @@ std::pair<size_t, std::optional<size_t>> getParameterExtents(const TxnLog* log,
++it; ++it;
} }
if (it.tail() && (!FFlag::LuauFunctionArgMismatchDetails || isVariadicTail(*it.tail(), *log, includeHiddenVariadics))) if (it.tail() && isVariadicTail(*it.tail(), *log, includeHiddenVariadics))
return {minCount, std::nullopt}; return {minCount, std::nullopt};
else else
return {minCount, minCount + optionalCount}; return {minCount, minCount + optionalCount};

View file

@ -25,7 +25,6 @@ LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0)
LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAG(LuauUnknownAndNeverType) LUAU_FASTFLAG(LuauUnknownAndNeverType)
LUAU_FASTFLAGVARIABLE(LuauMaybeGenericIntersectionTypes, false) LUAU_FASTFLAGVARIABLE(LuauMaybeGenericIntersectionTypes, false)
LUAU_FASTFLAGVARIABLE(LuauStringFormatArgumentErrorFix, false)
LUAU_FASTFLAGVARIABLE(LuauNoMoreGlobalSingletonTypes, false) LUAU_FASTFLAGVARIABLE(LuauNoMoreGlobalSingletonTypes, false)
LUAU_FASTFLAG(LuauInstantiateInSubtyping) 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 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)) if (numExpectedParams != numActualParams && (!tail || numExpectedParams < numActualParams))
typechecker.reportError(TypeError{expr.location, CountMismatch{numExpectedParams, std::nullopt, numActualParams}}); typechecker.reportError(TypeError{expr.location, CountMismatch{numExpectedParams, std::nullopt, numActualParams}});
}
else
{
size_t actualParamSize = params.size() - paramOffset;
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})}; return WithPredicate<TypePackId>{arena.addTypePack({typechecker.stringType})};
} }

View file

@ -18,14 +18,12 @@ LUAU_FASTINT(LuauTypeInferTypePackLoopLimit);
LUAU_FASTINT(LuauTypeInferIterationLimit); LUAU_FASTINT(LuauTypeInferIterationLimit);
LUAU_FASTFLAG(LuauAutocompleteDynamicLimits) LUAU_FASTFLAG(LuauAutocompleteDynamicLimits)
LUAU_FASTINTVARIABLE(LuauTypeInferLowerBoundsIterationLimit, 2000); LUAU_FASTINTVARIABLE(LuauTypeInferLowerBoundsIterationLimit, 2000);
LUAU_FASTFLAG(LuauLowerBoundsCalculation);
LUAU_FASTFLAG(LuauErrorRecoveryType); LUAU_FASTFLAG(LuauErrorRecoveryType);
LUAU_FASTFLAG(LuauUnknownAndNeverType) LUAU_FASTFLAG(LuauUnknownAndNeverType)
LUAU_FASTFLAGVARIABLE(LuauSubtypeNormalizer, false); LUAU_FASTFLAGVARIABLE(LuauSubtypeNormalizer, false);
LUAU_FASTFLAGVARIABLE(LuauScalarShapeSubtyping, false) LUAU_FASTFLAGVARIABLE(LuauScalarShapeSubtyping, false)
LUAU_FASTFLAGVARIABLE(LuauInstantiateInSubtyping, false) LUAU_FASTFLAGVARIABLE(LuauInstantiateInSubtyping, false)
LUAU_FASTFLAG(LuauClassTypeVarsInSubstitution) LUAU_FASTFLAG(LuauClassTypeVarsInSubstitution)
LUAU_FASTFLAG(LuauCallUnifyPackTails)
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution) LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
namespace Luau namespace Luau
@ -346,8 +344,7 @@ static bool subsumes(bool useScopes, TY_A* left, TY_B* right)
return left->level.subsumes(right->level); return left->level.subsumes(right->level);
} }
Unifier::Unifier(NotNull<Normalizer> normalizer, Mode mode, NotNull<Scope> scope, const Location& location, Unifier::Unifier(NotNull<Normalizer> normalizer, Mode mode, NotNull<Scope> scope, const Location& location, Variance variance, TxnLog* parentLog)
Variance variance, TxnLog* parentLog)
: types(normalizer->arena) : types(normalizer->arena)
, singletonTypes(normalizer->singletonTypes) , singletonTypes(normalizer->singletonTypes)
, normalizer(normalizer) , normalizer(normalizer)
@ -529,7 +526,7 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
{ {
tryUnifyUnionWithType(subTy, subUnion, superTy); 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); 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); LUAU_ASSERT(FFlag::LuauSubtypeNormalizer);
@ -1371,12 +1369,12 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
else else
{ {
// A union type including nil marks an optional argument // A union type including nil marks an optional argument
if ((!FFlag::LuauLowerBoundsCalculation || isNonstrictMode()) && superIter.good() && isOptional(*superIter)) if (superIter.good() && isOptional(*superIter))
{ {
superIter.advance(); superIter.advance();
continue; continue;
} }
else if ((!FFlag::LuauLowerBoundsCalculation || isNonstrictMode()) && subIter.good() && isOptional(*subIter)) else if (subIter.good() && isOptional(*subIter))
{ {
subIter.advance(); subIter.advance();
continue; continue;
@ -1394,7 +1392,7 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
return; return;
} }
if ((!FFlag::LuauLowerBoundsCalculation || isNonstrictMode()) && !isFunctionCall && subIter.good()) if (!isFunctionCall && subIter.good())
{ {
// Sometimes it is ok to pass too many arguments // Sometimes it is ok to pass too many arguments
return; return;
@ -1491,7 +1489,6 @@ void Unifier::tryUnifyFunctions(TypeId subTy, TypeId superTy, bool isFunctionCal
numGenerics = std::min(superFunction->generics.size(), subFunction->generics.size()); numGenerics = std::min(superFunction->generics.size(), subFunction->generics.size());
numGenericPacks = std::min(superFunction->genericPacks.size(), subFunction->genericPacks.size()); numGenericPacks = std::min(superFunction->genericPacks.size(), subFunction->genericPacks.size());
} }
else else
{ {
@ -2012,7 +2009,8 @@ void Unifier::tryUnifyWithMetatable(TypeId subTy, TypeId superTy, bool reversed)
if (auto e = hasUnificationTooComplex(innerState.errors)) if (auto e = hasUnificationTooComplex(innerState.errors))
reportError(*e); reportError(*e);
else if (!innerState.errors.empty()) 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) else if (!missingProperty)
{ {
log.concat(std::move(innerState.log)); log.concat(std::move(innerState.log));
@ -2448,8 +2446,7 @@ void Unifier::unifyLowerBound(TypePackId subTy, TypePackId superTy, TypeLevel de
for (; superIter != superEndIter; ++superIter) for (; superIter != superEndIter; ++superIter)
tp->head.push_back(*superIter); tp->head.push_back(*superIter);
} }
else if (const VariadicTypePack* subVariadic = log.getMutable<VariadicTypePack>(subTailPack); else if (const VariadicTypePack* subVariadic = log.getMutable<VariadicTypePack>(subTailPack))
subVariadic && FFlag::LuauCallUnifyPackTails)
{ {
while (superIter != superEndIter) while (superIter != superEndIter)
{ {

View file

@ -1,17 +1,13 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once #pragma once
#include "Luau/Common.h"
#include <vector> #include <vector>
#include <string> #include <string>
#include <stdarg.h> #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 namespace Luau
{ {

View file

@ -4,6 +4,7 @@
#include "lua.h" #include "lua.h"
#include "lualib.h" #include "lualib.h"
#include "Luau/CodeGen.h"
#include "Luau/Compiler.h" #include "Luau/Compiler.h"
#include "Luau/BytecodeBuilder.h" #include "Luau/BytecodeBuilder.h"
#include "Luau/Parser.h" #include "Luau/Parser.h"
@ -46,11 +47,15 @@ enum class CompileFormat
{ {
Text, Text,
Binary, Binary,
Remarks,
Codegen,
Null Null
}; };
constexpr int MaxTraversalLimit = 50; constexpr int MaxTraversalLimit = 50;
static bool codegen = false;
// Ctrl-C handling // Ctrl-C handling
static void sigintCallback(lua_State* L, int gc) 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()); std::string bytecode = Luau::compile(*source, copts());
if (luau_load(ML, chunkname.c_str(), bytecode.data(), bytecode.size(), 0) == 0) if (luau_load(ML, chunkname.c_str(), bytecode.data(), bytecode.size(), 0) == 0)
{ {
if (codegen)
Luau::CodeGen::compile(ML, -1);
if (coverageActive()) if (coverageActive())
coverageTrack(ML, -1); coverageTrack(ML, -1);
@ -242,6 +250,9 @@ static int lua_callgrind(lua_State* L)
void setupState(lua_State* L) void setupState(lua_State* L)
{ {
if (codegen)
Luau::CodeGen::create(L);
luaL_openlibs(L); luaL_openlibs(L);
static const luaL_Reg funcs[] = { static const luaL_Reg funcs[] = {
@ -276,6 +287,9 @@ std::string runCode(lua_State* L, const std::string& source)
return error; return error;
} }
if (codegen)
Luau::CodeGen::compile(L, -1);
lua_State* T = lua_newthread(L); lua_State* T = lua_newthread(L);
lua_pushvalue(L, -2); 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 (luau_load(L, chunkname.c_str(), bytecode.data(), bytecode.size(), 0) == 0)
{ {
if (codegen)
Luau::CodeGen::compile(L, -1);
if (coverageActive()) if (coverageActive())
coverageTrack(L, -1); coverageTrack(L, -1);
@ -656,6 +673,20 @@ static void reportError(const char* name, const Luau::CompileError& error)
report(name, error.getLocation(), "CompileError", error.what()); 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) static bool compileFile(const char* name, CompileFormat format)
{ {
std::optional<std::string> source = readFile(name); std::optional<std::string> source = readFile(name);
@ -675,6 +706,11 @@ static bool compileFile(const char* name, CompileFormat format)
Luau::BytecodeBuilder::Dump_Remarks); Luau::BytecodeBuilder::Dump_Remarks);
bcb.setDumpSource(*source); 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()); Luau::compileOrThrow(bcb, *source, copts());
@ -683,9 +719,15 @@ static bool compileFile(const char* name, CompileFormat format)
case CompileFormat::Text: case CompileFormat::Text:
printf("%s", bcb.dumpEverything().c_str()); printf("%s", bcb.dumpEverything().c_str());
break; break;
case CompileFormat::Remarks:
printf("%s", bcb.dumpSourceRemarks().c_str());
break;
case CompileFormat::Binary: case CompileFormat::Binary:
fwrite(bcb.getBytecode().data(), 1, bcb.getBytecode().size(), stdout); fwrite(bcb.getBytecode().data(), 1, bcb.getBytecode().size(), stdout);
break; break;
case CompileFormat::Codegen:
printf("%s", getCodegenAssembly(name, bcb.getBytecode()).c_str());
break;
case CompileFormat::Null: case CompileFormat::Null:
break; break;
} }
@ -713,7 +755,7 @@ static void displayHelp(const char* argv0)
printf("\n"); printf("\n");
printf("Available modes:\n"); printf("Available modes:\n");
printf(" omitted: compile and run input files one by one\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("\n");
printf("Available options:\n"); printf("Available options:\n");
printf(" --coverage: collect code coverage while running the code and output results to coverage.out\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(" -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(" --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(" --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) 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; 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) else if (strcmp(argv[1], "--compile=null") == 0)
{ {
compileFormat = CompileFormat::Null; compileFormat = CompileFormat::Null;
@ -811,6 +862,10 @@ int replMain(int argc, char** argv)
{ {
profile = atoi(argv[i] + 10); profile = atoi(argv[i] + 10);
} }
else if (strcmp(argv[i], "--codegen") == 0)
{
codegen = true;
}
else if (strcmp(argv[i], "--coverage") == 0) else if (strcmp(argv[i], "--coverage") == 0)
{ {
coverage = true; coverage = true;
@ -839,12 +894,26 @@ int replMain(int argc, char** argv)
} }
#endif #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); const std::vector<std::string> files = getSourceFiles(argc, argv);
if (mode == CliMode::Unknown) if (mode == CliMode::Unknown)
{ {
mode = files.empty() ? CliMode::Repl : CliMode::RunSourceFiles; 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) switch (mode)
{ {
case CliMode::Compile: case CliMode::Compile:

View file

@ -12,6 +12,7 @@ option(LUAU_BUILD_WEB "Build Web module" OFF)
option(LUAU_WERROR "Warnings as errors" OFF) option(LUAU_WERROR "Warnings as errors" OFF)
option(LUAU_STATIC_CRT "Link with the static CRT (/MT)" 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_EXTERN_C "Use extern C for all APIs" OFF)
option(LUAU_NATIVE "Enable support for native code generation" OFF)
if(LUAU_STATIC_CRT) if(LUAU_STATIC_CRT)
cmake_minimum_required(VERSION 3.15) cmake_minimum_required(VERSION 3.15)
@ -132,6 +133,10 @@ if(LUAU_EXTERN_C)
target_compile_definitions(Luau.Compiler PUBLIC LUACODE_API=extern\"C\") target_compile_definitions(Luau.Compiler PUBLIC LUACODE_API=extern\"C\")
endif() endif()
if(LUAU_NATIVE)
target_compile_definitions(Luau.VM PUBLIC LUA_CUSTOM_EXECUTION=1)
endif()
if (MSVC AND MSVC_VERSION GREATER_EQUAL 1924) if (MSVC AND MSVC_VERSION GREATER_EQUAL 1924)
# disable partial redundancy elimination which regresses interpreter codegen substantially in VS2022: # disable partial redundancy elimination which regresses interpreter codegen substantially in VS2022:
# https://developercommunity.visualstudio.com/t/performance-regression-on-a-complex-interpreter-lo/1631863 # 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_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) if(UNIX)
find_library(LIBPTHREAD pthread) find_library(LIBPTHREAD pthread)
@ -193,11 +198,11 @@ if(LUAU_BUILD_TESTS)
target_compile_options(Luau.Conformance PRIVATE ${LUAU_OPTIONS}) target_compile_options(Luau.Conformance PRIVATE ${LUAU_OPTIONS})
target_include_directories(Luau.Conformance PRIVATE extern) 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_compile_options(Luau.CLI.Test PRIVATE ${LUAU_OPTIONS})
target_include_directories(Luau.CLI.Test PRIVATE extern CLI) 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) if(UNIX)
find_library(LIBPTHREAD pthread) find_library(LIBPTHREAD pthread)
if (LIBPTHREAD) if (LIBPTHREAD)

View file

@ -15,6 +15,14 @@ namespace Luau
namespace CodeGen namespace CodeGen
{ {
enum class RoundingModeX64
{
RoundToNearestEven = 0b00,
RoundToNegativeInfinity = 0b01,
RoundToPositiveInfinity = 0b10,
RoundToZero = 0b11,
};
class AssemblyBuilderX64 class AssemblyBuilderX64
{ {
public: public:
@ -48,6 +56,8 @@ public:
void imul(OperandX64 op); void imul(OperandX64 op);
void neg(OperandX64 op); void neg(OperandX64 op);
void not_(OperandX64 op); void not_(OperandX64 op);
void dec(OperandX64 op);
void inc(OperandX64 op);
// Additional forms of imul // Additional forms of imul
void imul(OperandX64 lhs, OperandX64 rhs); void imul(OperandX64 lhs, OperandX64 rhs);
@ -82,13 +92,12 @@ public:
void vxorpd(OperandX64 dst, OperandX64 src1, OperandX64 src2); void vxorpd(OperandX64 dst, OperandX64 src1, OperandX64 src2);
void vcomisd(OperandX64 src1, OperandX64 src2);
void vucomisd(OperandX64 src1, OperandX64 src2); void vucomisd(OperandX64 src1, OperandX64 src2);
void vcvttsd2si(OperandX64 dst, OperandX64 src); void vcvttsd2si(OperandX64 dst, OperandX64 src);
void vcvtsi2sd(OperandX64 dst, OperandX64 src1, OperandX64 src2); void vcvtsi2sd(OperandX64 dst, OperandX64 src1, OperandX64 src2);
void 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 vsqrtpd(OperandX64 dst, OperandX64 src);
void vsqrtps(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 f32x4(float x, float y, float z, float w);
OperandX64 bytes(const void* ptr, size_t size, size_t align = 8); 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 // 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' // The *end* of 'data' has to be aligned to 16 bytes, this will also align 'code'
std::vector<uint8_t> data; std::vector<uint8_t> data;
@ -127,6 +138,8 @@ public:
std::string text; std::string text;
const bool logText = false;
private: private:
// Instruction archetypes // Instruction archetypes
void placeBinary(const char* name, OperandX64 lhs, OperandX64 rhs, uint8_t codeimm8, uint8_t codeimm, uint8_t codeimmImm8, uint8_t code8rev, 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(Label label);
LUAU_NOINLINE void log(const char* opcode, Label label); LUAU_NOINLINE void log(const char* opcode, Label label);
void log(OperandX64 op); void log(OperandX64 op);
void logAppend(const char* fmt, ...);
const char* getSizeName(SizeX64 size); const char* getSizeName(SizeX64 size);
const char* getRegisterName(RegisterX64 reg); const char* getRegisterName(RegisterX64 reg);
@ -187,7 +199,6 @@ private:
std::vector<Label> pendingLabels; std::vector<Label> pendingLabels;
std::vector<uint32_t> labelLocations; std::vector<uint32_t> labelLocations;
bool logText = false;
bool finalized = false; bool finalized = false;
size_t dataPos = 0; size_t dataPos = 0;

View file

@ -2,8 +2,9 @@
#pragma once #pragma once
#include <vector> #include <vector>
#include <stdint.h>
#include <stddef.h> #include <stddef.h>
#include <stdint.h>
namespace Luau namespace Luau
{ {

View 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

View file

@ -37,6 +37,9 @@ enum class Condition
Zero, Zero,
NotZero, NotZero,
Parity,
NotParity,
Count Count
}; };

View file

@ -14,9 +14,13 @@ namespace CodeGen
// TODO: more assertions on operand sizes // TODO: more assertions on operand sizes
const uint8_t codeForCondition[] = { 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"); 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_REG(op, reg) ((op) + (reg & 0x7))
#define OP_PLUS_CC(op, cc) ((op) + uint8_t(cc)) #define OP_PLUS_CC(op, cc) ((op) + uint8_t(cc))
@ -48,6 +52,8 @@ const unsigned AVX_F2 = 0b11;
const unsigned kMaxAlign = 16; const unsigned kMaxAlign = 16;
const uint8_t kRoundingPrecisionInexact = 0b1000;
AssemblyBuilderX64::AssemblyBuilderX64(bool logText) AssemblyBuilderX64::AssemblyBuilderX64(bool logText)
: logText(logText) : logText(logText)
{ {
@ -255,6 +261,16 @@ void AssemblyBuilderX64::not_(OperandX64 op)
placeUnaryModRegMem("not", op, 0xf6, 0xf7, 2); 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) void AssemblyBuilderX64::imul(OperandX64 lhs, OperandX64 rhs)
{ {
if (logText) if (logText)
@ -338,7 +354,7 @@ void AssemblyBuilderX64::ret()
void AssemblyBuilderX64::jcc(Condition cond, Label& label) 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) 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); 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) void AssemblyBuilderX64::vucomisd(OperandX64 src1, OperandX64 src2)
{ {
placeAvx("vucomisd", src1, src2, 0x2e, false, AVX_0F, AVX_66); 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); 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) void AssemblyBuilderX64::vsqrtpd(OperandX64 dst, OperandX64 src)
@ -534,6 +545,8 @@ void AssemblyBuilderX64::finalize()
// Resolve jump targets // Resolve jump targets
for (Label fixup : pendingLabels) 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); uint32_t value = labelLocations[fixup.id - 1] - (fixup.location + 4);
writeu32(&code[fixup.location], value); writeu32(&code[fixup.location], value);
} }
@ -552,7 +565,7 @@ void AssemblyBuilderX64::finalize()
Label AssemblyBuilderX64::setLabel() Label AssemblyBuilderX64::setLabel()
{ {
Label label{nextLabel++, getCodeSize()}; Label label{nextLabel++, getCodeSize()};
labelLocations.push_back(0); labelLocations.push_back(~0u);
if (logText) if (logText)
log(label); log(label);
@ -565,7 +578,7 @@ void AssemblyBuilderX64::setLabel(Label& label)
if (label.id == 0) if (label.id == 0)
{ {
label.id = nextLabel++; label.id = nextLabel++;
labelLocations.push_back(0); labelLocations.push_back(~0u);
} }
label.location = getCodeSize(); label.location = getCodeSize();
@ -1019,7 +1032,7 @@ void AssemblyBuilderX64::placeLabel(Label& label)
if (label.id == 0) if (label.id == 0)
{ {
label.id = nextLabel++; label.id = nextLabel++;
labelLocations.push_back(0); labelLocations.push_back(~0u);
} }
pendingLabels.push_back({label.id, getCodeSize()}); pendingLabels.push_back({label.id, getCodeSize()});

449
CodeGen/src/CodeGen.cpp Normal file
View 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
View 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
View 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

View 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

View 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

View 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

View 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
View 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

View 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

View 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

View file

@ -4,15 +4,17 @@
#include "Fallbacks.h" #include "Fallbacks.h"
#include "FallbacksProlog.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++; Instruction insn = *pc++;
LUAU_ASSERT(insn == 0); LUAU_ASSERT(insn == 0);
return pc; 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++; Instruction insn = *pc++;
StkId ra = VM_REG(LUAU_INSN_A(insn)); 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; 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++; Instruction insn = *pc++;
StkId ra = VM_REG(LUAU_INSN_A(insn)); 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; 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++; Instruction insn = *pc++;
StkId ra = VM_REG(LUAU_INSN_A(insn)); 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; 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++; Instruction insn = *pc++;
StkId ra = VM_REG(LUAU_INSN_A(insn)); StkId ra = VM_REG(LUAU_INSN_A(insn));
TValue* kv = VM_KV(LUAU_INSN_D(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; 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++; Instruction insn = *pc++;
StkId ra = VM_REG(LUAU_INSN_A(insn)); StkId ra = VM_REG(LUAU_INSN_A(insn));
StkId rb = VM_REG(LUAU_INSN_B(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; 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++; Instruction insn = *pc++;
StkId ra = VM_REG(LUAU_INSN_A(insn)); StkId ra = VM_REG(LUAU_INSN_A(insn));
uint32_t aux = *pc++; 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++; Instruction insn = *pc++;
StkId ra = VM_REG(LUAU_INSN_A(insn)); StkId ra = VM_REG(LUAU_INSN_A(insn));
uint32_t aux = *pc++; 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++; Instruction insn = *pc++;
StkId ra = VM_REG(LUAU_INSN_A(insn)); StkId ra = VM_REG(LUAU_INSN_A(insn));
TValue* ur = VM_UV(LUAU_INSN_B(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; 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++; Instruction insn = *pc++;
StkId ra = VM_REG(LUAU_INSN_A(insn)); StkId ra = VM_REG(LUAU_INSN_A(insn));
TValue* ur = VM_UV(LUAU_INSN_B(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; 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++; Instruction insn = *pc++;
StkId ra = VM_REG(LUAU_INSN_A(insn)); 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; 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++; Instruction insn = *pc++;
StkId ra = VM_REG(LUAU_INSN_A(insn)); StkId ra = VM_REG(LUAU_INSN_A(insn));
TValue* kv = VM_KV(LUAU_INSN_D(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++; Instruction insn = *pc++;
StkId ra = VM_REG(LUAU_INSN_A(insn)); StkId ra = VM_REG(LUAU_INSN_A(insn));
StkId rb = VM_REG(LUAU_INSN_B(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; 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++; Instruction insn = *pc++;
StkId ra = VM_REG(LUAU_INSN_A(insn)); StkId ra = VM_REG(LUAU_INSN_A(insn));
StkId rb = VM_REG(LUAU_INSN_B(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++; Instruction insn = *pc++;
StkId ra = VM_REG(LUAU_INSN_A(insn)); StkId ra = VM_REG(LUAU_INSN_A(insn));
StkId rb = VM_REG(LUAU_INSN_B(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; 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++; Instruction insn = *pc++;
StkId ra = VM_REG(LUAU_INSN_A(insn)); StkId ra = VM_REG(LUAU_INSN_A(insn));
StkId rb = VM_REG(LUAU_INSN_B(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; 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++; Instruction insn = *pc++;
StkId ra = VM_REG(LUAU_INSN_A(insn)); StkId ra = VM_REG(LUAU_INSN_A(insn));
StkId rb = VM_REG(LUAU_INSN_B(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; 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++; Instruction insn = *pc++;
StkId ra = VM_REG(LUAU_INSN_A(insn)); StkId ra = VM_REG(LUAU_INSN_A(insn));
StkId rb = VM_REG(LUAU_INSN_B(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; 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++; Instruction insn = *pc++;
StkId ra = VM_REG(LUAU_INSN_A(insn)); 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: default:
LUAU_ASSERT(!"Unknown upvalue capture type"); 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; 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++; Instruction insn = *pc++;
StkId ra = VM_REG(LUAU_INSN_A(insn)); StkId ra = VM_REG(LUAU_INSN_A(insn));
StkId rb = VM_REG(LUAU_INSN_B(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; 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(); VM_INTERRUPT();
Instruction insn = *pc++; Instruction insn = *pc++;
StkId ra = VM_REG(LUAU_INSN_A(insn)); 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(); VM_INTERRUPT();
Instruction insn = *pc++; 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 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; 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++; Instruction insn = *pc++;
pc += LUAU_INSN_D(insn); pc += LUAU_INSN_D(insn);
@ -794,8 +818,9 @@ const Instruction* execute_LOP_JUMP(lua_State* L, const Instruction* pc, Closure
return pc; 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++; Instruction insn = *pc++;
StkId ra = VM_REG(LUAU_INSN_A(insn)); 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; 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++; Instruction insn = *pc++;
StkId ra = VM_REG(LUAU_INSN_A(insn)); 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; 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++; Instruction insn = *pc++;
uint32_t aux = *pc; uint32_t aux = *pc;
StkId ra = VM_REG(LUAU_INSN_A(insn)); 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() // slow path after switch()
break; 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 // 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++; Instruction insn = *pc++;
uint32_t aux = *pc; uint32_t aux = *pc;
StkId ra = VM_REG(LUAU_INSN_A(insn)); 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() // slow path after switch()
break; 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 // 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++; Instruction insn = *pc++;
uint32_t aux = *pc; uint32_t aux = *pc;
StkId ra = VM_REG(LUAU_INSN_A(insn)); 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++; Instruction insn = *pc++;
uint32_t aux = *pc; uint32_t aux = *pc;
StkId ra = VM_REG(LUAU_INSN_A(insn)); 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++; Instruction insn = *pc++;
uint32_t aux = *pc; uint32_t aux = *pc;
StkId ra = VM_REG(LUAU_INSN_A(insn)); 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++; Instruction insn = *pc++;
uint32_t aux = *pc; uint32_t aux = *pc;
StkId ra = VM_REG(LUAU_INSN_A(insn)); 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++; Instruction insn = *pc++;
StkId ra = VM_REG(LUAU_INSN_A(insn)); StkId ra = VM_REG(LUAU_INSN_A(insn));
StkId rb = VM_REG(LUAU_INSN_B(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++; Instruction insn = *pc++;
StkId ra = VM_REG(LUAU_INSN_A(insn)); StkId ra = VM_REG(LUAU_INSN_A(insn));
StkId rb = VM_REG(LUAU_INSN_B(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++; Instruction insn = *pc++;
StkId ra = VM_REG(LUAU_INSN_A(insn)); StkId ra = VM_REG(LUAU_INSN_A(insn));
StkId rb = VM_REG(LUAU_INSN_B(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++; Instruction insn = *pc++;
StkId ra = VM_REG(LUAU_INSN_A(insn)); StkId ra = VM_REG(LUAU_INSN_A(insn));
StkId rb = VM_REG(LUAU_INSN_B(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++; Instruction insn = *pc++;
StkId ra = VM_REG(LUAU_INSN_A(insn)); StkId ra = VM_REG(LUAU_INSN_A(insn));
StkId rb = VM_REG(LUAU_INSN_B(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++; Instruction insn = *pc++;
StkId ra = VM_REG(LUAU_INSN_A(insn)); StkId ra = VM_REG(LUAU_INSN_A(insn));
StkId rb = VM_REG(LUAU_INSN_B(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++; Instruction insn = *pc++;
StkId ra = VM_REG(LUAU_INSN_A(insn)); StkId ra = VM_REG(LUAU_INSN_A(insn));
StkId rb = VM_REG(LUAU_INSN_B(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++; Instruction insn = *pc++;
StkId ra = VM_REG(LUAU_INSN_A(insn)); StkId ra = VM_REG(LUAU_INSN_A(insn));
StkId rb = VM_REG(LUAU_INSN_B(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++; Instruction insn = *pc++;
StkId ra = VM_REG(LUAU_INSN_A(insn)); StkId ra = VM_REG(LUAU_INSN_A(insn));
StkId rb = VM_REG(LUAU_INSN_B(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++; Instruction insn = *pc++;
StkId ra = VM_REG(LUAU_INSN_A(insn)); StkId ra = VM_REG(LUAU_INSN_A(insn));
StkId rb = VM_REG(LUAU_INSN_B(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++; Instruction insn = *pc++;
StkId ra = VM_REG(LUAU_INSN_A(insn)); StkId ra = VM_REG(LUAU_INSN_A(insn));
StkId rb = VM_REG(LUAU_INSN_B(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++; Instruction insn = *pc++;
StkId ra = VM_REG(LUAU_INSN_A(insn)); StkId ra = VM_REG(LUAU_INSN_A(insn));
StkId rb = VM_REG(LUAU_INSN_B(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++; Instruction insn = *pc++;
StkId ra = VM_REG(LUAU_INSN_A(insn)); StkId ra = VM_REG(LUAU_INSN_A(insn));
StkId rb = VM_REG(LUAU_INSN_B(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; 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++; Instruction insn = *pc++;
StkId ra = VM_REG(LUAU_INSN_A(insn)); StkId ra = VM_REG(LUAU_INSN_A(insn));
StkId rb = VM_REG(LUAU_INSN_B(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; 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++; Instruction insn = *pc++;
StkId ra = VM_REG(LUAU_INSN_A(insn)); StkId ra = VM_REG(LUAU_INSN_A(insn));
StkId rb = VM_REG(LUAU_INSN_B(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; 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++; Instruction insn = *pc++;
StkId ra = VM_REG(LUAU_INSN_A(insn)); StkId ra = VM_REG(LUAU_INSN_A(insn));
StkId rb = VM_REG(LUAU_INSN_B(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; 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++; Instruction insn = *pc++;
int b = LUAU_INSN_B(insn); int b = LUAU_INSN_B(insn);
int c = LUAU_INSN_C(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; 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++; Instruction insn = *pc++;
StkId ra = VM_REG(LUAU_INSN_A(insn)); StkId ra = VM_REG(LUAU_INSN_A(insn));
StkId rb = VM_REG(LUAU_INSN_B(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; 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++; Instruction insn = *pc++;
StkId ra = VM_REG(LUAU_INSN_A(insn)); StkId ra = VM_REG(LUAU_INSN_A(insn));
StkId rb = VM_REG(LUAU_INSN_B(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++; Instruction insn = *pc++;
StkId ra = VM_REG(LUAU_INSN_A(insn)); StkId ra = VM_REG(LUAU_INSN_A(insn));
StkId rb = VM_REG(LUAU_INSN_B(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++; Instruction insn = *pc++;
StkId ra = VM_REG(LUAU_INSN_A(insn)); StkId ra = VM_REG(LUAU_INSN_A(insn));
int b = LUAU_INSN_B(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; 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++; Instruction insn = *pc++;
StkId ra = VM_REG(LUAU_INSN_A(insn)); StkId ra = VM_REG(LUAU_INSN_A(insn));
TValue* kv = VM_KV(LUAU_INSN_D(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; 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++; Instruction insn = *pc++;
StkId ra = VM_REG(LUAU_INSN_A(insn)); 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 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; 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++; Instruction insn = *pc++;
StkId ra = VM_REG(LUAU_INSN_A(insn)); 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; 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(); VM_INTERRUPT();
Instruction insn = *pc++; Instruction insn = *pc++;
StkId ra = VM_REG(LUAU_INSN_A(insn)); 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++; Instruction insn = *pc++;
StkId ra = VM_REG(LUAU_INSN_A(insn)); 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; 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(); VM_INTERRUPT();
Instruction insn = *pc++; Instruction insn = *pc++;
StkId ra = VM_REG(LUAU_INSN_A(insn)); 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++; Instruction insn = *pc++;
StkId ra = VM_REG(LUAU_INSN_A(insn)); 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; 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_ASSERT(!"Unsupported deprecated opcode");
LUAU_UNREACHABLE(); 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++; Instruction insn = *pc++;
StkId ra = VM_REG(LUAU_INSN_A(insn)); 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; 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_ASSERT(!"Unsupported deprecated opcode");
LUAU_UNREACHABLE(); 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++; Instruction insn = *pc++;
int b = LUAU_INSN_B(insn) - 1; int b = LUAU_INSN_B(insn) - 1;
int n = cast_int(base - L->ci->func) - cl->l.p->numparams - 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++; Instruction insn = *pc++;
StkId ra = VM_REG(LUAU_INSN_A(insn)); StkId ra = VM_REG(LUAU_INSN_A(insn));
TValue* kv = VM_KV(LUAU_INSN_D(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; 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++; Instruction insn = *pc++;
int numparams = LUAU_INSN_A(insn); int numparams = LUAU_INSN_A(insn);
@ -2196,8 +2266,9 @@ const Instruction* execute_LOP_PREPVARARGS(lua_State* L, const Instruction* pc,
return 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(); VM_INTERRUPT();
Instruction insn = *pc++; Instruction insn = *pc++;
@ -2206,8 +2277,9 @@ const Instruction* execute_LOP_JUMPBACK(lua_State* L, const Instruction* pc, Clo
return pc; 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++; Instruction insn = *pc++;
StkId ra = VM_REG(LUAU_INSN_A(insn)); StkId ra = VM_REG(LUAU_INSN_A(insn));
uint32_t aux = *pc++; uint32_t aux = *pc++;
@ -2217,8 +2289,9 @@ const Instruction* execute_LOP_LOADKX(lua_State* L, const Instruction* pc, Closu
return pc; 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(); VM_INTERRUPT();
Instruction insn = *pc++; Instruction insn = *pc++;
@ -2227,8 +2300,9 @@ const Instruction* execute_LOP_JUMPX(lua_State* L, const Instruction* pc, Closur
return pc; 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++; Instruction insn = *pc++;
int bfid = LUAU_INSN_A(insn); int bfid = LUAU_INSN_A(insn);
int skip = LUAU_INSN_C(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++; Instruction insn = *pc++;
int hits = LUAU_INSN_E(insn); int hits = LUAU_INSN_E(insn);
@ -2285,26 +2360,30 @@ const Instruction* execute_LOP_COVERAGE(lua_State* L, const Instruction* pc, Clo
return pc; 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_ASSERT(!"CAPTURE is a pseudo-opcode and must be executed as part of NEWCLOSURE");
LUAU_UNREACHABLE(); 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_ASSERT(!"Unsupported deprecated opcode");
LUAU_UNREACHABLE(); 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_ASSERT(!"Unsupported deprecated opcode");
LUAU_UNREACHABLE(); 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++; Instruction insn = *pc++;
int bfid = LUAU_INSN_A(insn); int bfid = LUAU_INSN_A(insn);
TValue* arg = VM_REG(LUAU_INSN_B(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++; Instruction insn = *pc++;
int bfid = LUAU_INSN_A(insn); int bfid = LUAU_INSN_A(insn);
int skip = LUAU_INSN_C(insn) - 1; 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++; Instruction insn = *pc++;
int bfid = LUAU_INSN_A(insn); int bfid = LUAU_INSN_A(insn);
int skip = LUAU_INSN_C(insn) - 1; 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_ASSERT(!"Unsupported deprecated opcode");
LUAU_UNREACHABLE(); 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++; Instruction insn = *pc++;
uint32_t aux = *pc; uint32_t aux = *pc;
StkId ra = VM_REG(LUAU_INSN_A(insn)); 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; 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++; Instruction insn = *pc++;
uint32_t aux = *pc; uint32_t aux = *pc;
StkId ra = VM_REG(LUAU_INSN_A(insn)); 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; 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++; Instruction insn = *pc++;
uint32_t aux = *pc; uint32_t aux = *pc;
StkId ra = VM_REG(LUAU_INSN_A(insn)); 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; 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++; Instruction insn = *pc++;
uint32_t aux = *pc; uint32_t aux = *pc;
StkId ra = VM_REG(LUAU_INSN_A(insn)); StkId ra = VM_REG(LUAU_INSN_A(insn));

View file

@ -10,84 +10,84 @@ typedef uint32_t Instruction;
typedef struct lua_TValue TValue; typedef struct lua_TValue TValue;
typedef TValue* StkId; typedef TValue* StkId;
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);

122
CodeGen/src/NativeState.cpp Normal file
View 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 (size_t 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
View 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

View file

@ -122,3 +122,9 @@ FValue<T>* FValue<T>::list = nullptr;
{ \ { \
Luau::FValue<int> flag(#flag, def, true); \ 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

View file

@ -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, // 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. // or critical bugs that are found after the code has been submitted.
static const char* kList[] = { static const char* kList[] = {
"LuauLowerBoundsCalculation",
"LuauInterpolatedStringBaseSupport", "LuauInterpolatedStringBaseSupport",
"LuauInstantiateInSubtyping", // requires some fixes to lua-apps code "LuauInstantiateInSubtyping", // requires some fixes to lua-apps code
// makes sure we always have at least one entry // makes sure we always have at least one entry

View file

@ -102,6 +102,11 @@ public:
void setDumpSource(const std::string& source); void setDumpSource(const std::string& source);
bool needsDebugRemarks() const
{
return (dumpFlags & Dump_Remarks) != 0;
}
const std::string& getBytecode() const const std::string& getBytecode() const
{ {
LUAU_ASSERT(!bytecode.empty()); // did you forget to call finalize? LUAU_ASSERT(!bytecode.empty()); // did you forget to call finalize?
@ -110,6 +115,7 @@ public:
std::string dumpFunction(uint32_t id) const; std::string dumpFunction(uint32_t id) const;
std::string dumpEverything() const; std::string dumpEverything() const;
std::string dumpSourceRemarks() const;
static uint32_t getImportId(int32_t id0); static uint32_t getImportId(int32_t id0);
static uint32_t getImportId(int32_t id0, int32_t id1); static uint32_t getImportId(int32_t id0, int32_t id1);
@ -243,6 +249,7 @@ private:
uint32_t dumpFlags = 0; uint32_t dumpFlags = 0;
std::vector<std::string> dumpSource; std::vector<std::string> dumpSource;
std::vector<std::pair<int, std::string>> dumpRemarks;
std::string (BytecodeBuilder::*dumpFunctionPtr)() const = nullptr; std::string (BytecodeBuilder::*dumpFunctionPtr)() const = nullptr;

View file

@ -572,6 +572,7 @@ void BytecodeBuilder::addDebugRemark(const char* format, ...)
debugRemarkBuffer += '\0'; debugRemarkBuffer += '\0';
debugRemarks.emplace_back(uint32_t(insns.size()), uint32_t(offset)); debugRemarks.emplace_back(uint32_t(insns.size()), uint32_t(offset));
dumpRemarks.emplace_back(debugLine, debugRemarkBuffer.c_str() + offset);
} }
void BytecodeBuilder::finalize() void BytecodeBuilder::finalize()
@ -1949,4 +1950,40 @@ std::string BytecodeBuilder::dumpEverything() const
return result; 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 } // namespace Luau

View file

@ -709,6 +709,17 @@ struct Compiler
if (const int* id = builtins.find(expr)) if (const int* id = builtins.find(expr))
bfid = *id; 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) if (bfid == LBF_SELECT_VARARG)
{ {
// Optimization: compile select(_, ...) as FASTCALL1; the builtin will read variadic arguments directly // Optimization: compile select(_, ...) as FASTCALL1; the builtin will read variadic arguments directly
@ -918,6 +929,9 @@ struct Compiler
shared = int16_t(cid); shared = int16_t(cid);
} }
if (shared < 0)
bytecode.addDebugRemark("allocation: closure with %d upvalues", int(captures.size()));
if (shared >= 0) if (shared >= 0)
bytecode.emitAD(LOP_DUPCLOSURE, target, shared); bytecode.emitAD(LOP_DUPCLOSURE, target, shared);
else else
@ -1599,6 +1613,8 @@ struct Compiler
{ {
TableShape shape = tableShapes[expr]; TableShape shape = tableShapes[expr];
bytecode.addDebugRemark("allocation: table hash %d", shape.hashSize);
bytecode.emitABC(LOP_NEWTABLE, target, encodeHashSize(shape.hashSize), 0); bytecode.emitABC(LOP_NEWTABLE, target, encodeHashSize(shape.hashSize), 0);
bytecode.emitAux(shape.arraySize); bytecode.emitAux(shape.arraySize);
return; return;
@ -1671,6 +1687,8 @@ struct Compiler
if (tid < 0) if (tid < 0)
CompileError::raise(expr->location, "Exceeded constant limit; simplify the code to compile"); CompileError::raise(expr->location, "Exceeded constant limit; simplify the code to compile");
bytecode.addDebugRemark("allocation: table template %d", hashSize);
if (tid < 32768) if (tid < 32768)
{ {
bytecode.emitAD(LOP_DUPTABLE, reg, int16_t(tid)); 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>(); bool trailingVarargs = last && last->kind == AstExprTable::Item::List && last->value->is<AstExprVarargs>();
LUAU_ASSERT(!trailingVarargs || arraySize > 0); 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.emitABC(LOP_NEWTABLE, reg, uint8_t(encodedHashSize), 0);
bytecode.emitAux(arraySize - trailingVarargs + indexSize); bytecode.emitAux(arrayAllocation);
} }
unsigned int arrayChunkSize = std::min(16u, arraySize); unsigned int arrayChunkSize = std::min(16u, arraySize);

View file

@ -112,6 +112,11 @@ ifeq ($(protobuf),download)
EPROTOC=../build/libprotobuf-mutator/external.protobuf/bin/protoc EPROTOC=../build/libprotobuf-mutator/external.protobuf/bin/protoc
endif endif
ifneq ($(native),)
CXXFLAGS+=-DLUA_CUSTOM_EXECUTION=1
TESTS_ARGS+=--codegen
endif
# target-specific flags # target-specific flags
$(AST_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include $(AST_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include
$(COMPILER_OBJECTS): CXXFLAGS+=-std=c++17 -ICompiler/include -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 $(VM_OBJECTS): CXXFLAGS+=-std=c++11 -ICommon/include -IVM/include
$(ISOCLINE_OBJECTS): CXXFLAGS+=-Wno-unused-function -Iextern/isocline/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 $(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 $(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 $(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 # executable targets
$(TESTS_TARGET): $(TESTS_OBJECTS) $(ANALYSIS_TARGET) $(COMPILER_TARGET) $(AST_TARGET) $(CODEGEN_TARGET) $(VM_TARGET) $(ISOCLINE_TARGET) $(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) $(ANALYZE_CLI_TARGET): $(ANALYZE_CLI_OBJECTS) $(ANALYSIS_TARGET) $(AST_TARGET)
$(TESTS_TARGET) $(REPL_CLI_TARGET) $(ANALYZE_CLI_TARGET): $(TESTS_TARGET) $(REPL_CLI_TARGET) $(ANALYZE_CLI_TARGET):

View file

@ -58,6 +58,7 @@ target_sources(Luau.CodeGen PRIVATE
CodeGen/include/Luau/AssemblyBuilderX64.h CodeGen/include/Luau/AssemblyBuilderX64.h
CodeGen/include/Luau/CodeAllocator.h CodeGen/include/Luau/CodeAllocator.h
CodeGen/include/Luau/CodeBlockUnwind.h CodeGen/include/Luau/CodeBlockUnwind.h
CodeGen/include/Luau/CodeGen.h
CodeGen/include/Luau/Condition.h CodeGen/include/Luau/Condition.h
CodeGen/include/Luau/Label.h CodeGen/include/Luau/Label.h
CodeGen/include/Luau/OperandX64.h CodeGen/include/Luau/OperandX64.h
@ -69,13 +70,25 @@ target_sources(Luau.CodeGen PRIVATE
CodeGen/src/AssemblyBuilderX64.cpp CodeGen/src/AssemblyBuilderX64.cpp
CodeGen/src/CodeAllocator.cpp CodeGen/src/CodeAllocator.cpp
CodeGen/src/CodeBlockUnwind.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/Fallbacks.cpp
CodeGen/src/NativeState.cpp
CodeGen/src/UnwindBuilderDwarf2.cpp CodeGen/src/UnwindBuilderDwarf2.cpp
CodeGen/src/UnwindBuilderWin.cpp CodeGen/src/UnwindBuilderWin.cpp
CodeGen/src/ByteUtils.h 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/Fallbacks.h
CodeGen/src/FallbacksProlog.h CodeGen/src/FallbacksProlog.h
CodeGen/src/NativeState.h
) )
# Luau.Analysis Sources # Luau.Analysis Sources
@ -210,6 +223,7 @@ target_sources(Luau.VM PRIVATE
VM/src/lvmexecute.cpp VM/src/lvmexecute.cpp
VM/src/lvmload.cpp VM/src/lvmload.cpp
VM/src/lvmutils.cpp VM/src/lvmutils.cpp
VM/src/lapi.h VM/src/lapi.h
VM/src/lbuiltins.h VM/src/lbuiltins.h
VM/src/lbytecode.h VM/src/lbytecode.h

View file

@ -38,10 +38,10 @@ enum lua_Status
enum lua_CoStatus enum lua_CoStatus
{ {
LUA_CORUN = 0, // running LUA_CORUN = 0, // running
LUA_COSUS, // suspended LUA_COSUS, // suspended
LUA_CONOR, // 'normal' (it resumed another coroutine) LUA_CONOR, // 'normal' (it resumed another coroutine)
LUA_COFIN, // finished LUA_COFIN, // finished
LUA_COERR, // finished with error LUA_COERR, // finished with error
}; };
typedef struct lua_State lua_State; typedef struct lua_State lua_State;
@ -398,16 +398,16 @@ LUA_API const char* lua_debugtrace(lua_State* L);
struct lua_Debug struct lua_Debug
{ {
const char* name; // (n) const char* name; // (n)
const char* what; // (s) `Lua', `C', `main', `tail' const char* what; // (s) `Lua', `C', `main', `tail'
const char* source; // (s) const char* source; // (s)
const char* short_src; // (s) const char* short_src; // (s)
int linedefined; // (s) int linedefined; // (s)
int currentline; // (l) int currentline; // (l)
unsigned char nupvals; // (u) number of upvalues unsigned char nupvals; // (u) number of upvalues
unsigned char nparams; // (a) number of parameters unsigned char nparams; // (a) number of parameters
char isvararg; // (a) char isvararg; // (a)
void* userdata; // only valid in luau_callhook void* userdata; // only valid in luau_callhook
char ssbuf[LUA_IDSIZE]; char ssbuf[LUA_IDSIZE];
}; };

View file

@ -133,4 +133,4 @@
#define LUA_VECTOR_SIZE 3 // must be 3 or 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)

View file

@ -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) 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))) if (!ttisnumber(args + (i - 2)))
return -1; 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) 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))) if (!ttisnumber(args + (i - 2)))
return -1; 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) 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)) unsigned u1, u2;
return -1; luai_num2unsigned(u1, a1);
luai_num2unsigned(u2, a2);
{ uint32_t r = u1 & u2;
double a1 = nvalue(arg0);
unsigned u;
luai_num2unsigned(u, a1);
r &= u; for (int i = 3; i <= nparams; ++i)
}
for (int i = 2; i <= nparams; ++i)
{ {
if (!ttisnumber(args + (i - 2))) if (!ttisnumber(args + (i - 2)))
return -1; 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) 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)) unsigned u1, u2;
return -1; luai_num2unsigned(u1, a1);
luai_num2unsigned(u2, a2);
{ uint32_t r = u1 | u2;
double a1 = nvalue(arg0);
unsigned u;
luai_num2unsigned(u, a1);
r |= u; for (int i = 3; i <= nparams; ++i)
}
for (int i = 2; i <= nparams; ++i)
{ {
if (!ttisnumber(args + (i - 2))) if (!ttisnumber(args + (i - 2)))
return -1; 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) 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)) unsigned u1, u2;
return -1; luai_num2unsigned(u1, a1);
luai_num2unsigned(u2, a2);
{ uint32_t r = u1 ^ u2;
double a1 = nvalue(arg0);
unsigned u;
luai_num2unsigned(u, a1);
r ^= u; for (int i = 3; i <= nparams; ++i)
}
for (int i = 2; i <= nparams; ++i)
{ {
if (!ttisnumber(args + (i - 2))) if (!ttisnumber(args + (i - 2)))
return -1; 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) 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)) unsigned u1, u2;
return -1; luai_num2unsigned(u1, a1);
luai_num2unsigned(u2, a2);
{ uint32_t r = u1 & u2;
double a1 = nvalue(arg0);
unsigned u;
luai_num2unsigned(u, a1);
r &= u; for (int i = 3; i <= nparams; ++i)
}
for (int i = 2; i <= nparams; ++i)
{ {
if (!ttisnumber(args + (i - 2))) if (!ttisnumber(args + (i - 2)))
return -1; return -1;

View file

@ -108,7 +108,7 @@ UpVal* luaF_findupval(lua_State* L, StkId level)
void luaF_freeupval(lua_State* L, UpVal* uv, lua_Page* page) 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) void luaF_close(lua_State* L, StkId level)

View file

@ -13,8 +13,6 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
LUAU_FASTFLAG(LuauFasterGetInfo) LUAU_FASTFLAG(LuauFasterGetInfo)
const TValue luaO_nilobject_ = {{NULL}, {0}, LUA_TNIL}; const TValue luaO_nilobject_ = {{NULL}, {0}, LUA_TNIL};

View file

@ -290,6 +290,7 @@ typedef struct Proto
int sizelineinfo; int sizelineinfo;
int linegaplog2; int linegaplog2;
int linedefined; int linedefined;
int bytecodeid;
uint8_t nups; // number of upvalues uint8_t nups; // number of upvalues

View file

@ -100,6 +100,12 @@ static void close_state(lua_State* L)
LUAU_ASSERT(g->memcatbytes[0] == sizeof(LG)); LUAU_ASSERT(g->memcatbytes[0] == sizeof(LG));
for (int i = 1; i < LUA_MEMORY_CATEGORIES; i++) for (int i = 1; i < LUA_MEMORY_CATEGORIES; i++)
LUAU_ASSERT(g->memcatbytes[i] == 0); 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); (*g->frealloc)(g->ud, L, sizeof(LG), 0);
} }

View file

@ -146,18 +146,15 @@ struct GCMetrics
}; };
#endif #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/...) // 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* 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 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 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 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 ** `global state', shared by all threads of this state

View file

@ -16,6 +16,8 @@
#include <string.h> #include <string.h>
LUAU_FASTFLAGVARIABLE(LuauNoTopRestoreInFastCall, false)
// Disable c99-designator to avoid the warning in CGOTO dispatch table // Disable c99-designator to avoid the warning in CGOTO dispatch table
#ifdef __clang__ #ifdef __clang__
#if __has_warning("-Wc99-designator") #if __has_warning("-Wc99-designator")
@ -2537,6 +2539,8 @@ reentry:
if (n >= 0) 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; L->top = (nresults == LUA_MULTRET) ? ra + n : L->ci->top;
pc += skip + 1; // skip instructions that compute function as well as CALL pc += skip + 1; // skip instructions that compute function as well as CALL
@ -2613,7 +2617,15 @@ reentry:
if (n >= 0) 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 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)); LUAU_ASSERT(unsigned(pc - cl->l.p->code) < unsigned(cl->l.p->sizecode));
@ -2661,7 +2673,15 @@ reentry:
if (n >= 0) 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 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)); LUAU_ASSERT(unsigned(pc - cl->l.p->code) < unsigned(cl->l.p->sizecode));
@ -2709,7 +2729,15 @@ reentry:
if (n >= 0) 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 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)); LUAU_ASSERT(unsigned(pc - cl->l.p->code) < unsigned(cl->l.p->sizecode));

View file

@ -192,6 +192,7 @@ int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size
{ {
Proto* p = luaF_newproto(L); Proto* p = luaF_newproto(L);
p->source = source; p->source = source;
p->bytecodeid = int(i);
p->maxstacksize = read<uint8_t>(data, size, offset); p->maxstacksize = read<uint8_t>(data, size, offset);
p->numparams = read<uint8_t>(data, size, offset); p->numparams = read<uint8_t>(data, size, offset);

View file

@ -5,6 +5,7 @@ function test()
local band = bit32.band local band = bit32.band
local bnot = bit32.bnot local bnot = bit32.bnot
local bxor = bit32.bxor local bxor = bit32.bxor
local bor = bit32.bor
local rrotate = bit32.rrotate local rrotate = bit32.rrotate
local rshift = bit32.rshift 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 h, g, f, e, d, c, b, a = g, f, e, d + t1, c, b, a, t1 + t2
end end
hash[1] = band(hash[1] + a) hash[1] = bor(hash[1] + a, 0)
hash[2] = band(hash[2] + b) hash[2] = bor(hash[2] + b, 0)
hash[3] = band(hash[3] + c) hash[3] = bor(hash[3] + c, 0)
hash[4] = band(hash[4] + d) hash[4] = bor(hash[4] + d, 0)
hash[5] = band(hash[5] + e) hash[5] = bor(hash[5] + e, 0)
hash[6] = band(hash[6] + f) hash[6] = bor(hash[6] + f, 0)
hash[7] = band(hash[7] + g) hash[7] = bor(hash[7] + g, 0)
hash[8] = band(hash[8] + h) hash[8] = bor(hash[8] + h, 0)
end end
local function sha256(msg) local function sha256(msg)
@ -139,4 +140,4 @@ function test()
return ts1 - ts0 return ts1 - ts0
end end
bench.runCode(test, "sha256") bench.runCode(test, "sha256")

View file

@ -177,6 +177,9 @@ TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "BaseUnaryInstructionForms")
SINGLE_COMPARE(imul(r9), 0x49, 0xf7, 0xe9); SINGLE_COMPARE(imul(r9), 0x49, 0xf7, 0xe9);
SINGLE_COMPARE(neg(r9), 0x49, 0xf7, 0xd9); SINGLE_COMPARE(neg(r9), 0x49, 0xf7, 0xd9);
SINGLE_COMPARE(not_(r12), 0x49, 0xf7, 0xd4); 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") 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); SINGLE_COMPARE(vsqrtss(xmm8, xmm10, dword[r9]), 0xc4, 0x41, 0xaa, 0x51, 0x01);
// Coverage for other instructions that follow the same pattern // 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); SINGLE_COMPARE(vucomisd(xmm1, xmm4), 0xc4, 0xe1, 0xf9, 0x2e, 0xcc);
} }
@ -405,9 +407,10 @@ TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "AVXConversionInstructionForms")
TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "AVXTernaryInstructionForms") TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "AVXTernaryInstructionForms")
{ {
SINGLE_COMPARE(vroundsd(xmm7, xmm12, xmm3, 9), 0xc4, 0xe3, 0x99, 0x0b, 0xfb, 0x09); SINGLE_COMPARE(vroundsd(xmm7, xmm12, xmm3, RoundingModeX64::RoundToNegativeInfinity), 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(
SINGLE_COMPARE(vroundsd(xmm9, xmm14, xmmword[rcx + r10], 1), 0xc4, 0x23, 0x89, 0x0b, 0x0c, 0x11, 0x01); 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") TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "MiscInstructions")
@ -441,7 +444,7 @@ TEST_CASE("LogTest")
build.movsx(rsi, word[r12]); build.movsx(rsi, word[r12]);
build.imul(rcx, rdx); build.imul(rcx, rdx);
build.imul(rcx, rdx, 8); build.imul(rcx, rdx, 8);
build.vroundsd(xmm1, xmm2, xmm3, 5); build.vroundsd(xmm1, xmm2, xmm3, RoundingModeX64::RoundToNearestEven);
build.pop(r12); build.pop(r12);
build.ret(); build.ret();
build.int3(); build.int3();
@ -469,7 +472,7 @@ TEST_CASE("LogTest")
movsx rsi,word ptr [r12] movsx rsi,word ptr [r12]
imul rcx,rdx imul rcx,rdx
imul rcx,rdx,8 imul rcx,rdx,8
vroundsd xmm1,xmm2,xmm3,5 vroundsd xmm1,xmm2,xmm3,8
pop r12 pop r12
ret ret
int3 int3

View file

@ -42,4 +42,4 @@ bool FindNthOccurenceOf::visit(AstTypePack* t)
return checkIt(t); return checkIt(t);
} }
} } // namespace Luau

View file

@ -80,4 +80,4 @@ T* query(AstNode* node, const std::vector<Nth>& nths = {nth<T>(N)})
return node ? node->as<T>() : nullptr; return node ? node->as<T>() : nullptr;
} }
} } // namespace Luau

View file

@ -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") TEST_CASE("AssignmentConflict")
{ {
// assignments are left to right // assignments are left to right
@ -5042,7 +5079,6 @@ GETIMPORT R2 2
CALL R2 0 0 CALL R2 0 0
RETURN R1 1 RETURN R1 1
)"); )");
} }
TEST_CASE("InlineNestedLoops") TEST_CASE("InlineNestedLoops")

View file

@ -8,6 +8,7 @@
#include "Luau/TypeInfer.h" #include "Luau/TypeInfer.h"
#include "Luau/StringUtils.h" #include "Luau/StringUtils.h"
#include "Luau/BytecodeBuilder.h" #include "Luau/BytecodeBuilder.h"
#include "Luau/CodeGen.h"
#include "doctest.h" #include "doctest.h"
#include "ScopedFlags.h" #include "ScopedFlags.h"
@ -17,6 +18,7 @@
#include <math.h> #include <math.h>
extern bool verbose; extern bool verbose;
extern bool codegen;
extern int optimizationLevel; extern int optimizationLevel;
static lua_CompileOptions defaultOptions() static lua_CompileOptions defaultOptions()
@ -137,7 +139,7 @@ int lua_silence(lua_State* L)
using StateRef = std::unique_ptr<lua_State, void (*)(lua_State*)>; 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, 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__; std::string path = __FILE__;
path.erase(path.find_last_of("\\/")); 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); StateRef globalState(initialLuaState, lua_close);
lua_State* L = globalState.get(); lua_State* L = globalState.get();
if (codegen && !skipCodegen && Luau::CodeGen::isSupported())
Luau::CodeGen::create(L);
luaL_openlibs(L); luaL_openlibs(L);
// Register a few global functions for conformance tests // 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); int result = luau_load(L, chunkname.c_str(), bytecode, bytecodeSize, 0);
free(bytecode); 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; int status = (result == 0) ? lua_resume(L, nullptr, 0) : LUA_ERRSYNTAX;
while (yield && (status == LUA_YIELD || status == LUA_BREAK)) while (yield && (status == LUA_YIELD || status == LUA_BREAK))
@ -563,19 +571,19 @@ TEST_CASE("Debugger")
}; };
// add breakpoint() function // add breakpoint() function
lua_pushcfunction( lua_pushcclosurek(
L, L,
[](lua_State* L) -> int { [](lua_State* L) -> int {
int line = luaL_checkinteger(L, 1); int line = luaL_checkinteger(L, 1);
bool enabled = luaL_optboolean(L, 2, true); bool enabled = luaL_optboolean(L, 2, true);
lua_Debug ar = {}; lua_Debug ar = {};
lua_getinfo(L, 1, "f", &ar); lua_getinfo(L, lua_stackdepth(L) - 1, "f", &ar);
lua_breakpoint(L, -1, line, enabled); lua_breakpoint(L, -1, line, enabled);
return 0; return 0;
}, },
"breakpoint"); "breakpoint", 0, nullptr);
lua_setglobal(L, "breakpoint"); lua_setglobal(L, "breakpoint");
}, },
[](lua_State* L) { [](lua_State* L) {
@ -656,9 +664,9 @@ TEST_CASE("Debugger")
interruptedthread = nullptr; 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") TEST_CASE("SameHash")
@ -806,17 +814,17 @@ TEST_CASE("ApiIter")
{ {
sum1 += lua_tonumber(L, -2); // key sum1 += lua_tonumber(L, -2); // key
sum1 += lua_tonumber(L, -1); // value 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); CHECK(sum1 == 580);
// Luau iteration interface: lua_rawiter (faster and preferable to lua_next) // Luau iteration interface: lua_rawiter (faster and preferable to lua_next)
double sum2 = 0; 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, -2); // key
sum2 += lua_tonumber(L, -1); // value 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); CHECK(sum2 == 580);
@ -1188,7 +1196,13 @@ TEST_CASE("Interrupt")
5, 5,
5, 5,
6, 6,
11, 18,
13,
13,
13,
13,
16,
20,
}; };
static int index; static int index;
@ -1283,6 +1297,21 @@ TEST_CASE("UserdataApi")
*(int*)ud3 = 43; *(int*)ud3 = 43;
*(char*)ud4 = 3; *(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(); globalState.reset();
CHECK(dtorhits == 42); CHECK(dtorhits == 42);
@ -1494,4 +1523,9 @@ TEST_CASE("Userdata")
}); });
} }
TEST_CASE("SafeEnv")
{
runConformance("safeenv.lua");
}
TEST_SUITE_END(); TEST_SUITE_END();

View file

@ -14,4 +14,4 @@ ConstraintGraphBuilderFixture::ConstraintGraphBuilderFixture()
BlockedTypePack::nextIndex = 0; BlockedTypePack::nextIndex = 0;
} }
} } // namespace Luau

View file

@ -24,4 +24,4 @@ struct ConstraintGraphBuilderFixture : Fixture
ConstraintGraphBuilderFixture(); ConstraintGraphBuilderFixture();
}; };
} } // namespace Luau

View file

@ -92,7 +92,8 @@ std::optional<std::string> TestFileResolver::getEnvironmentForModule(const Modul
Fixture::Fixture(bool freeze, bool prepareAutocomplete) Fixture::Fixture(bool freeze, bool prepareAutocomplete)
: sff_DebugLuauFreezeArena("DebugLuauFreezeArena", freeze) : 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) , typeChecker(frontend.typeChecker)
, singletonTypes(frontend.singletonTypes) , singletonTypes(frontend.singletonTypes)
{ {

View file

@ -1029,7 +1029,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "reexport_cyclic_type")
{ {
ScopedFastFlag sff[] = { ScopedFastFlag sff[] = {
{"LuauForceExportSurfacesToBeNormal", true}, {"LuauForceExportSurfacesToBeNormal", true},
{"LuauLowerBoundsCalculation", true},
}; };
fileResolver.source["Module/A"] = R"( fileResolver.source["Module/A"] = R"(
@ -1065,7 +1064,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "reexport_type_alias")
{ {
ScopedFastFlag sff[] = { ScopedFastFlag sff[] = {
{"LuauForceExportSurfacesToBeNormal", true}, {"LuauForceExportSurfacesToBeNormal", true},
{"LuauLowerBoundsCalculation", true},
}; };
fileResolver.source["Module/A"] = R"( fileResolver.source["Module/A"] = R"(

View file

@ -45,8 +45,6 @@ TEST_CASE_FIXTURE(Fixture, "DeprecatedGlobal")
TEST_CASE_FIXTURE(Fixture, "DeprecatedGlobalNoReplacement") TEST_CASE_FIXTURE(Fixture, "DeprecatedGlobalNoReplacement")
{ {
ScopedFastFlag sff{"LuauLintFixDeprecationMessage", true};
// Normally this would be defined externally, so hack it in for testing // Normally this would be defined externally, so hack it in for testing
const char* deprecationReplacementString = ""; const char* deprecationReplacementString = "";
addGlobalBinding(frontend, "Version", Binding{typeChecker.anyType, {}, true, deprecationReplacementString}); addGlobalBinding(frontend, "Version", Binding{typeChecker.anyType, {}, true, deprecationReplacementString});

View file

@ -11,7 +11,6 @@
using namespace Luau; using namespace Luau;
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution); LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
LUAU_FASTFLAG(LuauLowerBoundsCalculation);
TEST_SUITE_BEGIN("ModuleTests"); TEST_SUITE_BEGIN("ModuleTests");
@ -107,11 +106,8 @@ TEST_CASE_FIXTURE(Fixture, "deepClone_cyclic_table")
REQUIRE(methodReturnType); REQUIRE(methodReturnType);
CHECK_EQ(methodReturnType, counterCopy); CHECK_EQ(methodReturnType, counterCopy);
if (FFlag::LuauLowerBoundsCalculation) CHECK_EQ(2, dest.typePacks.size()); // one for the function args, and another for its return type
CHECK_EQ(3, dest.typePacks.size()); // function args, its return type, and the hidden any... pack CHECK_EQ(2, dest.typeVars.size()); // One table and one function
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
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "builtin_types_point_into_globalTypes_arena") TEST_CASE_FIXTURE(BuiltinsFixture, "builtin_types_point_into_globalTypes_arena")

View file

@ -12,8 +12,6 @@ using namespace Luau;
struct NormalizeFixture : Fixture struct NormalizeFixture : Fixture
{ {
ScopedFastFlag sff1{"LuauLowerBoundsCalculation", true};
bool isSubtype(TypeId a, TypeId b) bool isSubtype(TypeId a, TypeId b)
{ {
return ::Luau::isSubtype(a, b, NotNull{getMainModule()->getModuleScope().get()}, singletonTypes, ice); 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)); 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") TEST_CASE_FIXTURE(NormalizeFixture, "variadic_functions_with_no_head")
{ {
check(R"( check(R"(
@ -356,7 +201,7 @@ TEST_CASE_FIXTURE(NormalizeFixture, "table_with_any_prop")
TEST_CASE_FIXTURE(NormalizeFixture, "intersection") TEST_CASE_FIXTURE(NormalizeFixture, "intersection")
{ {
ScopedFastFlag sffs[] { ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true}, {"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true}, {"LuauTypeNormalization2", true},
}; };
@ -398,16 +243,6 @@ TEST_CASE_FIXTURE(NormalizeFixture, "union_and_intersection")
CHECK(isSubtype(a, b)); 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") TEST_CASE_FIXTURE(NormalizeFixture, "tables")
{ {
check(R"( check(R"(
@ -552,50 +387,10 @@ TEST_CASE_FIXTURE(NormalizeFixture, "metatable" * doctest::expected_failures{1})
} }
#endif #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_END();
TEST_SUITE_BEGIN("Normalize"); 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") TEST_CASE_FIXTURE(NormalizeFixture, "union_with_overlapping_field_that_has_a_subtype_relationship")
{ {
check(R"( 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})); 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") TEST_CASE_FIXTURE(Fixture, "higher_order_function")
{ {
check(R"( 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"))); 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") TEST_CASE_FIXTURE(Fixture, "cyclic_table_normalizes_sensibly")
{ {
CheckResult result = check(R"( 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})); 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") TEST_CASE_FIXTURE(BuiltinsFixture, "skip_force_normal_on_external_types")
{ {
createSomeClasses(frontend); createSomeClasses(frontend);
@ -1108,68 +472,4 @@ export type t0 = (((any)&({_:l0.t0,n0:t0,_G:any,}))&({_:any,}))&(((any)&({_:l0.t
LUAU_REQUIRE_ERRORS(result); 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(); TEST_SUITE_END();

View file

@ -15,8 +15,6 @@
using namespace Luau; using namespace Luau;
LUAU_FASTFLAG(LuauLowerBoundsCalculation);
struct LimitFixture : BuiltinsFixture struct LimitFixture : BuiltinsFixture
{ {
#if defined(_NOOPT) || defined(_DEBUG) #if defined(_NOOPT) || defined(_DEBUG)
@ -267,10 +265,7 @@ TEST_CASE_FIXTURE(LimitFixture, "typescript_port_of_Result_type")
CheckResult result = check(src); CheckResult result = check(src);
CodeTooComplex ctc; CodeTooComplex ctc;
if (FFlag::LuauLowerBoundsCalculation) CHECK(hasError(result, &ctc));
LUAU_REQUIRE_ERRORS(result);
else
CHECK(hasError(result, &ctc));
} }
TEST_SUITE_END(); TEST_SUITE_END();

View file

@ -7,8 +7,6 @@
#include "doctest.h" #include "doctest.h"
LUAU_FASTFLAG(LuauLowerBoundsCalculation)
using namespace Luau; using namespace Luau;
struct ToDotClassFixture : Fixture struct ToDotClassFixture : Fixture
@ -111,29 +109,7 @@ local function f(a, ...: string) return a end
ToDotOptions opts; ToDotOptions opts;
opts.showPointers = false; opts.showPointers = false;
if (FFlag::LuauLowerBoundsCalculation) CHECK_EQ(R"(digraph graphname {
{
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 {
n1 [label="FunctionTypeVar 1"]; n1 [label="FunctionTypeVar 1"];
n1 -> n2 [label="arg"]; n1 -> n2 [label="arg"];
n2 [label="TypePack 2"]; n2 [label="TypePack 2"];
@ -149,8 +125,7 @@ n6 -> n7;
n7 [label="TypePack 7"]; n7 [label="TypePack 7"];
n7 -> n3; n7 -> n3;
})", })",
toDot(requireType("f"), opts)); toDot(requireType("f"), opts));
}
} }
TEST_CASE_FIXTURE(Fixture, "union") TEST_CASE_FIXTURE(Fixture, "union")

View file

@ -12,6 +12,7 @@ using namespace Luau;
LUAU_FASTFLAG(LuauRecursiveTypeParameterRestriction); LUAU_FASTFLAG(LuauRecursiveTypeParameterRestriction);
LUAU_FASTFLAG(LuauSpecialTypesAsterisked); LUAU_FASTFLAG(LuauSpecialTypesAsterisked);
LUAU_FASTFLAG(LuauFixNameMaps); LUAU_FASTFLAG(LuauFixNameMaps);
LUAU_FASTFLAG(LuauFunctionReturnStringificationFixup);
TEST_SUITE_BEGIN("ToString"); 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)); 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") TEST_CASE_FIXTURE(Fixture, "no_parentheses_around_cyclic_function_type_in_union")
{ {
CheckResult result = check(R"( CheckResult result = check(R"(

View file

@ -657,50 +657,9 @@ struct AssertionCatcher
int AssertionCatcher::tripped; int AssertionCatcher::tripped;
} // namespace } // 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") TEST_CASE_FIXTURE(Fixture, "luau_ice_triggers_an_ice_exception_with_flag")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag sffs{"DebugLuauMagicTypes", true};
{"DebugLuauMagicTypes", true},
{"LuauUseInternalCompilerErrorException", true},
};
AssertionCatcher ac; 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") TEST_CASE_FIXTURE(Fixture, "luau_ice_triggers_an_ice_exception_with_flag_handler")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag sffs{"DebugLuauMagicTypes", true};
{"DebugLuauMagicTypes", true},
{"LuauUseInternalCompilerErrorException", true},
};
bool caught = false; bool caught = false;

View file

@ -8,9 +8,7 @@
using namespace Luau; using namespace Luau;
LUAU_FASTFLAG(LuauLowerBoundsCalculation);
LUAU_FASTFLAG(LuauSpecialTypesAsterisked); LUAU_FASTFLAG(LuauSpecialTypesAsterisked);
LUAU_FASTFLAG(LuauStringFormatArgumentErrorFix)
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution) LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
TEST_SUITE_BEGIN("BuiltinTests"); 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. // Could be flaky if the fix has regressed.
TEST_CASE_FIXTURE(BuiltinsFixture, "bad_select_should_not_crash") TEST_CASE_FIXTURE(BuiltinsFixture, "bad_select_should_not_crash")
{ {
ScopedFastFlag luauFunctionArgMismatchDetails{"LuauFunctionArgMismatchDetails", true};
CheckResult result = check(R"( CheckResult result = check(R"(
do end do end
local _ = function(l0,...) local _ = function(l0,...)
@ -754,14 +750,7 @@ TEST_CASE_FIXTURE(Fixture, "string_format_use_correct_argument")
LUAU_REQUIRE_ERROR_COUNT(1, result); 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]));
{
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]));
}
} }
TEST_CASE_FIXTURE(Fixture, "string_format_use_correct_argument2") 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") TEST_CASE_FIXTURE(BuiltinsFixture, "string_format_use_correct_argument3")
{ {
ScopedFastFlag LuauStringFormatArgumentErrorFix{"LuauStringFormatArgumentErrorFix", true};
CheckResult result = check(R"( CheckResult result = check(R"(
local s1 = string.format("%d") local s1 = string.format("%d")
local s2 = string.format("%d", 1) local s2 = string.format("%d", 1)
@ -966,10 +953,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "assert_removes_falsy_types")
)"); )");
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::LuauLowerBoundsCalculation) CHECK_EQ("((boolean | number)?) -> boolean | number", toString(requireType("f")));
CHECK_EQ("((boolean | number)?) -> number | true", toString(requireType("f")));
else
CHECK_EQ("((boolean | number)?) -> boolean | number", toString(requireType("f")));
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "assert_removes_falsy_types2") 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") TEST_CASE_FIXTURE(BuiltinsFixture, "set_metatable_needs_arguments")
{ {
ScopedFastFlag luauFunctionArgMismatchDetails{"LuauFunctionArgMismatchDetails", true};
ScopedFastFlag sff{"LuauSetMetaTableArgsCheck", true}; ScopedFastFlag sff{"LuauSetMetaTableArgsCheck", true};
CheckResult result = check(R"( CheckResult result = check(R"(
local a = {b=setmetatable} local a = {b=setmetatable}

View file

@ -482,7 +482,7 @@ local a: ChildClass = i
TEST_CASE_FIXTURE(ClassFixture, "intersections_of_unions_of_classes") TEST_CASE_FIXTURE(ClassFixture, "intersections_of_unions_of_classes")
{ {
ScopedFastFlag sffs[] { ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true}, {"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", 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") TEST_CASE_FIXTURE(ClassFixture, "unions_of_intersections_of_classes")
{ {
ScopedFastFlag sffs[] { ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true}, {"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true}, {"LuauTypeNormalization2", true},
}; };

View file

@ -14,7 +14,6 @@
using namespace Luau; using namespace Luau;
LUAU_FASTFLAG(LuauLowerBoundsCalculation);
LUAU_FASTFLAG(LuauInstantiateInSubtyping); LUAU_FASTFLAG(LuauInstantiateInSubtyping);
LUAU_FASTFLAG(LuauSpecialTypesAsterisked); 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"))); 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") TEST_CASE_FIXTURE(Fixture, "another_higher_order_function")
{ {
CheckResult result = check(R"( CheckResult result = check(R"(
@ -1132,16 +1115,13 @@ f(function(x) return x * 2 end)
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ("Type 'number' could not be converted into 'Table'", toString(result.errors[0])); 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"(
// Return type doesn't inference 'nil' function f(a: (number) -> nil) return a(4) end
result = check(R"( f(function(x) print(x) end)
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") 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); LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ("Type 'number' could not be converted into 'Table'", toString(result.errors[0])); 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"(
// Return type doesn't inference 'nil' function f(a: (number) -> nil) return a(4) end
result = check(R"( f(function(x) print(x) end)
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") 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')"); 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") TEST_CASE_FIXTURE(Fixture, "inferred_higher_order_functions_are_quantified_at_the_right_time2")
{ {
CheckResult result = check(R"( 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])); 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") TEST_CASE_FIXTURE(Fixture, "free_is_not_bound_to_unknown")
{ {
CheckResult result = check(R"( 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") TEST_CASE_FIXTURE(BuiltinsFixture, "improved_function_arg_mismatch_errors")
{ {
ScopedFastFlag luauFunctionArgMismatchDetails{"LuauFunctionArgMismatchDetails", true};
CheckResult result = check(R"( CheckResult result = check(R"(
local function foo1(a: number) end local function foo1(a: number) end
foo1() 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 // 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") TEST_CASE_FIXTURE(BuiltinsFixture, "improved_function_arg_mismatch_error_nonstrict")
{ {
ScopedFastFlag luauFunctionArgMismatchDetails{"LuauFunctionArgMismatchDetails", true};
CheckResult result = check(R"( CheckResult result = check(R"(
--!nonstrict --!nonstrict
local function foo(a, b) end local function foo(a, b) end

View file

@ -9,7 +9,6 @@
#include "doctest.h" #include "doctest.h"
LUAU_FASTFLAG(LuauCheckGenericHOFTypes)
LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(LuauSpecialTypesAsterisked) LUAU_FASTFLAG(LuauSpecialTypesAsterisked)
@ -783,8 +782,6 @@ local TheDispatcher: Dispatcher = {
TEST_CASE_FIXTURE(Fixture, "generic_argument_count_too_few") TEST_CASE_FIXTURE(Fixture, "generic_argument_count_too_few")
{ {
ScopedFastFlag luauFunctionArgMismatchDetails{"LuauFunctionArgMismatchDetails", true};
CheckResult result = check(R"( CheckResult result = check(R"(
function test(a: number) function test(a: number)
return 1 return 1
@ -802,8 +799,6 @@ wrapper(test)
TEST_CASE_FIXTURE(Fixture, "generic_argument_count_too_many") TEST_CASE_FIXTURE(Fixture, "generic_argument_count_too_many")
{ {
ScopedFastFlag luauFunctionArgMismatchDetails{"LuauFunctionArgMismatchDetails", true};
CheckResult result = check(R"( CheckResult result = check(R"(
function test2(a: number, b: string) function test2(a: number, b: string)
return 1 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)); CHECK_EQ("<a, b...>((a) -> (b...), a) -> (b...)", toString(tm->givenType));
else else
CHECK_EQ("((number) -> number, number) -> number", toString(tm->givenType)); CHECK_EQ("((number) -> number, number) -> number", toString(tm->givenType));
} }
TEST_CASE_FIXTURE(Fixture, "instantiate_generic_function_in_assignments2") 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 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);
{
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]));
}
} }
TEST_CASE_FIXTURE(Fixture, "substitution_with_bound_table") 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[] = { ScopedFastFlag sffs[] = {
{"LuauInstantiateInSubtyping", true}, {"LuauInstantiateInSubtyping", true},
{"LuauCheckGenericHOFTypes", true}, // necessary because of interactions with the test
}; };
CheckResult result = check(R"( CheckResult result = check(R"(

View file

@ -8,7 +8,6 @@
using namespace Luau; using namespace Luau;
LUAU_FASTFLAG(LuauLowerBoundsCalculation);
TEST_SUITE_BEGIN("IntersectionTypes"); TEST_SUITE_BEGIN("IntersectionTypes");
@ -306,10 +305,7 @@ TEST_CASE_FIXTURE(Fixture, "table_intersection_write_sealed")
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauLowerBoundsCalculation) 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: number, y: number |}'");
else
CHECK_EQ(toString(result.errors[0]), "Cannot add property 'z' to table 'X & Y'");
} }
TEST_CASE_FIXTURE(Fixture, "table_intersection_write_sealed_indirect") 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' CHECK_EQ(toString(result.errors[0]), R"(Type '(string, number) -> string' could not be converted into '(string) -> string'
caused by: caused by:
Argument count mismatch. Function expects 2 arguments, but only 1 is specified)"); 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 & Y'");
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[2]), "Type 'number' could not be converted into 'string'"); CHECK_EQ(toString(result.errors[2]), "Type 'number' could not be converted into 'string'");
CHECK_EQ(toString(result.errors[3]), "Cannot add property 'w' to table 'X & Y'");
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'");
} }
TEST_CASE_FIXTURE(Fixture, "table_write_sealed_indirect") 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") TEST_CASE_FIXTURE(Fixture, "error_detailed_intersection_part")
{ {
ScopedFastFlag flags[] = {{"LuauLowerBoundsCalculation", false}};
CheckResult result = check(R"( CheckResult result = check(R"(
type X = { x: number } type X = { x: number }
type Y = { y: number } type Y = { y: number }
type Z = { z: number } type Z = { z: number }
type XYZ = X & Y & Z type XYZ = X & Y & Z
local a: XYZ = 3 local a: XYZ = 3
)"); )");
@ -401,15 +386,11 @@ caused by:
TEST_CASE_FIXTURE(Fixture, "error_detailed_intersection_all") TEST_CASE_FIXTURE(Fixture, "error_detailed_intersection_all")
{ {
ScopedFastFlag flags[] = {{"LuauLowerBoundsCalculation", false}};
CheckResult result = check(R"( CheckResult result = check(R"(
type X = { x: number } type X = { x: number }
type Y = { y: number } type Y = { y: number }
type Z = { z: number } type Z = { z: number }
type XYZ = X & Y & Z type XYZ = X & Y & Z
local a: XYZ local a: XYZ
local b: number = a local b: number = a
)"); )");
@ -468,12 +449,13 @@ TEST_CASE_FIXTURE(Fixture, "intersect_false_and_bool_and_false")
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
// TODO: odd stringification of `false & (boolean & false)`.) // 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") TEST_CASE_FIXTURE(Fixture, "intersect_saturate_overloaded_functions")
{ {
ScopedFastFlag sffs[] { ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true}, {"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true}, {"LuauTypeNormalization2", true},
}; };
@ -485,12 +467,13 @@ TEST_CASE_FIXTURE(Fixture, "intersect_saturate_overloaded_functions")
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); 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") TEST_CASE_FIXTURE(Fixture, "union_saturate_overloaded_functions")
{ {
ScopedFastFlag sffs[] { ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true}, {"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true}, {"LuauTypeNormalization2", true},
}; };
@ -502,12 +485,13 @@ TEST_CASE_FIXTURE(Fixture, "union_saturate_overloaded_functions")
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); 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") TEST_CASE_FIXTURE(Fixture, "intersection_of_tables")
{ {
ScopedFastFlag sffs[] { ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true}, {"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true}, {"LuauTypeNormalization2", true},
}; };
@ -519,7 +503,8 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables")
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); 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") 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); 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") TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_never_properties")
{ {
ScopedFastFlag sffs[] { ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true}, {"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", 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 } // TODO: this should not produce type errors, since never <: { p : never }
LUAU_REQUIRE_ERROR_COUNT(1, result); 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") TEST_CASE_FIXTURE(Fixture, "overloaded_functions_returning_intersections")
{ {
ScopedFastFlag sffs[] { ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true}, {"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true}, {"LuauTypeNormalization2", true},
}; };
@ -566,12 +553,14 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_returning_intersections")
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); 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") TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic")
{ {
ScopedFastFlag sffs[] { ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true}, {"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true}, {"LuauTypeNormalization2", true},
}; };
@ -585,12 +574,13 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic")
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); 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") TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generics")
{ {
ScopedFastFlag sffs[] { ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true}, {"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true}, {"LuauTypeNormalization2", true},
}; };
@ -604,12 +594,13 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generics")
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); 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") TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic_packs")
{ {
ScopedFastFlag sffs[] { ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true}, {"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true}, {"LuauTypeNormalization2", true},
}; };
@ -623,12 +614,13 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic_packs")
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); 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") TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_result")
{ {
ScopedFastFlag sffs[] { ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true}, {"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true}, {"LuauTypeNormalization2", true},
}; };
@ -642,12 +634,13 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_result")
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, 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") TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_arguments")
{ {
ScopedFastFlag sffs[] { ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true}, {"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true}, {"LuauTypeNormalization2", true},
}; };
@ -661,12 +654,13 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_arguments")
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); 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") TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_result")
{ {
ScopedFastFlag sffs[] { ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true}, {"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true}, {"LuauTypeNormalization2", true},
}; };
@ -680,12 +674,13 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_result")
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, 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") TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_arguments")
{ {
ScopedFastFlag sffs[] { ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true}, {"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true}, {"LuauTypeNormalization2", true},
}; };
@ -699,7 +694,8 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_arguments")
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); 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") 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); 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") 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); 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") 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); 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") 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); 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") 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); 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") TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatables")
{ {
ScopedFastFlag sffs[] { ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true}, {"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true}, {"LuauTypeNormalization2", true},
}; };
@ -800,7 +801,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatables")
TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatable_subtypes") TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatable_subtypes")
{ {
ScopedFastFlag sffs[] { ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true}, {"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true}, {"LuauTypeNormalization2", true},
}; };
@ -826,7 +827,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatable_subtypes")
TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatables_with_properties") TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatables_with_properties")
{ {
ScopedFastFlag sffs[] { ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true}, {"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true}, {"LuauTypeNormalization2", true},
}; };
@ -849,7 +850,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatables_with_properties")
TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatable_with table") TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatable_with table")
{ {
ScopedFastFlag sffs[] { ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true}, {"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true}, {"LuauTypeNormalization2", true},
}; };
@ -874,7 +875,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatable_with table")
TEST_CASE_FIXTURE(Fixture, "CLI-44817") TEST_CASE_FIXTURE(Fixture, "CLI-44817")
{ {
ScopedFastFlag sffs[] { ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true}, {"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true}, {"LuauTypeNormalization2", true},
}; };

View file

@ -622,9 +622,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "loop_iter_metamethod_not_enough_returns")
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK(result.errors[0] == TypeError{ CHECK(result.errors[0] == TypeError{
Location{{2, 36}, {2, 37}}, Location{{2, 36}, {2, 37}},
GenericError{"__iter must return at least one value"}, GenericError{"__iter must return at least one value"},
}); });
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "loop_iter_metamethod_ok") TEST_CASE_FIXTURE(BuiltinsFixture, "loop_iter_metamethod_ok")

View file

@ -254,20 +254,20 @@ return m
if (FFlag::LuauInstantiateInSubtyping) 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. // 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); LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ(R"(Type 'n' could not be converted into 't1 where t1 = {- Clone: (t1) -> (a...) -}' CHECK_EQ(R"(Type 'n' could not be converted into 't1 where t1 = {- Clone: (t1) -> (a...) -}'
caused by: 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)", 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 else
{ {
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
} }
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "custom_require_global") TEST_CASE_FIXTURE(BuiltinsFixture, "custom_require_global")

View file

@ -7,8 +7,6 @@
#include <algorithm> #include <algorithm>
LUAU_FASTFLAG(LuauLowerBoundsCalculation)
using namespace Luau; using namespace Luau;
TEST_SUITE_BEGIN("ProvisionalTests"); 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); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::LuauLowerBoundsCalculation) // f and g should have the type () -> ()
{ CHECK_EQ("() -> (a...)", toString(requireType("f")));
CHECK_EQ("() -> ()", toString(requireType("f"))); CHECK_EQ("<a...>() -> (a...)", toString(requireType("g")));
CHECK_EQ("() -> ()", toString(requireType("g"))); CHECK_EQ("any", toString(requireType("x"))); // any is returned instead of ICE for now
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
}
} }
TEST_CASE_FIXTURE(Fixture, "specialization_binds_with_prototypes_too_early") 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") TEST_CASE_FIXTURE(Fixture, "weird_fail_to_unify_type_pack")
{ {
ScopedFastFlag sff[] = { ScopedFastFlag sff[] = {
{"LuauLowerBoundsCalculation", false},
// I'm not sure why this is broken without DCR, but it seems to be fixed // I'm not sure why this is broken without DCR, but it seems to be fixed
// when DCR is enabled. // when DCR is enabled.
{"DebugLuauDeferredConstraintResolution", false}, {"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") TEST_CASE_FIXTURE(Fixture, "weird_fail_to_unify_variadic_pack")
{ {
ScopedFastFlag sff[] = { ScopedFastFlag sff[] = {
{"LuauLowerBoundsCalculation", false},
// I'm not sure why this is broken without DCR, but it seems to be fixed // I'm not sure why this is broken without DCR, but it seems to be fixed
// when DCR is enabled. // when DCR is enabled.
{"DebugLuauDeferredConstraintResolution", false}, {"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. 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. // Belongs in TypeInfer.builtins.test.cpp.
TEST_CASE_FIXTURE(BuiltinsFixture, "pcall_returns_at_least_two_value_but_function_returns_nothing") 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"))); 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") TEST_CASE_FIXTURE(Fixture, "free_is_not_bound_to_any")
{ {
CheckResult result = check(R"( CheckResult result = check(R"(
@ -695,4 +602,187 @@ return wrapStrictTable(Constants, "Constants")
CHECK(get<AnyTypeVar>(*result)); 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(); TEST_SUITE_END();

View file

@ -7,7 +7,6 @@
#include "doctest.h" #include "doctest.h"
LUAU_FASTFLAG(LuauLowerBoundsCalculation)
LUAU_FASTFLAG(LuauSpecialTypesAsterisked) LUAU_FASTFLAG(LuauSpecialTypesAsterisked)
using namespace Luau; using namespace Luau;
@ -608,10 +607,7 @@ TEST_CASE_FIXTURE(Fixture, "type_guard_can_filter_for_intersection_of_tables")
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::LuauLowerBoundsCalculation) CHECK_EQ("{| x: number |} & {| y: number |}", toString(requireTypeAtPosition({4, 28})));
CHECK_EQ("{| x: number, y: number |}", toString(requireTypeAtPosition({4, 28})));
else
CHECK_EQ("{| x: number |} & {| y: number |}", toString(requireTypeAtPosition({4, 28})));
CHECK_EQ("nil", toString(requireTypeAtPosition({6, 28}))); CHECK_EQ("nil", toString(requireTypeAtPosition({6, 28})));
} }

View file

@ -421,28 +421,6 @@ TEST_CASE_FIXTURE(Fixture, "widening_happens_almost_everywhere_except_for_tables
LUAU_REQUIRE_NO_ERRORS(result); 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") TEST_CASE_FIXTURE(Fixture, "functions_are_not_to_be_widened")
{ {
CheckResult result = check(R"( CheckResult result = check(R"(

View file

@ -11,7 +11,6 @@
using namespace Luau; using namespace Luau;
LUAU_FASTFLAG(LuauLowerBoundsCalculation)
LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauInstantiateInSubtyping)
TEST_SUITE_BEGIN("TableTests"); 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); LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauLowerBoundsCalculation) CHECK(get<TypeMismatch>(result.errors[0]));
CHECK(get<MissingProperties>(result.errors[0]));
else
CHECK(get<TypeMismatch>(result.errors[0]));
} }
// This unit test could be flaky if the fix has regressed. // 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") TEST_CASE_FIXTURE(BuiltinsFixture, "dont_crash_when_setmetatable_does_not_produce_a_metatabletypevar")
{ {
ScopedFastFlag luauFunctionArgMismatchDetails{"LuauFunctionArgMismatchDetails", true};
CheckResult result = check("local x = setmetatable({})"); CheckResult result = check("local x = setmetatable({})");
LUAU_REQUIRE_ERROR_COUNT(1, result); 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])); 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") TEST_CASE_FIXTURE(BuiltinsFixture, "table_simple_call")
{ {
ScopedFastFlag luauFunctionArgMismatchDetails{"LuauFunctionArgMismatchDetails", true};
CheckResult result = check(R"( CheckResult result = check(R"(
local a = setmetatable({ x = 2 }, { local a = setmetatable({ x = 2 }, {
__call = function(self) __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") TEST_CASE_FIXTURE(Fixture, "inferred_return_type_of_free_table")
{ {
ScopedFastFlag sff[] = { ScopedFastFlag sff[] = {
{"LuauLowerBoundsCalculation", true}, // {"LuauLowerBoundsCalculation", true},
{"DebugLuauSharedSelf", true}, {"DebugLuauSharedSelf", true},
}; };

View file

@ -14,12 +14,10 @@
#include <algorithm> #include <algorithm>
LUAU_FASTFLAG(LuauLowerBoundsCalculation);
LUAU_FASTFLAG(LuauFixLocationSpanTableIndexExpr); LUAU_FASTFLAG(LuauFixLocationSpanTableIndexExpr);
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution); LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
LUAU_FASTFLAG(LuauInstantiateInSubtyping); LUAU_FASTFLAG(LuauInstantiateInSubtyping);
LUAU_FASTFLAG(LuauSpecialTypesAsterisked); LUAU_FASTFLAG(LuauSpecialTypesAsterisked);
LUAU_FASTFLAG(LuauCheckGenericHOFTypes);
using namespace Luau; using namespace Luau;
@ -89,7 +87,6 @@ TEST_CASE_FIXTURE(Fixture, "infer_in_nocheck_mode")
{ {
ScopedFastFlag sff[]{ ScopedFastFlag sff[]{
{"DebugLuauDeferredConstraintResolution", false}, {"DebugLuauDeferredConstraintResolution", false},
{"LuauLowerBoundsCalculation", true},
}; };
CheckResult result = check(R"( CheckResult result = check(R"(
@ -1001,21 +998,23 @@ TEST_CASE_FIXTURE(Fixture, "cli_50041_committing_txnlog_in_apollo_client_error")
end 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. // 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); 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: 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' 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: caused by:
Argument #2 type is not compatible. Type 'FieldSpecifier' could not be converted into 'FieldSpecifier & {| from: number? |}' Argument #2 type is not compatible. Type 'FieldSpecifier' could not be converted into 'FieldSpecifier & {| from: number? |}'
caused by: caused by:
Not all intersection parts are compatible. Table type 'FieldSpecifier' not compatible with type '{| from: number? |}' because the former has extra field 'fieldName')", 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 else
{ {
@ -1044,7 +1043,7 @@ TEST_CASE_FIXTURE(Fixture, "type_infer_recursion_limit_no_ice")
TEST_CASE_FIXTURE(Fixture, "type_infer_recursion_limit_normalizer") TEST_CASE_FIXTURE(Fixture, "type_infer_recursion_limit_normalizer")
{ {
ScopedFastInt sfi("LuauTypeInferRecursionLimit", 10); ScopedFastInt sfi("LuauTypeInferRecursionLimit", 10);
ScopedFastFlag sffs[] { ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true}, {"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true}, {"LuauTypeNormalization2", true},
{"LuauAutocompleteDynamicLimits", true}, {"LuauAutocompleteDynamicLimits", true},
@ -1057,14 +1056,14 @@ TEST_CASE_FIXTURE(Fixture, "type_infer_recursion_limit_normalizer")
end 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])); 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") TEST_CASE_FIXTURE(Fixture, "type_infer_cache_limit_normalizer")
{ {
ScopedFastInt sfi("LuauNormalizeCacheLimit", 10); ScopedFastInt sfi("LuauNormalizeCacheLimit", 10);
ScopedFastFlag sffs[] { ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true}, {"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true}, {"LuauTypeNormalization2", true},
}; };
@ -1101,45 +1100,6 @@ TEST_CASE_FIXTURE(Fixture, "follow_on_new_types_in_substitution")
LUAU_REQUIRE_NO_ERRORS(result); 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") TEST_CASE_FIXTURE(Fixture, "types_stored_in_astResolvedTypes")
{ {
CheckResult result = check(R"( CheckResult result = check(R"(

View file

@ -302,8 +302,8 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "metatables_unify_against_shape_of_free_table
REQUIRE_EQ(state.errors.size(), 1); REQUIRE_EQ(state.errors.size(), 1);
std::string expected = "Type '{ @metatable {| __index: {| foo: string |} |}, { } }' could not be converted into '{- foo: number -}'\n" std::string expected = "Type '{ @metatable {| __index: {| foo: string |} |}, { } }' could not be converted into '{- foo: number -}'\n"
"caused by:\n" "caused by:\n"
" Type 'number' could not be converted into 'string'"; " Type 'number' could not be converted into 'string'";
CHECK_EQ(toString(state.errors[0]), expected); CHECK_EQ(toString(state.errors[0]), expected);
} }

View file

@ -7,9 +7,9 @@
#include "doctest.h" #include "doctest.h"
using namespace Luau; LUAU_FASTFLAG(LuauFunctionReturnStringificationFixup);
LUAU_FASTFLAG(LuauLowerBoundsCalculation); using namespace Luau;
TEST_SUITE_BEGIN("TypePackTests"); TEST_SUITE_BEGIN("TypePackTests");
@ -311,7 +311,7 @@ local c: Packed<string, number, boolean>
auto ttvA = get<TableTypeVar>(requireType("a")); auto ttvA = get<TableTypeVar>(requireType("a"));
REQUIRE(ttvA); REQUIRE(ttvA);
CHECK_EQ(toString(requireType("a")), "Packed<number>"); CHECK_EQ(toString(requireType("a")), "Packed<number>");
if (FFlag::LuauLowerBoundsCalculation) if (FFlag::LuauFunctionReturnStringificationFixup)
CHECK_EQ(toString(requireType("a"), {true}), "{| f: (number) -> number |}"); CHECK_EQ(toString(requireType("a"), {true}), "{| f: (number) -> number |}");
else else
CHECK_EQ(toString(requireType("a"), {true}), "{| f: (number) -> (number) |}"); 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") TEST_CASE_FIXTURE(Fixture, "unify_variadic_tails_in_arguments")
{ {
ScopedFastFlag luauCallUnifyPackTails{"LuauCallUnifyPackTails", true};
CheckResult result = check(R"( CheckResult result = check(R"(
function foo(...: string): number function foo(...: string): number
return 1 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") TEST_CASE_FIXTURE(Fixture, "unify_variadic_tails_in_arguments_free")
{ {
ScopedFastFlag luauCallUnifyPackTails{"LuauCallUnifyPackTails", true};
CheckResult result = check(R"( CheckResult result = check(R"(
function foo<T...>(...: T...): T... function foo<T...>(...: T...): T...
return ... return ...

View file

@ -6,7 +6,6 @@
#include "doctest.h" #include "doctest.h"
LUAU_FASTFLAG(LuauLowerBoundsCalculation)
LUAU_FASTFLAG(LuauSpecialTypesAsterisked) LUAU_FASTFLAG(LuauSpecialTypesAsterisked)
using namespace Luau; using namespace Luau;
@ -360,10 +359,7 @@ a.x = 2
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); 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]));
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]));
} }
TEST_CASE_FIXTURE(Fixture, "optional_length_error") 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); LUAU_REQUIRE_ERROR_COUNT(1, result);
// NOTE: union normalization will improve this message // NOTE: union normalization will improve this message
if (FFlag::LuauLowerBoundsCalculation) CHECK_EQ(toString(result.errors[0]),
CHECK_EQ(toString(result.errors[0]), "Type '(string) -> number' could not be converted into '(number) -> string'\n" R"(Type '(string) -> number' could not be converted into '((number) -> string) | ((number) -> string)'; none of the union options are compatible)");
"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)");
} }
TEST_CASE_FIXTURE(Fixture, "union_true_and_false") TEST_CASE_FIXTURE(Fixture, "union_true_and_false")
{ {
ScopedFastFlag sffs[] { ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true}, {"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true}, {"LuauTypeNormalization2", true},
}; };
@ -561,7 +552,7 @@ TEST_CASE_FIXTURE(Fixture, "union_true_and_false")
TEST_CASE_FIXTURE(Fixture, "union_of_functions") TEST_CASE_FIXTURE(Fixture, "union_of_functions")
{ {
ScopedFastFlag sffs[] { ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true}, {"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", 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") TEST_CASE_FIXTURE(Fixture, "union_of_functions_mentioning_generics")
{ {
ScopedFastFlag sffs[] { ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true}, {"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true}, {"LuauTypeNormalization2", true},
}; };
@ -612,12 +603,13 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_mentioning_generics")
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); 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") TEST_CASE_FIXTURE(Fixture, "union_of_functions_mentioning_generic_typepacks")
{ {
ScopedFastFlag sffs[] { ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true}, {"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true}, {"LuauTypeNormalization2", true},
}; };
@ -631,12 +623,13 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_mentioning_generic_typepacks")
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); 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") TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_arities")
{ {
ScopedFastFlag sffs[] { ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true}, {"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true}, {"LuauTypeNormalization2", true},
}; };
@ -648,12 +641,13 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_arities")
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); 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") TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_arities")
{ {
ScopedFastFlag sffs[] { ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true}, {"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true}, {"LuauTypeNormalization2", true},
}; };
@ -665,12 +659,13 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_arities")
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); 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") TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_variadics")
{ {
ScopedFastFlag sffs[] { ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true}, {"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true}, {"LuauTypeNormalization2", true},
}; };
@ -682,12 +677,13 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_variadics")
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); 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") TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_variadics")
{ {
ScopedFastFlag sffs[] { ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true}, {"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true}, {"LuauTypeNormalization2", true},
}; };
@ -699,12 +695,13 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_variadics")
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); 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") TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_variadics")
{ {
ScopedFastFlag sffs[] { ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true}, {"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true}, {"LuauTypeNormalization2", true},
}; };
@ -716,7 +713,8 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_variadics
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); 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(); TEST_SUITE_END();

View file

@ -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 = 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 = 5 a = a % 2 return a end)() == 1)
assert((function() local a = 3 a = a ^ 2 return a end)() == 9) 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' return a end)() == "12")
assert((function() local a = '1' a = a .. '2' .. '3' return a end)() == "123") 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(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") 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 -- metatable ops
local function vec3t(x, y, z) local function vec3t(x, y, z)
return setmetatable({x=x, y=y, z=z}, { return setmetatable({x=x, y=y, z=z}, {

View file

@ -71,6 +71,7 @@ for _, b in pairs(c) do
assert(bit32.bxor(b) == b) assert(bit32.bxor(b) == b)
assert(bit32.bxor(b, b) == 0) assert(bit32.bxor(b, b) == 0)
assert(bit32.bxor(b, 0) == b) assert(bit32.bxor(b, 0) == b)
assert(bit32.bxor(b, b, b) == b)
assert(bit32.bnot(b) ~= b) assert(bit32.bnot(b) ~= b)
assert(bit32.bnot(bit32.bnot(b)) == b) assert(bit32.bnot(bit32.bnot(b)) == b)
assert(bit32.bnot(b) == 2^32 - 1 - 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(0xa0001111, 31) == 1)
assert(bit32.extract(42, 1, 3) == 5) 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, -1))
assert(not pcall(bit32.extract, 0, 32)) assert(not pcall(bit32.extract, 0, 32))
assert(not pcall(bit32.extract, 0, 0, 33)) 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.rrotate("0x12345678", -4) == 0x23456781)
assert(bit32.arshift("0x12345678", 1) == 0x12345678 / 2) assert(bit32.arshift("0x12345678", 1) == 0x12345678 / 2)
assert(bit32.arshift("-1", 32) == 0xffffffff) assert(bit32.arshift("-1", 32) == 0xffffffff)
assert(bit32.arshift("-1", 1) == 0xffffffff)
assert(bit32.bnot("1") == 0xfffffffe) assert(bit32.bnot("1") == 0xfffffffe)
assert(bit32.band("1", 3) == 1) assert(bit32.band("1", 3) == 1)
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, "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") == 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.btest("1", 3) == true) assert(bit32.btest("1", 3) == true)
assert(bit32.countlz("42") == 26) assert(bit32.countlz("42") == 26)

View file

@ -54,4 +54,19 @@ breakpoint(49, false) -- validate that disabling breakpoints works
bar() 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' return 'OK'

View file

@ -4,20 +4,6 @@ print('testing metatables')
local unpack = table.unpack 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{} == nil)
assert(getmetatable(4) == nil) assert(getmetatable(4) == nil)
assert(getmetatable(nil) == 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(i == 3 and x[1] == 3 and x[3] == 5)
assert(_G.X == 20)
assert(_G == getfenv(0))
print'+' print'+'
local _g = _G
setfenv(1, setmetatable({}, {__index=function (_,k) return _g[k] end}))
-- testing proxies -- testing proxies
assert(getmetatable(newproxy()) == nil) assert(getmetatable(newproxy()) == nil)
assert(getmetatable(newproxy(false)) == nil) assert(getmetatable(newproxy(false)) == nil)
@ -480,4 +460,23 @@ do
end end
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' return 'OK'

View file

@ -8,4 +8,13 @@ end
foo() foo()
function bar()
local i = 0
while i < 10 do
i += i + 1
end
end
bar()
return "OK" return "OK"

View file

@ -152,14 +152,34 @@ assert(eq(a[1000][3], 1000/3, 0.001))
print('+') print('+')
do -- testing NaN do -- testing NaN
local NaN = 10e500 - 10e400 local NaN -- to avoid constant folding
NaN = 10e500 - 10e400
assert(NaN ~= NaN) 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 (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 (0 >= NaN))
assert(not (NaN == 0))
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 = {} local a = {}
assert(not pcall(function () a[NaN] = 1 end)) assert(not pcall(function () a[NaN] = 1 end))
assert(a[NaN] == nil) assert(a[NaN] == nil)
@ -215,6 +235,16 @@ assert(flag);
assert(select(2, pcall(math.random, 1, 2, 3)):match("wrong number of arguments")) 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 -- noise
assert(math.noise(0.5) == 0) assert(math.noise(0.5) == 0)
assert(math.noise(0.5, 0.5) == -0.25) 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.log("9", 3) == 2)
assert(math.max("1", 2) == 2) assert(math.max("1", 2) == 2)
assert(math.max(2, "1") == 2) assert(math.max(2, "1") == 2)
assert(math.max(1, 2, "3") == 3)
assert(math.min("1", 2) == 1) assert(math.min("1", 2) == 1)
assert(math.min(2, "1") == 1) assert(math.min(2, "1") == 1)
assert(math.min(1, 2, "3") == 1)
local v,f = math.modf("1.5") local v,f = math.modf("1.5")
assert(v == 1 and f == 0.5) assert(v == 1 and f == 0.5)
assert(math.pow("2", 2) == 4) assert(math.pow("2", 2) == 4)
@ -295,4 +327,9 @@ assert(math.sign("-2") == -1)
assert(math.sign("0") == 0) assert(math.sign("0") == 0)
assert(math.round("1.8") == 2) 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') return('OK')

View 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