Sync to upstream/release/529 (#505)

* Adds a currently unused x86-64 assembler as a prerequisite for possible future JIT compilation
* Fix a bug in table iteration (closes Possible table iteration bug #504)
* Improved warning method when function is used as a type
* Fix a bug with unsandboxed iteration with pairs()
* Type of coroutine.status() is now a union of value types
* Bytecode output for tests/debugging now has labels
* Improvements to loop unrolling cost estimation
* Report errors when the key obviously doesn't exist in the table
This commit is contained in:
rblanckaert 2022-05-26 15:08:16 -07:00 committed by GitHub
parent e13f17e225
commit 61766a692c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
69 changed files with 3837 additions and 1724 deletions

View file

@ -12,9 +12,6 @@
#include <vector> #include <vector>
#include <optional> #include <optional>
LUAU_FASTFLAG(LuauSeparateTypechecks)
LUAU_FASTFLAG(LuauDirtySourceModule)
namespace Luau namespace Luau
{ {
@ -60,17 +57,12 @@ struct SourceNode
{ {
bool hasDirtySourceModule() const bool hasDirtySourceModule() const
{ {
LUAU_ASSERT(FFlag::LuauDirtySourceModule);
return dirtySourceModule; return dirtySourceModule;
} }
bool hasDirtyModule(bool forAutocomplete) const bool hasDirtyModule(bool forAutocomplete) const
{ {
if (FFlag::LuauSeparateTypechecks) return forAutocomplete ? dirtyModuleForAutocomplete : dirtyModule;
return forAutocomplete ? dirtyModuleForAutocomplete : dirtyModule;
else
return dirtyModule;
} }
ModuleName name; ModuleName name;
@ -90,10 +82,6 @@ struct FrontendOptions
// is complete. // is complete.
bool retainFullTypeGraphs = false; bool retainFullTypeGraphs = false;
// When true, we run typechecking twice, once in the regular mode, and once in strict mode
// in order to get more precise type information (e.g. for autocomplete).
bool typecheckTwice_DEPRECATED = false;
// Run typechecking only in mode required for autocomplete (strict mode in order to get more precise type information) // Run typechecking only in mode required for autocomplete (strict mode in order to get more precise type information)
bool forAutocomplete = false; bool forAutocomplete = false;
}; };
@ -171,7 +159,7 @@ struct Frontend
void applyBuiltinDefinitionToEnvironment(const std::string& environmentName, const std::string& definitionName); void applyBuiltinDefinitionToEnvironment(const std::string& environmentName, const std::string& definitionName);
private: private:
std::pair<SourceNode*, SourceModule*> getSourceNode(CheckResult& checkResult, const ModuleName& name, bool forAutocomplete_DEPRECATED); std::pair<SourceNode*, SourceModule*> getSourceNode(CheckResult& checkResult, const ModuleName& name);
SourceModule parse(const ModuleName& name, std::string_view src, const ParseOptions& parseOptions); SourceModule parse(const ModuleName& name, std::string_view src, const ParseOptions& parseOptions);
bool parseGraph(std::vector<ModuleName>& buildQueue, CheckResult& checkResult, const ModuleName& root, bool forAutocomplete); bool parseGraph(std::vector<ModuleName>& buildQueue, CheckResult& checkResult, const ModuleName& root, bool forAutocomplete);

View file

@ -0,0 +1,53 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "Luau/Substitution.h"
#include "Luau/TypeVar.h"
#include "Luau/Unifiable.h"
namespace Luau
{
struct TypeArena;
struct TxnLog;
// A substitution which replaces generic types in a given set by free types.
struct ReplaceGenerics : Substitution
{
ReplaceGenerics(
const TxnLog* log, TypeArena* arena, TypeLevel level, const std::vector<TypeId>& generics, const std::vector<TypePackId>& genericPacks)
: Substitution(log, arena)
, level(level)
, generics(generics)
, genericPacks(genericPacks)
{
}
TypeLevel level;
std::vector<TypeId> generics;
std::vector<TypePackId> genericPacks;
bool ignoreChildren(TypeId ty) override;
bool isDirty(TypeId ty) override;
bool isDirty(TypePackId tp) override;
TypeId clean(TypeId ty) override;
TypePackId clean(TypePackId tp) override;
};
// A substitution which replaces generic functions by monomorphic functions
struct Instantiation : Substitution
{
Instantiation(const TxnLog* log, TypeArena* arena, TypeLevel level)
: Substitution(log, arena)
, level(level)
{
}
TypeLevel level;
bool ignoreChildren(TypeId ty) override;
bool isDirty(TypeId ty) override;
bool isDirty(TypePackId tp) override;
TypeId clean(TypeId ty) override;
TypePackId clean(TypePackId tp) override;
};
} // namespace Luau

View file

@ -39,4 +39,4 @@ struct TypeArena
void freeze(TypeArena& arena); void freeze(TypeArena& arena);
void unfreeze(TypeArena& arena); void unfreeze(TypeArena& arena);
} } // namespace Luau

View file

@ -34,45 +34,6 @@ const AstStat* getFallthrough(const AstStat* node);
struct UnifierOptions; struct UnifierOptions;
struct Unifier; struct Unifier;
// A substitution which replaces generic types in a given set by free types.
struct ReplaceGenerics : Substitution
{
ReplaceGenerics(
const TxnLog* log, TypeArena* arena, TypeLevel level, const std::vector<TypeId>& generics, const std::vector<TypePackId>& genericPacks)
: Substitution(log, arena)
, level(level)
, generics(generics)
, genericPacks(genericPacks)
{
}
TypeLevel level;
std::vector<TypeId> generics;
std::vector<TypePackId> genericPacks;
bool ignoreChildren(TypeId ty) override;
bool isDirty(TypeId ty) override;
bool isDirty(TypePackId tp) override;
TypeId clean(TypeId ty) override;
TypePackId clean(TypePackId tp) override;
};
// A substitution which replaces generic functions by monomorphic functions
struct Instantiation : Substitution
{
Instantiation(const TxnLog* log, TypeArena* arena, TypeLevel level)
: Substitution(log, arena)
, level(level)
{
}
TypeLevel level;
bool ignoreChildren(TypeId ty) override;
bool isDirty(TypeId ty) override;
bool isDirty(TypePackId tp) override;
TypeId clean(TypeId ty) override;
TypePackId clean(TypePackId tp) override;
};
// A substitution which replaces free types by any // A substitution which replaces free types by any
struct Anyification : Substitution struct Anyification : Substitution
{ {

View file

@ -32,6 +32,9 @@ struct Widen : Substitution
TypeId clean(TypeId ty) override; TypeId clean(TypeId ty) override;
TypePackId clean(TypePackId ty) override; TypePackId clean(TypePackId ty) override;
bool ignoreChildren(TypeId ty) override; bool ignoreChildren(TypeId ty) override;
TypeId operator()(TypeId ty);
TypePackId operator()(TypePackId ty);
}; };
// TODO: Use this more widely. // TODO: Use this more widely.

View file

@ -42,7 +42,6 @@ struct UnifierSharedState
InternalErrorReporter* iceHandler; InternalErrorReporter* iceHandler;
DenseHashSet<void*> seenAny{nullptr};
DenseHashMap<TypeId, bool> skipCacheForType{nullptr}; DenseHashMap<TypeId, bool> skipCacheForType{nullptr};
DenseHashSet<std::pair<TypeId, TypeId>, TypeIdPairHash> cachedUnify{{nullptr, nullptr}}; DenseHashSet<std::pair<TypeId, TypeId>, TypeIdPairHash> cachedUnify{{nullptr, nullptr}};
DenseHashMap<std::pair<TypeId, TypeId>, TypeErrorData, TypeIdPairHash> cachedUnifyError{{nullptr, nullptr}}; DenseHashMap<std::pair<TypeId, TypeId>, TypeErrorData, TypeIdPairHash> cachedUnifyError{{nullptr, nullptr}};

View file

@ -8,7 +8,6 @@
#include "Luau/TypePack.h" #include "Luau/TypePack.h"
#include "Luau/TypeVar.h" #include "Luau/TypeVar.h"
LUAU_FASTFLAG(LuauUseVisitRecursionLimit)
LUAU_FASTINT(LuauVisitRecursionLimit) LUAU_FASTINT(LuauVisitRecursionLimit)
LUAU_FASTFLAG(LuauNormalizeFlagIsConservative) LUAU_FASTFLAG(LuauNormalizeFlagIsConservative)
@ -62,168 +61,6 @@ inline void unsee(DenseHashSet<void*>& seen, const void* tv)
// When DenseHashSet is used for 'visitTypeVarOnce', where don't forget visited elements // When DenseHashSet is used for 'visitTypeVarOnce', where don't forget visited elements
} }
template<typename F, typename Set>
void visit(TypePackId tp, F& f, Set& seen);
template<typename F, typename Set>
void visit(TypeId ty, F& f, Set& seen)
{
if (visit_detail::hasSeen(seen, ty))
{
f.cycle(ty);
return;
}
if (auto btv = get<BoundTypeVar>(ty))
{
if (apply(ty, *btv, seen, f))
visit(btv->boundTo, f, seen);
}
else if (auto ftv = get<FreeTypeVar>(ty))
apply(ty, *ftv, seen, f);
else if (auto gtv = get<GenericTypeVar>(ty))
apply(ty, *gtv, seen, f);
else if (auto etv = get<ErrorTypeVar>(ty))
apply(ty, *etv, seen, f);
else if (auto ctv = get<ConstrainedTypeVar>(ty))
{
if (apply(ty, *ctv, seen, f))
{
for (TypeId part : ctv->parts)
visit(part, f, seen);
}
}
else if (auto ptv = get<PrimitiveTypeVar>(ty))
apply(ty, *ptv, seen, f);
else if (auto ftv = get<FunctionTypeVar>(ty))
{
if (apply(ty, *ftv, seen, f))
{
visit(ftv->argTypes, f, seen);
visit(ftv->retType, f, seen);
}
}
else if (auto ttv = get<TableTypeVar>(ty))
{
// Some visitors want to see bound tables, that's why we visit the original type
if (apply(ty, *ttv, seen, f))
{
if (ttv->boundTo)
{
visit(*ttv->boundTo, f, seen);
}
else
{
for (auto& [_name, prop] : ttv->props)
visit(prop.type, f, seen);
if (ttv->indexer)
{
visit(ttv->indexer->indexType, f, seen);
visit(ttv->indexer->indexResultType, f, seen);
}
}
}
}
else if (auto mtv = get<MetatableTypeVar>(ty))
{
if (apply(ty, *mtv, seen, f))
{
visit(mtv->table, f, seen);
visit(mtv->metatable, f, seen);
}
}
else if (auto ctv = get<ClassTypeVar>(ty))
{
if (apply(ty, *ctv, seen, f))
{
for (const auto& [name, prop] : ctv->props)
visit(prop.type, f, seen);
if (ctv->parent)
visit(*ctv->parent, f, seen);
if (ctv->metatable)
visit(*ctv->metatable, f, seen);
}
}
else if (auto atv = get<AnyTypeVar>(ty))
apply(ty, *atv, seen, f);
else if (auto utv = get<UnionTypeVar>(ty))
{
if (apply(ty, *utv, seen, f))
{
for (TypeId optTy : utv->options)
visit(optTy, f, seen);
}
}
else if (auto itv = get<IntersectionTypeVar>(ty))
{
if (apply(ty, *itv, seen, f))
{
for (TypeId partTy : itv->parts)
visit(partTy, f, seen);
}
}
visit_detail::unsee(seen, ty);
}
template<typename F, typename Set>
void visit(TypePackId tp, F& f, Set& seen)
{
if (visit_detail::hasSeen(seen, tp))
{
f.cycle(tp);
return;
}
if (auto btv = get<BoundTypePack>(tp))
{
if (apply(tp, *btv, seen, f))
visit(btv->boundTo, f, seen);
}
else if (auto ftv = get<Unifiable::Free>(tp))
apply(tp, *ftv, seen, f);
else if (auto gtv = get<Unifiable::Generic>(tp))
apply(tp, *gtv, seen, f);
else if (auto etv = get<Unifiable::Error>(tp))
apply(tp, *etv, seen, f);
else if (auto pack = get<TypePack>(tp))
{
apply(tp, *pack, seen, f);
for (TypeId ty : pack->head)
visit(ty, f, seen);
if (pack->tail)
visit(*pack->tail, f, seen);
}
else if (auto pack = get<VariadicTypePack>(tp))
{
apply(tp, *pack, seen, f);
visit(pack->ty, f, seen);
}
visit_detail::unsee(seen, tp);
}
} // namespace visit_detail } // namespace visit_detail
template<typename S> template<typename S>
@ -513,37 +350,4 @@ struct TypeVarOnceVisitor : GenericTypeVarVisitor<DenseHashSet<void*>>
} }
}; };
// Clip with FFlagLuauUseVisitRecursionLimit
template<typename TID, typename F>
void DEPRECATED_visitTypeVar(TID ty, F& f, std::unordered_set<void*>& seen)
{
visit_detail::visit(ty, f, seen);
}
// Delete and inline when clipping FFlagLuauUseVisitRecursionLimit
template<typename TID, typename F>
void DEPRECATED_visitTypeVar(TID ty, F& f)
{
if (FFlag::LuauUseVisitRecursionLimit)
f.traverse(ty);
else
{
std::unordered_set<void*> seen;
visit_detail::visit(ty, f, seen);
}
}
// Delete and inline when clipping FFlagLuauUseVisitRecursionLimit
template<typename TID, typename F>
void DEPRECATED_visitTypeVarOnce(TID ty, F& f, DenseHashSet<void*>& seen)
{
if (FFlag::LuauUseVisitRecursionLimit)
f.traverse(ty);
else
{
seen.clear();
visit_detail::visit(ty, f, seen);
}
}
} // namespace Luau } // namespace Luau

View file

@ -1700,31 +1700,18 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
AutocompleteResult autocomplete(Frontend& frontend, const ModuleName& moduleName, Position position, StringCompletionCallback callback) AutocompleteResult autocomplete(Frontend& frontend, const ModuleName& moduleName, Position position, StringCompletionCallback callback)
{ {
if (FFlag::LuauSeparateTypechecks) // FIXME: We can improve performance here by parsing without checking.
{ // The old type graph is probably fine. (famous last words!)
// FIXME: We can improve performance here by parsing without checking. FrontendOptions opts;
// The old type graph is probably fine. (famous last words!) opts.forAutocomplete = true;
FrontendOptions opts; frontend.check(moduleName, opts);
opts.forAutocomplete = true;
frontend.check(moduleName, opts);
}
else
{
// FIXME: We can improve performance here by parsing without checking.
// The old type graph is probably fine. (famous last words!)
// FIXME: We don't need to typecheck for script analysis here, just for autocomplete.
frontend.check(moduleName);
}
const SourceModule* sourceModule = frontend.getSourceModule(moduleName); const SourceModule* sourceModule = frontend.getSourceModule(moduleName);
if (!sourceModule) if (!sourceModule)
return {}; return {};
TypeChecker& typeChecker = TypeChecker& typeChecker = frontend.typeCheckerForAutocomplete;
(frontend.options.typecheckTwice_DEPRECATED || FFlag::LuauSeparateTypechecks ? frontend.typeCheckerForAutocomplete : frontend.typeChecker); ModulePtr module = frontend.moduleResolverForAutocomplete.getModule(moduleName);
ModulePtr module =
(frontend.options.typecheckTwice_DEPRECATED || FFlag::LuauSeparateTypechecks ? frontend.moduleResolverForAutocomplete.getModule(moduleName)
: frontend.moduleResolver.getModule(moduleName));
if (!module) if (!module)
return {}; return {};
@ -1752,9 +1739,7 @@ OwningAutocompleteResult autocompleteSource(Frontend& frontend, std::string_view
sourceModule->mode = Mode::Strict; sourceModule->mode = Mode::Strict;
sourceModule->commentLocations = std::move(result.commentLocations); sourceModule->commentLocations = std::move(result.commentLocations);
TypeChecker& typeChecker = TypeChecker& typeChecker = frontend.typeCheckerForAutocomplete;
(frontend.options.typecheckTwice_DEPRECATED || FFlag::LuauSeparateTypechecks ? frontend.typeCheckerForAutocomplete : frontend.typeChecker);
ModulePtr module = typeChecker.check(*sourceModule, Mode::Strict); ModulePtr module = typeChecker.check(*sourceModule, Mode::Strict);
OwningAutocompleteResult autocompleteResult = { OwningAutocompleteResult autocompleteResult = {

View file

@ -20,9 +20,7 @@ LUAU_FASTINT(LuauTypeInferIterationLimit)
LUAU_FASTINT(LuauTarjanChildLimit) LUAU_FASTINT(LuauTarjanChildLimit)
LUAU_FASTFLAG(LuauInferInNoCheckMode) LUAU_FASTFLAG(LuauInferInNoCheckMode)
LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3, false) LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3, false)
LUAU_FASTFLAGVARIABLE(LuauSeparateTypechecks, false)
LUAU_FASTFLAGVARIABLE(LuauAutocompleteDynamicLimits, false) LUAU_FASTFLAGVARIABLE(LuauAutocompleteDynamicLimits, false)
LUAU_FASTFLAGVARIABLE(LuauDirtySourceModule, false)
LUAU_FASTINTVARIABLE(LuauAutocompleteCheckTimeoutMs, 100) LUAU_FASTINTVARIABLE(LuauAutocompleteCheckTimeoutMs, 100)
namespace Luau namespace Luau
@ -361,32 +359,21 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
if (it != sourceNodes.end() && !it->second.hasDirtyModule(frontendOptions.forAutocomplete)) if (it != sourceNodes.end() && !it->second.hasDirtyModule(frontendOptions.forAutocomplete))
{ {
// No recheck required. // No recheck required.
if (FFlag::LuauSeparateTypechecks) if (frontendOptions.forAutocomplete)
{ {
if (frontendOptions.forAutocomplete) auto it2 = moduleResolverForAutocomplete.modules.find(name);
{ if (it2 == moduleResolverForAutocomplete.modules.end() || it2->second == nullptr)
auto it2 = moduleResolverForAutocomplete.modules.find(name); throw std::runtime_error("Frontend::modules does not have data for " + name);
if (it2 == moduleResolverForAutocomplete.modules.end() || it2->second == nullptr)
throw std::runtime_error("Frontend::modules does not have data for " + name);
}
else
{
auto it2 = moduleResolver.modules.find(name);
if (it2 == moduleResolver.modules.end() || it2->second == nullptr)
throw std::runtime_error("Frontend::modules does not have data for " + name);
}
return CheckResult{accumulateErrors(
sourceNodes, frontendOptions.forAutocomplete ? moduleResolverForAutocomplete.modules : moduleResolver.modules, name)};
} }
else else
{ {
auto it2 = moduleResolver.modules.find(name); auto it2 = moduleResolver.modules.find(name);
if (it2 == moduleResolver.modules.end() || it2->second == nullptr) if (it2 == moduleResolver.modules.end() || it2->second == nullptr)
throw std::runtime_error("Frontend::modules does not have data for " + name); throw std::runtime_error("Frontend::modules does not have data for " + name);
return CheckResult{accumulateErrors(sourceNodes, moduleResolver.modules, name)};
} }
return CheckResult{
accumulateErrors(sourceNodes, frontendOptions.forAutocomplete ? moduleResolverForAutocomplete.modules : moduleResolver.modules, name)};
} }
std::vector<ModuleName> buildQueue; std::vector<ModuleName> buildQueue;
@ -428,7 +415,7 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
// This is used by the type checker to replace the resulting type of cyclic modules with any // This is used by the type checker to replace the resulting type of cyclic modules with any
sourceModule.cyclic = !requireCycles.empty(); sourceModule.cyclic = !requireCycles.empty();
if (FFlag::LuauSeparateTypechecks && frontendOptions.forAutocomplete) if (frontendOptions.forAutocomplete)
{ {
// The autocomplete typecheck is always in strict mode with DM awareness // The autocomplete typecheck is always in strict mode with DM awareness
// to provide better type information for IDE features // to provide better type information for IDE features
@ -485,17 +472,6 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
ModulePtr module = typeChecker.check(sourceModule, mode, environmentScope); ModulePtr module = typeChecker.check(sourceModule, mode, environmentScope);
// If we're typechecking twice, we do so.
// The second typecheck is always in strict mode with DM awareness
// to provide better typen information for IDE features.
if (!FFlag::LuauSeparateTypechecks && frontendOptions.typecheckTwice_DEPRECATED)
{
typeCheckerForAutocomplete.requireCycles = requireCycles;
ModulePtr moduleForAutocomplete = typeCheckerForAutocomplete.check(sourceModule, Mode::Strict);
moduleResolverForAutocomplete.modules[moduleName] = moduleForAutocomplete;
}
stats.timeCheck += getTimestamp() - timestamp; stats.timeCheck += getTimestamp() - timestamp;
stats.filesStrict += mode == Mode::Strict; stats.filesStrict += mode == Mode::Strict;
stats.filesNonstrict += mode == Mode::Nonstrict; stats.filesNonstrict += mode == Mode::Nonstrict;
@ -563,7 +539,7 @@ bool Frontend::parseGraph(std::vector<ModuleName>& buildQueue, CheckResult& chec
bool cyclic = false; bool cyclic = false;
{ {
auto [sourceNode, _] = getSourceNode(checkResult, root, forAutocomplete); auto [sourceNode, _] = getSourceNode(checkResult, root);
if (sourceNode) if (sourceNode)
stack.push_back(sourceNode); stack.push_back(sourceNode);
} }
@ -627,7 +603,7 @@ bool Frontend::parseGraph(std::vector<ModuleName>& buildQueue, CheckResult& chec
} }
} }
auto [sourceNode, _] = getSourceNode(checkResult, dep, forAutocomplete); auto [sourceNode, _] = getSourceNode(checkResult, dep);
if (sourceNode) if (sourceNode)
{ {
stack.push_back(sourceNode); stack.push_back(sourceNode);
@ -671,7 +647,7 @@ LintResult Frontend::lint(const ModuleName& name, std::optional<Luau::LintOption
LUAU_TIMETRACE_ARGUMENT("name", name.c_str()); LUAU_TIMETRACE_ARGUMENT("name", name.c_str());
CheckResult checkResult; CheckResult checkResult;
auto [_sourceNode, sourceModule] = getSourceNode(checkResult, name, false); auto [_sourceNode, sourceModule] = getSourceNode(checkResult, name);
if (!sourceModule) if (!sourceModule)
return LintResult{}; // FIXME: We really should do something a bit more obvious when a file is too broken to lint. return LintResult{}; // FIXME: We really should do something a bit more obvious when a file is too broken to lint.
@ -752,16 +728,8 @@ bool Frontend::isDirty(const ModuleName& name, bool forAutocomplete) const
*/ */
void Frontend::markDirty(const ModuleName& name, std::vector<ModuleName>* markedDirty) void Frontend::markDirty(const ModuleName& name, std::vector<ModuleName>* markedDirty)
{ {
if (FFlag::LuauSeparateTypechecks) if (!moduleResolver.modules.count(name) && !moduleResolverForAutocomplete.modules.count(name))
{ return;
if (!moduleResolver.modules.count(name) && !moduleResolverForAutocomplete.modules.count(name))
return;
}
else
{
if (!moduleResolver.modules.count(name))
return;
}
std::unordered_map<ModuleName, std::vector<ModuleName>> reverseDeps; std::unordered_map<ModuleName, std::vector<ModuleName>> reverseDeps;
for (const auto& module : sourceNodes) for (const auto& module : sourceNodes)
@ -783,32 +751,12 @@ void Frontend::markDirty(const ModuleName& name, std::vector<ModuleName>* marked
if (markedDirty) if (markedDirty)
markedDirty->push_back(next); markedDirty->push_back(next);
if (FFlag::LuauDirtySourceModule) if (sourceNode.dirtySourceModule && sourceNode.dirtyModule && sourceNode.dirtyModuleForAutocomplete)
{ continue;
LUAU_ASSERT(FFlag::LuauSeparateTypechecks);
if (sourceNode.dirtySourceModule && sourceNode.dirtyModule && sourceNode.dirtyModuleForAutocomplete) sourceNode.dirtySourceModule = true;
continue; sourceNode.dirtyModule = true;
sourceNode.dirtyModuleForAutocomplete = true;
sourceNode.dirtySourceModule = true;
sourceNode.dirtyModule = true;
sourceNode.dirtyModuleForAutocomplete = true;
}
else if (FFlag::LuauSeparateTypechecks)
{
if (sourceNode.dirtyModule && sourceNode.dirtyModuleForAutocomplete)
continue;
sourceNode.dirtyModule = true;
sourceNode.dirtyModuleForAutocomplete = true;
}
else
{
if (sourceNode.dirtyModule)
continue;
sourceNode.dirtyModule = true;
}
if (0 == reverseDeps.count(name)) if (0 == reverseDeps.count(name))
continue; continue;
@ -835,14 +783,13 @@ const SourceModule* Frontend::getSourceModule(const ModuleName& moduleName) cons
} }
// Read AST into sourceModules if necessary. Trace require()s. Report parse errors. // Read AST into sourceModules if necessary. Trace require()s. Report parse errors.
std::pair<SourceNode*, SourceModule*> Frontend::getSourceNode(CheckResult& checkResult, const ModuleName& name, bool forAutocomplete_DEPRECATED) std::pair<SourceNode*, SourceModule*> Frontend::getSourceNode(CheckResult& checkResult, const ModuleName& name)
{ {
LUAU_TIMETRACE_SCOPE("Frontend::getSourceNode", "Frontend"); LUAU_TIMETRACE_SCOPE("Frontend::getSourceNode", "Frontend");
LUAU_TIMETRACE_ARGUMENT("name", name.c_str()); LUAU_TIMETRACE_ARGUMENT("name", name.c_str());
auto it = sourceNodes.find(name); auto it = sourceNodes.find(name);
if (it != sourceNodes.end() && if (it != sourceNodes.end() && !it->second.hasDirtySourceModule())
(FFlag::LuauDirtySourceModule ? !it->second.hasDirtySourceModule() : !it->second.hasDirtyModule(forAutocomplete_DEPRECATED)))
{ {
auto moduleIt = sourceModules.find(name); auto moduleIt = sourceModules.find(name);
if (moduleIt != sourceModules.end()) if (moduleIt != sourceModules.end())
@ -885,21 +832,12 @@ std::pair<SourceNode*, SourceModule*> Frontend::getSourceNode(CheckResult& check
sourceNode.name = name; sourceNode.name = name;
sourceNode.requires.clear(); sourceNode.requires.clear();
sourceNode.requireLocations.clear(); sourceNode.requireLocations.clear();
sourceNode.dirtySourceModule = false;
if (FFlag::LuauDirtySourceModule) if (it == sourceNodes.end())
sourceNode.dirtySourceModule = false;
if (FFlag::LuauSeparateTypechecks)
{
if (it == sourceNodes.end())
{
sourceNode.dirtyModule = true;
sourceNode.dirtyModuleForAutocomplete = true;
}
}
else
{ {
sourceNode.dirtyModule = true; sourceNode.dirtyModule = true;
sourceNode.dirtyModuleForAutocomplete = true;
} }
for (const auto& [moduleName, location] : requireTrace.requires) for (const auto& [moduleName, location] : requireTrace.requires)

View file

@ -0,0 +1,128 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Common.h"
#include "Luau/Instantiation.h"
#include "Luau/TxnLog.h"
#include "Luau/TypeArena.h"
LUAU_FASTFLAG(LuauNoMethodLocations)
namespace Luau
{
bool Instantiation::isDirty(TypeId ty)
{
if (const FunctionTypeVar* ftv = log->getMutable<FunctionTypeVar>(ty))
{
if (ftv->hasNoGenerics)
return false;
return true;
}
else
{
return false;
}
}
bool Instantiation::isDirty(TypePackId tp)
{
return false;
}
bool Instantiation::ignoreChildren(TypeId ty)
{
if (log->getMutable<FunctionTypeVar>(ty))
return true;
else
return false;
}
TypeId Instantiation::clean(TypeId ty)
{
const FunctionTypeVar* ftv = log->getMutable<FunctionTypeVar>(ty);
LUAU_ASSERT(ftv);
FunctionTypeVar clone = FunctionTypeVar{level, ftv->argTypes, ftv->retType, ftv->definition, ftv->hasSelf};
clone.magicFunction = ftv->magicFunction;
clone.tags = ftv->tags;
clone.argNames = ftv->argNames;
TypeId result = addType(std::move(clone));
// Annoyingly, we have to do this even if there are no generics,
// to replace any generic tables.
ReplaceGenerics replaceGenerics{log, arena, level, ftv->generics, ftv->genericPacks};
// TODO: What to do if this returns nullopt?
// We don't have access to the error-reporting machinery
result = replaceGenerics.substitute(result).value_or(result);
asMutable(result)->documentationSymbol = ty->documentationSymbol;
return result;
}
TypePackId Instantiation::clean(TypePackId tp)
{
LUAU_ASSERT(false);
return tp;
}
bool ReplaceGenerics::ignoreChildren(TypeId ty)
{
if (const FunctionTypeVar* ftv = log->getMutable<FunctionTypeVar>(ty))
{
if (ftv->hasNoGenerics)
return true;
// We aren't recursing in the case of a generic function which
// binds the same generics. This can happen if, for example, there's recursive types.
// If T = <a>(a,T)->T then instantiating T should produce T' = (X,T)->T not T' = (X,T')->T'.
// It's OK to use vector equality here, since we always generate fresh generics
// whenever we quantify, so the vectors overlap if and only if they are equal.
return (!generics.empty() || !genericPacks.empty()) && (ftv->generics == generics) && (ftv->genericPacks == genericPacks);
}
else
{
return false;
}
}
bool ReplaceGenerics::isDirty(TypeId ty)
{
if (const TableTypeVar* ttv = log->getMutable<TableTypeVar>(ty))
return ttv->state == TableState::Generic;
else if (log->getMutable<GenericTypeVar>(ty))
return std::find(generics.begin(), generics.end(), ty) != generics.end();
else
return false;
}
bool ReplaceGenerics::isDirty(TypePackId tp)
{
if (log->getMutable<GenericTypePack>(tp))
return std::find(genericPacks.begin(), genericPacks.end(), tp) != genericPacks.end();
else
return false;
}
TypeId ReplaceGenerics::clean(TypeId ty)
{
LUAU_ASSERT(isDirty(ty));
if (const TableTypeVar* ttv = log->getMutable<TableTypeVar>(ty))
{
TableTypeVar clone = TableTypeVar{ttv->props, ttv->indexer, level, TableState::Free};
if (!FFlag::LuauNoMethodLocations)
clone.methodDefinitionLocations = ttv->methodDefinitionLocations;
clone.definitionModuleName = ttv->definitionModuleName;
return addType(std::move(clone));
}
else
return addType(FreeTypeVar{level});
}
TypePackId ReplaceGenerics::clean(TypePackId tp)
{
LUAU_ASSERT(isDirty(tp));
return addTypePack(TypePackVar(FreeTypePack{level}));
}
} // namespace Luau

View file

@ -56,8 +56,18 @@ bool isWithinComment(const SourceModule& sourceModule, Position pos)
struct ForceNormal : TypeVarOnceVisitor struct ForceNormal : TypeVarOnceVisitor
{ {
const TypeArena* typeArena = nullptr;
ForceNormal(const TypeArena* typeArena)
: typeArena(typeArena)
{
}
bool visit(TypeId ty) override bool visit(TypeId ty) override
{ {
if (ty->owningArena != typeArena)
return false;
asMutable(ty)->normal = true; asMutable(ty)->normal = true;
return true; return true;
} }
@ -100,7 +110,7 @@ void Module::clonePublicInterface(InternalErrorReporter& ice)
normalize(*moduleScope->varargPack, interfaceTypes, ice); normalize(*moduleScope->varargPack, interfaceTypes, ice);
} }
ForceNormal forceNormal; ForceNormal forceNormal{&interfaceTypes};
for (auto& [name, tf] : moduleScope->exportedTypeBindings) for (auto& [name, tf] : moduleScope->exportedTypeBindings)
{ {

View file

@ -15,6 +15,7 @@ LUAU_FASTFLAGVARIABLE(DebugLuauCopyBeforeNormalizing, false)
LUAU_FASTINTVARIABLE(LuauNormalizeIterationLimit, 1200); LUAU_FASTINTVARIABLE(LuauNormalizeIterationLimit, 1200);
LUAU_FASTFLAGVARIABLE(LuauNormalizeCombineTableFix, false); LUAU_FASTFLAGVARIABLE(LuauNormalizeCombineTableFix, false);
LUAU_FASTFLAGVARIABLE(LuauNormalizeFlagIsConservative, false); LUAU_FASTFLAGVARIABLE(LuauNormalizeFlagIsConservative, false);
LUAU_FASTFLAGVARIABLE(LuauNormalizeCombineEqFix, false);
namespace Luau namespace Luau
{ {
@ -325,245 +326,6 @@ struct Normalize final : TypeVarVisitor
int iterationLimit = 0; int iterationLimit = 0;
bool limitExceeded = false; bool limitExceeded = false;
// TODO: Clip with FFlag::LuauUseVisitRecursionLimit
bool operator()(TypeId ty, const BoundTypeVar& btv, std::unordered_set<void*>& seen)
{
// A type could be considered normal when it is in the stack, but we will eventually find out it is not normal as normalization progresses.
// So we need to avoid eagerly saying that this bound type is normal if the thing it is bound to is in the stack.
if (seen.find(asMutable(btv.boundTo)) != seen.end())
return false;
// It should never be the case that this TypeVar is normal, but is bound to a non-normal type, except in nontrivial cases.
LUAU_ASSERT(!ty->normal || ty->normal == btv.boundTo->normal);
asMutable(ty)->normal = btv.boundTo->normal;
return !ty->normal;
}
bool operator()(TypeId ty, const FreeTypeVar& ftv)
{
return visit(ty, ftv);
}
bool operator()(TypeId ty, const PrimitiveTypeVar& ptv)
{
return visit(ty, ptv);
}
bool operator()(TypeId ty, const GenericTypeVar& gtv)
{
return visit(ty, gtv);
}
bool operator()(TypeId ty, const ErrorTypeVar& etv)
{
return visit(ty, etv);
}
bool operator()(TypeId ty, const ConstrainedTypeVar& ctvRef, std::unordered_set<void*>& seen)
{
CHECK_ITERATION_LIMIT(false);
ConstrainedTypeVar* ctv = const_cast<ConstrainedTypeVar*>(&ctvRef);
std::vector<TypeId> parts = std::move(ctv->parts);
// We might transmute, so it's not safe to rely on the builtin traversal logic of visitTypeVar
for (TypeId part : parts)
visit_detail::visit(part, *this, seen);
std::vector<TypeId> newParts = normalizeUnion(parts);
const bool normal = areNormal(newParts, seen, ice);
if (newParts.size() == 1)
*asMutable(ty) = BoundTypeVar{newParts[0]};
else
*asMutable(ty) = UnionTypeVar{std::move(newParts)};
asMutable(ty)->normal = normal;
return false;
}
bool operator()(TypeId ty, const FunctionTypeVar& ftv, std::unordered_set<void*>& seen)
{
CHECK_ITERATION_LIMIT(false);
if (ty->normal)
return false;
visit_detail::visit(ftv.argTypes, *this, seen);
visit_detail::visit(ftv.retType, *this, seen);
asMutable(ty)->normal = areNormal(ftv.argTypes, seen, ice) && areNormal(ftv.retType, seen, ice);
return false;
}
bool operator()(TypeId ty, const TableTypeVar& ttv, std::unordered_set<void*>& seen)
{
CHECK_ITERATION_LIMIT(false);
if (ty->normal)
return false;
bool normal = true;
auto checkNormal = [&](TypeId t) {
// if t is on the stack, it is possible that this type is normal.
// If t is not normal and it is not on the stack, this type is definitely not normal.
if (!t->normal && seen.find(asMutable(t)) == seen.end())
normal = false;
};
if (ttv.boundTo)
{
visit_detail::visit(*ttv.boundTo, *this, seen);
asMutable(ty)->normal = (*ttv.boundTo)->normal;
return false;
}
for (const auto& [_name, prop] : ttv.props)
{
visit_detail::visit(prop.type, *this, seen);
checkNormal(prop.type);
}
if (ttv.indexer)
{
visit_detail::visit(ttv.indexer->indexType, *this, seen);
checkNormal(ttv.indexer->indexType);
visit_detail::visit(ttv.indexer->indexResultType, *this, seen);
checkNormal(ttv.indexer->indexResultType);
}
asMutable(ty)->normal = normal;
return false;
}
bool operator()(TypeId ty, const MetatableTypeVar& mtv, std::unordered_set<void*>& seen)
{
CHECK_ITERATION_LIMIT(false);
if (ty->normal)
return false;
visit_detail::visit(mtv.table, *this, seen);
visit_detail::visit(mtv.metatable, *this, seen);
asMutable(ty)->normal = mtv.table->normal && mtv.metatable->normal;
return false;
}
bool operator()(TypeId ty, const ClassTypeVar& ctv)
{
return visit(ty, ctv);
}
bool operator()(TypeId ty, const AnyTypeVar& atv)
{
return visit(ty, atv);
}
bool operator()(TypeId ty, const UnionTypeVar& utvRef, std::unordered_set<void*>& seen)
{
CHECK_ITERATION_LIMIT(false);
if (ty->normal)
return false;
UnionTypeVar* utv = &const_cast<UnionTypeVar&>(utvRef);
std::vector<TypeId> options = std::move(utv->options);
// We might transmute, so it's not safe to rely on the builtin traversal logic of visitTypeVar
for (TypeId option : options)
visit_detail::visit(option, *this, seen);
std::vector<TypeId> newOptions = normalizeUnion(options);
const bool normal = areNormal(newOptions, seen, ice);
LUAU_ASSERT(!newOptions.empty());
if (newOptions.size() == 1)
*asMutable(ty) = BoundTypeVar{newOptions[0]};
else
utv->options = std::move(newOptions);
asMutable(ty)->normal = normal;
return false;
}
bool operator()(TypeId ty, const IntersectionTypeVar& itvRef, std::unordered_set<void*>& seen)
{
CHECK_ITERATION_LIMIT(false);
if (ty->normal)
return false;
IntersectionTypeVar* itv = &const_cast<IntersectionTypeVar&>(itvRef);
std::vector<TypeId> oldParts = std::move(itv->parts);
for (TypeId part : oldParts)
visit_detail::visit(part, *this, seen);
std::vector<TypeId> tables;
for (TypeId part : oldParts)
{
part = follow(part);
if (get<TableTypeVar>(part))
tables.push_back(part);
else
{
Replacer replacer{&arena, nullptr, nullptr}; // FIXME this is super super WEIRD
combineIntoIntersection(replacer, itv, part);
}
}
// Don't allocate a new table if there's just one in the intersection.
if (tables.size() == 1)
itv->parts.push_back(tables[0]);
else if (!tables.empty())
{
const TableTypeVar* first = get<TableTypeVar>(tables[0]);
LUAU_ASSERT(first);
TypeId newTable = arena.addType(TableTypeVar{first->state, first->level});
TableTypeVar* ttv = getMutable<TableTypeVar>(newTable);
for (TypeId part : tables)
{
// Intuition: If combineIntoTable() needs to clone a table, any references to 'part' are cyclic and need
// to be rewritten to point at 'newTable' in the clone.
Replacer replacer{&arena, part, newTable};
combineIntoTable(replacer, ttv, part);
}
itv->parts.push_back(newTable);
}
asMutable(ty)->normal = areNormal(itv->parts, seen, ice);
if (itv->parts.size() == 1)
{
TypeId part = itv->parts[0];
*asMutable(ty) = BoundTypeVar{part};
}
return false;
}
// TODO: Clip with FFlag::LuauUseVisitRecursionLimit
template<typename T>
bool operator()(TypePackId, const T&)
{
return true;
}
// TODO: Clip with FFlag::LuauUseVisitRecursionLimit
template<typename TID>
void cycle(TID)
{
}
bool visit(TypeId ty, const FreeTypeVar&) override bool visit(TypeId ty, const FreeTypeVar&) override
{ {
LUAU_ASSERT(!ty->normal); LUAU_ASSERT(!ty->normal);
@ -968,6 +730,9 @@ struct Normalize final : TypeVarVisitor
*/ */
TypeId combine(Replacer& replacer, TypeId a, TypeId b) TypeId combine(Replacer& replacer, TypeId a, TypeId b)
{ {
if (FFlag::LuauNormalizeCombineEqFix)
b = follow(b);
if (FFlag::LuauNormalizeCombineTableFix && a == b) if (FFlag::LuauNormalizeCombineTableFix && a == b)
return a; return a;
@ -986,7 +751,7 @@ struct Normalize final : TypeVarVisitor
} }
else if (auto ttv = getMutable<TableTypeVar>(a)) else if (auto ttv = getMutable<TableTypeVar>(a))
{ {
if (FFlag::LuauNormalizeCombineTableFix && !get<TableTypeVar>(follow(b))) if (FFlag::LuauNormalizeCombineTableFix && !get<TableTypeVar>(FFlag::LuauNormalizeCombineEqFix ? b : follow(b)))
return arena.addType(IntersectionTypeVar{{a, b}}); return arena.addType(IntersectionTypeVar{{a, b}});
combineIntoTable(replacer, ttv, b); combineIntoTable(replacer, ttv, b);
return a; return a;
@ -1009,15 +774,7 @@ std::pair<TypeId, bool> normalize(TypeId ty, TypeArena& arena, InternalErrorRepo
(void)clone(ty, arena, state); (void)clone(ty, arena, state);
Normalize n{arena, ice}; Normalize n{arena, ice};
if (FFlag::LuauNormalizeFlagIsConservative) n.traverse(ty);
{
DEPRECATED_visitTypeVar(ty, n);
}
else
{
std::unordered_set<void*> seen;
DEPRECATED_visitTypeVar(ty, n, seen);
}
return {ty, !n.limitExceeded}; return {ty, !n.limitExceeded};
} }
@ -1041,15 +798,7 @@ std::pair<TypePackId, bool> normalize(TypePackId tp, TypeArena& arena, InternalE
(void)clone(tp, arena, state); (void)clone(tp, arena, state);
Normalize n{arena, ice}; Normalize n{arena, ice};
if (FFlag::LuauNormalizeFlagIsConservative) n.traverse(tp);
{
DEPRECATED_visitTypeVar(tp, n);
}
else
{
std::unordered_set<void*> seen;
DEPRECATED_visitTypeVar(tp, n, seen);
}
return {tp, !n.limitExceeded}; return {tp, !n.limitExceeded};
} }

View file

@ -119,8 +119,7 @@ struct Quantifier final : TypeVarOnceVisitor
void quantify(TypeId ty, TypeLevel level) void quantify(TypeId ty, TypeLevel level)
{ {
Quantifier q{level}; Quantifier q{level};
DenseHashSet<void*> seen{nullptr}; q.traverse(ty);
DEPRECATED_visitTypeVarOnce(ty, q, seen);
FunctionTypeVar* ftv = getMutable<FunctionTypeVar>(ty); FunctionTypeVar* ftv = getMutable<FunctionTypeVar>(ty);
LUAU_ASSERT(ftv); LUAU_ASSERT(ftv);

View file

@ -48,46 +48,6 @@ struct FindCyclicTypes final : TypeVarVisitor
cycleTPs.insert(tp); cycleTPs.insert(tp);
} }
// TODO: Clip all the operator()s when we clip FFlagLuauUseVisitRecursionLimit
template<typename T>
bool operator()(TypeId ty, const T&)
{
return visit(ty);
}
bool operator()(TypeId ty, const TableTypeVar& ttv) = delete;
bool operator()(TypeId ty, const TableTypeVar& ttv, std::unordered_set<void*>& seen)
{
if (!visited.insert(ty).second)
return false;
if (ttv.name || ttv.syntheticName)
{
for (TypeId itp : ttv.instantiatedTypeParams)
DEPRECATED_visitTypeVar(itp, *this, seen);
for (TypePackId itp : ttv.instantiatedTypePackParams)
DEPRECATED_visitTypeVar(itp, *this, seen);
return exhaustive;
}
return true;
}
bool operator()(TypeId, const ClassTypeVar&)
{
return false;
}
template<typename T>
bool operator()(TypePackId tp, const T&)
{
return visit(tp);
}
bool visit(TypeId ty) override bool visit(TypeId ty) override
{ {
return visited.insert(ty).second; return visited.insert(ty).second;
@ -128,7 +88,7 @@ void findCyclicTypes(std::set<TypeId>& cycles, std::set<TypePackId>& cycleTPs, T
{ {
FindCyclicTypes fct; FindCyclicTypes fct;
fct.exhaustive = exhaustive; fct.exhaustive = exhaustive;
DEPRECATED_visitTypeVar(ty, fct); fct.traverse(ty);
cycles = std::move(fct.cycles); cycles = std::move(fct.cycles);
cycleTPs = std::move(fct.cycleTPs); cycleTPs = std::move(fct.cycleTPs);

View file

@ -85,4 +85,4 @@ void unfreeze(TypeArena& arena)
arena.typePacks.unfreeze(); arena.typePacks.unfreeze();
} }
} } // namespace Luau

View file

@ -3,6 +3,7 @@
#include "Luau/Clone.h" #include "Luau/Clone.h"
#include "Luau/Common.h" #include "Luau/Common.h"
#include "Luau/Instantiation.h"
#include "Luau/ModuleResolver.h" #include "Luau/ModuleResolver.h"
#include "Luau/Normalize.h" #include "Luau/Normalize.h"
#include "Luau/Parser.h" #include "Luau/Parser.h"
@ -10,13 +11,13 @@
#include "Luau/RecursionCounter.h" #include "Luau/RecursionCounter.h"
#include "Luau/Scope.h" #include "Luau/Scope.h"
#include "Luau/Substitution.h" #include "Luau/Substitution.h"
#include "Luau/TopoSortStatements.h"
#include "Luau/TypePack.h"
#include "Luau/ToString.h"
#include "Luau/TypeUtils.h"
#include "Luau/ToString.h"
#include "Luau/TypeVar.h"
#include "Luau/TimeTrace.h" #include "Luau/TimeTrace.h"
#include "Luau/TopoSortStatements.h"
#include "Luau/ToString.h"
#include "Luau/ToString.h"
#include "Luau/TypePack.h"
#include "Luau/TypeUtils.h"
#include "Luau/TypeVar.h"
#include <algorithm> #include <algorithm>
#include <iterator> #include <iterator>
@ -26,14 +27,11 @@ LUAU_FASTINTVARIABLE(LuauTypeInferRecursionLimit, 165)
LUAU_FASTINTVARIABLE(LuauTypeInferIterationLimit, 20000) LUAU_FASTINTVARIABLE(LuauTypeInferIterationLimit, 20000)
LUAU_FASTINTVARIABLE(LuauTypeInferTypePackLoopLimit, 5000) LUAU_FASTINTVARIABLE(LuauTypeInferTypePackLoopLimit, 5000)
LUAU_FASTINTVARIABLE(LuauCheckRecursionLimit, 300) LUAU_FASTINTVARIABLE(LuauCheckRecursionLimit, 300)
LUAU_FASTFLAGVARIABLE(LuauUseVisitRecursionLimit, false)
LUAU_FASTINTVARIABLE(LuauVisitRecursionLimit, 500) LUAU_FASTINTVARIABLE(LuauVisitRecursionLimit, 500)
LUAU_FASTFLAG(LuauKnowsTheDataModel3) LUAU_FASTFLAG(LuauKnowsTheDataModel3)
LUAU_FASTFLAG(LuauSeparateTypechecks)
LUAU_FASTFLAG(LuauAutocompleteDynamicLimits) LUAU_FASTFLAG(LuauAutocompleteDynamicLimits)
LUAU_FASTFLAGVARIABLE(LuauDoNotRelyOnNextBinding, false) LUAU_FASTFLAGVARIABLE(LuauDoNotRelyOnNextBinding, false)
LUAU_FASTFLAGVARIABLE(LuauExpectedPropTypeFromIndexer, false) LUAU_FASTFLAGVARIABLE(LuauExpectedPropTypeFromIndexer, false)
LUAU_FASTFLAGVARIABLE(LuauWeakEqConstraint, false) // Eventually removed as false.
LUAU_FASTFLAGVARIABLE(LuauLowerBoundsCalculation, false) LUAU_FASTFLAGVARIABLE(LuauLowerBoundsCalculation, false)
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false) LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false)
LUAU_FASTFLAGVARIABLE(LuauSelfCallAutocompleteFix, false) LUAU_FASTFLAGVARIABLE(LuauSelfCallAutocompleteFix, false)
@ -43,7 +41,6 @@ LUAU_FASTFLAGVARIABLE(LuauUnsealedTableLiteral, false)
LUAU_FASTFLAGVARIABLE(LuauTwoPassAliasDefinitionFix, false) LUAU_FASTFLAGVARIABLE(LuauTwoPassAliasDefinitionFix, false)
LUAU_FASTFLAGVARIABLE(LuauReturnAnyInsteadOfICE, false) // Eventually removed as false. LUAU_FASTFLAGVARIABLE(LuauReturnAnyInsteadOfICE, false) // Eventually removed as false.
LUAU_FASTFLAG(LuauNormalizeFlagIsConservative) LUAU_FASTFLAG(LuauNormalizeFlagIsConservative)
LUAU_FASTFLAG(LuauWidenIfSupertypeIsFree2)
LUAU_FASTFLAGVARIABLE(LuauReturnTypeInferenceInNonstrict, false) LUAU_FASTFLAGVARIABLE(LuauReturnTypeInferenceInNonstrict, false)
LUAU_FASTFLAGVARIABLE(LuauRecursionLimitException, false); LUAU_FASTFLAGVARIABLE(LuauRecursionLimitException, false);
LUAU_FASTFLAGVARIABLE(LuauApplyTypeFunctionFix, false); LUAU_FASTFLAGVARIABLE(LuauApplyTypeFunctionFix, false);
@ -51,6 +48,7 @@ LUAU_FASTFLAGVARIABLE(LuauTypecheckIter, false);
LUAU_FASTFLAGVARIABLE(LuauSuccessTypingForEqualityOperations, false) LUAU_FASTFLAGVARIABLE(LuauSuccessTypingForEqualityOperations, false)
LUAU_FASTFLAGVARIABLE(LuauNoMethodLocations, false); LUAU_FASTFLAGVARIABLE(LuauNoMethodLocations, false);
LUAU_FASTFLAGVARIABLE(LuauAlwaysQuantify, false); LUAU_FASTFLAGVARIABLE(LuauAlwaysQuantify, false);
LUAU_FASTFLAGVARIABLE(LuauReportErrorsOnIndexerKeyMismatch, false)
namespace Luau namespace Luau
{ {
@ -305,12 +303,8 @@ ModulePtr TypeChecker::checkWithoutRecursionCheck(const SourceModule& module, Mo
currentModule.reset(new Module()); currentModule.reset(new Module());
currentModule->type = module.type; currentModule->type = module.type;
currentModule->allocator = module.allocator;
if (FFlag::LuauSeparateTypechecks) currentModule->names = module.names;
{
currentModule->allocator = module.allocator;
currentModule->names = module.names;
}
iceHandler->moduleName = module.name; iceHandler->moduleName = module.name;
@ -338,21 +332,14 @@ ModulePtr TypeChecker::checkWithoutRecursionCheck(const SourceModule& module, Mo
if (prepareModuleScope) if (prepareModuleScope)
prepareModuleScope(module.name, currentModule->getModuleScope()); prepareModuleScope(module.name, currentModule->getModuleScope());
if (FFlag::LuauSeparateTypechecks) try
{
try
{
checkBlock(moduleScope, *module.root);
}
catch (const TimeLimitError&)
{
currentModule->timeout = true;
}
}
else
{ {
checkBlock(moduleScope, *module.root); checkBlock(moduleScope, *module.root);
} }
catch (const TimeLimitError&)
{
currentModule->timeout = true;
}
if (get<FreeTypePack>(follow(moduleScope->returnType))) if (get<FreeTypePack>(follow(moduleScope->returnType)))
moduleScope->returnType = addTypePack(TypePack{{}, std::nullopt}); moduleScope->returnType = addTypePack(TypePack{{}, std::nullopt});
@ -443,7 +430,7 @@ void TypeChecker::check(const ScopePtr& scope, const AstStat& program)
else else
ice("Unknown AstStat"); ice("Unknown AstStat");
if (FFlag::LuauSeparateTypechecks && finishTime && TimeTrace::getClock() > *finishTime) if (finishTime && TimeTrace::getClock() > *finishTime)
throw TimeLimitError(); throw TimeLimitError();
} }
@ -868,9 +855,9 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatAssign& assign)
TypeId right = nullptr; TypeId right = nullptr;
Location loc = 0 == assign.values.size Location loc = 0 == assign.values.size ? assign.location
? assign.location : i < assign.values.size ? assign.values.data[i]->location
: i < assign.values.size ? assign.values.data[i]->location : assign.values.data[assign.values.size - 1]->location; : assign.values.data[assign.values.size - 1]->location;
if (valueIter != valueEnd) if (valueIter != valueEnd)
{ {
@ -1825,7 +1812,7 @@ std::optional<TypeId> TypeChecker::findMetatableEntry(TypeId type, std::string e
} }
std::optional<TypeId> TypeChecker::getIndexTypeFromType( std::optional<TypeId> TypeChecker::getIndexTypeFromType(
const ScopePtr& scope, TypeId type, const Name& name, const Location& location, bool addErrors) const ScopePtr& scope, TypeId type, const std::string& name, const Location& location, bool addErrors)
{ {
type = follow(type); type = follow(type);
@ -1843,13 +1830,25 @@ std::optional<TypeId> TypeChecker::getIndexTypeFromType(
if (TableTypeVar* tableType = getMutableTableType(type)) if (TableTypeVar* tableType = getMutableTableType(type))
{ {
const auto& it = tableType->props.find(name); if (auto it = tableType->props.find(name); it != tableType->props.end())
if (it != tableType->props.end())
return it->second.type; return it->second.type;
else if (auto indexer = tableType->indexer) else if (auto indexer = tableType->indexer)
{ {
tryUnify(stringType, indexer->indexType, location); // TODO: Property lookup should work with string singletons or unions thereof as the indexer key type.
return indexer->indexResultType; ErrorVec errors = tryUnify(stringType, indexer->indexType, location);
if (FFlag::LuauReportErrorsOnIndexerKeyMismatch)
{
if (errors.empty())
return indexer->indexResultType;
if (addErrors)
reportError(location, UnknownProperty{type, name});
return std::nullopt;
}
else
return indexer->indexResultType;
} }
else if (tableType->state == TableState::Free) else if (tableType->state == TableState::Free)
{ {
@ -1858,8 +1857,7 @@ std::optional<TypeId> TypeChecker::getIndexTypeFromType(
return result; return result;
} }
auto found = findTablePropertyRespectingMeta(type, name, location); if (auto found = findTablePropertyRespectingMeta(type, name, location))
if (found)
return *found; return *found;
} }
else if (const ClassTypeVar* cls = get<ClassTypeVar>(type)) else if (const ClassTypeVar* cls = get<ClassTypeVar>(type))
@ -2512,8 +2510,9 @@ TypeId TypeChecker::checkRelationalOperation(
if (!matches) if (!matches)
{ {
reportError(expr.location, GenericError{format("Types %s and %s cannot be compared with %s because they do not have the same metatable", reportError(
toString(lhsType).c_str(), toString(rhsType).c_str(), toString(expr.op).c_str())}); expr.location, GenericError{format("Types %s and %s cannot be compared with %s because they do not have the same metatable",
toString(lhsType).c_str(), toString(rhsType).c_str(), toString(expr.op).c_str())});
return errorRecoveryType(booleanType); return errorRecoveryType(booleanType);
} }
} }
@ -2522,8 +2521,9 @@ TypeId TypeChecker::checkRelationalOperation(
{ {
if (bool(leftMetatable) != bool(rightMetatable) && leftMetatable != rightMetatable) if (bool(leftMetatable) != bool(rightMetatable) && leftMetatable != rightMetatable)
{ {
reportError(expr.location, GenericError{format("Types %s and %s cannot be compared with %s because they do not have the same metatable", reportError(
toString(lhsType).c_str(), toString(rhsType).c_str(), toString(expr.op).c_str())}); expr.location, GenericError{format("Types %s and %s cannot be compared with %s because they do not have the same metatable",
toString(lhsType).c_str(), toString(rhsType).c_str(), toString(expr.op).c_str())});
return errorRecoveryType(booleanType); return errorRecoveryType(booleanType);
} }
} }
@ -3636,10 +3636,7 @@ void TypeChecker::checkArgumentList(
} }
TypePackId varPack = addTypePack(TypePackVar{TypePack{rest, argIter.tail()}}); TypePackId varPack = addTypePack(TypePackVar{TypePack{rest, argIter.tail()}});
if (FFlag::LuauWidenIfSupertypeIsFree2) state.tryUnify(varPack, tail);
state.tryUnify(varPack, tail);
else
state.tryUnify(tail, varPack);
return; return;
} }
@ -3707,7 +3704,7 @@ ExprResult<TypePackId> TypeChecker::checkExprPack(const ScopePtr& scope, const A
} }
TypePackId retPack; TypePackId retPack;
if (FFlag::LuauLowerBoundsCalculation || !FFlag::LuauWidenIfSupertypeIsFree2) if (FFlag::LuauLowerBoundsCalculation)
{ {
retPack = freshTypePack(scope->level); retPack = freshTypePack(scope->level);
} }
@ -3868,9 +3865,7 @@ std::optional<ExprResult<TypePackId>> TypeChecker::checkCallOverload(const Scope
Widen widen{&currentModule->internalTypes}; Widen widen{&currentModule->internalTypes};
for (; it != endIt; ++it) for (; it != endIt; ++it)
{ {
TypeId t = *it; adjustedArgTypes.push_back(addType(ConstrainedTypeVar{level, {widen(*it)}}));
TypeId widened = widen.substitute(t).value_or(t); // Surely widening is infallible
adjustedArgTypes.push_back(addType(ConstrainedTypeVar{level, {widened}}));
} }
TypePackId adjustedArgPack = addTypePack(TypePack{std::move(adjustedArgTypes), it.tail()}); TypePackId adjustedArgPack = addTypePack(TypePack{std::move(adjustedArgTypes), it.tail()});
@ -3885,14 +3880,11 @@ std::optional<ExprResult<TypePackId>> TypeChecker::checkCallOverload(const Scope
else else
{ {
TypeId r = addType(FunctionTypeVar(scope->level, argPack, retPack)); TypeId r = addType(FunctionTypeVar(scope->level, argPack, retPack));
if (FFlag::LuauWidenIfSupertypeIsFree2)
{ UnifierOptions options;
UnifierOptions options; options.isFunctionCall = true;
options.isFunctionCall = true; unify(r, fn, expr.location, options);
unify(r, fn, expr.location, options);
}
else
unify(fn, r, expr.location);
return {{retPack}}; return {{retPack}};
} }
} }
@ -4375,122 +4367,6 @@ void TypeChecker::unifyWithInstantiationIfNeeded(const ScopePtr& scope, TypeId s
} }
} }
bool Instantiation::isDirty(TypeId ty)
{
if (const FunctionTypeVar* ftv = log->getMutable<FunctionTypeVar>(ty))
{
if (ftv->hasNoGenerics)
return false;
return true;
}
else
{
return false;
}
}
bool Instantiation::isDirty(TypePackId tp)
{
return false;
}
bool Instantiation::ignoreChildren(TypeId ty)
{
if (log->getMutable<FunctionTypeVar>(ty))
return true;
else
return false;
}
TypeId Instantiation::clean(TypeId ty)
{
const FunctionTypeVar* ftv = log->getMutable<FunctionTypeVar>(ty);
LUAU_ASSERT(ftv);
FunctionTypeVar clone = FunctionTypeVar{level, ftv->argTypes, ftv->retType, ftv->definition, ftv->hasSelf};
clone.magicFunction = ftv->magicFunction;
clone.tags = ftv->tags;
clone.argNames = ftv->argNames;
TypeId result = addType(std::move(clone));
// Annoyingly, we have to do this even if there are no generics,
// to replace any generic tables.
ReplaceGenerics replaceGenerics{log, arena, level, ftv->generics, ftv->genericPacks};
// TODO: What to do if this returns nullopt?
// We don't have access to the error-reporting machinery
result = replaceGenerics.substitute(result).value_or(result);
asMutable(result)->documentationSymbol = ty->documentationSymbol;
return result;
}
TypePackId Instantiation::clean(TypePackId tp)
{
LUAU_ASSERT(false);
return tp;
}
bool ReplaceGenerics::ignoreChildren(TypeId ty)
{
if (const FunctionTypeVar* ftv = log->getMutable<FunctionTypeVar>(ty))
{
if (ftv->hasNoGenerics)
return true;
// We aren't recursing in the case of a generic function which
// binds the same generics. This can happen if, for example, there's recursive types.
// If T = <a>(a,T)->T then instantiating T should produce T' = (X,T)->T not T' = (X,T')->T'.
// It's OK to use vector equality here, since we always generate fresh generics
// whenever we quantify, so the vectors overlap if and only if they are equal.
return (!generics.empty() || !genericPacks.empty()) && (ftv->generics == generics) && (ftv->genericPacks == genericPacks);
}
else
{
return false;
}
}
bool ReplaceGenerics::isDirty(TypeId ty)
{
if (const TableTypeVar* ttv = log->getMutable<TableTypeVar>(ty))
return ttv->state == TableState::Generic;
else if (log->getMutable<GenericTypeVar>(ty))
return std::find(generics.begin(), generics.end(), ty) != generics.end();
else
return false;
}
bool ReplaceGenerics::isDirty(TypePackId tp)
{
if (log->getMutable<GenericTypePack>(tp))
return std::find(genericPacks.begin(), genericPacks.end(), tp) != genericPacks.end();
else
return false;
}
TypeId ReplaceGenerics::clean(TypeId ty)
{
LUAU_ASSERT(isDirty(ty));
if (const TableTypeVar* ttv = log->getMutable<TableTypeVar>(ty))
{
TableTypeVar clone = TableTypeVar{ttv->props, ttv->indexer, level, TableState::Free};
if (!FFlag::LuauNoMethodLocations)
clone.methodDefinitionLocations = ttv->methodDefinitionLocations;
clone.definitionModuleName = ttv->definitionModuleName;
return addType(std::move(clone));
}
else
return addType(FreeTypeVar{level});
}
TypePackId ReplaceGenerics::clean(TypePackId tp)
{
LUAU_ASSERT(isDirty(tp));
return addTypePack(TypePackVar(FreeTypePack{level}));
}
bool Anyification::isDirty(TypeId ty) bool Anyification::isDirty(TypeId ty)
{ {
if (ty->persistent) if (ty->persistent)
@ -5295,7 +5171,7 @@ TypeId ApplyTypeFunction::clean(TypeId ty)
{ {
TypeId& arg = typeArguments[ty]; TypeId& arg = typeArguments[ty];
if (FFlag::LuauApplyTypeFunctionFix) if (FFlag::LuauApplyTypeFunctionFix)
{ {
LUAU_ASSERT(arg); LUAU_ASSERT(arg);
return arg; return arg;
} }
@ -5309,7 +5185,7 @@ TypePackId ApplyTypeFunction::clean(TypePackId tp)
{ {
TypePackId& arg = typePackArguments[tp]; TypePackId& arg = typePackArguments[tp];
if (FFlag::LuauApplyTypeFunctionFix) if (FFlag::LuauApplyTypeFunctionFix)
{ {
LUAU_ASSERT(arg); LUAU_ASSERT(arg);
return arg; return arg;
} }
@ -5837,9 +5713,6 @@ void TypeChecker::resolve(const EqPredicate& eqP, RefinementMap& refis, const Sc
return; // Optimization: the other side has unknown types, so there's probably an overlap. Refining is no-op here. return; // Optimization: the other side has unknown types, so there's probably an overlap. Refining is no-op here.
auto predicate = [&](TypeId option) -> std::optional<TypeId> { auto predicate = [&](TypeId option) -> std::optional<TypeId> {
if (sense && isUndecidable(option))
return FFlag::LuauWeakEqConstraint ? option : eqP.type;
if (!sense && isNil(eqP.type)) if (!sense && isNil(eqP.type))
return (isUndecidable(option) || !isNil(option)) ? std::optional<TypeId>(option) : std::nullopt; return (isUndecidable(option) || !isNil(option)) ? std::optional<TypeId>(option) : std::nullopt;

View file

@ -21,8 +21,6 @@ LUAU_FASTFLAGVARIABLE(LuauTableSubtypingVariance2, false);
LUAU_FASTFLAG(LuauLowerBoundsCalculation); LUAU_FASTFLAG(LuauLowerBoundsCalculation);
LUAU_FASTFLAG(LuauErrorRecoveryType); LUAU_FASTFLAG(LuauErrorRecoveryType);
LUAU_FASTFLAGVARIABLE(LuauSubtypingAddOptPropsToUnsealedTables, false) LUAU_FASTFLAGVARIABLE(LuauSubtypingAddOptPropsToUnsealedTables, false)
LUAU_FASTFLAGVARIABLE(LuauWidenIfSupertypeIsFree2, false)
LUAU_FASTFLAGVARIABLE(LuauDifferentOrderOfUnificationDoesntMatter2, false)
LUAU_FASTFLAGVARIABLE(LuauTxnLogRefreshFunctionPointers, false) LUAU_FASTFLAGVARIABLE(LuauTxnLogRefreshFunctionPointers, false)
namespace Luau namespace Luau
@ -149,8 +147,7 @@ static void promoteTypeLevels(TxnLog& log, const TypeArena* typeArena, TypeLevel
return; return;
PromoteTypeLevels ptl{log, typeArena, minLevel}; PromoteTypeLevels ptl{log, typeArena, minLevel};
DenseHashSet<void*> seen{nullptr}; ptl.traverse(ty);
DEPRECATED_visitTypeVarOnce(ty, ptl, seen);
} }
void promoteTypeLevels(TxnLog& log, const TypeArena* typeArena, TypeLevel minLevel, TypePackId tp) void promoteTypeLevels(TxnLog& log, const TypeArena* typeArena, TypeLevel minLevel, TypePackId tp)
@ -160,8 +157,7 @@ void promoteTypeLevels(TxnLog& log, const TypeArena* typeArena, TypeLevel minLev
return; return;
PromoteTypeLevels ptl{log, typeArena, minLevel}; PromoteTypeLevels ptl{log, typeArena, minLevel};
DenseHashSet<void*> seen{nullptr}; ptl.traverse(tp);
DEPRECATED_visitTypeVarOnce(tp, ptl, seen);
} }
struct SkipCacheForType final : TypeVarOnceVisitor struct SkipCacheForType final : TypeVarOnceVisitor
@ -172,49 +168,6 @@ struct SkipCacheForType final : TypeVarOnceVisitor
{ {
} }
// TODO cycle() and operator() can be clipped with FFlagLuauUseVisitRecursionLimit
void cycle(TypeId) override {}
void cycle(TypePackId) override {}
bool operator()(TypeId ty, const FreeTypeVar& ftv)
{
return visit(ty, ftv);
}
bool operator()(TypeId ty, const BoundTypeVar& btv)
{
return visit(ty, btv);
}
bool operator()(TypeId ty, const GenericTypeVar& gtv)
{
return visit(ty, gtv);
}
bool operator()(TypeId ty, const TableTypeVar& ttv)
{
return visit(ty, ttv);
}
bool operator()(TypePackId tp, const FreeTypePack& ftp)
{
return visit(tp, ftp);
}
bool operator()(TypePackId tp, const BoundTypePack& ftp)
{
return visit(tp, ftp);
}
bool operator()(TypePackId tp, const GenericTypePack& ftp)
{
return visit(tp, ftp);
}
template<typename T>
bool operator()(TypeId ty, const T& t)
{
return visit(ty);
}
template<typename T>
bool operator()(TypePackId tp, const T&)
{
return visit(tp);
}
bool visit(TypeId, const FreeTypeVar&) override bool visit(TypeId, const FreeTypeVar&) override
{ {
result = true; result = true;
@ -341,6 +294,16 @@ bool Widen::ignoreChildren(TypeId ty)
return !log->is<UnionTypeVar>(ty); return !log->is<UnionTypeVar>(ty);
} }
TypeId Widen::operator()(TypeId ty)
{
return substitute(ty).value_or(ty);
}
TypePackId Widen::operator()(TypePackId tp)
{
return substitute(tp).value_or(tp);
}
static std::optional<TypeError> hasUnificationTooComplex(const ErrorVec& errors) static std::optional<TypeError> hasUnificationTooComplex(const ErrorVec& errors)
{ {
auto isUnificationTooComplex = [](const TypeError& te) { auto isUnificationTooComplex = [](const TypeError& te) {
@ -475,6 +438,8 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
if (!occursFailed) if (!occursFailed)
{ {
promoteTypeLevels(log, types, superLevel, subTy); promoteTypeLevels(log, types, superLevel, subTy);
Widen widen{types};
log.replace(superTy, BoundTypeVar(widen(subTy))); log.replace(superTy, BoundTypeVar(widen(subTy)));
} }
@ -612,9 +577,6 @@ void Unifier::tryUnifyUnionWithType(TypeId subTy, const UnionTypeVar* uv, TypeId
std::optional<TypeError> unificationTooComplex; std::optional<TypeError> unificationTooComplex;
std::optional<TypeError> firstFailedOption; std::optional<TypeError> firstFailedOption;
size_t count = uv->options.size();
size_t i = 0;
for (TypeId type : uv->options) for (TypeId type : uv->options)
{ {
Unifier innerState = makeChildUnifier(); Unifier innerState = makeChildUnifier();
@ -630,60 +592,44 @@ void Unifier::tryUnifyUnionWithType(TypeId subTy, const UnionTypeVar* uv, TypeId
failed = true; failed = true;
} }
if (FFlag::LuauDifferentOrderOfUnificationDoesntMatter2)
{
}
else
{
if (i == count - 1)
{
log.concat(std::move(innerState.log));
}
++i;
}
} }
// even if A | B <: T fails, we want to bind some options of T with A | B iff A | B was a subtype of that option. // even if A | B <: T fails, we want to bind some options of T with A | B iff A | B was a subtype of that option.
if (FFlag::LuauDifferentOrderOfUnificationDoesntMatter2) auto tryBind = [this, subTy](TypeId superOption) {
{ superOption = log.follow(superOption);
auto tryBind = [this, subTy](TypeId superOption) {
superOption = log.follow(superOption);
// just skip if the superOption is not free-ish. // just skip if the superOption is not free-ish.
auto ttv = log.getMutable<TableTypeVar>(superOption); auto ttv = log.getMutable<TableTypeVar>(superOption);
if (!log.is<FreeTypeVar>(superOption) && (!ttv || ttv->state != TableState::Free)) if (!log.is<FreeTypeVar>(superOption) && (!ttv || ttv->state != TableState::Free))
return; return;
// If superOption is already present in subTy, do nothing. Nothing new has been learned, but the subtype // If superOption is already present in subTy, do nothing. Nothing new has been learned, but the subtype
// test is successful. // test is successful.
if (auto subUnion = get<UnionTypeVar>(subTy)) if (auto subUnion = get<UnionTypeVar>(subTy))
{
if (end(subUnion) != std::find(begin(subUnion), end(subUnion), superOption))
return;
}
// Since we have already checked if S <: T, checking it again will not queue up the type for replacement.
// So we'll have to do it ourselves. We assume they unified cleanly if they are still in the seen set.
if (log.haveSeen(subTy, superOption))
{
// TODO: would it be nice for TxnLog::replace to do this?
if (log.is<TableTypeVar>(superOption))
log.bindTable(superOption, subTy);
else
log.replace(superOption, *subTy);
}
};
if (auto utv = log.getMutable<UnionTypeVar>(superTy))
{ {
for (TypeId ty : utv) if (end(subUnion) != std::find(begin(subUnion), end(subUnion), superOption))
tryBind(ty); return;
} }
else
tryBind(superTy); // Since we have already checked if S <: T, checking it again will not queue up the type for replacement.
// So we'll have to do it ourselves. We assume they unified cleanly if they are still in the seen set.
if (log.haveSeen(subTy, superOption))
{
// TODO: would it be nice for TxnLog::replace to do this?
if (log.is<TableTypeVar>(superOption))
log.bindTable(superOption, subTy);
else
log.replace(superOption, *subTy);
}
};
if (auto utv = log.getMutable<UnionTypeVar>(superTy))
{
for (TypeId ty : utv)
tryBind(ty);
} }
else
tryBind(superTy);
if (unificationTooComplex) if (unificationTooComplex)
reportError(*unificationTooComplex); reportError(*unificationTooComplex);
@ -883,7 +829,7 @@ bool Unifier::canCacheResult(TypeId subTy, TypeId superTy)
auto skipCacheFor = [this](TypeId ty) { auto skipCacheFor = [this](TypeId ty) {
SkipCacheForType visitor{sharedState.skipCacheForType, types}; SkipCacheForType visitor{sharedState.skipCacheForType, types};
DEPRECATED_visitTypeVarOnce(ty, visitor, sharedState.seenAny); visitor.traverse(ty);
sharedState.skipCacheForType[ty] = visitor.result; sharedState.skipCacheForType[ty] = visitor.result;
@ -1088,6 +1034,7 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
if (!log.getMutable<ErrorTypeVar>(superTp)) if (!log.getMutable<ErrorTypeVar>(superTp))
{ {
Widen widen{types};
log.replace(superTp, Unifiable::Bound<TypePackId>(widen(subTp))); log.replace(superTp, Unifiable::Bound<TypePackId>(widen(subTp)));
} }
} }
@ -1671,28 +1618,6 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
} }
} }
TypeId Unifier::widen(TypeId ty)
{
if (!FFlag::LuauWidenIfSupertypeIsFree2)
return ty;
Widen widen{types};
std::optional<TypeId> result = widen.substitute(ty);
// TODO: what does it mean for substitution to fail to widen?
return result.value_or(ty);
}
TypePackId Unifier::widen(TypePackId tp)
{
if (!FFlag::LuauWidenIfSupertypeIsFree2)
return tp;
Widen widen{types};
std::optional<TypePackId> result = widen.substitute(tp);
// TODO: what does it mean for substitution to fail to widen?
return result.value_or(tp);
}
TypeId Unifier::deeplyOptional(TypeId ty, std::unordered_map<TypeId, TypeId> seen) TypeId Unifier::deeplyOptional(TypeId ty, std::unordered_map<TypeId, TypeId> seen)
{ {
ty = follow(ty); ty = follow(ty);
@ -1809,10 +1734,7 @@ void Unifier::tryUnifyFreeTable(TypeId subTy, TypeId superTy)
{ {
if (auto subProp = findTablePropertyRespectingMeta(subTy, freeName)) if (auto subProp = findTablePropertyRespectingMeta(subTy, freeName))
{ {
if (FFlag::LuauWidenIfSupertypeIsFree2) tryUnify_(*subProp, freeProp.type);
tryUnify_(*subProp, freeProp.type);
else
tryUnify_(freeProp.type, *subProp);
/* /*
* TypeVars are commonly cyclic, so it is entirely possible * TypeVars are commonly cyclic, so it is entirely possible

View file

@ -1,7 +1,7 @@
// 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 "Common.h" #include "Luau/Common.h"
#include <vector> #include <vector>

View file

@ -11,6 +11,8 @@
LUAU_FASTINTVARIABLE(LuauRecursionLimit, 1000) LUAU_FASTINTVARIABLE(LuauRecursionLimit, 1000)
LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100) LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100)
LUAU_FASTFLAGVARIABLE(LuauParserFunctionKeywordAsTypeHelp, false)
namespace Luau namespace Luau
{ {
@ -1589,6 +1591,17 @@ AstTypeOrPack Parser::parseSimpleTypeAnnotation(bool allowPack)
{ {
return parseFunctionTypeAnnotation(allowPack); return parseFunctionTypeAnnotation(allowPack);
} }
else if (FFlag::LuauParserFunctionKeywordAsTypeHelp && lexer.current().type == Lexeme::ReservedFunction)
{
Location location = lexer.current().location;
nextLexeme();
return {reportTypeAnnotationError(location, {}, /*isMissing*/ false,
"Using 'function' as a type annotation is not supported, consider replacing with a function type annotation e.g. '(...any) -> "
"...any'"),
{}};
}
else else
{ {
Location location = lexer.current().location; Location location = lexer.current().location;

View file

@ -19,9 +19,11 @@ if(LUAU_STATIC_CRT)
endif() endif()
project(Luau LANGUAGES CXX C) project(Luau LANGUAGES CXX C)
add_library(Luau.Common INTERFACE)
add_library(Luau.Ast STATIC) add_library(Luau.Ast STATIC)
add_library(Luau.Compiler STATIC) add_library(Luau.Compiler STATIC)
add_library(Luau.Analysis STATIC) add_library(Luau.Analysis STATIC)
add_library(Luau.CodeGen STATIC)
add_library(Luau.VM STATIC) add_library(Luau.VM STATIC)
add_library(isocline STATIC) add_library(isocline STATIC)
@ -48,8 +50,11 @@ endif()
include(Sources.cmake) include(Sources.cmake)
target_include_directories(Luau.Common INTERFACE Common/include)
target_compile_features(Luau.Ast PUBLIC cxx_std_17) target_compile_features(Luau.Ast PUBLIC cxx_std_17)
target_include_directories(Luau.Ast PUBLIC Ast/include) target_include_directories(Luau.Ast PUBLIC Ast/include)
target_link_libraries(Luau.Ast PUBLIC Luau.Common)
target_compile_features(Luau.Compiler PUBLIC cxx_std_17) target_compile_features(Luau.Compiler PUBLIC cxx_std_17)
target_include_directories(Luau.Compiler PUBLIC Compiler/include) target_include_directories(Luau.Compiler PUBLIC Compiler/include)
@ -59,8 +64,13 @@ target_compile_features(Luau.Analysis PUBLIC cxx_std_17)
target_include_directories(Luau.Analysis PUBLIC Analysis/include) target_include_directories(Luau.Analysis PUBLIC Analysis/include)
target_link_libraries(Luau.Analysis PUBLIC Luau.Ast) target_link_libraries(Luau.Analysis PUBLIC Luau.Ast)
target_compile_features(Luau.CodeGen PRIVATE cxx_std_17)
target_include_directories(Luau.CodeGen PUBLIC CodeGen/include)
target_link_libraries(Luau.CodeGen PUBLIC Luau.Common)
target_compile_features(Luau.VM PRIVATE cxx_std_11) target_compile_features(Luau.VM PRIVATE cxx_std_11)
target_include_directories(Luau.VM PUBLIC VM/include) target_include_directories(Luau.VM PUBLIC VM/include)
target_link_libraries(Luau.VM PUBLIC Luau.Common)
target_include_directories(isocline PUBLIC extern/isocline/include) target_include_directories(isocline PUBLIC extern/isocline/include)
@ -101,6 +111,7 @@ endif()
target_compile_options(Luau.Ast PRIVATE ${LUAU_OPTIONS}) target_compile_options(Luau.Ast PRIVATE ${LUAU_OPTIONS})
target_compile_options(Luau.Analysis PRIVATE ${LUAU_OPTIONS}) target_compile_options(Luau.Analysis PRIVATE ${LUAU_OPTIONS})
target_compile_options(Luau.CodeGen PRIVATE ${LUAU_OPTIONS})
target_compile_options(Luau.VM PRIVATE ${LUAU_OPTIONS}) target_compile_options(Luau.VM PRIVATE ${LUAU_OPTIONS})
target_compile_options(isocline PRIVATE ${LUAU_OPTIONS} ${ISOCLINE_OPTIONS}) target_compile_options(isocline PRIVATE ${LUAU_OPTIONS} ${ISOCLINE_OPTIONS})
@ -120,6 +131,7 @@ endif()
if(MSVC) if(MSVC)
target_link_options(Luau.Ast INTERFACE /NATVIS:${CMAKE_CURRENT_SOURCE_DIR}/tools/natvis/Ast.natvis) target_link_options(Luau.Ast INTERFACE /NATVIS:${CMAKE_CURRENT_SOURCE_DIR}/tools/natvis/Ast.natvis)
target_link_options(Luau.Analysis INTERFACE /NATVIS:${CMAKE_CURRENT_SOURCE_DIR}/tools/natvis/Analysis.natvis) target_link_options(Luau.Analysis INTERFACE /NATVIS:${CMAKE_CURRENT_SOURCE_DIR}/tools/natvis/Analysis.natvis)
target_link_options(Luau.CodeGen INTERFACE /NATVIS:${CMAKE_CURRENT_SOURCE_DIR}/tools/natvis/CodeGen.natvis)
target_link_options(Luau.VM INTERFACE /NATVIS:${CMAKE_CURRENT_SOURCE_DIR}/tools/natvis/VM.natvis) target_link_options(Luau.VM INTERFACE /NATVIS:${CMAKE_CURRENT_SOURCE_DIR}/tools/natvis/VM.natvis)
endif() endif()
@ -127,6 +139,7 @@ endif()
if(MSVC_IDE) if(MSVC_IDE)
target_sources(Luau.Ast PRIVATE tools/natvis/Ast.natvis) target_sources(Luau.Ast PRIVATE tools/natvis/Ast.natvis)
target_sources(Luau.Analysis PRIVATE tools/natvis/Analysis.natvis) target_sources(Luau.Analysis PRIVATE tools/natvis/Analysis.natvis)
target_sources(Luau.CodeGen PRIVATE tools/natvis/CodeGen.natvis)
target_sources(Luau.VM PRIVATE tools/natvis/VM.natvis) target_sources(Luau.VM PRIVATE tools/natvis/VM.natvis)
endif() endif()
@ -154,7 +167,7 @@ endif()
if(LUAU_BUILD_TESTS) if(LUAU_BUILD_TESTS)
target_compile_options(Luau.UnitTest PRIVATE ${LUAU_OPTIONS}) target_compile_options(Luau.UnitTest PRIVATE ${LUAU_OPTIONS})
target_include_directories(Luau.UnitTest PRIVATE extern) target_include_directories(Luau.UnitTest PRIVATE extern)
target_link_libraries(Luau.UnitTest PRIVATE Luau.Analysis Luau.Compiler) target_link_libraries(Luau.UnitTest PRIVATE Luau.Analysis Luau.Compiler Luau.CodeGen)
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)

View file

@ -0,0 +1,169 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "Luau/Common.h"
#include "Luau/Condition.h"
#include "Luau/Label.h"
#include "Luau/OperandX64.h"
#include "Luau/RegisterX64.h"
#include <string>
#include <vector>
namespace Luau
{
namespace CodeGen
{
class AssemblyBuilderX64
{
public:
explicit AssemblyBuilderX64(bool logText);
~AssemblyBuilderX64();
// Base two operand instructions with 9 opcode selection
void add(OperandX64 lhs, OperandX64 rhs);
void sub(OperandX64 lhs, OperandX64 rhs);
void cmp(OperandX64 lhs, OperandX64 rhs);
void and_(OperandX64 lhs, OperandX64 rhs);
void or_(OperandX64 lhs, OperandX64 rhs);
void xor_(OperandX64 lhs, OperandX64 rhs);
// Binary shift instructions with special rhs handling
void sal(OperandX64 lhs, OperandX64 rhs);
void sar(OperandX64 lhs, OperandX64 rhs);
void shl(OperandX64 lhs, OperandX64 rhs);
void shr(OperandX64 lhs, OperandX64 rhs);
// Two operand mov instruction has additional specialized encodings
void mov(OperandX64 lhs, OperandX64 rhs);
void mov64(RegisterX64 lhs, int64_t imm);
// Base one operand instruction with 2 opcode selection
void div(OperandX64 op);
void idiv(OperandX64 op);
void mul(OperandX64 op);
void neg(OperandX64 op);
void not_(OperandX64 op);
void test(OperandX64 lhs, OperandX64 rhs);
void lea(OperandX64 lhs, OperandX64 rhs);
void push(OperandX64 op);
void pop(OperandX64 op);
void ret();
// Control flow
void jcc(Condition cond, Label& label);
void jmp(Label& label);
void jmp(OperandX64 op);
// AVX
void vaddpd(OperandX64 dst, OperandX64 src1, OperandX64 src2);
void vaddps(OperandX64 dst, OperandX64 src1, OperandX64 src2);
void vaddsd(OperandX64 dst, OperandX64 src1, OperandX64 src2);
void vaddss(OperandX64 dst, OperandX64 src1, OperandX64 src2);
void vsqrtpd(OperandX64 dst, OperandX64 src);
void vsqrtps(OperandX64 dst, OperandX64 src);
void vsqrtsd(OperandX64 dst, OperandX64 src1, OperandX64 src2);
void vsqrtss(OperandX64 dst, OperandX64 src1, OperandX64 src2);
void vmovsd(OperandX64 dst, OperandX64 src);
void vmovsd(OperandX64 dst, OperandX64 src1, OperandX64 src2);
void vmovss(OperandX64 dst, OperandX64 src);
void vmovss(OperandX64 dst, OperandX64 src1, OperandX64 src2);
void vmovapd(OperandX64 dst, OperandX64 src);
void vmovaps(OperandX64 dst, OperandX64 src);
void vmovupd(OperandX64 dst, OperandX64 src);
void vmovups(OperandX64 dst, OperandX64 src);
// Run final checks
void finalize();
// Places a label at current location and returns it
Label setLabel();
// Assigns label position to the current location
void setLabel(Label& label);
// Constant allocation (uses rip-relative addressing)
OperandX64 i64(int64_t value);
OperandX64 f32(float value);
OperandX64 f64(double value);
OperandX64 f32x4(float x, float y, float z, float w);
// Resulting data and code that need to be copied over one after the other
// The *end* of 'data' has to be aligned to 16 bytes, this will also align 'code'
std::vector<uint8_t> data;
std::vector<uint8_t> code;
std::string text;
private:
// Instruction archetypes
void placeBinary(const char* name, OperandX64 lhs, OperandX64 rhs, uint8_t codeimm8, uint8_t codeimm, uint8_t codeimmImm8, uint8_t code8rev,
uint8_t coderev, uint8_t code8, uint8_t code, uint8_t opreg);
void placeBinaryRegMemAndImm(OperandX64 lhs, OperandX64 rhs, uint8_t code8, uint8_t code, uint8_t codeImm8, uint8_t opreg);
void placeBinaryRegAndRegMem(OperandX64 lhs, OperandX64 rhs, uint8_t code8, uint8_t code);
void placeBinaryRegMemAndReg(OperandX64 lhs, OperandX64 rhs, uint8_t code8, uint8_t code);
void placeUnaryModRegMem(const char* name, OperandX64 op, uint8_t code8, uint8_t code, uint8_t opreg);
void placeShift(const char* name, OperandX64 lhs, OperandX64 rhs, uint8_t opreg);
void placeJcc(const char* name, Label& label, uint8_t cc);
void placeAvx(const char* name, OperandX64 dst, OperandX64 src, uint8_t code, bool setW, uint8_t mode, uint8_t prefix);
void placeAvx(const char* name, OperandX64 dst, OperandX64 src, uint8_t code, uint8_t coderev, bool setW, uint8_t mode, uint8_t prefix);
void placeAvx(const char* name, OperandX64 dst, OperandX64 src1, OperandX64 src2, uint8_t code, bool setW, uint8_t mode, uint8_t prefix);
// Instruction components
void placeRegAndModRegMem(OperandX64 lhs, OperandX64 rhs);
void placeModRegMem(OperandX64 rhs, uint8_t regop);
void placeRex(RegisterX64 op);
void placeRex(OperandX64 op);
void placeRex(RegisterX64 lhs, OperandX64 rhs);
void placeVex(OperandX64 dst, OperandX64 src1, OperandX64 src2, bool setW, uint8_t mode, uint8_t prefix);
void placeImm8Or32(int32_t imm);
void placeImm8(int32_t imm);
void placeImm32(int32_t imm);
void placeImm64(int64_t imm);
void placeLabel(Label& label);
void place(uint8_t byte);
void commit();
LUAU_NOINLINE void extend();
uint32_t getCodeSize();
// Data
size_t allocateData(size_t size, size_t align);
// Logging of assembly in text form (Intel asm with VS disassembly formatting)
LUAU_NOINLINE void log(const char* opcode);
LUAU_NOINLINE void log(const char* opcode, OperandX64 op);
LUAU_NOINLINE void log(const char* opcode, OperandX64 op1, OperandX64 op2);
LUAU_NOINLINE void log(const char* opcode, OperandX64 op1, OperandX64 op2, OperandX64 op3);
LUAU_NOINLINE void log(Label label);
LUAU_NOINLINE void log(const char* opcode, Label label);
void log(OperandX64 op);
void logAppend(const char* fmt, ...);
const char* getSizeName(SizeX64 size);
const char* getRegisterName(RegisterX64 reg);
uint32_t nextLabel = 1;
std::vector<Label> pendingLabels;
std::vector<uint32_t> labelLocations;
bool logText = false;
bool finalized = false;
size_t dataPos = 0;
uint8_t* codePos = nullptr;
uint8_t* codeEnd = nullptr;
};
} // namespace CodeGen
} // namespace Luau

View file

@ -0,0 +1,46 @@
// 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
{
enum class Condition
{
Overflow,
NoOverflow,
Carry,
NoCarry,
Below,
BelowEqual,
Above,
AboveEqual,
Equal,
Less,
LessEqual,
Greater,
GreaterEqual,
NotBelow,
NotBelowEqual,
NotAbove,
NotAboveEqual,
NotEqual,
NotLess,
NotLessEqual,
NotGreater,
NotGreaterEqual,
Zero,
NotZero,
// TODO: ordered and unordered floating-point conditions
Count
};
} // namespace CodeGen
} // namespace Luau

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
#include <stdint.h>
namespace Luau
{
namespace CodeGen
{
struct Label
{
uint32_t id = 0;
uint32_t location = ~0u;
};
} // namespace CodeGen
} // namespace Luau

View file

@ -0,0 +1,136 @@
#pragma once
#include "Luau/Common.h"
#include "Luau/RegisterX64.h"
#include <stdint.h>
namespace Luau
{
namespace CodeGen
{
enum class CategoryX64 : uint8_t
{
reg,
mem,
imm,
};
struct OperandX64
{
constexpr OperandX64(RegisterX64 reg)
: cat(CategoryX64::reg)
, index(noreg)
, base(reg)
, memSize(SizeX64::none)
, scale(1)
, imm(0)
{
}
constexpr OperandX64(int32_t imm)
: cat(CategoryX64::imm)
, index(noreg)
, base(noreg)
, memSize(SizeX64::none)
, scale(1)
, imm(imm)
{
}
constexpr explicit OperandX64(SizeX64 size, RegisterX64 index, uint8_t scale, RegisterX64 base, int32_t disp)
: cat(CategoryX64::mem)
, index(index)
, base(base)
, memSize(size)
, scale(scale)
, imm(disp)
{
}
// Fields are carefully placed to make this struct fit into an 8 byte register
CategoryX64 cat;
RegisterX64 index;
RegisterX64 base;
SizeX64 memSize : 4;
uint8_t scale : 4;
int32_t imm;
constexpr OperandX64 operator[](OperandX64&& addr) const
{
LUAU_ASSERT(cat == CategoryX64::mem);
LUAU_ASSERT(memSize != SizeX64::none && index == noreg && scale == 1 && base == noreg && imm == 0);
LUAU_ASSERT(addr.memSize == SizeX64::none);
addr.cat = CategoryX64::mem;
addr.memSize = memSize;
return addr;
}
};
constexpr OperandX64 byte{SizeX64::byte, noreg, 1, noreg, 0};
constexpr OperandX64 word{SizeX64::word, noreg, 1, noreg, 0};
constexpr OperandX64 dword{SizeX64::dword, noreg, 1, noreg, 0};
constexpr OperandX64 qword{SizeX64::qword, noreg, 1, noreg, 0};
constexpr OperandX64 xmmword{SizeX64::xmmword, noreg, 1, noreg, 0};
constexpr OperandX64 ymmword{SizeX64::ymmword, noreg, 1, noreg, 0};
constexpr OperandX64 ptr{sizeof(void*) == 4 ? SizeX64::dword : SizeX64::qword, noreg, 1, noreg, 0};
constexpr OperandX64 operator*(RegisterX64 reg, uint8_t scale)
{
if (scale == 1)
return OperandX64(reg);
LUAU_ASSERT(scale == 1 || scale == 2 || scale == 4 || scale == 8);
LUAU_ASSERT(reg.index != 0b100 && "can't scale SP");
return OperandX64(SizeX64::none, reg, scale, noreg, 0);
}
constexpr OperandX64 operator+(RegisterX64 reg, int32_t disp)
{
return OperandX64(SizeX64::none, noreg, 1, reg, disp);
}
constexpr OperandX64 operator+(RegisterX64 base, RegisterX64 index)
{
LUAU_ASSERT(index.index != 4 && "sp cannot be used as index");
LUAU_ASSERT(base.size == index.size);
return OperandX64(SizeX64::none, index, 1, base, 0);
}
constexpr OperandX64 operator+(OperandX64 op, int32_t disp)
{
LUAU_ASSERT(op.cat == CategoryX64::mem);
LUAU_ASSERT(op.memSize == SizeX64::none);
op.imm += disp;
return op;
}
constexpr OperandX64 operator+(OperandX64 op, RegisterX64 base)
{
LUAU_ASSERT(op.cat == CategoryX64::mem);
LUAU_ASSERT(op.memSize == SizeX64::none);
LUAU_ASSERT(op.base == noreg);
LUAU_ASSERT(op.index == noreg || op.index.size == base.size);
op.base = base;
return op;
}
constexpr OperandX64 operator+(RegisterX64 base, OperandX64 op)
{
LUAU_ASSERT(op.cat == CategoryX64::mem);
LUAU_ASSERT(op.memSize == SizeX64::none);
LUAU_ASSERT(op.base == noreg);
LUAU_ASSERT(op.index == noreg || op.index.size == base.size);
op.base = base;
return op;
}
} // namespace CodeGen
} // namespace Luau

View file

@ -0,0 +1,116 @@
#pragma once
#include "Luau/Common.h"
#include <stdint.h>
namespace Luau
{
namespace CodeGen
{
enum class SizeX64 : uint8_t
{
none,
byte,
word,
dword,
qword,
xmmword,
ymmword,
};
struct RegisterX64
{
SizeX64 size : 3;
uint8_t index : 5;
constexpr bool operator==(RegisterX64 rhs) const
{
return size == rhs.size && index == rhs.index;
}
constexpr bool operator!=(RegisterX64 rhs) const
{
return !(*this == rhs);
}
};
constexpr RegisterX64 noreg{SizeX64::none, 16};
constexpr RegisterX64 rip{SizeX64::none, 0};
constexpr RegisterX64 al{SizeX64::byte, 0};
constexpr RegisterX64 cl{SizeX64::byte, 1};
constexpr RegisterX64 dl{SizeX64::byte, 2};
constexpr RegisterX64 bl{SizeX64::byte, 3};
constexpr RegisterX64 eax{SizeX64::dword, 0};
constexpr RegisterX64 ecx{SizeX64::dword, 1};
constexpr RegisterX64 edx{SizeX64::dword, 2};
constexpr RegisterX64 ebx{SizeX64::dword, 3};
constexpr RegisterX64 esp{SizeX64::dword, 4};
constexpr RegisterX64 ebp{SizeX64::dword, 5};
constexpr RegisterX64 esi{SizeX64::dword, 6};
constexpr RegisterX64 edi{SizeX64::dword, 7};
constexpr RegisterX64 r8d{SizeX64::dword, 8};
constexpr RegisterX64 r9d{SizeX64::dword, 9};
constexpr RegisterX64 r10d{SizeX64::dword, 10};
constexpr RegisterX64 r11d{SizeX64::dword, 11};
constexpr RegisterX64 r12d{SizeX64::dword, 12};
constexpr RegisterX64 r13d{SizeX64::dword, 13};
constexpr RegisterX64 r14d{SizeX64::dword, 14};
constexpr RegisterX64 r15d{SizeX64::dword, 15};
constexpr RegisterX64 rax{SizeX64::qword, 0};
constexpr RegisterX64 rcx{SizeX64::qword, 1};
constexpr RegisterX64 rdx{SizeX64::qword, 2};
constexpr RegisterX64 rbx{SizeX64::qword, 3};
constexpr RegisterX64 rsp{SizeX64::qword, 4};
constexpr RegisterX64 rbp{SizeX64::qword, 5};
constexpr RegisterX64 rsi{SizeX64::qword, 6};
constexpr RegisterX64 rdi{SizeX64::qword, 7};
constexpr RegisterX64 r8{SizeX64::qword, 8};
constexpr RegisterX64 r9{SizeX64::qword, 9};
constexpr RegisterX64 r10{SizeX64::qword, 10};
constexpr RegisterX64 r11{SizeX64::qword, 11};
constexpr RegisterX64 r12{SizeX64::qword, 12};
constexpr RegisterX64 r13{SizeX64::qword, 13};
constexpr RegisterX64 r14{SizeX64::qword, 14};
constexpr RegisterX64 r15{SizeX64::qword, 15};
constexpr RegisterX64 xmm0{SizeX64::xmmword, 0};
constexpr RegisterX64 xmm1{SizeX64::xmmword, 1};
constexpr RegisterX64 xmm2{SizeX64::xmmword, 2};
constexpr RegisterX64 xmm3{SizeX64::xmmword, 3};
constexpr RegisterX64 xmm4{SizeX64::xmmword, 4};
constexpr RegisterX64 xmm5{SizeX64::xmmword, 5};
constexpr RegisterX64 xmm6{SizeX64::xmmword, 6};
constexpr RegisterX64 xmm7{SizeX64::xmmword, 7};
constexpr RegisterX64 xmm8{SizeX64::xmmword, 8};
constexpr RegisterX64 xmm9{SizeX64::xmmword, 9};
constexpr RegisterX64 xmm10{SizeX64::xmmword, 10};
constexpr RegisterX64 xmm11{SizeX64::xmmword, 11};
constexpr RegisterX64 xmm12{SizeX64::xmmword, 12};
constexpr RegisterX64 xmm13{SizeX64::xmmword, 13};
constexpr RegisterX64 xmm14{SizeX64::xmmword, 14};
constexpr RegisterX64 xmm15{SizeX64::xmmword, 15};
constexpr RegisterX64 ymm0{SizeX64::ymmword, 0};
constexpr RegisterX64 ymm1{SizeX64::ymmword, 1};
constexpr RegisterX64 ymm2{SizeX64::ymmword, 2};
constexpr RegisterX64 ymm3{SizeX64::ymmword, 3};
constexpr RegisterX64 ymm4{SizeX64::ymmword, 4};
constexpr RegisterX64 ymm5{SizeX64::ymmword, 5};
constexpr RegisterX64 ymm6{SizeX64::ymmword, 6};
constexpr RegisterX64 ymm7{SizeX64::ymmword, 7};
constexpr RegisterX64 ymm8{SizeX64::ymmword, 8};
constexpr RegisterX64 ymm9{SizeX64::ymmword, 9};
constexpr RegisterX64 ymm10{SizeX64::ymmword, 10};
constexpr RegisterX64 ymm11{SizeX64::ymmword, 11};
constexpr RegisterX64 ymm12{SizeX64::ymmword, 12};
constexpr RegisterX64 ymm13{SizeX64::ymmword, 13};
constexpr RegisterX64 ymm14{SizeX64::ymmword, 14};
constexpr RegisterX64 ymm15{SizeX64::ymmword, 15};
} // namespace CodeGen
} // namespace Luau

File diff suppressed because it is too large Load diff

View file

@ -247,7 +247,7 @@ private:
void validate() const; void validate() const;
std::string dumpCurrentFunction() const; std::string dumpCurrentFunction() const;
const uint32_t* dumpInstruction(const uint32_t* opcode, std::string& output) const; void dumpInstruction(const uint32_t* opcode, std::string& output, int targetLabel) const;
void writeFunction(std::string& ss, uint32_t id) const; void writeFunction(std::string& ss, uint32_t id) const;
void writeLineInfo(std::string& ss) const; void writeLineInfo(std::string& ss) const;

View file

@ -130,6 +130,20 @@ inline bool isSkipC(LuauOpcode op)
} }
} }
static int getJumpTarget(uint32_t insn, uint32_t pc)
{
LuauOpcode op = LuauOpcode(LUAU_INSN_OP(insn));
if (isJumpD(op))
return int(pc + LUAU_INSN_D(insn) + 1);
else if (isSkipC(op) && LUAU_INSN_C(insn))
return int(pc + LUAU_INSN_C(insn) + 1);
else if (op == LOP_JUMPX)
return int(pc + LUAU_INSN_E(insn) + 1);
else
return -1;
}
bool BytecodeBuilder::StringRef::operator==(const StringRef& other) const bool BytecodeBuilder::StringRef::operator==(const StringRef& other) const
{ {
return (data && other.data) ? (length == other.length && memcmp(data, other.data, length) == 0) : (data == other.data); return (data && other.data) ? (length == other.length && memcmp(data, other.data, length) == 0) : (data == other.data);
@ -1408,7 +1422,7 @@ void BytecodeBuilder::validate() const
} }
#endif #endif
const uint32_t* BytecodeBuilder::dumpInstruction(const uint32_t* code, std::string& result) const void BytecodeBuilder::dumpInstruction(const uint32_t* code, std::string& result, int targetLabel) const
{ {
uint32_t insn = *code++; uint32_t insn = *code++;
@ -1503,39 +1517,39 @@ const uint32_t* BytecodeBuilder::dumpInstruction(const uint32_t* code, std::stri
break; break;
case LOP_JUMP: case LOP_JUMP:
formatAppend(result, "JUMP %+d\n", LUAU_INSN_D(insn)); formatAppend(result, "JUMP L%d\n", targetLabel);
break; break;
case LOP_JUMPIF: case LOP_JUMPIF:
formatAppend(result, "JUMPIF R%d %+d\n", LUAU_INSN_A(insn), LUAU_INSN_D(insn)); formatAppend(result, "JUMPIF R%d L%d\n", LUAU_INSN_A(insn), targetLabel);
break; break;
case LOP_JUMPIFNOT: case LOP_JUMPIFNOT:
formatAppend(result, "JUMPIFNOT R%d %+d\n", LUAU_INSN_A(insn), LUAU_INSN_D(insn)); formatAppend(result, "JUMPIFNOT R%d L%d\n", LUAU_INSN_A(insn), targetLabel);
break; break;
case LOP_JUMPIFEQ: case LOP_JUMPIFEQ:
formatAppend(result, "JUMPIFEQ R%d R%d %+d\n", LUAU_INSN_A(insn), *code++, LUAU_INSN_D(insn)); formatAppend(result, "JUMPIFEQ R%d R%d L%d\n", LUAU_INSN_A(insn), *code++, targetLabel);
break; break;
case LOP_JUMPIFLE: case LOP_JUMPIFLE:
formatAppend(result, "JUMPIFLE R%d R%d %+d\n", LUAU_INSN_A(insn), *code++, LUAU_INSN_D(insn)); formatAppend(result, "JUMPIFLE R%d R%d L%d\n", LUAU_INSN_A(insn), *code++, targetLabel);
break; break;
case LOP_JUMPIFLT: case LOP_JUMPIFLT:
formatAppend(result, "JUMPIFLT R%d R%d %+d\n", LUAU_INSN_A(insn), *code++, LUAU_INSN_D(insn)); formatAppend(result, "JUMPIFLT R%d R%d L%d\n", LUAU_INSN_A(insn), *code++, targetLabel);
break; break;
case LOP_JUMPIFNOTEQ: case LOP_JUMPIFNOTEQ:
formatAppend(result, "JUMPIFNOTEQ R%d R%d %+d\n", LUAU_INSN_A(insn), *code++, LUAU_INSN_D(insn)); formatAppend(result, "JUMPIFNOTEQ R%d R%d L%d\n", LUAU_INSN_A(insn), *code++, targetLabel);
break; break;
case LOP_JUMPIFNOTLE: case LOP_JUMPIFNOTLE:
formatAppend(result, "JUMPIFNOTLE R%d R%d %+d\n", LUAU_INSN_A(insn), *code++, LUAU_INSN_D(insn)); formatAppend(result, "JUMPIFNOTLE R%d R%d L%d\n", LUAU_INSN_A(insn), *code++, targetLabel);
break; break;
case LOP_JUMPIFNOTLT: case LOP_JUMPIFNOTLT:
formatAppend(result, "JUMPIFNOTLT R%d R%d %+d\n", LUAU_INSN_A(insn), *code++, LUAU_INSN_D(insn)); formatAppend(result, "JUMPIFNOTLT R%d R%d L%d\n", LUAU_INSN_A(insn), *code++, targetLabel);
break; break;
case LOP_ADD: case LOP_ADD:
@ -1631,35 +1645,35 @@ const uint32_t* BytecodeBuilder::dumpInstruction(const uint32_t* code, std::stri
break; break;
case LOP_FORNPREP: case LOP_FORNPREP:
formatAppend(result, "FORNPREP R%d %+d\n", LUAU_INSN_A(insn), LUAU_INSN_D(insn)); formatAppend(result, "FORNPREP R%d L%d\n", LUAU_INSN_A(insn), targetLabel);
break; break;
case LOP_FORNLOOP: case LOP_FORNLOOP:
formatAppend(result, "FORNLOOP R%d %+d\n", LUAU_INSN_A(insn), LUAU_INSN_D(insn)); formatAppend(result, "FORNLOOP R%d L%d\n", LUAU_INSN_A(insn), targetLabel);
break; break;
case LOP_FORGPREP: case LOP_FORGPREP:
formatAppend(result, "FORGPREP R%d %+d\n", LUAU_INSN_A(insn), LUAU_INSN_D(insn)); formatAppend(result, "FORGPREP R%d L%d\n", LUAU_INSN_A(insn), targetLabel);
break; break;
case LOP_FORGLOOP: case LOP_FORGLOOP:
formatAppend(result, "FORGLOOP R%d %+d %d\n", LUAU_INSN_A(insn), LUAU_INSN_D(insn), *code++); formatAppend(result, "FORGLOOP R%d L%d %d\n", LUAU_INSN_A(insn), targetLabel, *code++);
break; break;
case LOP_FORGPREP_INEXT: case LOP_FORGPREP_INEXT:
formatAppend(result, "FORGPREP_INEXT R%d %+d\n", LUAU_INSN_A(insn), LUAU_INSN_D(insn)); formatAppend(result, "FORGPREP_INEXT R%d L%d\n", LUAU_INSN_A(insn), targetLabel);
break; break;
case LOP_FORGLOOP_INEXT: case LOP_FORGLOOP_INEXT:
formatAppend(result, "FORGLOOP_INEXT R%d %+d\n", LUAU_INSN_A(insn), LUAU_INSN_D(insn)); formatAppend(result, "FORGLOOP_INEXT R%d L%d\n", LUAU_INSN_A(insn), targetLabel);
break; break;
case LOP_FORGPREP_NEXT: case LOP_FORGPREP_NEXT:
formatAppend(result, "FORGPREP_NEXT R%d %+d\n", LUAU_INSN_A(insn), LUAU_INSN_D(insn)); formatAppend(result, "FORGPREP_NEXT R%d L%d\n", LUAU_INSN_A(insn), targetLabel);
break; break;
case LOP_FORGLOOP_NEXT: case LOP_FORGLOOP_NEXT:
formatAppend(result, "FORGLOOP_NEXT R%d %+d\n", LUAU_INSN_A(insn), LUAU_INSN_D(insn)); formatAppend(result, "FORGLOOP_NEXT R%d L%d\n", LUAU_INSN_A(insn), targetLabel);
break; break;
case LOP_GETVARARGS: case LOP_GETVARARGS:
@ -1675,7 +1689,7 @@ const uint32_t* BytecodeBuilder::dumpInstruction(const uint32_t* code, std::stri
break; break;
case LOP_JUMPBACK: case LOP_JUMPBACK:
formatAppend(result, "JUMPBACK %+d\n", LUAU_INSN_D(insn)); formatAppend(result, "JUMPBACK L%d\n", targetLabel);
break; break;
case LOP_LOADKX: case LOP_LOADKX:
@ -1683,26 +1697,26 @@ const uint32_t* BytecodeBuilder::dumpInstruction(const uint32_t* code, std::stri
break; break;
case LOP_JUMPX: case LOP_JUMPX:
formatAppend(result, "JUMPX %+d\n", LUAU_INSN_E(insn)); formatAppend(result, "JUMPX L%d\n", targetLabel);
break; break;
case LOP_FASTCALL: case LOP_FASTCALL:
formatAppend(result, "FASTCALL %d %+d\n", LUAU_INSN_A(insn), LUAU_INSN_C(insn)); formatAppend(result, "FASTCALL %d L%d\n", LUAU_INSN_A(insn), targetLabel);
break; break;
case LOP_FASTCALL1: case LOP_FASTCALL1:
formatAppend(result, "FASTCALL1 %d R%d %+d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn)); formatAppend(result, "FASTCALL1 %d R%d L%d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), targetLabel);
break; break;
case LOP_FASTCALL2: case LOP_FASTCALL2:
{ {
uint32_t aux = *code++; uint32_t aux = *code++;
formatAppend(result, "FASTCALL2 %d R%d R%d %+d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), aux, LUAU_INSN_C(insn)); formatAppend(result, "FASTCALL2 %d R%d R%d L%d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), aux, targetLabel);
break; break;
} }
case LOP_FASTCALL2K: case LOP_FASTCALL2K:
{ {
uint32_t aux = *code++; uint32_t aux = *code++;
formatAppend(result, "FASTCALL2K %d R%d K%d %+d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), aux, LUAU_INSN_C(insn)); formatAppend(result, "FASTCALL2K %d R%d K%d L%d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), aux, targetLabel);
break; break;
} }
@ -1712,23 +1726,24 @@ const uint32_t* BytecodeBuilder::dumpInstruction(const uint32_t* code, std::stri
case LOP_CAPTURE: case LOP_CAPTURE:
formatAppend(result, "CAPTURE %s %c%d\n", formatAppend(result, "CAPTURE %s %c%d\n",
LUAU_INSN_A(insn) == LCT_UPVAL ? "UPVAL" : LUAU_INSN_A(insn) == LCT_REF ? "REF" : LUAU_INSN_A(insn) == LCT_VAL ? "VAL" : "", LUAU_INSN_A(insn) == LCT_UPVAL ? "UPVAL"
: LUAU_INSN_A(insn) == LCT_REF ? "REF"
: LUAU_INSN_A(insn) == LCT_VAL ? "VAL"
: "",
LUAU_INSN_A(insn) == LCT_UPVAL ? 'U' : 'R', LUAU_INSN_B(insn)); LUAU_INSN_A(insn) == LCT_UPVAL ? 'U' : 'R', LUAU_INSN_B(insn));
break; break;
case LOP_JUMPIFEQK: case LOP_JUMPIFEQK:
formatAppend(result, "JUMPIFEQK R%d K%d %+d\n", LUAU_INSN_A(insn), *code++, LUAU_INSN_D(insn)); formatAppend(result, "JUMPIFEQK R%d K%d L%d\n", LUAU_INSN_A(insn), *code++, targetLabel);
break; break;
case LOP_JUMPIFNOTEQK: case LOP_JUMPIFNOTEQK:
formatAppend(result, "JUMPIFNOTEQK R%d K%d %+d\n", LUAU_INSN_A(insn), *code++, LUAU_INSN_D(insn)); formatAppend(result, "JUMPIFNOTEQK R%d K%d L%d\n", LUAU_INSN_A(insn), *code++, targetLabel);
break; break;
default: default:
LUAU_ASSERT(!"Unsupported opcode"); LUAU_ASSERT(!"Unsupported opcode");
} }
return code;
} }
std::string BytecodeBuilder::dumpCurrentFunction() const std::string BytecodeBuilder::dumpCurrentFunction() const
@ -1736,9 +1751,6 @@ std::string BytecodeBuilder::dumpCurrentFunction() const
if ((dumpFlags & Dump_Code) == 0) if ((dumpFlags & Dump_Code) == 0)
return std::string(); return std::string();
const uint32_t* code = insns.data();
const uint32_t* codeEnd = insns.data() + insns.size();
int lastLine = -1; int lastLine = -1;
size_t nextRemark = 0; size_t nextRemark = 0;
@ -1760,21 +1772,45 @@ std::string BytecodeBuilder::dumpCurrentFunction() const
} }
} }
while (code != codeEnd) std::vector<int> labels(insns.size(), -1);
// annotate valid jump targets with 0
for (size_t i = 0; i < insns.size();)
{ {
int target = getJumpTarget(insns[i], uint32_t(i));
if (target >= 0)
{
LUAU_ASSERT(size_t(target) < insns.size());
labels[target] = 0;
}
i += getOpLength(LuauOpcode(LUAU_INSN_OP(insns[i])));
LUAU_ASSERT(i <= insns.size());
}
int nextLabel = 0;
// compute label ids (sequential integers for all jump targets)
for (size_t i = 0; i < labels.size(); ++i)
if (labels[i] == 0)
labels[i] = nextLabel++;
for (size_t i = 0; i < insns.size();)
{
const uint32_t* code = &insns[i];
uint8_t op = LUAU_INSN_OP(*code); uint8_t op = LUAU_INSN_OP(*code);
uint32_t pc = uint32_t(code - insns.data());
if (op == LOP_PREPVARARGS) if (op == LOP_PREPVARARGS)
{ {
// Don't emit function header in bytecode - it's used for call dispatching and doesn't contain "interesting" information // Don't emit function header in bytecode - it's used for call dispatching and doesn't contain "interesting" information
code++; i++;
continue; continue;
} }
if (dumpFlags & Dump_Remarks) if (dumpFlags & Dump_Remarks)
{ {
while (nextRemark < debugRemarks.size() && debugRemarks[nextRemark].first == pc) while (nextRemark < debugRemarks.size() && debugRemarks[nextRemark].first == i)
{ {
formatAppend(result, "REMARK %s\n", debugRemarkBuffer.c_str() + debugRemarks[nextRemark].second); formatAppend(result, "REMARK %s\n", debugRemarkBuffer.c_str() + debugRemarks[nextRemark].second);
nextRemark++; nextRemark++;
@ -1783,7 +1819,7 @@ std::string BytecodeBuilder::dumpCurrentFunction() const
if (dumpFlags & Dump_Source) if (dumpFlags & Dump_Source)
{ {
int line = lines[pc]; int line = lines[i];
if (line > 0 && line != lastLine) if (line > 0 && line != lastLine)
{ {
@ -1794,11 +1830,17 @@ std::string BytecodeBuilder::dumpCurrentFunction() const
} }
if (dumpFlags & Dump_Lines) if (dumpFlags & Dump_Lines)
{ formatAppend(result, "%d: ", lines[i]);
formatAppend(result, "%d: ", lines[pc]);
}
code = dumpInstruction(code, result); if (labels[i] != -1)
formatAppend(result, "L%d: ", labels[i]);
int target = getJumpTarget(*code, uint32_t(i));
dumpInstruction(code, result, target >= 0 ? labels[target] : -1);
i += getOpLength(LuauOpcode(op));
LUAU_ASSERT(i <= insns.size());
} }
return result; return result;

View file

@ -15,10 +15,8 @@
#include <algorithm> #include <algorithm>
#include <bitset> #include <bitset>
#include <math.h> #include <math.h>
#include <limits.h>
LUAU_FASTFLAGVARIABLE(LuauCompileIter, false) LUAU_FASTFLAGVARIABLE(LuauCompileIter, false)
LUAU_FASTFLAGVARIABLE(LuauCompileIterNoReserve, false)
LUAU_FASTFLAGVARIABLE(LuauCompileIterNoPairs, false) LUAU_FASTFLAGVARIABLE(LuauCompileIterNoPairs, false)
LUAU_FASTINTVARIABLE(LuauCompileLoopUnrollThreshold, 25) LUAU_FASTINTVARIABLE(LuauCompileLoopUnrollThreshold, 25)
@ -176,25 +174,20 @@ struct Compiler
bool canInlineFunctionBody(AstStat* stat) bool canInlineFunctionBody(AstStat* stat)
{ {
if (FFlag::LuauCompileNestedClosureO2)
return true; // TODO: remove this function
struct CanInlineVisitor : AstVisitor struct CanInlineVisitor : AstVisitor
{ {
bool result = true; bool result = true;
bool visit(AstExprFunction* node) override bool visit(AstExprFunction* node) override
{ {
if (!FFlag::LuauCompileNestedClosureO2) result = false;
result = false;
// short-circuit to avoid analyzing nested closure bodies // short-circuit to avoid analyzing nested closure bodies
return false; return false;
} }
bool visit(AstStat* node) override
{
// loops may need to be unrolled which can result in cost amplification
result = result && !node->is<AstStatFor>();
return result;
}
}; };
CanInlineVisitor canInline; CanInlineVisitor canInline;
@ -494,7 +487,8 @@ struct Compiler
varc[i] = isConstant(expr->args.data[i]); varc[i] = isConstant(expr->args.data[i]);
// if the last argument only returns a single value, all following arguments are nil // if the last argument only returns a single value, all following arguments are nil
if (expr->args.size != 0 && !(expr->args.data[expr->args.size - 1]->is<AstExprCall>() || expr->args.data[expr->args.size - 1]->is<AstExprVarargs>())) if (expr->args.size != 0 &&
!(expr->args.data[expr->args.size - 1]->is<AstExprCall>() || expr->args.data[expr->args.size - 1]->is<AstExprVarargs>()))
for (size_t i = expr->args.size; i < func->args.size && i < 8; ++i) for (size_t i = expr->args.size; i < func->args.size && i < 8; ++i)
varc[i] = true; varc[i] = true;
@ -665,10 +659,10 @@ struct Compiler
{ {
if (func->vararg) if (func->vararg)
bytecode.addDebugRemark("inlining failed: function is variadic"); bytecode.addDebugRemark("inlining failed: function is variadic");
else if (fi) else if (!fi)
bytecode.addDebugRemark("inlining failed: complex constructs in function body");
else
bytecode.addDebugRemark("inlining failed: can't inline recursive calls"); bytecode.addDebugRemark("inlining failed: can't inline recursive calls");
else if (getfenvUsed || setfenvUsed)
bytecode.addDebugRemark("inlining failed: module uses getfenv/setfenv");
} }
} }
@ -1031,6 +1025,13 @@ struct Compiler
return cv && cv->type != Constant::Type_Unknown && !cv->isTruthful(); return cv && cv->type != Constant::Type_Unknown && !cv->isTruthful();
} }
Constant getConstant(AstExpr* node)
{
const Constant* cv = constants.find(node);
return cv ? *cv : Constant{Constant::Type_Unknown};
}
size_t compileCompareJump(AstExprBinary* expr, bool not_ = false) size_t compileCompareJump(AstExprBinary* expr, bool not_ = false)
{ {
RegScope rs(this); RegScope rs(this);
@ -1141,9 +1142,7 @@ struct Compiler
void compileConditionValue(AstExpr* node, const uint8_t* target, std::vector<size_t>& skipJump, bool onlyTruth) void compileConditionValue(AstExpr* node, const uint8_t* target, std::vector<size_t>& skipJump, bool onlyTruth)
{ {
// Optimization: we don't need to compute constant values // Optimization: we don't need to compute constant values
const Constant* cv = constants.find(node); if (const Constant* cv = constants.find(node); cv && cv->type != Constant::Type_Unknown)
if (cv && cv->type != Constant::Type_Unknown)
{ {
// note that we only need to compute the value if it's truthy; otherwise we cal fall through // note that we only need to compute the value if it's truthy; otherwise we cal fall through
if (cv->isTruthful() == onlyTruth) if (cv->isTruthful() == onlyTruth)
@ -1301,9 +1300,7 @@ struct Compiler
RegScope rs(this); RegScope rs(this);
// Optimization: when left hand side is a constant, we can emit left hand side or right hand side // Optimization: when left hand side is a constant, we can emit left hand side or right hand side
const Constant* cl = constants.find(expr->left); if (const Constant* cl = constants.find(expr->left); cl && cl->type != Constant::Type_Unknown)
if (cl && cl->type != Constant::Type_Unknown)
{ {
compileExpr(and_ == cl->isTruthful() ? expr->right : expr->left, target, targetTemp); compileExpr(and_ == cl->isTruthful() ? expr->right : expr->left, target, targetTemp);
return; return;
@ -1735,13 +1732,11 @@ struct Compiler
{ {
RegScope rs(this); RegScope rs(this);
// note: cv may be invalidated by compileExpr* so we stop using it before calling compile recursively Constant cv = getConstant(expr->index);
const Constant* cv = constants.find(expr->index);
if (cv && cv->type == Constant::Type_Number && cv->valueNumber >= 1 && cv->valueNumber <= 256 && if (cv.type == Constant::Type_Number && cv.valueNumber >= 1 && cv.valueNumber <= 256 && double(int(cv.valueNumber)) == cv.valueNumber)
double(int(cv->valueNumber)) == cv->valueNumber)
{ {
uint8_t i = uint8_t(int(cv->valueNumber) - 1); uint8_t i = uint8_t(int(cv.valueNumber) - 1);
uint8_t rt = compileExprAuto(expr->expr, rs); uint8_t rt = compileExprAuto(expr->expr, rs);
@ -1749,9 +1744,9 @@ struct Compiler
bytecode.emitABC(LOP_GETTABLEN, target, rt, i); bytecode.emitABC(LOP_GETTABLEN, target, rt, i);
} }
else if (cv && cv->type == Constant::Type_String) else if (cv.type == Constant::Type_String)
{ {
BytecodeBuilder::StringRef iname = sref(cv->getString()); BytecodeBuilder::StringRef iname = sref(cv.getString());
int32_t cid = bytecode.addConstantString(iname); int32_t cid = bytecode.addConstantString(iname);
if (cid < 0) if (cid < 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");
@ -1864,13 +1859,10 @@ struct Compiler
} }
// Optimization: if expression has a constant value, we can emit it directly // Optimization: if expression has a constant value, we can emit it directly
if (const Constant* cv = constants.find(node)) if (const Constant* cv = constants.find(node); cv && cv->type != Constant::Type_Unknown)
{ {
if (cv->type != Constant::Type_Unknown) compileExprConstant(node, cv, target);
{ return;
compileExprConstant(node, cv, target);
return;
}
} }
if (AstExprGroup* expr = node->as<AstExprGroup>()) if (AstExprGroup* expr = node->as<AstExprGroup>())
@ -2069,23 +2061,22 @@ struct Compiler
LValue compileLValueIndex(uint8_t reg, AstExpr* index, RegScope& rs) LValue compileLValueIndex(uint8_t reg, AstExpr* index, RegScope& rs)
{ {
const Constant* cv = constants.find(index); Constant cv = getConstant(index);
if (cv && cv->type == Constant::Type_Number && cv->valueNumber >= 1 && cv->valueNumber <= 256 && if (cv.type == Constant::Type_Number && cv.valueNumber >= 1 && cv.valueNumber <= 256 && double(int(cv.valueNumber)) == cv.valueNumber)
double(int(cv->valueNumber)) == cv->valueNumber)
{ {
LValue result = {LValue::Kind_IndexNumber}; LValue result = {LValue::Kind_IndexNumber};
result.reg = reg; result.reg = reg;
result.number = uint8_t(int(cv->valueNumber) - 1); result.number = uint8_t(int(cv.valueNumber) - 1);
result.location = index->location; result.location = index->location;
return result; return result;
} }
else if (cv && cv->type == Constant::Type_String) else if (cv.type == Constant::Type_String)
{ {
LValue result = {LValue::Kind_IndexName}; LValue result = {LValue::Kind_IndexName};
result.reg = reg; result.reg = reg;
result.name = sref(cv->getString()); result.name = sref(cv.getString());
result.location = index->location; result.location = index->location;
return result; return result;
@ -2520,43 +2511,22 @@ struct Compiler
pushLocal(stat->vars.data[i], uint8_t(vars + i)); pushLocal(stat->vars.data[i], uint8_t(vars + i));
} }
int getConstantShort(AstExpr* expr)
{
const Constant* c = constants.find(expr);
if (c && c->type == Constant::Type_Number)
{
double n = c->valueNumber;
if (n >= -32767 && n <= 32767 && double(int(n)) == n)
return int(n);
}
return INT_MIN;
}
bool canUnrollForBody(AstStatFor* stat) bool canUnrollForBody(AstStatFor* stat)
{ {
if (FFlag::LuauCompileNestedClosureO2)
return true; // TODO: remove this function
struct CanUnrollVisitor : AstVisitor struct CanUnrollVisitor : AstVisitor
{ {
bool result = true; bool result = true;
bool visit(AstExprFunction* node) override bool visit(AstExprFunction* node) override
{ {
if (!FFlag::LuauCompileNestedClosureO2) result = false;
result = false;
// short-circuit to avoid analyzing nested closure bodies // short-circuit to avoid analyzing nested closure bodies
return false; return false;
} }
bool visit(AstStat* node) override
{
// while we can easily unroll nested loops, our cost model doesn't take unrolling into account so this can result in code explosion
// we also avoid continue/break since they introduce control flow across iterations
result = result && !node->is<AstStatFor>() && !node->is<AstStatContinue>() && !node->is<AstStatBreak>();
return result;
}
}; };
CanUnrollVisitor canUnroll; CanUnrollVisitor canUnroll;
@ -2567,17 +2537,29 @@ struct Compiler
bool tryCompileUnrolledFor(AstStatFor* stat, int thresholdBase, int thresholdMaxBoost) bool tryCompileUnrolledFor(AstStatFor* stat, int thresholdBase, int thresholdMaxBoost)
{ {
int from = getConstantShort(stat->from); Constant one = {Constant::Type_Number};
int to = getConstantShort(stat->to); one.valueNumber = 1.0;
int step = stat->step ? getConstantShort(stat->step) : 1;
// check that limits are reasonably small and trip count can be computed Constant fromc = getConstant(stat->from);
if (from == INT_MIN || to == INT_MIN || step == INT_MIN || step == 0 || (step < 0 && to > from) || (step > 0 && to < from)) Constant toc = getConstant(stat->to);
Constant stepc = stat->step ? getConstant(stat->step) : one;
int tripCount = (fromc.type == Constant::Type_Number && toc.type == Constant::Type_Number && stepc.type == Constant::Type_Number)
? getTripCount(fromc.valueNumber, toc.valueNumber, stepc.valueNumber)
: -1;
if (tripCount < 0)
{ {
bytecode.addDebugRemark("loop unroll failed: invalid iteration count"); bytecode.addDebugRemark("loop unroll failed: invalid iteration count");
return false; return false;
} }
if (tripCount > thresholdBase)
{
bytecode.addDebugRemark("loop unroll failed: too many iterations (%d)", tripCount);
return false;
}
if (!canUnrollForBody(stat)) if (!canUnrollForBody(stat))
{ {
bytecode.addDebugRemark("loop unroll failed: unsupported loop body"); bytecode.addDebugRemark("loop unroll failed: unsupported loop body");
@ -2590,14 +2572,6 @@ struct Compiler
return false; return false;
} }
int tripCount = (to - from) / step + 1;
if (tripCount > thresholdBase)
{
bytecode.addDebugRemark("loop unroll failed: too many iterations (%d)", tripCount);
return false;
}
AstLocal* var = stat->var; AstLocal* var = stat->var;
uint64_t costModel = modelCost(stat->body, &var, 1); uint64_t costModel = modelCost(stat->body, &var, 1);
@ -2618,23 +2592,54 @@ struct Compiler
bytecode.addDebugRemark("loop unroll succeeded (iterations %d, cost %d, profit %.2fx)", tripCount, unrolledCost, double(unrollProfit) / 100); bytecode.addDebugRemark("loop unroll succeeded (iterations %d, cost %d, profit %.2fx)", tripCount, unrolledCost, double(unrollProfit) / 100);
for (int i = from; step > 0 ? i <= to : i >= to; i += step) compileUnrolledFor(stat, tripCount, fromc.valueNumber, stepc.valueNumber);
return true;
}
void compileUnrolledFor(AstStatFor* stat, int tripCount, double from, double step)
{
AstLocal* var = stat->var;
size_t oldLocals = localStack.size();
size_t oldJumps = loopJumps.size();
loops.push_back({oldLocals, nullptr});
for (int iv = 0; iv < tripCount; ++iv)
{ {
// we need to re-fold constants in the loop body with the new value; this reuses computed constant values elsewhere in the tree // we need to re-fold constants in the loop body with the new value; this reuses computed constant values elsewhere in the tree
locstants[var].type = Constant::Type_Number; locstants[var].type = Constant::Type_Number;
locstants[var].valueNumber = i; locstants[var].valueNumber = from + iv * step;
foldConstants(constants, variables, locstants, stat); foldConstants(constants, variables, locstants, stat);
size_t iterJumps = loopJumps.size();
compileStat(stat->body); compileStat(stat->body);
// all continue jumps need to go to the next iteration
size_t contLabel = bytecode.emitLabel();
for (size_t i = iterJumps; i < loopJumps.size(); ++i)
if (loopJumps[i].type == LoopJump::Continue)
patchJump(stat, loopJumps[i].label, contLabel);
} }
// all break jumps need to go past the loop
size_t endLabel = bytecode.emitLabel();
for (size_t i = oldJumps; i < loopJumps.size(); ++i)
if (loopJumps[i].type == LoopJump::Break)
patchJump(stat, loopJumps[i].label, endLabel);
loopJumps.resize(oldJumps);
loops.pop_back();
// clean up fold state in case we need to recompile - normally we compile the loop body once, but due to inlining we may need to do it again // clean up fold state in case we need to recompile - normally we compile the loop body once, but due to inlining we may need to do it again
locstants[var].type = Constant::Type_Unknown; locstants[var].type = Constant::Type_Unknown;
foldConstants(constants, variables, locstants, stat); foldConstants(constants, variables, locstants, stat);
return true;
} }
void compileStatFor(AstStatFor* stat) void compileStatFor(AstStatFor* stat)
@ -2721,16 +2726,6 @@ struct Compiler
// this puts initial values of (generator, state, index) into the loop registers // this puts initial values of (generator, state, index) into the loop registers
compileExprListTemp(stat->values, regs, 3, /* targetTop= */ true); compileExprListTemp(stat->values, regs, 3, /* targetTop= */ true);
// we don't need this because the extra stack space is just for calling the function with a loop protocol which is similar to calling
// metamethods - it should fit into the extra stack reservation
if (!FFlag::LuauCompileIterNoReserve)
{
// for the general case, we will execute a CALL for every iteration that needs to evaluate "variables... = generator(state, index)"
// this requires at least extra 3 stack slots after index
// note that these stack slots overlap with the variables so we only need to reserve them to make sure stack frame is large enough
reserveReg(stat, 3);
}
// note that we reserve at least 2 variables; this allows our fast path to assume that we need 2 variables instead of 1 or 2 // note that we reserve at least 2 variables; this allows our fast path to assume that we need 2 variables instead of 1 or 2
uint8_t vars = allocReg(stat, std::max(unsigned(stat->vars.size), 2u)); uint8_t vars = allocReg(stat, std::max(unsigned(stat->vars.size), 2u));
LUAU_ASSERT(vars == regs + 3); LUAU_ASSERT(vars == regs + 3);

View file

@ -4,6 +4,8 @@
#include "Luau/Common.h" #include "Luau/Common.h"
#include "Luau/DenseHash.h" #include "Luau/DenseHash.h"
#include <limits.h>
namespace Luau namespace Luau
{ {
namespace Compile namespace Compile
@ -11,10 +13,49 @@ namespace Compile
inline uint64_t parallelAddSat(uint64_t x, uint64_t y) inline uint64_t parallelAddSat(uint64_t x, uint64_t y)
{ {
uint64_t s = x + y; uint64_t r = x + y;
uint64_t m = s & 0x8080808080808080ull; // saturation mask uint64_t s = r & 0x8080808080808080ull; // saturation mask
return (s ^ m) | (m - (m >> 7)); return (r ^ s) | (s - (s >> 7));
}
static uint64_t parallelMulSat(uint64_t a, int b)
{
int bs = (b < 127) ? b : 127;
// multiply every other value by b, yielding 14-bit products
uint64_t l = bs * ((a >> 0) & 0x007f007f007f007full);
uint64_t h = bs * ((a >> 8) & 0x007f007f007f007full);
// each product is 14-bit, so adding 32768-128 sets high bit iff the sum is 128 or larger without an overflow
uint64_t ls = l + 0x7f807f807f807f80ull;
uint64_t hs = h + 0x7f807f807f807f80ull;
// we now merge saturation bits as well as low 7-bits of each product into one
uint64_t s = (hs & 0x8000800080008000ull) | ((ls & 0x8000800080008000ull) >> 8);
uint64_t r = ((h & 0x007f007f007f007full) << 8) | (l & 0x007f007f007f007full);
// the low bits are now correct for values that didn't saturate, and we simply need to mask them if high bit is 1
return r | (s - (s >> 7));
}
inline bool getNumber(AstExpr* node, double& result)
{
// since constant model doesn't use constant folding atm, we perform the basic extraction that's sufficient to handle positive/negative literals
if (AstExprConstantNumber* ne = node->as<AstExprConstantNumber>())
{
result = ne->value;
return true;
}
if (AstExprUnary* ue = node->as<AstExprUnary>(); ue && ue->op == AstExprUnary::Minus)
if (AstExprConstantNumber* ne = ue->expr->as<AstExprConstantNumber>())
{
result = -ne->value;
return true;
}
return false;
} }
struct Cost struct Cost
@ -46,6 +87,13 @@ struct Cost
return *this; return *this;
} }
Cost operator*(int other) const
{
Cost result;
result.model = parallelMulSat(model, other);
return result;
}
static Cost fold(const Cost& x, const Cost& y) static Cost fold(const Cost& x, const Cost& y)
{ {
uint64_t newmodel = parallelAddSat(x.model, y.model); uint64_t newmodel = parallelAddSat(x.model, y.model);
@ -173,6 +221,16 @@ struct CostVisitor : AstVisitor
*i = 0; *i = 0;
} }
void loop(AstStatBlock* body, Cost iterCost, int factor = 3)
{
Cost before = result;
result = Cost();
body->visit(this);
result = before + (result + iterCost) * factor;
}
bool visit(AstExpr* node) override bool visit(AstExpr* node) override
{ {
// note: we short-circuit the visitor traversal through any expression trees by returning false // note: we short-circuit the visitor traversal through any expression trees by returning false
@ -182,12 +240,52 @@ struct CostVisitor : AstVisitor
return false; return false;
} }
bool visit(AstStatFor* node) override
{
result += model(node->from);
result += model(node->to);
if (node->step)
result += model(node->step);
int tripCount = -1;
double from, to, step = 1;
if (getNumber(node->from, from) && getNumber(node->to, to) && (!node->step || getNumber(node->step, step)))
tripCount = getTripCount(from, to, step);
loop(node->body, 1, tripCount < 0 ? 3 : tripCount);
return false;
}
bool visit(AstStatForIn* node) override
{
for (size_t i = 0; i < node->values.size; ++i)
result += model(node->values.data[i]);
loop(node->body, 1);
return false;
}
bool visit(AstStatWhile* node) override
{
Cost condition = model(node->condition);
loop(node->body, condition);
return false;
}
bool visit(AstStatRepeat* node) override
{
Cost condition = model(node->condition);
loop(node->body, condition);
return false;
}
bool visit(AstStat* node) override bool visit(AstStat* node) override
{ {
if (node->is<AstStatIf>()) if (node->is<AstStatIf>())
result += 2; result += 2;
else if (node->is<AstStatWhile>() || node->is<AstStatRepeat>() || node->is<AstStatFor>() || node->is<AstStatForIn>())
result += 5;
else if (node->is<AstStatBreak>() || node->is<AstStatContinue>()) else if (node->is<AstStatBreak>() || node->is<AstStatContinue>())
result += 1; result += 1;
@ -254,5 +352,21 @@ int computeCost(uint64_t model, const bool* varsConst, size_t varCount)
return cost; return cost;
} }
int getTripCount(double from, double to, double step)
{
// we compute trip count in integers because that way we know that the loop math (repeated addition) is precise
int fromi = (from >= -32767 && from <= 32767 && double(int(from)) == from) ? int(from) : INT_MIN;
int toi = (to >= -32767 && to <= 32767 && double(int(to)) == to) ? int(to) : INT_MIN;
int stepi = (step >= -32767 && step <= 32767 && double(int(step)) == step) ? int(step) : INT_MIN;
if (fromi == INT_MIN || toi == INT_MIN || stepi == INT_MIN || stepi == 0)
return -1;
if ((stepi < 0 && toi > fromi) || (stepi > 0 && toi < fromi))
return 0;
return (toi - fromi) / stepi + 1;
}
} // namespace Compile } // namespace Compile
} // namespace Luau } // namespace Luau

View file

@ -14,5 +14,8 @@ uint64_t modelCost(AstNode* root, AstLocal* const* vars, size_t varCount);
// cost is computed as B - sum(Di * Ci), where B is baseline cost, Di is the discount for each variable and Ci is 1 when variable #i is constant // cost is computed as B - sum(Di * Ci), where B is baseline cost, Di is the discount for each variable and Ci is 1 when variable #i is constant
int computeCost(uint64_t model, const bool* varsConst, size_t varCount); int computeCost(uint64_t model, const bool* varsConst, size_t varCount);
// get loop trip count or -1 if we can't compute it precisely
int getTripCount(double from, double to, double step);
} // namespace Compile } // namespace Compile
} // namespace Luau } // namespace Luau

View file

@ -19,6 +19,10 @@ ANALYSIS_SOURCES=$(wildcard Analysis/src/*.cpp)
ANALYSIS_OBJECTS=$(ANALYSIS_SOURCES:%=$(BUILD)/%.o) ANALYSIS_OBJECTS=$(ANALYSIS_SOURCES:%=$(BUILD)/%.o)
ANALYSIS_TARGET=$(BUILD)/libluauanalysis.a ANALYSIS_TARGET=$(BUILD)/libluauanalysis.a
CODEGEN_SOURCES=$(wildcard CodeGen/src/*.cpp)
CODEGEN_OBJECTS=$(CODEGEN_SOURCES:%=$(BUILD)/%.o)
CODEGEN_TARGET=$(BUILD)/libluaucodegen.a
VM_SOURCES=$(wildcard VM/src/*.cpp) VM_SOURCES=$(wildcard VM/src/*.cpp)
VM_OBJECTS=$(VM_SOURCES:%=$(BUILD)/%.o) VM_OBJECTS=$(VM_SOURCES:%=$(BUILD)/%.o)
VM_TARGET=$(BUILD)/libluauvm.a VM_TARGET=$(BUILD)/libluauvm.a
@ -47,7 +51,7 @@ ifneq ($(flags),)
TESTS_ARGS+=--fflags=$(flags) TESTS_ARGS+=--fflags=$(flags)
endif endif
OBJECTS=$(AST_OBJECTS) $(COMPILER_OBJECTS) $(ANALYSIS_OBJECTS) $(VM_OBJECTS) $(ISOCLINE_OBJECTS) $(TESTS_OBJECTS) $(CLI_OBJECTS) $(FUZZ_OBJECTS) OBJECTS=$(AST_OBJECTS) $(COMPILER_OBJECTS) $(ANALYSIS_OBJECTS) $(CODEGEN_OBJECTS) $(VM_OBJECTS) $(ISOCLINE_OBJECTS) $(TESTS_OBJECTS) $(CLI_OBJECTS) $(FUZZ_OBJECTS)
# common flags # common flags
CXXFLAGS=-g -Wall CXXFLAGS=-g -Wall
@ -90,15 +94,16 @@ ifeq ($(config),fuzz)
endif endif
# target-specific flags # target-specific flags
$(AST_OBJECTS): CXXFLAGS+=-std=c++17 -IAst/include $(AST_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include
$(COMPILER_OBJECTS): CXXFLAGS+=-std=c++17 -ICompiler/include -IAst/include $(COMPILER_OBJECTS): CXXFLAGS+=-std=c++17 -ICompiler/include -ICommon/include -IAst/include
$(ANALYSIS_OBJECTS): CXXFLAGS+=-std=c++17 -IAst/include -IAnalysis/include $(ANALYSIS_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -IAnalysis/include
$(VM_OBJECTS): CXXFLAGS+=-std=c++11 -IVM/include $(CODEGEN_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -ICodeGen/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 -IAst/include -ICompiler/include -IAnalysis/include -IVM/include -ICLI -Iextern $(TESTS_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IAnalysis/include -ICodeGen/include -IVM/include -ICLI -Iextern
$(REPL_CLI_OBJECTS): CXXFLAGS+=-std=c++17 -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 -Iextern -Iextern/isocline/include
$(ANALYZE_CLI_OBJECTS): CXXFLAGS+=-std=c++17 -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 -IAst/include -ICompiler/include -IAnalysis/include -IVM/include $(FUZZ_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IAnalysis/include -IVM/include
$(TESTS_TARGET): LDFLAGS+=-lpthread $(TESTS_TARGET): LDFLAGS+=-lpthread
$(REPL_CLI_TARGET): LDFLAGS+=-lpthread $(REPL_CLI_TARGET): LDFLAGS+=-lpthread
@ -126,7 +131,7 @@ coverage: $(TESTS_TARGET)
llvm-cov export -ignore-filename-regex=\(tests\|extern\|CLI\)/.* -format lcov --instr-profile default.profdata build/coverage/luau-tests >coverage.info llvm-cov export -ignore-filename-regex=\(tests\|extern\|CLI\)/.* -format lcov --instr-profile default.profdata build/coverage/luau-tests >coverage.info
format: format:
find . -name '*.h' -or -name '*.cpp' | xargs clang-format -i find . -name '*.h' -or -name '*.cpp' | xargs clang-format-11 -i
luau-size: luau luau-size: luau
nm --print-size --demangle luau | grep ' t void luau_execute<false>' | awk -F ' ' '{sum += strtonum("0x" $$2)} END {print sum " interpreter" }' nm --print-size --demangle luau | grep ' t void luau_execute<false>' | awk -F ' ' '{sum += strtonum("0x" $$2)} END {print sum " interpreter" }'
@ -140,7 +145,7 @@ luau-analyze: $(ANALYZE_CLI_TARGET)
ln -fs $^ $@ ln -fs $^ $@
# executable targets # executable targets
$(TESTS_TARGET): $(TESTS_OBJECTS) $(ANALYSIS_TARGET) $(COMPILER_TARGET) $(AST_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) $(VM_TARGET) $(ISOCLINE_TARGET)
$(ANALYZE_CLI_TARGET): $(ANALYZE_CLI_OBJECTS) $(ANALYSIS_TARGET) $(AST_TARGET) $(ANALYZE_CLI_TARGET): $(ANALYZE_CLI_OBJECTS) $(ANALYSIS_TARGET) $(AST_TARGET)
@ -158,10 +163,11 @@ fuzz-prototest: $(BUILD)/fuzz/prototest.cpp.o $(BUILD)/fuzz/protoprint.cpp.o $(B
$(AST_TARGET): $(AST_OBJECTS) $(AST_TARGET): $(AST_OBJECTS)
$(COMPILER_TARGET): $(COMPILER_OBJECTS) $(COMPILER_TARGET): $(COMPILER_OBJECTS)
$(ANALYSIS_TARGET): $(ANALYSIS_OBJECTS) $(ANALYSIS_TARGET): $(ANALYSIS_OBJECTS)
$(CODEGEN_TARGET): $(CODEGEN_OBJECTS)
$(VM_TARGET): $(VM_OBJECTS) $(VM_TARGET): $(VM_OBJECTS)
$(ISOCLINE_TARGET): $(ISOCLINE_OBJECTS) $(ISOCLINE_TARGET): $(ISOCLINE_OBJECTS)
$(AST_TARGET) $(COMPILER_TARGET) $(ANALYSIS_TARGET) $(VM_TARGET) $(ISOCLINE_TARGET): $(AST_TARGET) $(COMPILER_TARGET) $(ANALYSIS_TARGET) $(CODEGEN_TARGET) $(VM_TARGET) $(ISOCLINE_TARGET):
ar rcs $@ $^ ar rcs $@ $^
# object file targets # object file targets

View file

@ -1,7 +1,15 @@
# Luau.Common Sources
# Note: Until 3.19, INTERFACE targets couldn't have SOURCES property set
if(NOT ${CMAKE_VERSION} VERSION_LESS "3.19")
target_sources(Luau.Common PRIVATE
Common/include/Luau/Common.h
Common/include/Luau/Bytecode.h
)
endif()
# Luau.Ast Sources # Luau.Ast Sources
target_sources(Luau.Ast PRIVATE target_sources(Luau.Ast PRIVATE
Ast/include/Luau/Ast.h Ast/include/Luau/Ast.h
Ast/include/Luau/Common.h
Ast/include/Luau/Confusables.h Ast/include/Luau/Confusables.h
Ast/include/Luau/DenseHash.h Ast/include/Luau/DenseHash.h
Ast/include/Luau/Lexer.h Ast/include/Luau/Lexer.h
@ -23,7 +31,6 @@ target_sources(Luau.Ast PRIVATE
# Luau.Compiler Sources # Luau.Compiler Sources
target_sources(Luau.Compiler PRIVATE target_sources(Luau.Compiler PRIVATE
Compiler/include/Luau/Bytecode.h
Compiler/include/Luau/BytecodeBuilder.h Compiler/include/Luau/BytecodeBuilder.h
Compiler/include/Luau/Compiler.h Compiler/include/Luau/Compiler.h
Compiler/include/luacode.h Compiler/include/luacode.h
@ -43,6 +50,17 @@ target_sources(Luau.Compiler PRIVATE
Compiler/src/ValueTracking.h Compiler/src/ValueTracking.h
) )
# Luau.CodeGen Sources
target_sources(Luau.CodeGen PRIVATE
CodeGen/include/Luau/AssemblyBuilderX64.h
CodeGen/include/Luau/Condition.h
CodeGen/include/Luau/Label.h
CodeGen/include/Luau/OperandX64.h
CodeGen/include/Luau/RegisterX64.h
CodeGen/src/AssemblyBuilderX64.cpp
)
# Luau.Analysis Sources # Luau.Analysis Sources
target_sources(Luau.Analysis PRIVATE target_sources(Luau.Analysis PRIVATE
Analysis/include/Luau/AstQuery.h Analysis/include/Luau/AstQuery.h
@ -54,6 +72,7 @@ target_sources(Luau.Analysis PRIVATE
Analysis/include/Luau/Error.h Analysis/include/Luau/Error.h
Analysis/include/Luau/FileResolver.h Analysis/include/Luau/FileResolver.h
Analysis/include/Luau/Frontend.h Analysis/include/Luau/Frontend.h
Analysis/include/Luau/Instantiation.h
Analysis/include/Luau/IostreamHelpers.h Analysis/include/Luau/IostreamHelpers.h
Analysis/include/Luau/JsonEncoder.h Analysis/include/Luau/JsonEncoder.h
Analysis/include/Luau/Linter.h Analysis/include/Luau/Linter.h
@ -93,6 +112,7 @@ target_sources(Luau.Analysis PRIVATE
Analysis/src/Clone.cpp Analysis/src/Clone.cpp
Analysis/src/Error.cpp Analysis/src/Error.cpp
Analysis/src/Frontend.cpp Analysis/src/Frontend.cpp
Analysis/src/Instantiation.cpp
Analysis/src/IostreamHelpers.cpp Analysis/src/IostreamHelpers.cpp
Analysis/src/JsonEncoder.cpp Analysis/src/JsonEncoder.cpp
Analysis/src/Linter.cpp Analysis/src/Linter.cpp
@ -267,6 +287,7 @@ if(TARGET Luau.UnitTest)
tests/TypeVar.test.cpp tests/TypeVar.test.cpp
tests/Variant.test.cpp tests/Variant.test.cpp
tests/VisitTypeVar.test.cpp tests/VisitTypeVar.test.cpp
tests/AssemblyBuilderX64.test.cpp
tests/main.cpp) tests/main.cpp)
endif() endif()

View file

@ -487,13 +487,12 @@ void* lua_tolightuserdata(lua_State* L, int idx)
void* lua_touserdata(lua_State* L, int idx) void* lua_touserdata(lua_State* L, int idx)
{ {
StkId o = index2addr(L, idx); StkId o = index2addr(L, idx);
// fast-path: check userdata first since it is most likely the expected result
if (ttisuserdata(o)) if (ttisuserdata(o))
return uvalue(o)->data; return uvalue(o)->data;
else if (ttislightuserdata(o)) else if (ttislightuserdata(o))
return pvalue(o); return pvalue(o);
else else
return NULL; return NULL;
} }
void* lua_touserdatatagged(lua_State* L, int idx, int tag) void* lua_touserdatatagged(lua_State* L, int idx, int tag)

View file

@ -15,8 +15,6 @@
#include <intrin.h> #include <intrin.h>
#endif #endif
LUAU_FASTFLAGVARIABLE(LuauFixBuiltinsStackLimit, false)
// luauF functions implement FASTCALL instruction that performs a direct execution of some builtin functions from the VM // luauF functions implement FASTCALL instruction that performs a direct execution of some builtin functions from the VM
// The rule of thumb is that FASTCALL functions can not call user code, yield, fail, or reallocate stack. // The rule of thumb is that FASTCALL functions can not call user code, yield, fail, or reallocate stack.
// If types of the arguments mismatch, luauF_* needs to return -1 and the execution will fall back to the usual call path // If types of the arguments mismatch, luauF_* needs to return -1 and the execution will fall back to the usual call path
@ -1005,7 +1003,7 @@ static int luauF_tunpack(lua_State* L, StkId res, TValue* arg0, int nresults, St
else if (nparams == 3 && ttisnumber(args) && ttisnumber(args + 1) && nvalue(args) == 1.0) else if (nparams == 3 && ttisnumber(args) && ttisnumber(args + 1) && nvalue(args) == 1.0)
n = int(nvalue(args + 1)); n = int(nvalue(args + 1));
if (n >= 0 && n <= t->sizearray && cast_int(L->stack_last - res) >= n && (!FFlag::LuauFixBuiltinsStackLimit || n + nparams <= LUAI_MAXCSTACK)) if (n >= 0 && n <= t->sizearray && cast_int(L->stack_last - res) >= n && n + nparams <= LUAI_MAXCSTACK)
{ {
TValue* array = t->array; TValue* array = t->array;
for (int i = 0; i < n; ++i) for (int i = 0; i < n; ++i)

View file

@ -3,7 +3,4 @@
#pragma once #pragma once
// This is a forwarding header for Luau bytecode definition // This is a forwarding header for Luau bytecode definition
// Luau consists of several components, including compiler (Ast, Compiler) and VM (virtual machine) #include "Luau/Bytecode.h"
// These components are fully independent, but they both need the bytecode format defined in this header
// so it needs to be shared.
#include "../../Compiler/include/Luau/Bytecode.h"

View file

@ -7,11 +7,7 @@
#include "luaconf.h" #include "luaconf.h"
// This is a forwarding header for Luau common definition (assertions, flags) #include "Luau/Common.h"
// Luau consists of several components, including compiler (Ast, Compiler) and VM (virtual machine)
// These components are fully independent, but they need a common set of utilities defined in this header
// so it needs to be shared.
#include "../../Ast/include/Luau/Common.h"
typedef LUAI_USER_ALIGNMENT_T L_Umaxalign; typedef LUAI_USER_ALIGNMENT_T L_Umaxalign;

View file

@ -245,6 +245,7 @@ void luaD_call(lua_State* L, StkId func, int nResults)
if (!oldactive) if (!oldactive)
resetbit(L->stackstate, THREAD_ACTIVEBIT); resetbit(L->stackstate, THREAD_ACTIVEBIT);
} }
L->nCcalls--; L->nCcalls--;
luaC_checkGC(L); luaC_checkGC(L);
} }

View file

@ -694,7 +694,7 @@ static void luau_execute(lua_State* L)
} }
else else
{ {
// slow-path, may invoke Lua calls via __index metamethod // slow-path, may invoke Lua calls via __newindex metamethod
L->cachedslot = slot; L->cachedslot = slot;
VM_PROTECT(luaV_settable(L, rb, kv, ra)); VM_PROTECT(luaV_settable(L, rb, kv, ra));
// save cachedslot to accelerate future lookups; patches currently executing instruction since pc-2 rolls back two pc++ // save cachedslot to accelerate future lookups; patches currently executing instruction since pc-2 rolls back two pc++
@ -704,7 +704,7 @@ static void luau_execute(lua_State* L)
} }
else else
{ {
// fast-path: user data with C __index TM // fast-path: user data with C __newindex TM
const TValue* fn = 0; const TValue* fn = 0;
if (ttisuserdata(rb) && (fn = fasttm(L, uvalue(rb)->metatable, TM_NEWINDEX)) && ttisfunction(fn) && clvalue(fn)->isC) if (ttisuserdata(rb) && (fn = fasttm(L, uvalue(rb)->metatable, TM_NEWINDEX)) && ttisfunction(fn) && clvalue(fn)->isC)
{ {
@ -725,7 +725,7 @@ static void luau_execute(lua_State* L)
} }
else else
{ {
// slow-path, may invoke Lua calls via __index metamethod // slow-path, may invoke Lua calls via __newindex metamethod
VM_PROTECT(luaV_settable(L, rb, kv, ra)); VM_PROTECT(luaV_settable(L, rb, kv, ra));
VM_NEXT(); VM_NEXT();
} }
@ -2358,9 +2358,8 @@ static void luau_execute(lua_State* L)
// fast-path: ipairs/inext // fast-path: ipairs/inext
if (cl->env->safeenv && ttistable(ra + 1) && ttisnumber(ra + 2) && nvalue(ra + 2) == 0.0) if (cl->env->safeenv && ttistable(ra + 1) && ttisnumber(ra + 2) && nvalue(ra + 2) == 0.0)
{ {
if (FFlag::LuauIter) setnilvalue(ra);
setnilvalue(ra); /* ra+1 is already the table */
setpvalue(ra + 2, reinterpret_cast<void*>(uintptr_t(0))); setpvalue(ra + 2, reinterpret_cast<void*>(uintptr_t(0)));
} }
else if (FFlag::LuauIter && !ttisfunction(ra)) else if (FFlag::LuauIter && !ttisfunction(ra))
@ -2380,7 +2379,7 @@ static void luau_execute(lua_State* L)
StkId ra = VM_REG(LUAU_INSN_A(insn)); StkId ra = VM_REG(LUAU_INSN_A(insn));
// fast-path: ipairs/inext // fast-path: ipairs/inext
if (ttistable(ra + 1) && ttislightuserdata(ra + 2)) if (ttisnil(ra) && ttistable(ra + 1) && ttislightuserdata(ra + 2))
{ {
Table* h = hvalue(ra + 1); Table* h = hvalue(ra + 1);
int index = int(reinterpret_cast<uintptr_t>(pvalue(ra + 2))); int index = int(reinterpret_cast<uintptr_t>(pvalue(ra + 2)));
@ -2431,9 +2430,8 @@ static void luau_execute(lua_State* L)
// fast-path: pairs/next // fast-path: pairs/next
if (cl->env->safeenv && ttistable(ra + 1) && ttisnil(ra + 2)) if (cl->env->safeenv && ttistable(ra + 1) && ttisnil(ra + 2))
{ {
if (FFlag::LuauIter) setnilvalue(ra);
setnilvalue(ra); /* ra+1 is already the table */
setpvalue(ra + 2, reinterpret_cast<void*>(uintptr_t(0))); setpvalue(ra + 2, reinterpret_cast<void*>(uintptr_t(0)));
} }
else if (FFlag::LuauIter && !ttisfunction(ra)) else if (FFlag::LuauIter && !ttisfunction(ra))
@ -2453,7 +2451,7 @@ static void luau_execute(lua_State* L)
StkId ra = VM_REG(LUAU_INSN_A(insn)); StkId ra = VM_REG(LUAU_INSN_A(insn));
// fast-path: pairs/next // fast-path: pairs/next
if (ttistable(ra + 1) && ttislightuserdata(ra + 2)) if (ttisnil(ra) && ttistable(ra + 1) && ttislightuserdata(ra + 2))
{ {
Table* h = hvalue(ra + 1); Table* h = hvalue(ra + 1);
int index = int(reinterpret_cast<uintptr_t>(pvalue(ra + 2))); int index = int(reinterpret_cast<uintptr_t>(pvalue(ra + 2)));

View file

@ -0,0 +1,410 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/AssemblyBuilderX64.h"
#include "Luau/StringUtils.h"
#include "doctest.h"
#include <functional>
#include <string.h>
using namespace Luau::CodeGen;
std::string bytecodeAsArray(const std::vector<uint8_t>& bytecode)
{
std::string result = "{";
for (size_t i = 0; i < bytecode.size(); i++)
Luau::formatAppend(result, "%s0x%02x", i == 0 ? "" : ", ", bytecode[i]);
return result.append("}");
}
class AssemblyBuilderX64Fixture
{
public:
void check(std::function<void(AssemblyBuilderX64& build)> f, std::vector<uint8_t> result)
{
AssemblyBuilderX64 build(/* logText= */ false);
f(build);
build.finalize();
if (build.code != result)
{
printf("Expected: %s\nReceived: %s\n", bytecodeAsArray(result).c_str(), bytecodeAsArray(build.code).c_str());
CHECK(false);
}
}
};
TEST_SUITE_BEGIN("x64Assembly");
#define SINGLE_COMPARE(inst, ...) \
check( \
[](AssemblyBuilderX64& build) { \
build.inst; \
}, \
{__VA_ARGS__})
TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "BaseBinaryInstructionForms")
{
// reg, reg
SINGLE_COMPARE(add(rax, rcx), 0x48, 0x03, 0xc1);
SINGLE_COMPARE(add(rsp, r12), 0x49, 0x03, 0xe4);
SINGLE_COMPARE(add(r14, r10), 0x4d, 0x03, 0xf2);
// reg, imm
SINGLE_COMPARE(add(rax, 0), 0x48, 0x83, 0xc0, 0x00);
SINGLE_COMPARE(add(rax, 0x7f), 0x48, 0x83, 0xc0, 0x7f);
SINGLE_COMPARE(add(rax, 0x80), 0x48, 0x81, 0xc0, 0x80, 0x00, 0x00, 0x00);
SINGLE_COMPARE(add(r10, 0x7fffffff), 0x49, 0x81, 0xc2, 0xff, 0xff, 0xff, 0x7f);
// reg, [reg]
SINGLE_COMPARE(add(rax, qword[rax]), 0x48, 0x03, 0x00);
SINGLE_COMPARE(add(rax, qword[rbx]), 0x48, 0x03, 0x03);
SINGLE_COMPARE(add(rax, qword[rsp]), 0x48, 0x03, 0x04, 0x24);
SINGLE_COMPARE(add(rax, qword[rbp]), 0x48, 0x03, 0x45, 0x00);
SINGLE_COMPARE(add(rax, qword[r10]), 0x49, 0x03, 0x02);
SINGLE_COMPARE(add(rax, qword[r12]), 0x49, 0x03, 0x04, 0x24);
SINGLE_COMPARE(add(rax, qword[r13]), 0x49, 0x03, 0x45, 0x00);
SINGLE_COMPARE(add(r12, qword[rax]), 0x4c, 0x03, 0x20);
SINGLE_COMPARE(add(r12, qword[rbx]), 0x4c, 0x03, 0x23);
SINGLE_COMPARE(add(r12, qword[rsp]), 0x4c, 0x03, 0x24, 0x24);
SINGLE_COMPARE(add(r12, qword[rbp]), 0x4c, 0x03, 0x65, 0x00);
SINGLE_COMPARE(add(r12, qword[r10]), 0x4d, 0x03, 0x22);
SINGLE_COMPARE(add(r12, qword[r12]), 0x4d, 0x03, 0x24, 0x24);
SINGLE_COMPARE(add(r12, qword[r13]), 0x4d, 0x03, 0x65, 0x00);
// reg, [base+imm8]
SINGLE_COMPARE(add(rax, qword[rax + 0x1b]), 0x48, 0x03, 0x40, 0x1b);
SINGLE_COMPARE(add(rax, qword[rbx + 0x1b]), 0x48, 0x03, 0x43, 0x1b);
SINGLE_COMPARE(add(rax, qword[rsp + 0x1b]), 0x48, 0x03, 0x44, 0x24, 0x1b);
SINGLE_COMPARE(add(rax, qword[rbp + 0x1b]), 0x48, 0x03, 0x45, 0x1b);
SINGLE_COMPARE(add(rax, qword[r10 + 0x1b]), 0x49, 0x03, 0x42, 0x1b);
SINGLE_COMPARE(add(rax, qword[r12 + 0x1b]), 0x49, 0x03, 0x44, 0x24, 0x1b);
SINGLE_COMPARE(add(rax, qword[r13 + 0x1b]), 0x49, 0x03, 0x45, 0x1b);
SINGLE_COMPARE(add(r12, qword[rax + 0x1b]), 0x4c, 0x03, 0x60, 0x1b);
SINGLE_COMPARE(add(r12, qword[rbx + 0x1b]), 0x4c, 0x03, 0x63, 0x1b);
SINGLE_COMPARE(add(r12, qword[rsp + 0x1b]), 0x4c, 0x03, 0x64, 0x24, 0x1b);
SINGLE_COMPARE(add(r12, qword[rbp + 0x1b]), 0x4c, 0x03, 0x65, 0x1b);
SINGLE_COMPARE(add(r12, qword[r10 + 0x1b]), 0x4d, 0x03, 0x62, 0x1b);
SINGLE_COMPARE(add(r12, qword[r12 + 0x1b]), 0x4d, 0x03, 0x64, 0x24, 0x1b);
SINGLE_COMPARE(add(r12, qword[r13 + 0x1b]), 0x4d, 0x03, 0x65, 0x1b);
// reg, [base+imm32]
SINGLE_COMPARE(add(rax, qword[rax + 0xabab]), 0x48, 0x03, 0x80, 0xab, 0xab, 0x00, 0x00);
SINGLE_COMPARE(add(rax, qword[rbx + 0xabab]), 0x48, 0x03, 0x83, 0xab, 0xab, 0x00, 0x00);
SINGLE_COMPARE(add(rax, qword[rsp + 0xabab]), 0x48, 0x03, 0x84, 0x24, 0xab, 0xab, 0x00, 0x00);
SINGLE_COMPARE(add(rax, qword[rbp + 0xabab]), 0x48, 0x03, 0x85, 0xab, 0xab, 0x00, 0x00);
SINGLE_COMPARE(add(rax, qword[r10 + 0xabab]), 0x49, 0x03, 0x82, 0xab, 0xab, 0x00, 0x00);
SINGLE_COMPARE(add(rax, qword[r12 + 0xabab]), 0x49, 0x03, 0x84, 0x24, 0xab, 0xab, 0x00, 0x00);
SINGLE_COMPARE(add(rax, qword[r13 + 0xabab]), 0x49, 0x03, 0x85, 0xab, 0xab, 0x00, 0x00);
SINGLE_COMPARE(add(r12, qword[rax + 0xabab]), 0x4c, 0x03, 0xa0, 0xab, 0xab, 0x00, 0x00);
SINGLE_COMPARE(add(r12, qword[rbx + 0xabab]), 0x4c, 0x03, 0xa3, 0xab, 0xab, 0x00, 0x00);
SINGLE_COMPARE(add(r12, qword[rsp + 0xabab]), 0x4c, 0x03, 0xa4, 0x24, 0xab, 0xab, 0x00, 0x00);
SINGLE_COMPARE(add(r12, qword[rbp + 0xabab]), 0x4c, 0x03, 0xa5, 0xab, 0xab, 0x00, 0x00);
SINGLE_COMPARE(add(r12, qword[r10 + 0xabab]), 0x4d, 0x03, 0xa2, 0xab, 0xab, 0x00, 0x00);
SINGLE_COMPARE(add(r12, qword[r12 + 0xabab]), 0x4d, 0x03, 0xa4, 0x24, 0xab, 0xab, 0x00, 0x00);
SINGLE_COMPARE(add(r12, qword[r13 + 0xabab]), 0x4d, 0x03, 0xa5, 0xab, 0xab, 0x00, 0x00);
// reg, [index*scale]
SINGLE_COMPARE(add(rax, qword[rax * 2]), 0x48, 0x03, 0x04, 0x45, 0x00, 0x00, 0x00, 0x00);
SINGLE_COMPARE(add(rax, qword[rbx * 2]), 0x48, 0x03, 0x04, 0x5d, 0x00, 0x00, 0x00, 0x00);
SINGLE_COMPARE(add(rax, qword[rbp * 2]), 0x48, 0x03, 0x04, 0x6d, 0x00, 0x00, 0x00, 0x00);
SINGLE_COMPARE(add(rax, qword[r10 * 2]), 0x4a, 0x03, 0x04, 0x55, 0x00, 0x00, 0x00, 0x00);
SINGLE_COMPARE(add(rax, qword[r12 * 2]), 0x4a, 0x03, 0x04, 0x65, 0x00, 0x00, 0x00, 0x00);
SINGLE_COMPARE(add(rax, qword[r13 * 2]), 0x4a, 0x03, 0x04, 0x6d, 0x00, 0x00, 0x00, 0x00);
SINGLE_COMPARE(add(r12, qword[rax * 2]), 0x4c, 0x03, 0x24, 0x45, 0x00, 0x00, 0x00, 0x00);
SINGLE_COMPARE(add(r12, qword[rbx * 2]), 0x4c, 0x03, 0x24, 0x5d, 0x00, 0x00, 0x00, 0x00);
SINGLE_COMPARE(add(r12, qword[rbp * 2]), 0x4c, 0x03, 0x24, 0x6d, 0x00, 0x00, 0x00, 0x00);
SINGLE_COMPARE(add(r12, qword[r10 * 2]), 0x4e, 0x03, 0x24, 0x55, 0x00, 0x00, 0x00, 0x00);
SINGLE_COMPARE(add(r12, qword[r12 * 2]), 0x4e, 0x03, 0x24, 0x65, 0x00, 0x00, 0x00, 0x00);
SINGLE_COMPARE(add(r12, qword[r13 * 2]), 0x4e, 0x03, 0x24, 0x6d, 0x00, 0x00, 0x00, 0x00);
// reg, [base+index*scale+imm]
SINGLE_COMPARE(add(rax, qword[rax + rax * 2]), 0x48, 0x03, 0x04, 0x40);
SINGLE_COMPARE(add(rax, qword[rax + rbx * 2 + 0x1b]), 0x48, 0x03, 0x44, 0x58, 0x1b);
SINGLE_COMPARE(add(rax, qword[rax + rbp * 2]), 0x48, 0x03, 0x04, 0x68);
SINGLE_COMPARE(add(rax, qword[rax + rbp + 0xabab]), 0x48, 0x03, 0x84, 0x28, 0xAB, 0xab, 0x00, 0x00);
SINGLE_COMPARE(add(rax, qword[rax + r12 + 0x1b]), 0x4a, 0x03, 0x44, 0x20, 0x1b);
SINGLE_COMPARE(add(rax, qword[rax + r12 * 4 + 0xabab]), 0x4a, 0x03, 0x84, 0xa0, 0xab, 0xab, 0x00, 0x00);
SINGLE_COMPARE(add(rax, qword[rax + r13 * 2 + 0x1b]), 0x4a, 0x03, 0x44, 0x68, 0x1b);
SINGLE_COMPARE(add(rax, qword[rax + r13 + 0xabab]), 0x4a, 0x03, 0x84, 0x28, 0xab, 0xab, 0x00, 0x00);
SINGLE_COMPARE(add(r12, qword[rax + r12 * 2]), 0x4e, 0x03, 0x24, 0x60);
SINGLE_COMPARE(add(r12, qword[rax + r13 + 0xabab]), 0x4e, 0x03, 0xA4, 0x28, 0xab, 0xab, 0x00, 0x00);
SINGLE_COMPARE(add(r12, qword[rax + rbp * 2 + 0x1b]), 0x4c, 0x03, 0x64, 0x68, 0x1b);
// reg, [imm32]
SINGLE_COMPARE(add(rax, qword[0]), 0x48, 0x03, 0x04, 0x25, 0x00, 0x00, 0x00, 0x00);
SINGLE_COMPARE(add(rax, qword[0xabab]), 0x48, 0x03, 0x04, 0x25, 0xab, 0xab, 0x00, 0x00);
// [addr], reg
SINGLE_COMPARE(add(qword[rax], rax), 0x48, 0x01, 0x00);
SINGLE_COMPARE(add(qword[rax + rax * 4 + 0xabab], rax), 0x48, 0x01, 0x84, 0x80, 0xab, 0xab, 0x00, 0x00);
SINGLE_COMPARE(add(qword[rbx + rax * 2 + 0x1b], rax), 0x48, 0x01, 0x44, 0x43, 0x1b);
SINGLE_COMPARE(add(qword[rbx + rbp * 2 + 0x1b], rax), 0x48, 0x01, 0x44, 0x6b, 0x1b);
SINGLE_COMPARE(add(qword[rbp + rbp * 4 + 0xabab], rax), 0x48, 0x01, 0x84, 0xad, 0xab, 0xab, 0x00, 0x00);
SINGLE_COMPARE(add(qword[rbp + r12 + 0x1b], rax), 0x4a, 0x01, 0x44, 0x25, 0x1b);
SINGLE_COMPARE(add(qword[r12], rax), 0x49, 0x01, 0x04, 0x24);
SINGLE_COMPARE(add(qword[r13 + rbx + 0xabab], rax), 0x49, 0x01, 0x84, 0x1d, 0xab, 0xab, 0x00, 0x00);
SINGLE_COMPARE(add(qword[rax + r13 * 2 + 0x1b], rsi), 0x4a, 0x01, 0x74, 0x68, 0x1b);
SINGLE_COMPARE(add(qword[rbp + rbx * 2], rsi), 0x48, 0x01, 0x74, 0x5d, 0x00);
SINGLE_COMPARE(add(qword[rsp + r10 * 2 + 0x1b], r10), 0x4e, 0x01, 0x54, 0x54, 0x1b);
}
TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "BaseUnaryInstructionForms")
{
SINGLE_COMPARE(div(rcx), 0x48, 0xf7, 0xf1);
SINGLE_COMPARE(idiv(qword[rax]), 0x48, 0xf7, 0x38);
SINGLE_COMPARE(mul(qword[rax + rbx]), 0x48, 0xf7, 0x24, 0x18);
SINGLE_COMPARE(neg(r9), 0x49, 0xf7, 0xd9);
SINGLE_COMPARE(not_(r12), 0x49, 0xf7, 0xd4);
}
TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "FormsOfMov")
{
SINGLE_COMPARE(mov(rcx, 1), 0x48, 0xb9, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
SINGLE_COMPARE(mov64(rcx, 0x1234567812345678ll), 0x48, 0xb9, 0x78, 0x56, 0x34, 0x12, 0x78, 0x56, 0x34, 0x12);
SINGLE_COMPARE(mov(ecx, 2), 0xb9, 0x02, 0x00, 0x00, 0x00);
SINGLE_COMPARE(mov(cl, 2), 0xb1, 0x02);
SINGLE_COMPARE(mov(rcx, qword[rdi]), 0x48, 0x8b, 0x0f);
SINGLE_COMPARE(mov(dword[rax], 0xabcd), 0xc7, 0x00, 0xcd, 0xab, 0x00, 0x00);
SINGLE_COMPARE(mov(r13, 1), 0x49, 0xbd, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
SINGLE_COMPARE(mov64(r13, 0x1234567812345678ll), 0x49, 0xbd, 0x78, 0x56, 0x34, 0x12, 0x78, 0x56, 0x34, 0x12);
SINGLE_COMPARE(mov(r13d, 2), 0x41, 0xbd, 0x02, 0x00, 0x00, 0x00);
SINGLE_COMPARE(mov(r13, qword[r12]), 0x4d, 0x8b, 0x2c, 0x24);
SINGLE_COMPARE(mov(dword[r13], 0xabcd), 0x41, 0xc7, 0x45, 0x00, 0xcd, 0xab, 0x00, 0x00);
SINGLE_COMPARE(mov(qword[rdx], r9), 0x4c, 0x89, 0x0a);
SINGLE_COMPARE(mov(byte[rsi], 0x3), 0xc6, 0x06, 0x03);
SINGLE_COMPARE(mov(byte[rsi], al), 0x88, 0x06);
}
TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "FormsOfTest")
{
SINGLE_COMPARE(test(al, 8), 0xf6, 0xc0, 0x08);
SINGLE_COMPARE(test(eax, 8), 0xf7, 0xc0, 0x08, 0x00, 0x00, 0x00);
SINGLE_COMPARE(test(rax, 8), 0x48, 0xf7, 0xc0, 0x08, 0x00, 0x00, 0x00);
SINGLE_COMPARE(test(rcx, 0xabab), 0x48, 0xf7, 0xc1, 0xab, 0xab, 0x00, 0x00);
SINGLE_COMPARE(test(rcx, rax), 0x48, 0x85, 0xc8);
SINGLE_COMPARE(test(rax, qword[rcx]), 0x48, 0x85, 0x01);
}
TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "FormsOfShift")
{
SINGLE_COMPARE(shl(al, 1), 0xd0, 0xe0);
SINGLE_COMPARE(shl(al, cl), 0xd2, 0xe0);
SINGLE_COMPARE(shr(al, 4), 0xc0, 0xe8, 0x04);
SINGLE_COMPARE(shr(eax, 1), 0xd1, 0xe8);
SINGLE_COMPARE(sal(eax, cl), 0xd3, 0xe0);
SINGLE_COMPARE(sal(eax, 4), 0xc1, 0xe0, 0x04);
SINGLE_COMPARE(sar(rax, 4), 0x48, 0xc1, 0xf8, 0x04);
SINGLE_COMPARE(sar(r11, 1), 0x49, 0xd1, 0xfb);
}
TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "FormsOfLea")
{
SINGLE_COMPARE(lea(rax, qword[rdx + rcx]), 0x48, 0x8d, 0x04, 0x0a);
SINGLE_COMPARE(lea(rax, qword[rdx + rax * 4]), 0x48, 0x8d, 0x04, 0x82);
SINGLE_COMPARE(lea(rax, qword[r13 + r12 * 4 + 4]), 0x4b, 0x8d, 0x44, 0xa5, 0x04);
}
TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "ControlFlow")
{
// Jump back
check(
[](AssemblyBuilderX64& build) {
Label start = build.setLabel();
build.add(rsi, 1);
build.cmp(rsi, rdi);
build.jcc(Condition::Equal, start);
},
{0x48, 0x83, 0xc6, 0x01, 0x48, 0x3b, 0xf7, 0x0f, 0x84, 0xf3, 0xff, 0xff, 0xff});
// Jump back, but the label is set before use
check(
[](AssemblyBuilderX64& build) {
Label start;
build.add(rsi, 1);
build.setLabel(start);
build.cmp(rsi, rdi);
build.jcc(Condition::Equal, start);
},
{0x48, 0x83, 0xc6, 0x01, 0x48, 0x3b, 0xf7, 0x0f, 0x84, 0xf7, 0xff, 0xff, 0xff});
// Jump forward
check(
[](AssemblyBuilderX64& build) {
Label skip;
build.cmp(rsi, rdi);
build.jcc(Condition::Greater, skip);
build.or_(rdi, 0x3e);
build.setLabel(skip);
},
{0x48, 0x3b, 0xf7, 0x0f, 0x8f, 0x04, 0x00, 0x00, 0x00, 0x48, 0x83, 0xcf, 0x3e});
// Regular jump
check(
[](AssemblyBuilderX64& build) {
Label skip;
build.jmp(skip);
build.and_(rdi, 0x3e);
build.setLabel(skip);
},
{0xe9, 0x04, 0x00, 0x00, 0x00, 0x48, 0x83, 0xe7, 0x3e});
}
TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "AVXBinaryInstructionForms")
{
SINGLE_COMPARE(vaddpd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0xa9, 0x58, 0xc6);
SINGLE_COMPARE(vaddpd(xmm8, xmm10, xmmword[r9]), 0xc4, 0x41, 0xa9, 0x58, 0x01);
SINGLE_COMPARE(vaddpd(ymm8, ymm10, ymm14), 0xc4, 0x41, 0xad, 0x58, 0xc6);
SINGLE_COMPARE(vaddpd(ymm8, ymm10, ymmword[r9]), 0xc4, 0x41, 0xad, 0x58, 0x01);
SINGLE_COMPARE(vaddps(xmm8, xmm10, xmm14), 0xc4, 0x41, 0xa8, 0x58, 0xc6);
SINGLE_COMPARE(vaddps(xmm8, xmm10, xmmword[r9]), 0xc4, 0x41, 0xa8, 0x58, 0x01);
SINGLE_COMPARE(vaddsd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0xab, 0x58, 0xc6);
SINGLE_COMPARE(vaddsd(xmm8, xmm10, qword[r9]), 0xc4, 0x41, 0xab, 0x58, 0x01);
SINGLE_COMPARE(vaddss(xmm8, xmm10, xmm14), 0xc4, 0x41, 0xaa, 0x58, 0xc6);
SINGLE_COMPARE(vaddss(xmm8, xmm10, dword[r9]), 0xc4, 0x41, 0xaa, 0x58, 0x01);
SINGLE_COMPARE(vaddps(xmm1, xmm2, xmm3), 0xc4, 0xe1, 0xe8, 0x58, 0xcb);
SINGLE_COMPARE(vaddps(xmm9, xmm12, xmmword[r9 + r14 * 2 + 0x1c]), 0xc4, 0x01, 0x98, 0x58, 0x4c, 0x71, 0x1c);
SINGLE_COMPARE(vaddps(ymm1, ymm2, ymm3), 0xc4, 0xe1, 0xec, 0x58, 0xcb);
SINGLE_COMPARE(vaddps(ymm9, ymm12, ymmword[r9 + r14 * 2 + 0x1c]), 0xc4, 0x01, 0x9c, 0x58, 0x4c, 0x71, 0x1c);
}
TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "AVXUnaryMergeInstructionForms")
{
SINGLE_COMPARE(vsqrtpd(xmm8, xmm10), 0xc4, 0x41, 0xf9, 0x51, 0xc2);
SINGLE_COMPARE(vsqrtpd(xmm8, xmmword[r9]), 0xc4, 0x41, 0xf9, 0x51, 0x01);
SINGLE_COMPARE(vsqrtpd(ymm8, ymm10), 0xc4, 0x41, 0xfd, 0x51, 0xc2);
SINGLE_COMPARE(vsqrtpd(ymm8, ymmword[r9]), 0xc4, 0x41, 0xfd, 0x51, 0x01);
SINGLE_COMPARE(vsqrtps(xmm8, xmm10), 0xc4, 0x41, 0xf8, 0x51, 0xc2);
SINGLE_COMPARE(vsqrtps(xmm8, xmmword[r9]), 0xc4, 0x41, 0xf8, 0x51, 0x01);
SINGLE_COMPARE(vsqrtsd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0xab, 0x51, 0xc6);
SINGLE_COMPARE(vsqrtsd(xmm8, xmm10, qword[r9]), 0xc4, 0x41, 0xab, 0x51, 0x01);
SINGLE_COMPARE(vsqrtss(xmm8, xmm10, xmm14), 0xc4, 0x41, 0xaa, 0x51, 0xc6);
SINGLE_COMPARE(vsqrtss(xmm8, xmm10, dword[r9]), 0xc4, 0x41, 0xaa, 0x51, 0x01);
}
TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "AVXMoveInstructionForms")
{
SINGLE_COMPARE(vmovsd(qword[r9], xmm10), 0xc4, 0x41, 0xfb, 0x11, 0x11);
SINGLE_COMPARE(vmovsd(xmm8, qword[r9]), 0xc4, 0x41, 0xfb, 0x10, 0x01);
SINGLE_COMPARE(vmovsd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0xab, 0x10, 0xc6);
SINGLE_COMPARE(vmovss(dword[r9], xmm10), 0xc4, 0x41, 0xfa, 0x11, 0x11);
SINGLE_COMPARE(vmovss(xmm8, dword[r9]), 0xc4, 0x41, 0xfa, 0x10, 0x01);
SINGLE_COMPARE(vmovss(xmm8, xmm10, xmm14), 0xc4, 0x41, 0xaa, 0x10, 0xc6);
SINGLE_COMPARE(vmovapd(xmm8, xmmword[r9]), 0xc4, 0x41, 0xf9, 0x28, 0x01);
SINGLE_COMPARE(vmovapd(xmmword[r9], xmm10), 0xc4, 0x41, 0xf9, 0x29, 0x11);
SINGLE_COMPARE(vmovapd(ymm8, ymmword[r9]), 0xc4, 0x41, 0xfd, 0x28, 0x01);
SINGLE_COMPARE(vmovaps(xmm8, xmmword[r9]), 0xc4, 0x41, 0xf8, 0x28, 0x01);
SINGLE_COMPARE(vmovaps(xmmword[r9], xmm10), 0xc4, 0x41, 0xf8, 0x29, 0x11);
SINGLE_COMPARE(vmovaps(ymm8, ymmword[r9]), 0xc4, 0x41, 0xfc, 0x28, 0x01);
SINGLE_COMPARE(vmovupd(xmm8, xmmword[r9]), 0xc4, 0x41, 0xf9, 0x10, 0x01);
SINGLE_COMPARE(vmovupd(xmmword[r9], xmm10), 0xc4, 0x41, 0xf9, 0x11, 0x11);
SINGLE_COMPARE(vmovupd(ymm8, ymmword[r9]), 0xc4, 0x41, 0xfd, 0x10, 0x01);
SINGLE_COMPARE(vmovups(xmm8, xmmword[r9]), 0xc4, 0x41, 0xf8, 0x10, 0x01);
SINGLE_COMPARE(vmovups(xmmword[r9], xmm10), 0xc4, 0x41, 0xf8, 0x11, 0x11);
SINGLE_COMPARE(vmovups(ymm8, ymmword[r9]), 0xc4, 0x41, 0xfc, 0x10, 0x01);
}
TEST_CASE("LogTest")
{
AssemblyBuilderX64 build(/* logText= */ true);
build.push(r12);
build.add(rax, rdi);
build.add(rcx, 8);
build.sub(dword[rax], 0x1fdc);
build.and_(dword[rcx], 0x37);
build.mov(rdi, qword[rax + rsi * 2]);
build.vaddss(xmm0, xmm0, dword[rax + r14 * 2 + 0x1c]);
Label start = build.setLabel();
build.cmp(rsi, rdi);
build.jcc(Condition::Equal, start);
build.jmp(qword[rdx]);
build.vaddps(ymm9, ymm12, ymmword[rbp + 0xc]);
build.vaddpd(ymm2, ymm7, build.f64(2.5));
build.neg(qword[rbp + r12 * 2]);
build.mov64(r10, 0x1234567812345678ll);
build.vmovapd(xmmword[rax], xmm11);
build.pop(r12);
build.ret();
build.finalize();
bool same = "\n" + build.text == R"(
push r12
add rax,rdi
add rcx,8
sub dword ptr [rax],1FDCh
and dword ptr [rcx],37h
mov rdi,qword ptr [rax+rsi*2]
vaddss xmm0,xmm0,dword ptr [rax+r14*2+01Ch]
.L1:
cmp rsi,rdi
je .L1
jmp qword ptr [rdx]
vaddps ymm9,ymm12,ymmword ptr [rbp+0Ch]
vaddpd ymm2,ymm7,qword ptr [.start-8]
neg qword ptr [rbp+r12*2]
mov r10,1234567812345678h
vmovapd xmmword ptr [rax],xmm11
pop r12
ret
)";
CHECK(same);
}
TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "Constants")
{
// clang-format off
check(
[](AssemblyBuilderX64& build) {
build.xor_(rax, rax);
build.add(rax, build.i64(0x1234567887654321));
build.vmovss(xmm2, build.f32(1.0f));
build.vmovsd(xmm3, build.f64(1.0));
build.vmovaps(xmm4, build.f32x4(1.0f, 2.0f, 4.0f, 8.0f));
build.ret();
},
{
0x48, 0x33, 0xc0,
0x48, 0x03, 0x05, 0xee, 0xff, 0xff, 0xff,
0xc4, 0xe1, 0xfa, 0x10, 0x15, 0xe1, 0xff, 0xff, 0xff,
0xc4, 0xe1, 0xfb, 0x10, 0x1d, 0xcc, 0xff, 0xff, 0xff,
0xc4, 0xe1, 0xf8, 0x28, 0x25, 0xab, 0xff, 0xff, 0xff,
0xc3
});
// clang-format on
}
TEST_CASE("ConstantStorage")
{
AssemblyBuilderX64 build(/* logText= */ false);
for (int i = 0; i <= 3000; i++)
build.vaddss(xmm0, xmm0, build.f32(float(i)));
build.finalize();
LUAU_ASSERT(build.data.size() == 12004);
for (int i = 0; i <= 3000; i++)
{
float v;
memcpy(&v, &build.data[build.data.size() - (i + 1) * sizeof(float)], sizeof(v));
LUAU_ASSERT(v == float(i));
}
}
TEST_SUITE_END();

View file

@ -14,7 +14,6 @@
LUAU_FASTFLAG(LuauTraceTypesInNonstrictMode2) LUAU_FASTFLAG(LuauTraceTypesInNonstrictMode2)
LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel) LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel)
LUAU_FASTFLAG(LuauSeparateTypechecks)
using namespace Luau; using namespace Luau;
@ -83,20 +82,13 @@ struct ACFixtureImpl : BaseType
LoadDefinitionFileResult loadDefinition(const std::string& source) LoadDefinitionFileResult loadDefinition(const std::string& source)
{ {
if (FFlag::LuauSeparateTypechecks) TypeChecker& typeChecker = this->frontend.typeCheckerForAutocomplete;
{ unfreeze(typeChecker.globalTypes);
TypeChecker& typeChecker = this->frontend.typeCheckerForAutocomplete; LoadDefinitionFileResult result = loadDefinitionFile(typeChecker, typeChecker.globalScope, source, "@test");
unfreeze(typeChecker.globalTypes); freeze(typeChecker.globalTypes);
LoadDefinitionFileResult result = loadDefinitionFile(typeChecker, typeChecker.globalScope, source, "@test");
freeze(typeChecker.globalTypes);
REQUIRE_MESSAGE(result.success, "loadDefinition: unable to load definition file"); REQUIRE_MESSAGE(result.success, "loadDefinition: unable to load definition file");
return result; return result;
}
else
{
return BaseType::loadDefinition(source);
}
} }
const Position& getPosition(char marker) const const Position& getPosition(char marker) const
@ -2846,7 +2838,7 @@ f(@1)
TEST_CASE_FIXTURE(ACFixture, "autocomplete_string_singleton_escape") TEST_CASE_FIXTURE(ACFixture, "autocomplete_string_singleton_escape")
{ {
ScopedFastFlag sff{"LuauTwoPassAliasDefinitionFix", true}; ScopedFastFlag sff{"LuauTwoPassAliasDefinitionFix", true};
check(R"( check(R"(
type tag = "strange\t\"cat\"" | 'nice\t"dog"' type tag = "strange\t\"cat\"" | 'nice\t"dog"'

File diff suppressed because it is too large Load diff

View file

@ -241,9 +241,14 @@ TEST_CASE("Math")
TEST_CASE("Table") TEST_CASE("Table")
{ {
ScopedFastFlag sff("LuauFixBuiltinsStackLimit", true); runConformance("nextvar.lua", [](lua_State* L) {
lua_pushcfunction(L, [](lua_State* L) {
runConformance("nextvar.lua"); unsigned v = luaL_checkunsigned(L, 1);
lua_pushlightuserdata(L, reinterpret_cast<void*>(uintptr_t(v)));
return 1;
}, "makelud");
lua_setglobal(L, "makelud");
});
} }
TEST_CASE("PatternMatch") TEST_CASE("PatternMatch")
@ -1106,11 +1111,170 @@ TEST_CASE("UserdataApi")
TEST_CASE("Iter") TEST_CASE("Iter")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag sffs[] = {
{ "LuauCompileIter", true }, {"LuauCompileIter", true},
{ "LuauIter", true }, {"LuauIter", true},
}; };
runConformance("iter.lua"); runConformance("iter.lua");
} }
const int kInt64Tag = 1;
static int gInt64MT = -1;
static int64_t getInt64(lua_State* L, int idx)
{
if (void* p = lua_touserdatatagged(L, idx, kInt64Tag))
return *static_cast<int64_t*>(p);
if (lua_isnumber(L, idx))
return lua_tointeger(L, idx);
luaL_typeerror(L, 1, "int64");
}
static void pushInt64(lua_State* L, int64_t value)
{
void* p = lua_newuserdatatagged(L, sizeof(int64_t), kInt64Tag);
lua_getref(L, gInt64MT);
lua_setmetatable(L, -2);
*static_cast<int64_t*>(p) = value;
}
TEST_CASE("Userdata")
{
runConformance("userdata.lua", [](lua_State* L) {
// create metatable with all the metamethods
lua_newtable(L);
gInt64MT = lua_ref(L, -1);
// __index
lua_pushcfunction(L, [](lua_State* L) {
void* p = lua_touserdatatagged(L, 1, kInt64Tag);
if (!p)
luaL_typeerror(L, 1, "int64");
const char* name = luaL_checkstring(L, 2);
if (strcmp(name, "value") == 0)
{
lua_pushnumber(L, double(*static_cast<int64_t*>(p)));
return 1;
}
luaL_error(L, "unknown field %s", name);
}, nullptr);
lua_setfield(L, -2, "__index");
// __newindex
lua_pushcfunction(L, [](lua_State* L) {
void* p = lua_touserdatatagged(L, 1, kInt64Tag);
if (!p)
luaL_typeerror(L, 1, "int64");
const char* name = luaL_checkstring(L, 2);
if (strcmp(name, "value") == 0)
{
double value = luaL_checknumber(L, 3);
*static_cast<int64_t*>(p) = int64_t(value);
return 0;
}
luaL_error(L, "unknown field %s", name);
}, nullptr);
lua_setfield(L, -2, "__newindex");
// __eq
lua_pushcfunction(L, [](lua_State* L) {
lua_pushboolean(L, getInt64(L, 1) == getInt64(L, 2));
return 1;
}, nullptr);
lua_setfield(L, -2, "__eq");
// __lt
lua_pushcfunction(L, [](lua_State* L) {
lua_pushboolean(L, getInt64(L, 1) < getInt64(L, 2));
return 1;
}, nullptr);
lua_setfield(L, -2, "__lt");
// __le
lua_pushcfunction(L, [](lua_State* L) {
lua_pushboolean(L, getInt64(L, 1) <= getInt64(L, 2));
return 1;
}, nullptr);
lua_setfield(L, -2, "__le");
// __add
lua_pushcfunction(L, [](lua_State* L) {
pushInt64(L, getInt64(L, 1) + getInt64(L, 2));
return 1;
}, nullptr);
lua_setfield(L, -2, "__add");
// __sub
lua_pushcfunction(L, [](lua_State* L) {
pushInt64(L, getInt64(L, 1) - getInt64(L, 2));
return 1;
}, nullptr);
lua_setfield(L, -2, "__sub");
// __mul
lua_pushcfunction(L, [](lua_State* L) {
pushInt64(L, getInt64(L, 1) * getInt64(L, 2));
return 1;
}, nullptr);
lua_setfield(L, -2, "__mul");
// __div
lua_pushcfunction(L, [](lua_State* L) {
// ideally we'd guard against 0 but it's a test so eh
pushInt64(L, getInt64(L, 1) / getInt64(L, 2));
return 1;
}, nullptr);
lua_setfield(L, -2, "__div");
// __mod
lua_pushcfunction(L, [](lua_State* L) {
// ideally we'd guard against 0 and INT64_MIN but it's a test so eh
pushInt64(L, getInt64(L, 1) % getInt64(L, 2));
return 1;
}, nullptr);
lua_setfield(L, -2, "__mod");
// __pow
lua_pushcfunction(L, [](lua_State* L) {
pushInt64(L, int64_t(pow(double(getInt64(L, 1)), double(getInt64(L, 2)))));
return 1;
}, nullptr);
lua_setfield(L, -2, "__pow");
// __unm
lua_pushcfunction(L, [](lua_State* L) {
pushInt64(L, -getInt64(L, 1));
return 1;
}, nullptr);
lua_setfield(L, -2, "__unm");
// __tostring
lua_pushcfunction(L, [](lua_State* L) {
int64_t value = getInt64(L, 1);
std::string str = std::to_string(value);
lua_pushlstring(L, str.c_str(), str.length());
return 1;
}, nullptr);
lua_setfield(L, -2, "__tostring");
// ctor
lua_pushcfunction(L, [](lua_State* L) {
double v = luaL_checknumber(L, 1);
pushInt64(L, int64_t(v));
return 1;
}, "int64");
lua_setglobal(L, "int64");
});
}
TEST_SUITE_END(); TEST_SUITE_END();

View file

@ -136,7 +136,7 @@ function test(a)
while a < 0 do while a < 0 do
a += 1 a += 1
end end
for i=1,2 do for i=10,1,-1 do
a += 1 a += 1
end end
for i in pairs({}) do for i in pairs({}) do
@ -154,8 +154,8 @@ end
const bool args1[] = {false}; const bool args1[] = {false};
const bool args2[] = {true}; const bool args2[] = {true};
CHECK_EQ(50, Luau::Compile::computeCost(model, args1, 1)); CHECK_EQ(82, Luau::Compile::computeCost(model, args1, 1));
CHECK_EQ(49, Luau::Compile::computeCost(model, args2, 1)); CHECK_EQ(79, Luau::Compile::computeCost(model, args2, 1));
} }
TEST_CASE("Conditional") TEST_CASE("Conditional")

View file

@ -116,7 +116,8 @@ TEST_CASE("encode_AstExprGroup")
std::string json = toJson(&group); std::string json = toJson(&group);
const std::string expected = R"({"type":"AstExprGroup","location":"0,0 - 0,0","expr":{"type":"AstExprConstantNumber","location":"0,0 - 0,0","value":5}})"; const std::string expected =
R"({"type":"AstExprGroup","location":"0,0 - 0,0","expr":{"type":"AstExprConstantNumber","location":"0,0 - 0,0","value":5}})";
CHECK(json == expected); CHECK(json == expected);
} }
@ -136,7 +137,7 @@ TEST_CASE("encode_AstExprLocal")
AstLocal local{AstName{"foo"}, Location{}, nullptr, 0, 0, nullptr}; AstLocal local{AstName{"foo"}, Location{}, nullptr, 0, 0, nullptr};
AstExprLocal exprLocal{Location{}, &local, false}; AstExprLocal exprLocal{Location{}, &local, false};
CHECK(toJson(&exprLocal) == R"({"type":"AstExprLocal","location":"0,0 - 0,0","local":{"type":null,"name":"foo","location":"0,0 - 0,0"}})"); CHECK(toJson(&exprLocal) == R"({"type":"AstExprLocal","location":"0,0 - 0,0","local":{"type":null,"name":"foo","location":"0,0 - 0,0"}})");
} }
TEST_CASE("encode_AstExprVarargs") TEST_CASE("encode_AstExprVarargs")
@ -149,7 +150,8 @@ TEST_CASE("encode_AstExprVarargs")
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstExprCall") TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstExprCall")
{ {
AstExpr* expr = expectParseExpr("foo(1, 2, 3)"); AstExpr* expr = expectParseExpr("foo(1, 2, 3)");
std::string_view expected = R"({"type":"AstExprCall","location":"0,4 - 0,16","func":{"type":"AstExprGlobal","location":"0,4 - 0,7","global":"foo"},"args":[{"type":"AstExprConstantNumber","location":"0,8 - 0,9","value":1},{"type":"AstExprConstantNumber","location":"0,11 - 0,12","value":2},{"type":"AstExprConstantNumber","location":"0,14 - 0,15","value":3}],"self":false,"argLocation":"0,8 - 0,16"})"; std::string_view expected =
R"({"type":"AstExprCall","location":"0,4 - 0,16","func":{"type":"AstExprGlobal","location":"0,4 - 0,7","global":"foo"},"args":[{"type":"AstExprConstantNumber","location":"0,8 - 0,9","value":1},{"type":"AstExprConstantNumber","location":"0,11 - 0,12","value":2},{"type":"AstExprConstantNumber","location":"0,14 - 0,15","value":3}],"self":false,"argLocation":"0,8 - 0,16"})";
CHECK(toJson(expr) == expected); CHECK(toJson(expr) == expected);
} }
@ -158,7 +160,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstExprIndexName")
{ {
AstExpr* expr = expectParseExpr("foo.bar"); AstExpr* expr = expectParseExpr("foo.bar");
std::string_view expected = R"({"type":"AstExprIndexName","location":"0,4 - 0,11","expr":{"type":"AstExprGlobal","location":"0,4 - 0,7","global":"foo"},"index":"bar","indexLocation":"0,8 - 0,11","op":"."})"; std::string_view expected =
R"({"type":"AstExprIndexName","location":"0,4 - 0,11","expr":{"type":"AstExprGlobal","location":"0,4 - 0,7","global":"foo"},"index":"bar","indexLocation":"0,8 - 0,11","op":"."})";
CHECK(toJson(expr) == expected); CHECK(toJson(expr) == expected);
} }
@ -167,7 +170,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstExprIndexExpr")
{ {
AstExpr* expr = expectParseExpr("foo['bar']"); AstExpr* expr = expectParseExpr("foo['bar']");
std::string_view expected = R"({"type":"AstExprIndexExpr","location":"0,4 - 0,14","expr":{"type":"AstExprGlobal","location":"0,4 - 0,7","global":"foo"},"index":{"type":"AstExprConstantString","location":"0,8 - 0,13","value":"bar"}})"; std::string_view expected =
R"({"type":"AstExprIndexExpr","location":"0,4 - 0,14","expr":{"type":"AstExprGlobal","location":"0,4 - 0,7","global":"foo"},"index":{"type":"AstExprConstantString","location":"0,8 - 0,13","value":"bar"}})";
CHECK(toJson(expr) == expected); CHECK(toJson(expr) == expected);
} }
@ -176,7 +180,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstExprFunction")
{ {
AstExpr* expr = expectParseExpr("function (a) return a end"); AstExpr* expr = expectParseExpr("function (a) return a end");
std::string_view expected = R"({"type":"AstExprFunction","location":"0,4 - 0,29","generics":[],"genericPacks":[],"args":[{"type":null,"name":"a","location":"0,14 - 0,15"}],"vararg":false,"varargLocation":"0,0 - 0,0","body":{"type":"AstStatBlock","location":"0,16 - 0,26","body":[{"type":"AstStatReturn","location":"0,17 - 0,25","list":[{"type":"AstExprLocal","location":"0,24 - 0,25","local":{"type":null,"name":"a","location":"0,14 - 0,15"}}]}]},"functionDepth":1,"debugname":"","hasEnd":true})"; std::string_view expected =
R"({"type":"AstExprFunction","location":"0,4 - 0,29","generics":[],"genericPacks":[],"args":[{"type":null,"name":"a","location":"0,14 - 0,15"}],"vararg":false,"varargLocation":"0,0 - 0,0","body":{"type":"AstStatBlock","location":"0,16 - 0,26","body":[{"type":"AstStatReturn","location":"0,17 - 0,25","list":[{"type":"AstExprLocal","location":"0,24 - 0,25","local":{"type":null,"name":"a","location":"0,14 - 0,15"}}]}]},"functionDepth":1,"debugname":"","hasEnd":true})";
CHECK(toJson(expr) == expected); CHECK(toJson(expr) == expected);
} }
@ -185,7 +190,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstExprTable")
{ {
AstExpr* expr = expectParseExpr("{true, key=true, [key2]=true}"); AstExpr* expr = expectParseExpr("{true, key=true, [key2]=true}");
std::string_view expected = R"({"type":"AstExprTable","location":"0,4 - 0,33","items":[{"kind":"item","value":{"type":"AstExprConstantBool","location":"0,5 - 0,9","value":true}},{"kind":"record","key":{"type":"AstExprConstantString","location":"0,11 - 0,14","value":"key"},"value":{"type":"AstExprConstantBool","location":"0,15 - 0,19","value":true}},{"kind":"general","key":{"type":"AstExprGlobal","location":"0,22 - 0,26","global":"key2"},"value":{"type":"AstExprConstantBool","location":"0,28 - 0,32","value":true}}]})"; std::string_view expected =
R"({"type":"AstExprTable","location":"0,4 - 0,33","items":[{"kind":"item","value":{"type":"AstExprConstantBool","location":"0,5 - 0,9","value":true}},{"kind":"record","key":{"type":"AstExprConstantString","location":"0,11 - 0,14","value":"key"},"value":{"type":"AstExprConstantBool","location":"0,15 - 0,19","value":true}},{"kind":"general","key":{"type":"AstExprGlobal","location":"0,22 - 0,26","global":"key2"},"value":{"type":"AstExprConstantBool","location":"0,28 - 0,32","value":true}}]})";
CHECK(toJson(expr) == expected); CHECK(toJson(expr) == expected);
} }
@ -194,7 +200,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstExprUnary")
{ {
AstExpr* expr = expectParseExpr("-b"); AstExpr* expr = expectParseExpr("-b");
std::string_view expected = R"({"type":"AstExprUnary","location":"0,4 - 0,6","op":"minus","expr":{"type":"AstExprGlobal","location":"0,5 - 0,6","global":"b"}})"; std::string_view expected =
R"({"type":"AstExprUnary","location":"0,4 - 0,6","op":"minus","expr":{"type":"AstExprGlobal","location":"0,5 - 0,6","global":"b"}})";
CHECK(toJson(expr) == expected); CHECK(toJson(expr) == expected);
} }
@ -203,7 +210,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstExprBinary")
{ {
AstExpr* expr = expectParseExpr("b + c"); AstExpr* expr = expectParseExpr("b + c");
std::string_view expected = R"({"type":"AstExprBinary","location":"0,4 - 0,9","op":"Add","left":{"type":"AstExprGlobal","location":"0,4 - 0,5","global":"b"},"right":{"type":"AstExprGlobal","location":"0,8 - 0,9","global":"c"}})"; std::string_view expected =
R"({"type":"AstExprBinary","location":"0,4 - 0,9","op":"Add","left":{"type":"AstExprGlobal","location":"0,4 - 0,5","global":"b"},"right":{"type":"AstExprGlobal","location":"0,8 - 0,9","global":"c"}})";
CHECK(toJson(expr) == expected); CHECK(toJson(expr) == expected);
} }
@ -212,7 +220,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstExprTypeAssertion")
{ {
AstExpr* expr = expectParseExpr("b :: any"); AstExpr* expr = expectParseExpr("b :: any");
std::string_view expected = R"({"type":"AstExprTypeAssertion","location":"0,4 - 0,12","expr":{"type":"AstExprGlobal","location":"0,4 - 0,5","global":"b"},"annotation":{"type":"AstTypeReference","location":"0,9 - 0,12","name":"any","parameters":[]}})"; std::string_view expected =
R"({"type":"AstExprTypeAssertion","location":"0,4 - 0,12","expr":{"type":"AstExprGlobal","location":"0,4 - 0,5","global":"b"},"annotation":{"type":"AstTypeReference","location":"0,9 - 0,12","name":"any","parameters":[]}})";
CHECK(toJson(expr) == expected); CHECK(toJson(expr) == expected);
} }
@ -239,7 +248,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatIf")
{ {
AstStat* statement = expectParseStatement("if true then else end"); AstStat* statement = expectParseStatement("if true then else end");
std::string_view expected = R"({"type":"AstStatIf","location":"0,0 - 0,21","condition":{"type":"AstExprConstantBool","location":"0,3 - 0,7","value":true},"thenbody":{"type":"AstStatBlock","location":"0,12 - 0,13","body":[]},"elsebody":{"type":"AstStatBlock","location":"0,17 - 0,18","body":[]},"hasThen":true,"hasEnd":true})"; std::string_view expected =
R"({"type":"AstStatIf","location":"0,0 - 0,21","condition":{"type":"AstExprConstantBool","location":"0,3 - 0,7","value":true},"thenbody":{"type":"AstStatBlock","location":"0,12 - 0,13","body":[]},"elsebody":{"type":"AstStatBlock","location":"0,17 - 0,18","body":[]},"hasThen":true,"hasEnd":true})";
CHECK(toJson(statement) == expected); CHECK(toJson(statement) == expected);
} }
@ -248,7 +258,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatWhile")
{ {
AstStat* statement = expectParseStatement("while true do end"); AstStat* statement = expectParseStatement("while true do end");
std::string_view expected = R"({"type":"AtStatWhile","location":"0,0 - 0,17","condition":{"type":"AstExprConstantBool","location":"0,6 - 0,10","value":true},"body":{"type":"AstStatBlock","location":"0,13 - 0,14","body":[]},"hasDo":true,"hasEnd":true})"; std::string_view expected =
R"({"type":"AtStatWhile","location":"0,0 - 0,17","condition":{"type":"AstExprConstantBool","location":"0,6 - 0,10","value":true},"body":{"type":"AstStatBlock","location":"0,13 - 0,14","body":[]},"hasDo":true,"hasEnd":true})";
CHECK(toJson(statement) == expected); CHECK(toJson(statement) == expected);
} }
@ -257,7 +268,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatRepeat")
{ {
AstStat* statement = expectParseStatement("repeat until true"); AstStat* statement = expectParseStatement("repeat until true");
std::string_view expected = R"({"type":"AstStatRepeat","location":"0,0 - 0,17","condition":{"type":"AstExprConstantBool","location":"0,13 - 0,17","value":true},"body":{"type":"AstStatBlock","location":"0,6 - 0,7","body":[]},"hasUntil":true})"; std::string_view expected =
R"({"type":"AstStatRepeat","location":"0,0 - 0,17","condition":{"type":"AstExprConstantBool","location":"0,13 - 0,17","value":true},"body":{"type":"AstStatBlock","location":"0,6 - 0,7","body":[]},"hasUntil":true})";
CHECK(toJson(statement) == expected); CHECK(toJson(statement) == expected);
} }
@ -266,7 +278,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatBreak")
{ {
AstStat* statement = expectParseStatement("while true do break end"); AstStat* statement = expectParseStatement("while true do break end");
std::string_view expected = R"({"type":"AtStatWhile","location":"0,0 - 0,23","condition":{"type":"AstExprConstantBool","location":"0,6 - 0,10","value":true},"body":{"type":"AstStatBlock","location":"0,13 - 0,20","body":[{"type":"AstStatBreak","location":"0,14 - 0,19"}]},"hasDo":true,"hasEnd":true})"; std::string_view expected =
R"({"type":"AtStatWhile","location":"0,0 - 0,23","condition":{"type":"AstExprConstantBool","location":"0,6 - 0,10","value":true},"body":{"type":"AstStatBlock","location":"0,13 - 0,20","body":[{"type":"AstStatBreak","location":"0,14 - 0,19"}]},"hasDo":true,"hasEnd":true})";
CHECK(toJson(statement) == expected); CHECK(toJson(statement) == expected);
} }
@ -275,7 +288,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatContinue")
{ {
AstStat* statement = expectParseStatement("while true do continue end"); AstStat* statement = expectParseStatement("while true do continue end");
std::string_view expected = R"({"type":"AtStatWhile","location":"0,0 - 0,26","condition":{"type":"AstExprConstantBool","location":"0,6 - 0,10","value":true},"body":{"type":"AstStatBlock","location":"0,13 - 0,23","body":[{"type":"AstStatContinue","location":"0,14 - 0,22"}]},"hasDo":true,"hasEnd":true})"; std::string_view expected =
R"({"type":"AtStatWhile","location":"0,0 - 0,26","condition":{"type":"AstExprConstantBool","location":"0,6 - 0,10","value":true},"body":{"type":"AstStatBlock","location":"0,13 - 0,23","body":[{"type":"AstStatContinue","location":"0,14 - 0,22"}]},"hasDo":true,"hasEnd":true})";
CHECK(toJson(statement) == expected); CHECK(toJson(statement) == expected);
} }
@ -284,7 +298,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatFor")
{ {
AstStat* statement = expectParseStatement("for a=0,1 do end"); AstStat* statement = expectParseStatement("for a=0,1 do end");
std::string_view expected = R"({"type":"AstStatFor","location":"0,0 - 0,16","var":{"type":null,"name":"a","location":"0,4 - 0,5"},"from":{"type":"AstExprConstantNumber","location":"0,6 - 0,7","value":0},"to":{"type":"AstExprConstantNumber","location":"0,8 - 0,9","value":1},"body":{"type":"AstStatBlock","location":"0,12 - 0,13","body":[]},"hasDo":true,"hasEnd":true})"; std::string_view expected =
R"({"type":"AstStatFor","location":"0,0 - 0,16","var":{"type":null,"name":"a","location":"0,4 - 0,5"},"from":{"type":"AstExprConstantNumber","location":"0,6 - 0,7","value":0},"to":{"type":"AstExprConstantNumber","location":"0,8 - 0,9","value":1},"body":{"type":"AstStatBlock","location":"0,12 - 0,13","body":[]},"hasDo":true,"hasEnd":true})";
CHECK(toJson(statement) == expected); CHECK(toJson(statement) == expected);
} }
@ -293,7 +308,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatForIn")
{ {
AstStat* statement = expectParseStatement("for a in b do end"); AstStat* statement = expectParseStatement("for a in b do end");
std::string_view expected = R"({"type":"AstStatForIn","location":"0,0 - 0,17","vars":[{"type":null,"name":"a","location":"0,4 - 0,5"}],"values":[{"type":"AstExprGlobal","location":"0,9 - 0,10","global":"b"}],"body":{"type":"AstStatBlock","location":"0,13 - 0,14","body":[]},"hasIn":true,"hasDo":true,"hasEnd":true})"; std::string_view expected =
R"({"type":"AstStatForIn","location":"0,0 - 0,17","vars":[{"type":null,"name":"a","location":"0,4 - 0,5"}],"values":[{"type":"AstExprGlobal","location":"0,9 - 0,10","global":"b"}],"body":{"type":"AstStatBlock","location":"0,13 - 0,14","body":[]},"hasIn":true,"hasDo":true,"hasEnd":true})";
CHECK(toJson(statement) == expected); CHECK(toJson(statement) == expected);
} }
@ -302,7 +318,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatCompoundAssign")
{ {
AstStat* statement = expectParseStatement("a += b"); AstStat* statement = expectParseStatement("a += b");
std::string_view expected = R"({"type":"AstStatCompoundAssign","location":"0,0 - 0,6","op":"Add","var":{"type":"AstExprGlobal","location":"0,0 - 0,1","global":"a"},"value":{"type":"AstExprGlobal","location":"0,5 - 0,6","global":"b"}})"; std::string_view expected =
R"({"type":"AstStatCompoundAssign","location":"0,0 - 0,6","op":"Add","var":{"type":"AstExprGlobal","location":"0,0 - 0,1","global":"a"},"value":{"type":"AstExprGlobal","location":"0,5 - 0,6","global":"b"}})";
CHECK(toJson(statement) == expected); CHECK(toJson(statement) == expected);
} }
@ -311,7 +328,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatLocalFunction")
{ {
AstStat* statement = expectParseStatement("local function a(b) return end"); AstStat* statement = expectParseStatement("local function a(b) return end");
std::string_view expected = R"({"type":"AstStatLocalFunction","location":"0,0 - 0,30","name":{"type":null,"name":"a","location":"0,15 - 0,16"},"func":{"type":"AstExprFunction","location":"0,0 - 0,30","generics":[],"genericPacks":[],"args":[{"type":null,"name":"b","location":"0,17 - 0,18"}],"vararg":false,"varargLocation":"0,0 - 0,0","body":{"type":"AstStatBlock","location":"0,19 - 0,27","body":[{"type":"AstStatReturn","location":"0,20 - 0,26","list":[]}]},"functionDepth":1,"debugname":"a","hasEnd":true}})"; std::string_view expected =
R"({"type":"AstStatLocalFunction","location":"0,0 - 0,30","name":{"type":null,"name":"a","location":"0,15 - 0,16"},"func":{"type":"AstExprFunction","location":"0,0 - 0,30","generics":[],"genericPacks":[],"args":[{"type":null,"name":"b","location":"0,17 - 0,18"}],"vararg":false,"varargLocation":"0,0 - 0,0","body":{"type":"AstStatBlock","location":"0,19 - 0,27","body":[{"type":"AstStatReturn","location":"0,20 - 0,26","list":[]}]},"functionDepth":1,"debugname":"a","hasEnd":true}})";
CHECK(toJson(statement) == expected); CHECK(toJson(statement) == expected);
} }
@ -320,7 +338,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatTypeAlias")
{ {
AstStat* statement = expectParseStatement("type A = B"); AstStat* statement = expectParseStatement("type A = B");
std::string_view expected = R"({"type":"AstStatTypeAlias","location":"0,0 - 0,10","name":"A","generics":[],"genericPacks":[],"type":{"type":"AstTypeReference","location":"0,9 - 0,10","name":"B","parameters":[]},"exported":false})"; std::string_view expected =
R"({"type":"AstStatTypeAlias","location":"0,0 - 0,10","name":"A","generics":[],"genericPacks":[],"type":{"type":"AstTypeReference","location":"0,9 - 0,10","name":"B","parameters":[]},"exported":false})";
CHECK(toJson(statement) == expected); CHECK(toJson(statement) == expected);
} }
@ -329,7 +348,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatDeclareFunction")
{ {
AstStat* statement = expectParseStatement("declare function foo(x: number): string"); AstStat* statement = expectParseStatement("declare function foo(x: number): string");
std::string_view expected = R"({"type":"AstStatDeclareFunction","location":"0,0 - 0,39","name":"foo","params":{"types":[{"type":"AstTypeReference","location":"0,24 - 0,30","name":"number","parameters":[]}]},"retTypes":{"types":[{"type":"AstTypeReference","location":"0,33 - 0,39","name":"string","parameters":[]}]},"generics":[],"genericPacks":[]})"; std::string_view expected =
R"({"type":"AstStatDeclareFunction","location":"0,0 - 0,39","name":"foo","params":{"types":[{"type":"AstTypeReference","location":"0,24 - 0,30","name":"number","parameters":[]}]},"retTypes":{"types":[{"type":"AstTypeReference","location":"0,33 - 0,39","name":"string","parameters":[]}]},"generics":[],"genericPacks":[]})";
CHECK(toJson(statement) == expected); CHECK(toJson(statement) == expected);
} }
@ -349,10 +369,12 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatDeclareClass")
REQUIRE(2 == root->body.size); REQUIRE(2 == root->body.size);
std::string_view expected1 = R"({"type":"AstStatDeclareClass","location":"1,22 - 4,11","name":"Foo","props":[{"name":"prop","type":{"type":"AstTypeReference","location":"2,18 - 2,24","name":"number","parameters":[]}},{"name":"method","type":{"type":"AstTypeFunction","location":"3,21 - 4,11","generics":[],"genericPacks":[],"argTypes":{"types":[{"type":"AstTypeReference","location":"3,39 - 3,45","name":"number","parameters":[]}]},"returnTypes":{"types":[{"type":"AstTypeReference","location":"3,48 - 3,54","name":"string","parameters":[]}]}}}]})"; std::string_view expected1 =
R"({"type":"AstStatDeclareClass","location":"1,22 - 4,11","name":"Foo","props":[{"name":"prop","type":{"type":"AstTypeReference","location":"2,18 - 2,24","name":"number","parameters":[]}},{"name":"method","type":{"type":"AstTypeFunction","location":"3,21 - 4,11","generics":[],"genericPacks":[],"argTypes":{"types":[{"type":"AstTypeReference","location":"3,39 - 3,45","name":"number","parameters":[]}]},"returnTypes":{"types":[{"type":"AstTypeReference","location":"3,48 - 3,54","name":"string","parameters":[]}]}}}]})";
CHECK(toJson(root->body.data[0]) == expected1); CHECK(toJson(root->body.data[0]) == expected1);
std::string_view expected2 = R"({"type":"AstStatDeclareClass","location":"6,22 - 8,11","name":"Bar","superName":"Foo","props":[{"name":"prop2","type":{"type":"AstTypeReference","location":"7,19 - 7,25","name":"string","parameters":[]}}]})"; std::string_view expected2 =
R"({"type":"AstStatDeclareClass","location":"6,22 - 8,11","name":"Bar","superName":"Foo","props":[{"name":"prop2","type":{"type":"AstTypeReference","location":"7,19 - 7,25","name":"string","parameters":[]}}]})";
CHECK(toJson(root->body.data[1]) == expected2); CHECK(toJson(root->body.data[1]) == expected2);
} }
@ -360,7 +382,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_annotation")
{ {
AstStat* statement = expectParseStatement("type T = ((number) -> (string | nil)) & ((string) -> ())"); AstStat* statement = expectParseStatement("type T = ((number) -> (string | nil)) & ((string) -> ())");
std::string_view expected = R"({"type":"AstStatTypeAlias","location":"0,0 - 0,55","name":"T","generics":[],"genericPacks":[],"type":{"type":"AstTypeIntersection","location":"0,9 - 0,55","types":[{"type":"AstTypeFunction","location":"0,10 - 0,35","generics":[],"genericPacks":[],"argTypes":{"types":[{"type":"AstTypeReference","location":"0,11 - 0,17","name":"number","parameters":[]}]},"returnTypes":{"types":[{"type":"AstTypeUnion","location":"0,23 - 0,35","types":[{"type":"AstTypeReference","location":"0,23 - 0,29","name":"string","parameters":[]},{"type":"AstTypeReference","location":"0,32 - 0,35","name":"nil","parameters":[]}]}]}},{"type":"AstTypeFunction","location":"0,41 - 0,55","generics":[],"genericPacks":[],"argTypes":{"types":[{"type":"AstTypeReference","location":"0,42 - 0,48","name":"string","parameters":[]}]},"returnTypes":{"types":[]}}]},"exported":false})"; std::string_view expected =
R"({"type":"AstStatTypeAlias","location":"0,0 - 0,55","name":"T","generics":[],"genericPacks":[],"type":{"type":"AstTypeIntersection","location":"0,9 - 0,55","types":[{"type":"AstTypeFunction","location":"0,10 - 0,35","generics":[],"genericPacks":[],"argTypes":{"types":[{"type":"AstTypeReference","location":"0,11 - 0,17","name":"number","parameters":[]}]},"returnTypes":{"types":[{"type":"AstTypeUnion","location":"0,23 - 0,35","types":[{"type":"AstTypeReference","location":"0,23 - 0,29","name":"string","parameters":[]},{"type":"AstTypeReference","location":"0,32 - 0,35","name":"nil","parameters":[]}]}]}},{"type":"AstTypeFunction","location":"0,41 - 0,55","generics":[],"genericPacks":[],"argTypes":{"types":[{"type":"AstTypeReference","location":"0,42 - 0,48","name":"string","parameters":[]}]},"returnTypes":{"types":[]}}]},"exported":false})";
CHECK(toJson(statement) == expected); CHECK(toJson(statement) == expected);
} }
@ -372,7 +395,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstTypeError")
AstStat* statement = parseResult.root->body.data[0]; AstStat* statement = parseResult.root->body.data[0];
std::string_view expected = R"({"type":"AstStatTypeAlias","location":"0,0 - 0,9","name":"T","generics":[],"genericPacks":[],"type":{"type":"AstTypeError","location":"0,8 - 0,9","types":[],"messageIndex":0},"exported":false})"; std::string_view expected =
R"({"type":"AstStatTypeAlias","location":"0,0 - 0,9","name":"T","generics":[],"genericPacks":[],"type":{"type":"AstTypeError","location":"0,8 - 0,9","types":[],"messageIndex":0},"exported":false})";
CHECK(toJson(statement) == expected); CHECK(toJson(statement) == expected);
} }
@ -386,7 +410,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstTypePackExplicit")
CHECK(2 == root->body.size); CHECK(2 == root->body.size);
std::string_view expected = R"({"type":"AstStatLocal","location":"2,8 - 2,36","vars":[{"type":{"type":"AstTypeReference","location":"2,17 - 2,36","name":"A","parameters":[{"type":"AstTypePackExplicit","location":"2,19 - 2,20","typeList":{"types":[{"type":"AstTypeReference","location":"2,20 - 2,26","name":"number","parameters":[]},{"type":"AstTypeReference","location":"2,28 - 2,34","name":"string","parameters":[]}]}}]},"name":"a","location":"2,14 - 2,15"}],"values":[]})"; std::string_view expected =
R"({"type":"AstStatLocal","location":"2,8 - 2,36","vars":[{"type":{"type":"AstTypeReference","location":"2,17 - 2,36","name":"A","parameters":[{"type":"AstTypePackExplicit","location":"2,19 - 2,20","typeList":{"types":[{"type":"AstTypeReference","location":"2,20 - 2,26","name":"number","parameters":[]},{"type":"AstTypeReference","location":"2,28 - 2,34","name":"string","parameters":[]}]}}]},"name":"a","location":"2,14 - 2,15"}],"values":[]})";
CHECK(toJson(root->body.data[1]) == expected); CHECK(toJson(root->body.data[1]) == expected);
} }

View file

@ -15,10 +15,7 @@ TEST_SUITE_BEGIN("NonstrictModeTests");
TEST_CASE_FIXTURE(BuiltinsFixture, "function_returns_number_or_string") TEST_CASE_FIXTURE(BuiltinsFixture, "function_returns_number_or_string")
{ {
ScopedFastFlag sff[]{ ScopedFastFlag sff[]{{"LuauReturnTypeInferenceInNonstrict", true}, {"LuauLowerBoundsCalculation", true}};
{"LuauReturnTypeInferenceInNonstrict", true},
{"LuauLowerBoundsCalculation", true}
};
CheckResult result = check(R"( CheckResult result = check(R"(
--!nonstrict --!nonstrict

View file

@ -44,6 +44,9 @@ void createSomeClasses(TypeChecker& typeChecker)
addGlobalBinding(typeChecker, "Unrelated", {unrelatedType}); addGlobalBinding(typeChecker, "Unrelated", {unrelatedType});
typeChecker.globalScope->exportedTypeBindings["Unrelated"] = TypeFun{{}, unrelatedType}; typeChecker.globalScope->exportedTypeBindings["Unrelated"] = TypeFun{{}, unrelatedType};
for (const auto& [name, ty] : typeChecker.globalScope->exportedTypeBindings)
persist(ty.type);
freeze(arena); freeze(arena);
} }
@ -681,10 +684,7 @@ TEST_CASE_FIXTURE(Fixture, "higher_order_function_with_annotation")
TEST_CASE_FIXTURE(Fixture, "cyclic_table_is_marked_normal") TEST_CASE_FIXTURE(Fixture, "cyclic_table_is_marked_normal")
{ {
ScopedFastFlag flags[] = { ScopedFastFlag flags[] = {{"LuauLowerBoundsCalculation", true}, {"LuauNormalizeFlagIsConservative", false}};
{"LuauLowerBoundsCalculation", true},
{"LuauNormalizeFlagIsConservative", false}
};
check(R"( check(R"(
type Fiber = { type Fiber = {
@ -701,10 +701,7 @@ TEST_CASE_FIXTURE(Fixture, "cyclic_table_is_marked_normal")
// Unfortunately, getting this right in the general case is difficult. // Unfortunately, getting this right in the general case is difficult.
TEST_CASE_FIXTURE(Fixture, "cyclic_table_is_not_marked_normal") TEST_CASE_FIXTURE(Fixture, "cyclic_table_is_not_marked_normal")
{ {
ScopedFastFlag flags[] = { ScopedFastFlag flags[] = {{"LuauLowerBoundsCalculation", true}, {"LuauNormalizeFlagIsConservative", true}};
{"LuauLowerBoundsCalculation", true},
{"LuauNormalizeFlagIsConservative", true}
};
check(R"( check(R"(
type Fiber = { type Fiber = {
@ -1042,4 +1039,27 @@ TEST_CASE_FIXTURE(Fixture, "bound_typevars_should_only_be_marked_normal_if_their
)"); )");
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "skip_force_normal_on_external_types")
{
createSomeClasses(typeChecker);
CheckResult result = check(R"(
export type t0 = { a: Child }
export type t1 = { a: typeof(string.byte) }
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(Fixture, "intersection_combine_on_bound_self")
{
ScopedFastFlag luauNormalizeCombineEqFix{"LuauNormalizeCombineEqFix", true};
CheckResult result = check(R"(
export type t0 = (((any)&({_:l0.t0,n0:t0,_G:any,}))&({_:any,}))&(((any)&({_:l0.t0,n0:t0,_G:any,}))&({_:any,}))
)");
LUAU_REQUIRE_ERRORS(result);
}
TEST_SUITE_END(); TEST_SUITE_END();

View file

@ -2622,4 +2622,15 @@ type Z<T> = { a: string | T..., b: number }
REQUIRE_EQ(3, result.errors.size()); REQUIRE_EQ(3, result.errors.size());
} }
TEST_CASE_FIXTURE(Fixture, "error_message_for_using_function_as_type_annotation")
{
ScopedFastFlag sff{"LuauParserFunctionKeywordAsTypeHelp", true};
ParseResult result = tryParse(R"(
type Foo = function
)");
REQUIRE_EQ(1, result.errors.size());
CHECK_EQ("Using 'function' as a type annotation is not supported, consider replacing with a function type annotation e.g. '(...any) -> ...any'",
result.errors[0].getMessage());
}
TEST_SUITE_END(); TEST_SUITE_END();

View file

@ -24,7 +24,7 @@ struct LimitFixture : BuiltinsFixture
#endif #endif
}; };
template <typename T> template<typename T>
bool hasError(const CheckResult& result, T* = nullptr) bool hasError(const CheckResult& result, T* = nullptr)
{ {
auto it = std::find_if(result.errors.begin(), result.errors.end(), [](const TypeError& a) { auto it = std::find_if(result.errors.begin(), result.errors.end(), [](const TypeError& a) {

View file

@ -33,6 +33,9 @@ struct ToDotClassFixture : Fixture
}; };
typeChecker.globalScope->exportedTypeBindings["ChildClass"] = TypeFun{{}, childClassInstanceType}; typeChecker.globalScope->exportedTypeBindings["ChildClass"] = TypeFun{{}, childClassInstanceType};
for (const auto& [name, ty] : typeChecker.globalScope->exportedTypeBindings)
persist(ty.type);
freeze(arena); freeze(arena);
} }
}; };
@ -425,13 +428,14 @@ n1 -> n2;
n2 [label="SingletonTypeVar string: hi"]; n2 [label="SingletonTypeVar string: hi"];
n1 -> n3; n1 -> n3;
)" )"
"n3 [label=\"SingletonTypeVar string: \\\"hello\\\"\"];" "n3 [label=\"SingletonTypeVar string: \\\"hello\\\"\"];"
R"( R"(
n1 -> n4; n1 -> n4;
n4 [label="SingletonTypeVar boolean: true"]; n4 [label="SingletonTypeVar boolean: true"];
n1 -> n5; n1 -> n5;
n5 [label="SingletonTypeVar boolean: false"]; n5 [label="SingletonTypeVar boolean: false"];
})", toDot(requireType("x"), opts)); })",
toDot(requireType("x"), opts));
} }
TEST_SUITE_END(); TEST_SUITE_END();

View file

@ -600,7 +600,7 @@ 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(Fixture, "bad_select_should_not_crash") TEST_CASE_FIXTURE(BuiltinsFixture, "bad_select_should_not_crash")
{ {
CheckResult result = check(R"( CheckResult result = check(R"(
do end do end
@ -612,7 +612,9 @@ TEST_CASE_FIXTURE(Fixture, "bad_select_should_not_crash")
end end
)"); )");
CHECK_LE(0, result.errors.size()); LUAU_REQUIRE_ERROR_COUNT(2, result);
CHECK_EQ("Argument count mismatch. Function expects at least 1 argument, but none are specified", toString(result.errors[0]));
CHECK_EQ("Argument count mismatch. Function expects 1 argument, but none are specified", toString(result.errors[1]));
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "select_way_out_of_range") TEST_CASE_FIXTURE(BuiltinsFixture, "select_way_out_of_range")
@ -877,10 +879,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "dont_add_definitions_to_persistent_types")
TEST_CASE_FIXTURE(BuiltinsFixture, "assert_removes_falsy_types") TEST_CASE_FIXTURE(BuiltinsFixture, "assert_removes_falsy_types")
{ {
ScopedFastFlag sff[]{
{"LuauWidenIfSupertypeIsFree2", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
local function f(x: (number | boolean)?) local function f(x: (number | boolean)?)
return assert(x) return assert(x)
@ -896,10 +894,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "assert_removes_falsy_types")
TEST_CASE_FIXTURE(BuiltinsFixture, "assert_removes_falsy_types2") TEST_CASE_FIXTURE(BuiltinsFixture, "assert_removes_falsy_types2")
{ {
ScopedFastFlag sff[]{
{"LuauWidenIfSupertypeIsFree2", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
local function f(x: (number | boolean)?): number | true local function f(x: (number | boolean)?): number | true
return assert(x) return assert(x)

View file

@ -1001,7 +1001,7 @@ TEST_CASE_FIXTURE(Fixture, "no_stack_overflow_from_quantifying")
type t0 = t0 | {} type t0 = t0 | {}
)"); )");
CHECK_LE(0, result.errors.size()); LUAU_REQUIRE_ERRORS(result);
std::optional<TypeFun> t0 = getMainModule()->getModuleScope()->lookupType("t0"); std::optional<TypeFun> t0 = getMainModule()->getModuleScope()->lookupType("t0");
REQUIRE(t0); REQUIRE(t0);

View file

@ -443,7 +443,7 @@ TEST_CASE_FIXTURE(Fixture, "no_stack_overflow_from_flattenintersection")
until _(_)(_)._ until _(_)(_)._
)"); )");
CHECK_LE(0, result.errors.size()); LUAU_REQUIRE_ERRORS(result);
} }
TEST_SUITE_END(); TEST_SUITE_END();

View file

@ -249,7 +249,7 @@ local ModuleA = require(game.A)
CHECK_EQ("*unknown*", toString(*oty)); CHECK_EQ("*unknown*", toString(*oty));
} }
TEST_CASE_FIXTURE(Fixture, "do_not_modify_imported_types") TEST_CASE_FIXTURE(BuiltinsFixture, "do_not_modify_imported_types")
{ {
fileResolver.source["game/A"] = R"( fileResolver.source["game/A"] = R"(
export type Type = { unrelated: boolean } export type Type = { unrelated: boolean }
@ -264,7 +264,7 @@ function x:Destroy(): () end
)"; )";
CheckResult result = frontend.check("game/B"); CheckResult result = frontend.check("game/B");
LUAU_REQUIRE_ERRORS(result); LUAU_REQUIRE_ERROR_COUNT(2, result);
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "do_not_modify_imported_types_2") TEST_CASE_FIXTURE(BuiltinsFixture, "do_not_modify_imported_types_2")

View file

@ -409,14 +409,15 @@ TEST_CASE_FIXTURE(Fixture, "normalization_fails_on_certain_kinds_of_cyclic_table
} }
// Belongs in TypeInfer.builtins.test.cpp. // Belongs in TypeInfer.builtins.test.cpp.
TEST_CASE_FIXTURE(Fixture, "pcall_returns_at_least_two_value_but_function_returns_nothing") TEST_CASE_FIXTURE(BuiltinsFixture, "pcall_returns_at_least_two_value_but_function_returns_nothing")
{ {
CheckResult result = check(R"( CheckResult result = check(R"(
local function f(): () end local function f(): () end
local ok, res = pcall(f) local ok, res = pcall(f)
)"); )");
LUAU_REQUIRE_ERRORS(result); LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ("Function only returns 1 value. 2 are required here", toString(result.errors[0]));
// LUAU_REQUIRE_NO_ERRORS(result); // LUAU_REQUIRE_NO_ERRORS(result);
// CHECK_EQ("boolean", toString(requireType("ok"))); // CHECK_EQ("boolean", toString(requireType("ok")));
// CHECK_EQ("any", toString(requireType("res"))); // CHECK_EQ("any", toString(requireType("res")));

View file

@ -7,7 +7,6 @@
#include "doctest.h" #include "doctest.h"
LUAU_FASTFLAG(LuauWeakEqConstraint)
LUAU_FASTFLAG(LuauLowerBoundsCalculation) LUAU_FASTFLAG(LuauLowerBoundsCalculation)
using namespace Luau; using namespace Luau;
@ -77,6 +76,10 @@ struct RefinementClassFixture : Fixture
typeChecker.globalScope->exportedTypeBindings["Instance"] = TypeFun{{}, inst}; typeChecker.globalScope->exportedTypeBindings["Instance"] = TypeFun{{}, inst};
typeChecker.globalScope->exportedTypeBindings["Folder"] = TypeFun{{}, folder}; typeChecker.globalScope->exportedTypeBindings["Folder"] = TypeFun{{}, folder};
typeChecker.globalScope->exportedTypeBindings["Part"] = TypeFun{{}, part}; typeChecker.globalScope->exportedTypeBindings["Part"] = TypeFun{{}, part};
for (const auto& [name, ty] : typeChecker.globalScope->exportedTypeBindings)
persist(ty.type);
freeze(typeChecker.globalTypes); freeze(typeChecker.globalTypes);
} }
}; };
@ -444,8 +447,6 @@ TEST_CASE_FIXTURE(Fixture, "lvalue_is_not_nil")
TEST_CASE_FIXTURE(Fixture, "free_type_is_equal_to_an_lvalue") TEST_CASE_FIXTURE(Fixture, "free_type_is_equal_to_an_lvalue")
{ {
ScopedFastFlag sff2{"LuauWeakEqConstraint", true};
CheckResult result = check(R"( CheckResult result = check(R"(
local function f(a, b: string?) local function f(a, b: string?)
if a == b then if a == b then

View file

@ -326,11 +326,6 @@ local a: Animal = if true then { tag = 'cat', catfood = 'something' } else { tag
TEST_CASE_FIXTURE(Fixture, "widen_the_supertype_if_it_is_free_and_subtype_has_singleton") TEST_CASE_FIXTURE(Fixture, "widen_the_supertype_if_it_is_free_and_subtype_has_singleton")
{ {
ScopedFastFlag sff[]{
{"LuauWidenIfSupertypeIsFree2", true},
{"LuauWeakEqConstraint", false},
};
CheckResult result = check(R"( CheckResult result = check(R"(
local function foo(f, x) local function foo(f, x)
if x == "hi" then if x == "hi" then
@ -349,11 +344,6 @@ TEST_CASE_FIXTURE(Fixture, "widen_the_supertype_if_it_is_free_and_subtype_has_si
TEST_CASE_FIXTURE(Fixture, "return_type_of_f_is_not_widened") TEST_CASE_FIXTURE(Fixture, "return_type_of_f_is_not_widened")
{ {
ScopedFastFlag sff[]{
{"LuauWidenIfSupertypeIsFree2", true},
{"LuauWeakEqConstraint", false},
};
CheckResult result = check(R"( CheckResult result = check(R"(
local function foo(f, x): "hello"? -- anyone there? local function foo(f, x): "hello"? -- anyone there?
return if x == "hi" return if x == "hi"
@ -371,10 +361,6 @@ TEST_CASE_FIXTURE(Fixture, "return_type_of_f_is_not_widened")
TEST_CASE_FIXTURE(Fixture, "widening_happens_almost_everywhere") TEST_CASE_FIXTURE(Fixture, "widening_happens_almost_everywhere")
{ {
ScopedFastFlag sff[]{
{"LuauWidenIfSupertypeIsFree2", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
local foo: "foo" = "foo" local foo: "foo" = "foo"
local copy = foo local copy = foo
@ -386,10 +372,6 @@ TEST_CASE_FIXTURE(Fixture, "widening_happens_almost_everywhere")
TEST_CASE_FIXTURE(Fixture, "widening_happens_almost_everywhere_except_for_tables") TEST_CASE_FIXTURE(Fixture, "widening_happens_almost_everywhere_except_for_tables")
{ {
ScopedFastFlag sff[]{
{"LuauWidenIfSupertypeIsFree2", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
type Cat = {tag: "Cat", meows: boolean} type Cat = {tag: "Cat", meows: boolean}
type Dog = {tag: "Dog", barks: boolean} type Dog = {tag: "Dog", barks: boolean}
@ -413,10 +395,7 @@ TEST_CASE_FIXTURE(Fixture, "widening_happens_almost_everywhere_except_for_tables
TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_with_a_singleton_argument") TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_with_a_singleton_argument")
{ {
ScopedFastFlag sff[]{ ScopedFastFlag sff{"LuauLowerBoundsCalculation", true};
{"LuauWidenIfSupertypeIsFree2", true},
{"LuauWeakEqConstraint", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
local function foo(t, x) local function foo(t, x)
@ -438,10 +417,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_with_a_singleton_argument")
TEST_CASE_FIXTURE(Fixture, "functions_are_not_to_be_widened") TEST_CASE_FIXTURE(Fixture, "functions_are_not_to_be_widened")
{ {
ScopedFastFlag sff[]{
{"LuauWidenIfSupertypeIsFree2", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
local function foo(my_enum: "A" | "B") end local function foo(my_enum: "A" | "B") end
)"); )");

View file

@ -1891,7 +1891,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "quantifying_a_bound_var_works")
REQUIRE_EQ(ttv->state, TableState::Sealed); REQUIRE_EQ(ttv->state, TableState::Sealed);
} }
TEST_CASE_FIXTURE(Fixture, "less_exponential_blowup_please") TEST_CASE_FIXTURE(BuiltinsFixture, "less_exponential_blowup_please")
{ {
CheckResult result = check(R"( CheckResult result = check(R"(
--!strict --!strict
@ -1920,7 +1920,7 @@ TEST_CASE_FIXTURE(Fixture, "less_exponential_blowup_please")
newData:First() newData:First()
)"); )");
LUAU_REQUIRE_ERRORS(result); LUAU_REQUIRE_ERROR_COUNT(1, result);
} }
TEST_CASE_FIXTURE(Fixture, "common_table_element_union_in_call") TEST_CASE_FIXTURE(Fixture, "common_table_element_union_in_call")
@ -2327,8 +2327,6 @@ TEST_CASE_FIXTURE(Fixture, "confusing_indexing")
TEST_CASE_FIXTURE(Fixture, "pass_a_union_of_tables_to_a_function_that_requires_a_table") TEST_CASE_FIXTURE(Fixture, "pass_a_union_of_tables_to_a_function_that_requires_a_table")
{ {
ScopedFastFlag sff{"LuauDifferentOrderOfUnificationDoesntMatter2", true};
CheckResult result = check(R"( CheckResult result = check(R"(
local a: {x: number, y: number, [any]: any} | {y: number} local a: {x: number, y: number, [any]: any} | {y: number}
@ -2347,8 +2345,6 @@ TEST_CASE_FIXTURE(Fixture, "pass_a_union_of_tables_to_a_function_that_requires_a
TEST_CASE_FIXTURE(Fixture, "pass_a_union_of_tables_to_a_function_that_requires_a_table_2") TEST_CASE_FIXTURE(Fixture, "pass_a_union_of_tables_to_a_function_that_requires_a_table_2")
{ {
ScopedFastFlag sff{"LuauDifferentOrderOfUnificationDoesntMatter2", true};
CheckResult result = check(R"( CheckResult result = check(R"(
local a: {y: number} | {x: number, y: number, [any]: any} local a: {y: number} | {x: number, y: number, [any]: any}
@ -2680,10 +2676,11 @@ do end
)"); )");
} }
TEST_CASE_FIXTURE(Fixture, "dont_crash_when_setmetatable_does_not_produce_a_metatabletypevar") TEST_CASE_FIXTURE(BuiltinsFixture, "dont_crash_when_setmetatable_does_not_produce_a_metatabletypevar")
{ {
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 expects 2 arguments, but only 1 is specified", toString(result.errors[0]));
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "instantiate_table_cloning") TEST_CASE_FIXTURE(BuiltinsFixture, "instantiate_table_cloning")
@ -3006,4 +3003,30 @@ TEST_CASE_FIXTURE(Fixture, "expected_indexer_value_type_extra_2")
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
} }
TEST_CASE_FIXTURE(Fixture, "prop_access_on_key_whose_types_mismatches")
{
ScopedFastFlag sff{"LuauReportErrorsOnIndexerKeyMismatch", true};
CheckResult result = check(R"(
local t: {number} = {}
local x = t.x
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ("Key 'x' not found in table '{number}'", toString(result.errors[0]));
}
TEST_CASE_FIXTURE(Fixture, "prop_access_on_unions_of_indexers_where_key_whose_types_mismatches")
{
ScopedFastFlag sff{"LuauReportErrorsOnIndexerKeyMismatch", true};
CheckResult result = check(R"(
local t: { [number]: number } | { [boolean]: number } = {}
local u = t.x
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ("Type '{number} | {| [boolean]: number |}' does not have key 'x'", toString(result.errors[0]));
}
TEST_SUITE_END(); TEST_SUITE_END();

View file

@ -681,7 +681,7 @@ TEST_CASE_FIXTURE(Fixture, "no_stack_overflow_from_isoptional")
_(nil) _(nil)
)"); )");
CHECK_LE(0, result.errors.size()); LUAU_REQUIRE_ERRORS(result);
std::optional<TypeFun> t0 = getMainModule()->getModuleScope()->lookupType("t0"); std::optional<TypeFun> t0 = getMainModule()->getModuleScope()->lookupType("t0");
REQUIRE(t0); REQUIRE(t0);
@ -693,7 +693,7 @@ TEST_CASE_FIXTURE(Fixture, "no_stack_overflow_from_isoptional")
CHECK(it != result.errors.end()); CHECK(it != result.errors.end());
} }
TEST_CASE_FIXTURE(Fixture, "no_stack_overflow_from_isoptional2") TEST_CASE_FIXTURE(BuiltinsFixture, "no_stack_overflow_from_isoptional2")
{ {
CheckResult result = check(R"( CheckResult result = check(R"(
function _(l0:({})|(t0)):((((typeof((xpcall)))|(t96<t0>))|(t13))&(t96<t0>),()->typeof(...)) function _(l0:({})|(t0)):((((typeof((xpcall)))|(t96<t0>))|(t13))&(t96<t0>),()->typeof(...))
@ -720,10 +720,10 @@ TEST_CASE_FIXTURE(Fixture, "no_infinite_loop_when_trying_to_unify_uh_this")
_() _()
)"); )");
CHECK_LE(0, result.errors.size()); LUAU_REQUIRE_ERRORS(result);
} }
TEST_CASE_FIXTURE(Fixture, "no_heap_use_after_free_error") TEST_CASE_FIXTURE(BuiltinsFixture, "no_heap_use_after_free_error")
{ {
CheckResult result = check(R"( CheckResult result = check(R"(
--!nonstrict --!nonstrict
@ -737,7 +737,7 @@ TEST_CASE_FIXTURE(Fixture, "no_heap_use_after_free_error")
end end
)"); )");
CHECK_LE(0, result.errors.size()); LUAU_REQUIRE_ERRORS(result);
} }
TEST_CASE_FIXTURE(Fixture, "infer_type_assertion_value_type") TEST_CASE_FIXTURE(Fixture, "infer_type_assertion_value_type")
@ -1038,7 +1038,6 @@ TEST_CASE_FIXTURE(Fixture, "do_not_bind_a_free_table_to_a_union_containing_that_
{ {
ScopedFastFlag flag[] = { ScopedFastFlag flag[] = {
{"LuauLowerBoundsCalculation", true}, {"LuauLowerBoundsCalculation", true},
{"LuauDifferentOrderOfUnificationDoesntMatter2", true},
}; };
CheckResult result = check(R"( CheckResult result = check(R"(

View file

@ -939,7 +939,7 @@ until _
)"); )");
} }
TEST_CASE_FIXTURE(Fixture, "detect_cyclic_typepacks") TEST_CASE_FIXTURE(BuiltinsFixture, "detect_cyclic_typepacks")
{ {
CheckResult result = check(R"( CheckResult result = check(R"(
type ( ... ) ( ) ; type ( ... ) ( ) ;
@ -949,10 +949,10 @@ TEST_CASE_FIXTURE(Fixture, "detect_cyclic_typepacks")
( ... ) "" ( ... ) ""
)"); )");
CHECK_LE(0, result.errors.size()); LUAU_REQUIRE_ERRORS(result);
} }
TEST_CASE_FIXTURE(Fixture, "detect_cyclic_typepacks2") TEST_CASE_FIXTURE(BuiltinsFixture, "detect_cyclic_typepacks2")
{ {
CheckResult result = check(R"( CheckResult result = check(R"(
function _(l0:((typeof((pcall)))|((((t0)->())|(typeof(-67108864)))|(any)))|(any),...):(((typeof(0))|(any))|(any),typeof(-67108864),any) function _(l0:((typeof((pcall)))|((((t0)->())|(typeof(-67108864)))|(any)))|(any),...):(((typeof(0))|(any))|(any),typeof(-67108864),any)
@ -961,7 +961,7 @@ TEST_CASE_FIXTURE(Fixture, "detect_cyclic_typepacks2")
end end
)"); )");
CHECK_LE(0, result.errors.size()); LUAU_REQUIRE_ERRORS(result);
} }
TEST_SUITE_END(); TEST_SUITE_END();

View file

@ -357,8 +357,7 @@ local b: (T, T, T) -> T
TypeId bType = requireType("b"); TypeId bType = requireType("b");
VisitCountTracker tester; VisitCountTracker tester;
DenseHashSet<void*> seen{nullptr}; tester.traverse(bType);
DEPRECATED_visitTypeVarOnce(bType, tester, seen);
for (auto [_, count] : tester.tyVisits) for (auto [_, count] : tester.tyVisits)
CHECK_EQ(count, 1); CHECK_EQ(count, 1);

View file

@ -8,12 +8,10 @@
using namespace Luau; using namespace Luau;
LUAU_FASTFLAG(LuauUseVisitRecursionLimit)
LUAU_FASTINT(LuauVisitRecursionLimit) LUAU_FASTINT(LuauVisitRecursionLimit)
struct VisitTypeVarFixture : Fixture struct VisitTypeVarFixture : Fixture
{ {
ScopedFastFlag flag1 = {"LuauUseVisitRecursionLimit", true};
ScopedFastFlag flag2 = {"LuauRecursionLimitException", true}; ScopedFastFlag flag2 = {"LuauRecursionLimitException", true};
}; };

View file

@ -382,4 +382,9 @@ assert(ecall(function() return "a" <= 5 end) == "attempt to compare string <= nu
assert(ecall(function() local t = {} setmetatable(t, { __newindex = function(t,i,v) end }) t[nil] = 2 end) == "table index is nil") assert(ecall(function() local t = {} setmetatable(t, { __newindex = function(t,i,v) end }) t[nil] = 2 end) == "table index is nil")
-- for loop type errors
assert(ecall(function() for i='a',2 do end end) == "invalid 'for' initial value (number expected, got string)")
assert(ecall(function() for i=1,'a' do end end) == "invalid 'for' limit (number expected, got string)")
assert(ecall(function() for i=1,2,'a' do end end) == "invalid 'for' step (number expected, got string)")
return('OK') return('OK')

View file

@ -567,4 +567,29 @@ do
assert(not ok) assert(not ok)
end end
-- test iteration with lightuserdata keys
do
function countud()
local t = {}
t[makelud(1)] = 1
t[makelud(2)] = 2
local count = 0
for k,v in pairs(t) do
count += v
end
return count
end
assert(countud() == 3)
end
-- test iteration with lightuserdata keys with a substituted environment
do
local env = { makelud = makelud, pairs = pairs }
setfenv(countud, env)
assert(countud() == 3)
end
return"OK" return"OK"

View file

@ -0,0 +1,45 @@
-- This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
print('testing userdata')
-- int64 is a userdata type defined in C++
-- equality
assert(int64(1) == int64(1))
assert(int64(1) ~= int64(2))
-- relational
assert(not (int64(1) < int64(1)))
assert(int64(1) < int64(2))
assert(int64(1) <= int64(1))
assert(int64(1) <= int64(2))
-- arithmetics
assert(-int64(2) == int64(-2))
assert(int64(1) + int64(2) == int64(3))
assert(int64(1) - int64(2) == int64(-1))
assert(int64(2) * int64(3) == int64(6))
assert(int64(4) / int64(2) == int64(2))
assert(int64(4) % int64(3) == int64(1))
assert(int64(2) ^ int64(3) == int64(8))
assert(int64(1) + 2 == int64(3))
assert(int64(1) - 2 == int64(-1))
assert(int64(2) * 3 == int64(6))
assert(int64(4) / 2 == int64(2))
assert(int64(4) % 3 == int64(1))
assert(int64(2) ^ 3 == int64(8))
-- tostring
assert(tostring(int64(2)) == "2")
-- index/newindex; note, mutable userdatas aren't very idiomatic but we need to test this
do
local v = int64(42)
assert(v.value == 42)
v.value = 4
assert(v.value == 4)
assert(v == int64(4))
end
return 'OK'

View file

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
<Type Name="Luau::CodeGen::RegisterX64">
<DisplayString Condition="size == Luau::CodeGen::SizeX64::none &amp;&amp; index == 31">noreg</DisplayString>
<DisplayString Condition="size == Luau::CodeGen::SizeX64::none &amp;&amp; index == 0">rip</DisplayString>
<DisplayString Condition="size == Luau::CodeGen::SizeX64::byte &amp;&amp; index == 0">al</DisplayString>
<DisplayString Condition="size == Luau::CodeGen::SizeX64::byte &amp;&amp; index == 1">cl</DisplayString>
<DisplayString Condition="size == Luau::CodeGen::SizeX64::byte &amp;&amp; index == 2">dl</DisplayString>
<DisplayString Condition="size == Luau::CodeGen::SizeX64::byte &amp;&amp; index == 3">bl</DisplayString>
<DisplayString Condition="size == Luau::CodeGen::SizeX64::dword &amp;&amp; index == 0">eax</DisplayString>
<DisplayString Condition="size == Luau::CodeGen::SizeX64::dword &amp;&amp; index == 1">ecx</DisplayString>
<DisplayString Condition="size == Luau::CodeGen::SizeX64::dword &amp;&amp; index == 2">edx</DisplayString>
<DisplayString Condition="size == Luau::CodeGen::SizeX64::dword &amp;&amp; index == 3">ebx</DisplayString>
<DisplayString Condition="size == Luau::CodeGen::SizeX64::dword &amp;&amp; index == 4">esp</DisplayString>
<DisplayString Condition="size == Luau::CodeGen::SizeX64::dword &amp;&amp; index == 5">ebp</DisplayString>
<DisplayString Condition="size == Luau::CodeGen::SizeX64::dword &amp;&amp; index == 6">esi</DisplayString>
<DisplayString Condition="size == Luau::CodeGen::SizeX64::dword &amp;&amp; index == 7">edi</DisplayString>
<DisplayString Condition="size == Luau::CodeGen::SizeX64::dword &amp;&amp; index >= 8">e{(int)index,d}d</DisplayString>
<DisplayString Condition="size == Luau::CodeGen::SizeX64::qword &amp;&amp; index == 0">rax</DisplayString>
<DisplayString Condition="size == Luau::CodeGen::SizeX64::qword &amp;&amp; index == 1">rcx</DisplayString>
<DisplayString Condition="size == Luau::CodeGen::SizeX64::qword &amp;&amp; index == 2">rdx</DisplayString>
<DisplayString Condition="size == Luau::CodeGen::SizeX64::qword &amp;&amp; index == 3">rbx</DisplayString>
<DisplayString Condition="size == Luau::CodeGen::SizeX64::qword &amp;&amp; index == 4">rsp</DisplayString>
<DisplayString Condition="size == Luau::CodeGen::SizeX64::qword &amp;&amp; index == 5">rbp</DisplayString>
<DisplayString Condition="size == Luau::CodeGen::SizeX64::qword &amp;&amp; index == 6">rsi</DisplayString>
<DisplayString Condition="size == Luau::CodeGen::SizeX64::qword &amp;&amp; index == 7">rdi</DisplayString>
<DisplayString Condition="size == Luau::CodeGen::SizeX64::qword &amp;&amp; index >= 8">r{(int)index,d}</DisplayString>
<DisplayString Condition="size == Luau::CodeGen::SizeX64::xmmword">xmm{(int)index,d}</DisplayString>
<DisplayString Condition="size == Luau::CodeGen::SizeX64::ymmword">ymm{(int)index,d}</DisplayString>
</Type>
<Type Name="Luau::CodeGen::OperandX64">
<DisplayString Condition="cat == 0">{reg}</DisplayString>
<DisplayString Condition="cat == 1">{mem.size,en} ptr[{mem.base} + {mem.index}*{(int)mem.scale,d} + {disp}]</DisplayString>
<DisplayString Condition="cat == 2">{imm}</DisplayString>
<Expand>
<ExpandedItem Condition="cat == 0">reg</ExpandedItem>
<ExpandedItem Condition="cat == 1">mem</ExpandedItem>
<ExpandedItem Condition="cat == 2">imm</ExpandedItem>
<Item Condition="cat == 1" Name="disp">disp</Item>
</Expand>
</Type>
</AutoVisualizer>

76
tools/patchtests.py Normal file
View file

@ -0,0 +1,76 @@
#!/usr/bin/python
# This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
# This code can be used to patch Compiler.test.cpp following bytecode changes, based on error output
import sys
import re
(_, filename) = sys.argv
input = sys.stdin.readlines()
errors = []
# 0: looking for error, 1: looking for replacement, 2: collecting replacement, 3: collecting initial text
state = 0
# parse input into errors[] with the state machine; this is using doctest output and expects multi-line match failures
for line in input:
if state == 0:
match = re.match("tests/[^:]+:(\d+): ERROR: CHECK_EQ", line)
if match:
error_line = int(match[1])
state = 1
elif state == 1:
if re.match("\s*values: CHECK_EQ\(\s*$", line):
error_repl = []
state = 2
elif re.match("\s*values: CHECK_EQ", line):
state = 0 # skipping single-line checks since we can't patch them
elif state == 2:
if line.strip() == ",":
error_orig = []
state = 3
else:
error_repl.append(line)
elif state == 3:
if line.strip() == ")":
errors.append((error_line, error_orig, error_repl))
state = 0
else:
error_orig.append(line)
# make sure we fully process each individual check
assert(state == 0)
errors.sort(key = lambda e: e[0])
with open(filename, "r") as fp:
source = fp.readlines()
# patch source text into result[] using errors[]; we expect every match to appear at or after the line error was reported at
result = []
current = 0
index = 0
while index < len(source):
line = source[index]
error = errors[current] if current < len(errors) else None
if not error or index < error[0] or line != error[1][0]:
result.append(line)
index += 1
else:
# validate that the patch has a complete match in source text
for v in range(len(error[1])):
assert(source[index + v] == error[1][v])
result += error[2]
index += len(error[1])
current += 1
# make sure we patch all errors
assert(current == len(errors))
with open(filename, "w") as fp:
fp.writelines(result)