mirror of
https://github.com/luau-lang/luau.git
synced 2024-12-13 13:30:40 +00:00
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:
parent
e13f17e225
commit
61766a692c
69 changed files with 3837 additions and 1724 deletions
|
@ -12,9 +12,6 @@
|
|||
#include <vector>
|
||||
#include <optional>
|
||||
|
||||
LUAU_FASTFLAG(LuauSeparateTypechecks)
|
||||
LUAU_FASTFLAG(LuauDirtySourceModule)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
|
@ -60,17 +57,12 @@ struct SourceNode
|
|||
{
|
||||
bool hasDirtySourceModule() const
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauDirtySourceModule);
|
||||
|
||||
return dirtySourceModule;
|
||||
}
|
||||
|
||||
bool hasDirtyModule(bool forAutocomplete) const
|
||||
{
|
||||
if (FFlag::LuauSeparateTypechecks)
|
||||
return forAutocomplete ? dirtyModuleForAutocomplete : dirtyModule;
|
||||
else
|
||||
return dirtyModule;
|
||||
}
|
||||
|
||||
ModuleName name;
|
||||
|
@ -90,10 +82,6 @@ struct FrontendOptions
|
|||
// is complete.
|
||||
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)
|
||||
bool forAutocomplete = false;
|
||||
};
|
||||
|
@ -171,7 +159,7 @@ struct Frontend
|
|||
void applyBuiltinDefinitionToEnvironment(const std::string& environmentName, const std::string& definitionName);
|
||||
|
||||
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);
|
||||
|
||||
bool parseGraph(std::vector<ModuleName>& buildQueue, CheckResult& checkResult, const ModuleName& root, bool forAutocomplete);
|
||||
|
|
53
Analysis/include/Luau/Instantiation.h
Normal file
53
Analysis/include/Luau/Instantiation.h
Normal 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
|
|
@ -39,4 +39,4 @@ struct TypeArena
|
|||
void freeze(TypeArena& arena);
|
||||
void unfreeze(TypeArena& arena);
|
||||
|
||||
}
|
||||
} // namespace Luau
|
||||
|
|
|
@ -34,45 +34,6 @@ const AstStat* getFallthrough(const AstStat* node);
|
|||
struct UnifierOptions;
|
||||
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
|
||||
struct Anyification : Substitution
|
||||
{
|
||||
|
|
|
@ -32,6 +32,9 @@ struct Widen : Substitution
|
|||
TypeId clean(TypeId ty) override;
|
||||
TypePackId clean(TypePackId ty) override;
|
||||
bool ignoreChildren(TypeId ty) override;
|
||||
|
||||
TypeId operator()(TypeId ty);
|
||||
TypePackId operator()(TypePackId ty);
|
||||
};
|
||||
|
||||
// TODO: Use this more widely.
|
||||
|
|
|
@ -42,7 +42,6 @@ struct UnifierSharedState
|
|||
|
||||
InternalErrorReporter* iceHandler;
|
||||
|
||||
DenseHashSet<void*> seenAny{nullptr};
|
||||
DenseHashMap<TypeId, bool> skipCacheForType{nullptr};
|
||||
DenseHashSet<std::pair<TypeId, TypeId>, TypeIdPairHash> cachedUnify{{nullptr, nullptr}};
|
||||
DenseHashMap<std::pair<TypeId, TypeId>, TypeErrorData, TypeIdPairHash> cachedUnifyError{{nullptr, nullptr}};
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
#include "Luau/TypePack.h"
|
||||
#include "Luau/TypeVar.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauUseVisitRecursionLimit)
|
||||
LUAU_FASTINT(LuauVisitRecursionLimit)
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
|
|
@ -1700,31 +1700,18 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
|
|||
|
||||
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!)
|
||||
FrontendOptions 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);
|
||||
if (!sourceModule)
|
||||
return {};
|
||||
|
||||
TypeChecker& typeChecker =
|
||||
(frontend.options.typecheckTwice_DEPRECATED || FFlag::LuauSeparateTypechecks ? frontend.typeCheckerForAutocomplete : frontend.typeChecker);
|
||||
ModulePtr module =
|
||||
(frontend.options.typecheckTwice_DEPRECATED || FFlag::LuauSeparateTypechecks ? frontend.moduleResolverForAutocomplete.getModule(moduleName)
|
||||
: frontend.moduleResolver.getModule(moduleName));
|
||||
TypeChecker& typeChecker = frontend.typeCheckerForAutocomplete;
|
||||
ModulePtr module = frontend.moduleResolverForAutocomplete.getModule(moduleName);
|
||||
|
||||
if (!module)
|
||||
return {};
|
||||
|
@ -1752,9 +1739,7 @@ OwningAutocompleteResult autocompleteSource(Frontend& frontend, std::string_view
|
|||
sourceModule->mode = Mode::Strict;
|
||||
sourceModule->commentLocations = std::move(result.commentLocations);
|
||||
|
||||
TypeChecker& typeChecker =
|
||||
(frontend.options.typecheckTwice_DEPRECATED || FFlag::LuauSeparateTypechecks ? frontend.typeCheckerForAutocomplete : frontend.typeChecker);
|
||||
|
||||
TypeChecker& typeChecker = frontend.typeCheckerForAutocomplete;
|
||||
ModulePtr module = typeChecker.check(*sourceModule, Mode::Strict);
|
||||
|
||||
OwningAutocompleteResult autocompleteResult = {
|
||||
|
|
|
@ -20,9 +20,7 @@ LUAU_FASTINT(LuauTypeInferIterationLimit)
|
|||
LUAU_FASTINT(LuauTarjanChildLimit)
|
||||
LUAU_FASTFLAG(LuauInferInNoCheckMode)
|
||||
LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSeparateTypechecks, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauAutocompleteDynamicLimits, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauDirtySourceModule, false)
|
||||
LUAU_FASTINTVARIABLE(LuauAutocompleteCheckTimeoutMs, 100)
|
||||
|
||||
namespace Luau
|
||||
|
@ -361,8 +359,6 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
|
|||
if (it != sourceNodes.end() && !it->second.hasDirtyModule(frontendOptions.forAutocomplete))
|
||||
{
|
||||
// No recheck required.
|
||||
if (FFlag::LuauSeparateTypechecks)
|
||||
{
|
||||
if (frontendOptions.forAutocomplete)
|
||||
{
|
||||
auto it2 = moduleResolverForAutocomplete.modules.find(name);
|
||||
|
@ -376,17 +372,8 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
|
|||
throw std::runtime_error("Frontend::modules does not have data for " + name);
|
||||
}
|
||||
|
||||
return CheckResult{accumulateErrors(
|
||||
sourceNodes, frontendOptions.forAutocomplete ? moduleResolverForAutocomplete.modules : moduleResolver.modules, 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, moduleResolver.modules, name)};
|
||||
}
|
||||
return CheckResult{
|
||||
accumulateErrors(sourceNodes, frontendOptions.forAutocomplete ? moduleResolverForAutocomplete.modules : moduleResolver.modules, name)};
|
||||
}
|
||||
|
||||
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
|
||||
sourceModule.cyclic = !requireCycles.empty();
|
||||
|
||||
if (FFlag::LuauSeparateTypechecks && frontendOptions.forAutocomplete)
|
||||
if (frontendOptions.forAutocomplete)
|
||||
{
|
||||
// The autocomplete typecheck is always in strict mode with DM awareness
|
||||
// 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);
|
||||
|
||||
// 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.filesStrict += mode == Mode::Strict;
|
||||
stats.filesNonstrict += mode == Mode::Nonstrict;
|
||||
|
@ -563,7 +539,7 @@ bool Frontend::parseGraph(std::vector<ModuleName>& buildQueue, CheckResult& chec
|
|||
bool cyclic = false;
|
||||
|
||||
{
|
||||
auto [sourceNode, _] = getSourceNode(checkResult, root, forAutocomplete);
|
||||
auto [sourceNode, _] = getSourceNode(checkResult, root);
|
||||
if (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)
|
||||
{
|
||||
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());
|
||||
|
||||
CheckResult checkResult;
|
||||
auto [_sourceNode, sourceModule] = getSourceNode(checkResult, name, false);
|
||||
auto [_sourceNode, sourceModule] = getSourceNode(checkResult, name);
|
||||
|
||||
if (!sourceModule)
|
||||
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)
|
||||
{
|
||||
if (FFlag::LuauSeparateTypechecks)
|
||||
{
|
||||
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;
|
||||
for (const auto& module : sourceNodes)
|
||||
|
@ -783,32 +751,12 @@ void Frontend::markDirty(const ModuleName& name, std::vector<ModuleName>* marked
|
|||
if (markedDirty)
|
||||
markedDirty->push_back(next);
|
||||
|
||||
if (FFlag::LuauDirtySourceModule)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauSeparateTypechecks);
|
||||
|
||||
if (sourceNode.dirtySourceModule && sourceNode.dirtyModule && sourceNode.dirtyModuleForAutocomplete)
|
||||
continue;
|
||||
|
||||
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))
|
||||
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.
|
||||
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_ARGUMENT("name", name.c_str());
|
||||
|
||||
auto it = sourceNodes.find(name);
|
||||
if (it != sourceNodes.end() &&
|
||||
(FFlag::LuauDirtySourceModule ? !it->second.hasDirtySourceModule() : !it->second.hasDirtyModule(forAutocomplete_DEPRECATED)))
|
||||
if (it != sourceNodes.end() && !it->second.hasDirtySourceModule())
|
||||
{
|
||||
auto moduleIt = sourceModules.find(name);
|
||||
if (moduleIt != sourceModules.end())
|
||||
|
@ -885,22 +832,13 @@ std::pair<SourceNode*, SourceModule*> Frontend::getSourceNode(CheckResult& check
|
|||
sourceNode.name = name;
|
||||
sourceNode.requires.clear();
|
||||
sourceNode.requireLocations.clear();
|
||||
|
||||
if (FFlag::LuauDirtySourceModule)
|
||||
sourceNode.dirtySourceModule = false;
|
||||
|
||||
if (FFlag::LuauSeparateTypechecks)
|
||||
{
|
||||
if (it == sourceNodes.end())
|
||||
{
|
||||
sourceNode.dirtyModule = true;
|
||||
sourceNode.dirtyModuleForAutocomplete = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sourceNode.dirtyModule = true;
|
||||
}
|
||||
|
||||
for (const auto& [moduleName, location] : requireTrace.requires)
|
||||
sourceNode.requires.insert(moduleName);
|
||||
|
|
128
Analysis/src/Instantiation.cpp
Normal file
128
Analysis/src/Instantiation.cpp
Normal 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
|
|
@ -56,8 +56,18 @@ bool isWithinComment(const SourceModule& sourceModule, Position pos)
|
|||
|
||||
struct ForceNormal : TypeVarOnceVisitor
|
||||
{
|
||||
const TypeArena* typeArena = nullptr;
|
||||
|
||||
ForceNormal(const TypeArena* typeArena)
|
||||
: typeArena(typeArena)
|
||||
{
|
||||
}
|
||||
|
||||
bool visit(TypeId ty) override
|
||||
{
|
||||
if (ty->owningArena != typeArena)
|
||||
return false;
|
||||
|
||||
asMutable(ty)->normal = true;
|
||||
return true;
|
||||
}
|
||||
|
@ -100,7 +110,7 @@ void Module::clonePublicInterface(InternalErrorReporter& ice)
|
|||
normalize(*moduleScope->varargPack, interfaceTypes, ice);
|
||||
}
|
||||
|
||||
ForceNormal forceNormal;
|
||||
ForceNormal forceNormal{&interfaceTypes};
|
||||
|
||||
for (auto& [name, tf] : moduleScope->exportedTypeBindings)
|
||||
{
|
||||
|
|
|
@ -15,6 +15,7 @@ LUAU_FASTFLAGVARIABLE(DebugLuauCopyBeforeNormalizing, false)
|
|||
LUAU_FASTINTVARIABLE(LuauNormalizeIterationLimit, 1200);
|
||||
LUAU_FASTFLAGVARIABLE(LuauNormalizeCombineTableFix, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauNormalizeFlagIsConservative, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauNormalizeCombineEqFix, false);
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -325,245 +326,6 @@ struct Normalize final : TypeVarVisitor
|
|||
int iterationLimit = 0;
|
||||
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
|
||||
{
|
||||
LUAU_ASSERT(!ty->normal);
|
||||
|
@ -968,6 +730,9 @@ struct Normalize final : TypeVarVisitor
|
|||
*/
|
||||
TypeId combine(Replacer& replacer, TypeId a, TypeId b)
|
||||
{
|
||||
if (FFlag::LuauNormalizeCombineEqFix)
|
||||
b = follow(b);
|
||||
|
||||
if (FFlag::LuauNormalizeCombineTableFix && a == b)
|
||||
return a;
|
||||
|
||||
|
@ -986,7 +751,7 @@ struct Normalize final : TypeVarVisitor
|
|||
}
|
||||
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}});
|
||||
combineIntoTable(replacer, ttv, b);
|
||||
return a;
|
||||
|
@ -1009,15 +774,7 @@ std::pair<TypeId, bool> normalize(TypeId ty, TypeArena& arena, InternalErrorRepo
|
|||
(void)clone(ty, arena, state);
|
||||
|
||||
Normalize n{arena, ice};
|
||||
if (FFlag::LuauNormalizeFlagIsConservative)
|
||||
{
|
||||
DEPRECATED_visitTypeVar(ty, n);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::unordered_set<void*> seen;
|
||||
DEPRECATED_visitTypeVar(ty, n, seen);
|
||||
}
|
||||
n.traverse(ty);
|
||||
|
||||
return {ty, !n.limitExceeded};
|
||||
}
|
||||
|
@ -1041,15 +798,7 @@ std::pair<TypePackId, bool> normalize(TypePackId tp, TypeArena& arena, InternalE
|
|||
(void)clone(tp, arena, state);
|
||||
|
||||
Normalize n{arena, ice};
|
||||
if (FFlag::LuauNormalizeFlagIsConservative)
|
||||
{
|
||||
DEPRECATED_visitTypeVar(tp, n);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::unordered_set<void*> seen;
|
||||
DEPRECATED_visitTypeVar(tp, n, seen);
|
||||
}
|
||||
n.traverse(tp);
|
||||
|
||||
return {tp, !n.limitExceeded};
|
||||
}
|
||||
|
|
|
@ -119,8 +119,7 @@ struct Quantifier final : TypeVarOnceVisitor
|
|||
void quantify(TypeId ty, TypeLevel level)
|
||||
{
|
||||
Quantifier q{level};
|
||||
DenseHashSet<void*> seen{nullptr};
|
||||
DEPRECATED_visitTypeVarOnce(ty, q, seen);
|
||||
q.traverse(ty);
|
||||
|
||||
FunctionTypeVar* ftv = getMutable<FunctionTypeVar>(ty);
|
||||
LUAU_ASSERT(ftv);
|
||||
|
|
|
@ -48,46 +48,6 @@ struct FindCyclicTypes final : TypeVarVisitor
|
|||
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
|
||||
{
|
||||
return visited.insert(ty).second;
|
||||
|
@ -128,7 +88,7 @@ void findCyclicTypes(std::set<TypeId>& cycles, std::set<TypePackId>& cycleTPs, T
|
|||
{
|
||||
FindCyclicTypes fct;
|
||||
fct.exhaustive = exhaustive;
|
||||
DEPRECATED_visitTypeVar(ty, fct);
|
||||
fct.traverse(ty);
|
||||
|
||||
cycles = std::move(fct.cycles);
|
||||
cycleTPs = std::move(fct.cycleTPs);
|
||||
|
|
|
@ -85,4 +85,4 @@ void unfreeze(TypeArena& arena)
|
|||
arena.typePacks.unfreeze();
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace Luau
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include "Luau/Clone.h"
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/Instantiation.h"
|
||||
#include "Luau/ModuleResolver.h"
|
||||
#include "Luau/Normalize.h"
|
||||
#include "Luau/Parser.h"
|
||||
|
@ -10,13 +11,13 @@
|
|||
#include "Luau/RecursionCounter.h"
|
||||
#include "Luau/Scope.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/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 <iterator>
|
||||
|
@ -26,14 +27,11 @@ LUAU_FASTINTVARIABLE(LuauTypeInferRecursionLimit, 165)
|
|||
LUAU_FASTINTVARIABLE(LuauTypeInferIterationLimit, 20000)
|
||||
LUAU_FASTINTVARIABLE(LuauTypeInferTypePackLoopLimit, 5000)
|
||||
LUAU_FASTINTVARIABLE(LuauCheckRecursionLimit, 300)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUseVisitRecursionLimit, false)
|
||||
LUAU_FASTINTVARIABLE(LuauVisitRecursionLimit, 500)
|
||||
LUAU_FASTFLAG(LuauKnowsTheDataModel3)
|
||||
LUAU_FASTFLAG(LuauSeparateTypechecks)
|
||||
LUAU_FASTFLAG(LuauAutocompleteDynamicLimits)
|
||||
LUAU_FASTFLAGVARIABLE(LuauDoNotRelyOnNextBinding, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauExpectedPropTypeFromIndexer, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauWeakEqConstraint, false) // Eventually removed as false.
|
||||
LUAU_FASTFLAGVARIABLE(LuauLowerBoundsCalculation, false)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSelfCallAutocompleteFix, false)
|
||||
|
@ -43,7 +41,6 @@ LUAU_FASTFLAGVARIABLE(LuauUnsealedTableLiteral, false)
|
|||
LUAU_FASTFLAGVARIABLE(LuauTwoPassAliasDefinitionFix, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauReturnAnyInsteadOfICE, false) // Eventually removed as false.
|
||||
LUAU_FASTFLAG(LuauNormalizeFlagIsConservative)
|
||||
LUAU_FASTFLAG(LuauWidenIfSupertypeIsFree2)
|
||||
LUAU_FASTFLAGVARIABLE(LuauReturnTypeInferenceInNonstrict, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauRecursionLimitException, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauApplyTypeFunctionFix, false);
|
||||
|
@ -51,6 +48,7 @@ LUAU_FASTFLAGVARIABLE(LuauTypecheckIter, false);
|
|||
LUAU_FASTFLAGVARIABLE(LuauSuccessTypingForEqualityOperations, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNoMethodLocations, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauAlwaysQuantify, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauReportErrorsOnIndexerKeyMismatch, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -305,12 +303,8 @@ ModulePtr TypeChecker::checkWithoutRecursionCheck(const SourceModule& module, Mo
|
|||
|
||||
currentModule.reset(new Module());
|
||||
currentModule->type = module.type;
|
||||
|
||||
if (FFlag::LuauSeparateTypechecks)
|
||||
{
|
||||
currentModule->allocator = module.allocator;
|
||||
currentModule->names = module.names;
|
||||
}
|
||||
|
||||
iceHandler->moduleName = module.name;
|
||||
|
||||
|
@ -338,8 +332,6 @@ ModulePtr TypeChecker::checkWithoutRecursionCheck(const SourceModule& module, Mo
|
|||
if (prepareModuleScope)
|
||||
prepareModuleScope(module.name, currentModule->getModuleScope());
|
||||
|
||||
if (FFlag::LuauSeparateTypechecks)
|
||||
{
|
||||
try
|
||||
{
|
||||
checkBlock(moduleScope, *module.root);
|
||||
|
@ -348,11 +340,6 @@ ModulePtr TypeChecker::checkWithoutRecursionCheck(const SourceModule& module, Mo
|
|||
{
|
||||
currentModule->timeout = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
checkBlock(moduleScope, *module.root);
|
||||
}
|
||||
|
||||
if (get<FreeTypePack>(follow(moduleScope->returnType)))
|
||||
moduleScope->returnType = addTypePack(TypePack{{}, std::nullopt});
|
||||
|
@ -443,7 +430,7 @@ void TypeChecker::check(const ScopePtr& scope, const AstStat& program)
|
|||
else
|
||||
ice("Unknown AstStat");
|
||||
|
||||
if (FFlag::LuauSeparateTypechecks && finishTime && TimeTrace::getClock() > *finishTime)
|
||||
if (finishTime && TimeTrace::getClock() > *finishTime)
|
||||
throw TimeLimitError();
|
||||
}
|
||||
|
||||
|
@ -868,9 +855,9 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatAssign& assign)
|
|||
|
||||
TypeId right = nullptr;
|
||||
|
||||
Location loc = 0 == assign.values.size
|
||||
? assign.location
|
||||
: i < assign.values.size ? assign.values.data[i]->location : assign.values.data[assign.values.size - 1]->location;
|
||||
Location loc = 0 == assign.values.size ? assign.location
|
||||
: i < assign.values.size ? assign.values.data[i]->location
|
||||
: assign.values.data[assign.values.size - 1]->location;
|
||||
|
||||
if (valueIter != valueEnd)
|
||||
{
|
||||
|
@ -1825,7 +1812,7 @@ std::optional<TypeId> TypeChecker::findMetatableEntry(TypeId type, std::string e
|
|||
}
|
||||
|
||||
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);
|
||||
|
||||
|
@ -1843,12 +1830,24 @@ std::optional<TypeId> TypeChecker::getIndexTypeFromType(
|
|||
|
||||
if (TableTypeVar* tableType = getMutableTableType(type))
|
||||
{
|
||||
const auto& it = tableType->props.find(name);
|
||||
if (it != tableType->props.end())
|
||||
if (auto it = tableType->props.find(name); it != tableType->props.end())
|
||||
return it->second.type;
|
||||
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.
|
||||
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)
|
||||
|
@ -1858,8 +1857,7 @@ std::optional<TypeId> TypeChecker::getIndexTypeFromType(
|
|||
return result;
|
||||
}
|
||||
|
||||
auto found = findTablePropertyRespectingMeta(type, name, location);
|
||||
if (found)
|
||||
if (auto found = findTablePropertyRespectingMeta(type, name, location))
|
||||
return *found;
|
||||
}
|
||||
else if (const ClassTypeVar* cls = get<ClassTypeVar>(type))
|
||||
|
@ -2512,7 +2510,8 @@ TypeId TypeChecker::checkRelationalOperation(
|
|||
|
||||
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(
|
||||
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);
|
||||
}
|
||||
|
@ -2522,7 +2521,8 @@ TypeId TypeChecker::checkRelationalOperation(
|
|||
{
|
||||
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(
|
||||
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);
|
||||
}
|
||||
|
@ -3636,10 +3636,7 @@ void TypeChecker::checkArgumentList(
|
|||
}
|
||||
|
||||
TypePackId varPack = addTypePack(TypePackVar{TypePack{rest, argIter.tail()}});
|
||||
if (FFlag::LuauWidenIfSupertypeIsFree2)
|
||||
state.tryUnify(varPack, tail);
|
||||
else
|
||||
state.tryUnify(tail, varPack);
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -3707,7 +3704,7 @@ ExprResult<TypePackId> TypeChecker::checkExprPack(const ScopePtr& scope, const A
|
|||
}
|
||||
|
||||
TypePackId retPack;
|
||||
if (FFlag::LuauLowerBoundsCalculation || !FFlag::LuauWidenIfSupertypeIsFree2)
|
||||
if (FFlag::LuauLowerBoundsCalculation)
|
||||
{
|
||||
retPack = freshTypePack(scope->level);
|
||||
}
|
||||
|
@ -3868,9 +3865,7 @@ std::optional<ExprResult<TypePackId>> TypeChecker::checkCallOverload(const Scope
|
|||
Widen widen{¤tModule->internalTypes};
|
||||
for (; it != endIt; ++it)
|
||||
{
|
||||
TypeId t = *it;
|
||||
TypeId widened = widen.substitute(t).value_or(t); // Surely widening is infallible
|
||||
adjustedArgTypes.push_back(addType(ConstrainedTypeVar{level, {widened}}));
|
||||
adjustedArgTypes.push_back(addType(ConstrainedTypeVar{level, {widen(*it)}}));
|
||||
}
|
||||
|
||||
TypePackId adjustedArgPack = addTypePack(TypePack{std::move(adjustedArgTypes), it.tail()});
|
||||
|
@ -3885,14 +3880,11 @@ std::optional<ExprResult<TypePackId>> TypeChecker::checkCallOverload(const Scope
|
|||
else
|
||||
{
|
||||
TypeId r = addType(FunctionTypeVar(scope->level, argPack, retPack));
|
||||
if (FFlag::LuauWidenIfSupertypeIsFree2)
|
||||
{
|
||||
|
||||
UnifierOptions options;
|
||||
options.isFunctionCall = true;
|
||||
unify(r, fn, expr.location, options);
|
||||
}
|
||||
else
|
||||
unify(fn, r, expr.location);
|
||||
|
||||
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)
|
||||
{
|
||||
if (ty->persistent)
|
||||
|
@ -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.
|
||||
|
||||
auto predicate = [&](TypeId option) -> std::optional<TypeId> {
|
||||
if (sense && isUndecidable(option))
|
||||
return FFlag::LuauWeakEqConstraint ? option : eqP.type;
|
||||
|
||||
if (!sense && isNil(eqP.type))
|
||||
return (isUndecidable(option) || !isNil(option)) ? std::optional<TypeId>(option) : std::nullopt;
|
||||
|
||||
|
|
|
@ -21,8 +21,6 @@ LUAU_FASTFLAGVARIABLE(LuauTableSubtypingVariance2, false);
|
|||
LUAU_FASTFLAG(LuauLowerBoundsCalculation);
|
||||
LUAU_FASTFLAG(LuauErrorRecoveryType);
|
||||
LUAU_FASTFLAGVARIABLE(LuauSubtypingAddOptPropsToUnsealedTables, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauWidenIfSupertypeIsFree2, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauDifferentOrderOfUnificationDoesntMatter2, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTxnLogRefreshFunctionPointers, false)
|
||||
|
||||
namespace Luau
|
||||
|
@ -149,8 +147,7 @@ static void promoteTypeLevels(TxnLog& log, const TypeArena* typeArena, TypeLevel
|
|||
return;
|
||||
|
||||
PromoteTypeLevels ptl{log, typeArena, minLevel};
|
||||
DenseHashSet<void*> seen{nullptr};
|
||||
DEPRECATED_visitTypeVarOnce(ty, ptl, seen);
|
||||
ptl.traverse(ty);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
PromoteTypeLevels ptl{log, typeArena, minLevel};
|
||||
DenseHashSet<void*> seen{nullptr};
|
||||
DEPRECATED_visitTypeVarOnce(tp, ptl, seen);
|
||||
ptl.traverse(tp);
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
result = true;
|
||||
|
@ -341,6 +294,16 @@ bool Widen::ignoreChildren(TypeId 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)
|
||||
{
|
||||
auto isUnificationTooComplex = [](const TypeError& te) {
|
||||
|
@ -475,6 +438,8 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
|
|||
if (!occursFailed)
|
||||
{
|
||||
promoteTypeLevels(log, types, superLevel, subTy);
|
||||
|
||||
Widen widen{types};
|
||||
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> firstFailedOption;
|
||||
|
||||
size_t count = uv->options.size();
|
||||
size_t i = 0;
|
||||
|
||||
for (TypeId type : uv->options)
|
||||
{
|
||||
Unifier innerState = makeChildUnifier();
|
||||
|
@ -630,24 +592,9 @@ void Unifier::tryUnifyUnionWithType(TypeId subTy, const UnionTypeVar* uv, TypeId
|
|||
|
||||
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.
|
||||
if (FFlag::LuauDifferentOrderOfUnificationDoesntMatter2)
|
||||
{
|
||||
auto tryBind = [this, subTy](TypeId superOption) {
|
||||
superOption = log.follow(superOption);
|
||||
|
||||
|
@ -683,7 +630,6 @@ void Unifier::tryUnifyUnionWithType(TypeId subTy, const UnionTypeVar* uv, TypeId
|
|||
}
|
||||
else
|
||||
tryBind(superTy);
|
||||
}
|
||||
|
||||
if (unificationTooComplex)
|
||||
reportError(*unificationTooComplex);
|
||||
|
@ -883,7 +829,7 @@ bool Unifier::canCacheResult(TypeId subTy, TypeId superTy)
|
|||
|
||||
auto skipCacheFor = [this](TypeId ty) {
|
||||
SkipCacheForType visitor{sharedState.skipCacheForType, types};
|
||||
DEPRECATED_visitTypeVarOnce(ty, visitor, sharedState.seenAny);
|
||||
visitor.traverse(ty);
|
||||
|
||||
sharedState.skipCacheForType[ty] = visitor.result;
|
||||
|
||||
|
@ -1088,6 +1034,7 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
|
|||
|
||||
if (!log.getMutable<ErrorTypeVar>(superTp))
|
||||
{
|
||||
Widen widen{types};
|
||||
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)
|
||||
{
|
||||
ty = follow(ty);
|
||||
|
@ -1809,10 +1734,7 @@ void Unifier::tryUnifyFreeTable(TypeId subTy, TypeId superTy)
|
|||
{
|
||||
if (auto subProp = findTablePropertyRespectingMeta(subTy, freeName))
|
||||
{
|
||||
if (FFlag::LuauWidenIfSupertypeIsFree2)
|
||||
tryUnify_(*subProp, freeProp.type);
|
||||
else
|
||||
tryUnify_(freeProp.type, *subProp);
|
||||
|
||||
/*
|
||||
* TypeVars are commonly cyclic, so it is entirely possible
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Common.h"
|
||||
#include "Luau/Common.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
LUAU_FASTINTVARIABLE(LuauRecursionLimit, 1000)
|
||||
LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauParserFunctionKeywordAsTypeHelp, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
|
@ -1589,6 +1591,17 @@ AstTypeOrPack Parser::parseSimpleTypeAnnotation(bool 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
|
||||
{
|
||||
Location location = lexer.current().location;
|
||||
|
|
|
@ -19,9 +19,11 @@ if(LUAU_STATIC_CRT)
|
|||
endif()
|
||||
|
||||
project(Luau LANGUAGES CXX C)
|
||||
add_library(Luau.Common INTERFACE)
|
||||
add_library(Luau.Ast STATIC)
|
||||
add_library(Luau.Compiler STATIC)
|
||||
add_library(Luau.Analysis STATIC)
|
||||
add_library(Luau.CodeGen STATIC)
|
||||
add_library(Luau.VM STATIC)
|
||||
add_library(isocline STATIC)
|
||||
|
||||
|
@ -48,8 +50,11 @@ endif()
|
|||
|
||||
include(Sources.cmake)
|
||||
|
||||
target_include_directories(Luau.Common INTERFACE Common/include)
|
||||
|
||||
target_compile_features(Luau.Ast PUBLIC cxx_std_17)
|
||||
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_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_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_include_directories(Luau.VM PUBLIC VM/include)
|
||||
target_link_libraries(Luau.VM PUBLIC Luau.Common)
|
||||
|
||||
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.Analysis PRIVATE ${LUAU_OPTIONS})
|
||||
target_compile_options(Luau.CodeGen PRIVATE ${LUAU_OPTIONS})
|
||||
target_compile_options(Luau.VM PRIVATE ${LUAU_OPTIONS})
|
||||
target_compile_options(isocline PRIVATE ${LUAU_OPTIONS} ${ISOCLINE_OPTIONS})
|
||||
|
||||
|
@ -120,6 +131,7 @@ endif()
|
|||
if(MSVC)
|
||||
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.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)
|
||||
endif()
|
||||
|
||||
|
@ -127,6 +139,7 @@ endif()
|
|||
if(MSVC_IDE)
|
||||
target_sources(Luau.Ast PRIVATE tools/natvis/Ast.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)
|
||||
endif()
|
||||
|
||||
|
@ -154,7 +167,7 @@ endif()
|
|||
if(LUAU_BUILD_TESTS)
|
||||
target_compile_options(Luau.UnitTest PRIVATE ${LUAU_OPTIONS})
|
||||
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_include_directories(Luau.Conformance PRIVATE extern)
|
||||
|
|
169
CodeGen/include/Luau/AssemblyBuilderX64.h
Normal file
169
CodeGen/include/Luau/AssemblyBuilderX64.h
Normal 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
|
46
CodeGen/include/Luau/Condition.h
Normal file
46
CodeGen/include/Luau/Condition.h
Normal 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
|
18
CodeGen/include/Luau/Label.h
Normal file
18
CodeGen/include/Luau/Label.h
Normal file
|
@ -0,0 +1,18 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
namespace CodeGen
|
||||
{
|
||||
|
||||
struct Label
|
||||
{
|
||||
uint32_t id = 0;
|
||||
uint32_t location = ~0u;
|
||||
};
|
||||
|
||||
} // namespace CodeGen
|
||||
} // namespace Luau
|
136
CodeGen/include/Luau/OperandX64.h
Normal file
136
CodeGen/include/Luau/OperandX64.h
Normal 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
|
116
CodeGen/include/Luau/RegisterX64.h
Normal file
116
CodeGen/include/Luau/RegisterX64.h
Normal 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
|
1005
CodeGen/src/AssemblyBuilderX64.cpp
Normal file
1005
CodeGen/src/AssemblyBuilderX64.cpp
Normal file
File diff suppressed because it is too large
Load diff
|
@ -247,7 +247,7 @@ private:
|
|||
void validate() 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 writeLineInfo(std::string& ss) const;
|
||||
|
|
|
@ -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
|
||||
{
|
||||
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
|
||||
|
||||
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++;
|
||||
|
||||
|
@ -1503,39 +1517,39 @@ const uint32_t* BytecodeBuilder::dumpInstruction(const uint32_t* code, std::stri
|
|||
break;
|
||||
|
||||
case LOP_JUMP:
|
||||
formatAppend(result, "JUMP %+d\n", LUAU_INSN_D(insn));
|
||||
formatAppend(result, "JUMP L%d\n", targetLabel);
|
||||
break;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
case LOP_ADD:
|
||||
|
@ -1631,35 +1645,35 @@ const uint32_t* BytecodeBuilder::dumpInstruction(const uint32_t* code, std::stri
|
|||
break;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
case LOP_GETVARARGS:
|
||||
|
@ -1675,7 +1689,7 @@ const uint32_t* BytecodeBuilder::dumpInstruction(const uint32_t* code, std::stri
|
|||
break;
|
||||
|
||||
case LOP_JUMPBACK:
|
||||
formatAppend(result, "JUMPBACK %+d\n", LUAU_INSN_D(insn));
|
||||
formatAppend(result, "JUMPBACK L%d\n", targetLabel);
|
||||
break;
|
||||
|
||||
case LOP_LOADKX:
|
||||
|
@ -1683,26 +1697,26 @@ const uint32_t* BytecodeBuilder::dumpInstruction(const uint32_t* code, std::stri
|
|||
break;
|
||||
|
||||
case LOP_JUMPX:
|
||||
formatAppend(result, "JUMPX %+d\n", LUAU_INSN_E(insn));
|
||||
formatAppend(result, "JUMPX L%d\n", targetLabel);
|
||||
break;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
case LOP_FASTCALL2:
|
||||
{
|
||||
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;
|
||||
}
|
||||
case LOP_FASTCALL2K:
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -1712,23 +1726,24 @@ const uint32_t* BytecodeBuilder::dumpInstruction(const uint32_t* code, std::stri
|
|||
|
||||
case LOP_CAPTURE:
|
||||
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));
|
||||
break;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
default:
|
||||
LUAU_ASSERT(!"Unsupported opcode");
|
||||
}
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
std::string BytecodeBuilder::dumpCurrentFunction() const
|
||||
|
@ -1736,9 +1751,6 @@ std::string BytecodeBuilder::dumpCurrentFunction() const
|
|||
if ((dumpFlags & Dump_Code) == 0)
|
||||
return std::string();
|
||||
|
||||
const uint32_t* code = insns.data();
|
||||
const uint32_t* codeEnd = insns.data() + insns.size();
|
||||
|
||||
int lastLine = -1;
|
||||
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);
|
||||
uint32_t pc = uint32_t(code - insns.data());
|
||||
|
||||
if (op == LOP_PREPVARARGS)
|
||||
{
|
||||
// Don't emit function header in bytecode - it's used for call dispatching and doesn't contain "interesting" information
|
||||
code++;
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
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);
|
||||
nextRemark++;
|
||||
|
@ -1783,7 +1819,7 @@ std::string BytecodeBuilder::dumpCurrentFunction() const
|
|||
|
||||
if (dumpFlags & Dump_Source)
|
||||
{
|
||||
int line = lines[pc];
|
||||
int line = lines[i];
|
||||
|
||||
if (line > 0 && line != lastLine)
|
||||
{
|
||||
|
@ -1794,11 +1830,17 @@ std::string BytecodeBuilder::dumpCurrentFunction() const
|
|||
}
|
||||
|
||||
if (dumpFlags & Dump_Lines)
|
||||
{
|
||||
formatAppend(result, "%d: ", lines[pc]);
|
||||
}
|
||||
formatAppend(result, "%d: ", lines[i]);
|
||||
|
||||
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;
|
||||
|
|
|
@ -15,10 +15,8 @@
|
|||
#include <algorithm>
|
||||
#include <bitset>
|
||||
#include <math.h>
|
||||
#include <limits.h>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauCompileIter, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCompileIterNoReserve, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCompileIterNoPairs, false)
|
||||
|
||||
LUAU_FASTINTVARIABLE(LuauCompileLoopUnrollThreshold, 25)
|
||||
|
@ -176,25 +174,20 @@ struct Compiler
|
|||
|
||||
bool canInlineFunctionBody(AstStat* stat)
|
||||
{
|
||||
if (FFlag::LuauCompileNestedClosureO2)
|
||||
return true; // TODO: remove this function
|
||||
|
||||
struct CanInlineVisitor : AstVisitor
|
||||
{
|
||||
bool result = true;
|
||||
|
||||
bool visit(AstExprFunction* node) override
|
||||
{
|
||||
if (!FFlag::LuauCompileNestedClosureO2)
|
||||
result = false;
|
||||
|
||||
// short-circuit to avoid analyzing nested closure bodies
|
||||
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;
|
||||
|
@ -494,7 +487,8 @@ struct Compiler
|
|||
varc[i] = isConstant(expr->args.data[i]);
|
||||
|
||||
// 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)
|
||||
varc[i] = true;
|
||||
|
||||
|
@ -665,10 +659,10 @@ struct Compiler
|
|||
{
|
||||
if (func->vararg)
|
||||
bytecode.addDebugRemark("inlining failed: function is variadic");
|
||||
else if (fi)
|
||||
bytecode.addDebugRemark("inlining failed: complex constructs in function body");
|
||||
else
|
||||
else if (!fi)
|
||||
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();
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
RegScope rs(this);
|
||||
|
@ -1141,9 +1142,7 @@ struct Compiler
|
|||
void compileConditionValue(AstExpr* node, const uint8_t* target, std::vector<size_t>& skipJump, bool onlyTruth)
|
||||
{
|
||||
// Optimization: we don't need to compute constant values
|
||||
const Constant* cv = constants.find(node);
|
||||
|
||||
if (cv && cv->type != Constant::Type_Unknown)
|
||||
if (const Constant* cv = constants.find(node); cv && cv->type != Constant::Type_Unknown)
|
||||
{
|
||||
// note that we only need to compute the value if it's truthy; otherwise we cal fall through
|
||||
if (cv->isTruthful() == onlyTruth)
|
||||
|
@ -1301,9 +1300,7 @@ struct Compiler
|
|||
RegScope rs(this);
|
||||
|
||||
// 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 (cl && cl->type != Constant::Type_Unknown)
|
||||
if (const Constant* cl = constants.find(expr->left); cl && cl->type != Constant::Type_Unknown)
|
||||
{
|
||||
compileExpr(and_ == cl->isTruthful() ? expr->right : expr->left, target, targetTemp);
|
||||
return;
|
||||
|
@ -1735,13 +1732,11 @@ struct Compiler
|
|||
{
|
||||
RegScope rs(this);
|
||||
|
||||
// note: cv may be invalidated by compileExpr* so we stop using it before calling compile recursively
|
||||
const Constant* cv = constants.find(expr->index);
|
||||
Constant cv = getConstant(expr->index);
|
||||
|
||||
if (cv && cv->type == Constant::Type_Number && cv->valueNumber >= 1 && cv->valueNumber <= 256 &&
|
||||
double(int(cv->valueNumber)) == cv->valueNumber)
|
||||
if (cv.type == Constant::Type_Number && cv.valueNumber >= 1 && cv.valueNumber <= 256 && 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);
|
||||
|
||||
|
@ -1749,9 +1744,9 @@ struct Compiler
|
|||
|
||||
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);
|
||||
if (cid < 0)
|
||||
CompileError::raise(expr->location, "Exceeded constant limit; simplify the code to compile");
|
||||
|
@ -1864,14 +1859,11 @@ struct Compiler
|
|||
}
|
||||
|
||||
// Optimization: if expression has a constant value, we can emit it directly
|
||||
if (const Constant* cv = constants.find(node))
|
||||
{
|
||||
if (cv->type != Constant::Type_Unknown)
|
||||
if (const Constant* cv = constants.find(node); cv && cv->type != Constant::Type_Unknown)
|
||||
{
|
||||
compileExprConstant(node, cv, target);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (AstExprGroup* expr = node->as<AstExprGroup>())
|
||||
{
|
||||
|
@ -2069,23 +2061,22 @@ struct Compiler
|
|||
|
||||
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 &&
|
||||
double(int(cv->valueNumber)) == cv->valueNumber)
|
||||
if (cv.type == Constant::Type_Number && cv.valueNumber >= 1 && cv.valueNumber <= 256 && double(int(cv.valueNumber)) == cv.valueNumber)
|
||||
{
|
||||
LValue result = {LValue::Kind_IndexNumber};
|
||||
result.reg = reg;
|
||||
result.number = uint8_t(int(cv->valueNumber) - 1);
|
||||
result.number = uint8_t(int(cv.valueNumber) - 1);
|
||||
result.location = index->location;
|
||||
|
||||
return result;
|
||||
}
|
||||
else if (cv && cv->type == Constant::Type_String)
|
||||
else if (cv.type == Constant::Type_String)
|
||||
{
|
||||
LValue result = {LValue::Kind_IndexName};
|
||||
result.reg = reg;
|
||||
result.name = sref(cv->getString());
|
||||
result.name = sref(cv.getString());
|
||||
result.location = index->location;
|
||||
|
||||
return result;
|
||||
|
@ -2520,43 +2511,22 @@ struct Compiler
|
|||
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)
|
||||
{
|
||||
if (FFlag::LuauCompileNestedClosureO2)
|
||||
return true; // TODO: remove this function
|
||||
|
||||
struct CanUnrollVisitor : AstVisitor
|
||||
{
|
||||
bool result = true;
|
||||
|
||||
bool visit(AstExprFunction* node) override
|
||||
{
|
||||
if (!FFlag::LuauCompileNestedClosureO2)
|
||||
result = false;
|
||||
|
||||
// short-circuit to avoid analyzing nested closure bodies
|
||||
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;
|
||||
|
@ -2567,17 +2537,29 @@ struct Compiler
|
|||
|
||||
bool tryCompileUnrolledFor(AstStatFor* stat, int thresholdBase, int thresholdMaxBoost)
|
||||
{
|
||||
int from = getConstantShort(stat->from);
|
||||
int to = getConstantShort(stat->to);
|
||||
int step = stat->step ? getConstantShort(stat->step) : 1;
|
||||
Constant one = {Constant::Type_Number};
|
||||
one.valueNumber = 1.0;
|
||||
|
||||
// check that limits are reasonably small and trip count can be computed
|
||||
if (from == INT_MIN || to == INT_MIN || step == INT_MIN || step == 0 || (step < 0 && to > from) || (step > 0 && to < from))
|
||||
Constant fromc = getConstant(stat->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");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tripCount > thresholdBase)
|
||||
{
|
||||
bytecode.addDebugRemark("loop unroll failed: too many iterations (%d)", tripCount);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!canUnrollForBody(stat))
|
||||
{
|
||||
bytecode.addDebugRemark("loop unroll failed: unsupported loop body");
|
||||
|
@ -2590,14 +2572,6 @@ struct Compiler
|
|||
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;
|
||||
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);
|
||||
|
||||
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
|
||||
locstants[var].type = Constant::Type_Number;
|
||||
locstants[var].valueNumber = i;
|
||||
locstants[var].valueNumber = from + iv * step;
|
||||
|
||||
foldConstants(constants, variables, locstants, stat);
|
||||
|
||||
size_t iterJumps = loopJumps.size();
|
||||
|
||||
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
|
||||
locstants[var].type = Constant::Type_Unknown;
|
||||
|
||||
foldConstants(constants, variables, locstants, stat);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void compileStatFor(AstStatFor* stat)
|
||||
|
@ -2721,16 +2726,6 @@ struct Compiler
|
|||
// this puts initial values of (generator, state, index) into the loop registers
|
||||
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
|
||||
uint8_t vars = allocReg(stat, std::max(unsigned(stat->vars.size), 2u));
|
||||
LUAU_ASSERT(vars == regs + 3);
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
#include "Luau/Common.h"
|
||||
#include "Luau/DenseHash.h"
|
||||
|
||||
#include <limits.h>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
namespace Compile
|
||||
|
@ -11,10 +13,49 @@ namespace Compile
|
|||
|
||||
inline uint64_t parallelAddSat(uint64_t x, uint64_t y)
|
||||
{
|
||||
uint64_t s = x + y;
|
||||
uint64_t m = s & 0x8080808080808080ull; // saturation mask
|
||||
uint64_t r = x + y;
|
||||
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
|
||||
|
@ -46,6 +87,13 @@ struct Cost
|
|||
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)
|
||||
{
|
||||
uint64_t newmodel = parallelAddSat(x.model, y.model);
|
||||
|
@ -173,6 +221,16 @@ struct CostVisitor : AstVisitor
|
|||
*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
|
||||
{
|
||||
// note: we short-circuit the visitor traversal through any expression trees by returning false
|
||||
|
@ -182,12 +240,52 @@ struct CostVisitor : AstVisitor
|
|||
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
|
||||
{
|
||||
if (node->is<AstStatIf>())
|
||||
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>())
|
||||
result += 1;
|
||||
|
||||
|
@ -254,5 +352,21 @@ int computeCost(uint64_t model, const bool* varsConst, size_t varCount)
|
|||
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 Luau
|
||||
|
|
|
@ -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
|
||||
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 Luau
|
||||
|
|
30
Makefile
30
Makefile
|
@ -19,6 +19,10 @@ ANALYSIS_SOURCES=$(wildcard Analysis/src/*.cpp)
|
|||
ANALYSIS_OBJECTS=$(ANALYSIS_SOURCES:%=$(BUILD)/%.o)
|
||||
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_OBJECTS=$(VM_SOURCES:%=$(BUILD)/%.o)
|
||||
VM_TARGET=$(BUILD)/libluauvm.a
|
||||
|
@ -47,7 +51,7 @@ ifneq ($(flags),)
|
|||
TESTS_ARGS+=--fflags=$(flags)
|
||||
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
|
||||
CXXFLAGS=-g -Wall
|
||||
|
@ -90,15 +94,16 @@ ifeq ($(config),fuzz)
|
|||
endif
|
||||
|
||||
# target-specific flags
|
||||
$(AST_OBJECTS): CXXFLAGS+=-std=c++17 -IAst/include
|
||||
$(COMPILER_OBJECTS): CXXFLAGS+=-std=c++17 -ICompiler/include -IAst/include
|
||||
$(ANALYSIS_OBJECTS): CXXFLAGS+=-std=c++17 -IAst/include -IAnalysis/include
|
||||
$(VM_OBJECTS): CXXFLAGS+=-std=c++11 -IVM/include
|
||||
$(AST_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include
|
||||
$(COMPILER_OBJECTS): CXXFLAGS+=-std=c++17 -ICompiler/include -ICommon/include -IAst/include
|
||||
$(ANALYSIS_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -IAnalysis/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
|
||||
$(TESTS_OBJECTS): CXXFLAGS+=-std=c++17 -IAst/include -ICompiler/include -IAnalysis/include -IVM/include -ICLI -Iextern
|
||||
$(REPL_CLI_OBJECTS): CXXFLAGS+=-std=c++17 -IAst/include -ICompiler/include -IVM/include -Iextern -Iextern/isocline/include
|
||||
$(ANALYZE_CLI_OBJECTS): CXXFLAGS+=-std=c++17 -IAst/include -IAnalysis/include -Iextern
|
||||
$(FUZZ_OBJECTS): CXXFLAGS+=-std=c++17 -IAst/include -ICompiler/include -IAnalysis/include -IVM/include
|
||||
$(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 -ICommon/include -IAst/include -ICompiler/include -IVM/include -Iextern -Iextern/isocline/include
|
||||
$(ANALYZE_CLI_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -IAnalysis/include -Iextern
|
||||
$(FUZZ_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IAnalysis/include -IVM/include
|
||||
|
||||
$(TESTS_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
|
||||
|
||||
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
|
||||
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 $^ $@
|
||||
|
||||
# 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)
|
||||
$(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)
|
||||
$(COMPILER_TARGET): $(COMPILER_OBJECTS)
|
||||
$(ANALYSIS_TARGET): $(ANALYSIS_OBJECTS)
|
||||
$(CODEGEN_TARGET): $(CODEGEN_OBJECTS)
|
||||
$(VM_TARGET): $(VM_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 $@ $^
|
||||
|
||||
# object file targets
|
||||
|
|
|
@ -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
|
||||
target_sources(Luau.Ast PRIVATE
|
||||
Ast/include/Luau/Ast.h
|
||||
Ast/include/Luau/Common.h
|
||||
Ast/include/Luau/Confusables.h
|
||||
Ast/include/Luau/DenseHash.h
|
||||
Ast/include/Luau/Lexer.h
|
||||
|
@ -23,7 +31,6 @@ target_sources(Luau.Ast PRIVATE
|
|||
|
||||
# Luau.Compiler Sources
|
||||
target_sources(Luau.Compiler PRIVATE
|
||||
Compiler/include/Luau/Bytecode.h
|
||||
Compiler/include/Luau/BytecodeBuilder.h
|
||||
Compiler/include/Luau/Compiler.h
|
||||
Compiler/include/luacode.h
|
||||
|
@ -43,6 +50,17 @@ target_sources(Luau.Compiler PRIVATE
|
|||
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
|
||||
target_sources(Luau.Analysis PRIVATE
|
||||
Analysis/include/Luau/AstQuery.h
|
||||
|
@ -54,6 +72,7 @@ target_sources(Luau.Analysis PRIVATE
|
|||
Analysis/include/Luau/Error.h
|
||||
Analysis/include/Luau/FileResolver.h
|
||||
Analysis/include/Luau/Frontend.h
|
||||
Analysis/include/Luau/Instantiation.h
|
||||
Analysis/include/Luau/IostreamHelpers.h
|
||||
Analysis/include/Luau/JsonEncoder.h
|
||||
Analysis/include/Luau/Linter.h
|
||||
|
@ -93,6 +112,7 @@ target_sources(Luau.Analysis PRIVATE
|
|||
Analysis/src/Clone.cpp
|
||||
Analysis/src/Error.cpp
|
||||
Analysis/src/Frontend.cpp
|
||||
Analysis/src/Instantiation.cpp
|
||||
Analysis/src/IostreamHelpers.cpp
|
||||
Analysis/src/JsonEncoder.cpp
|
||||
Analysis/src/Linter.cpp
|
||||
|
@ -267,6 +287,7 @@ if(TARGET Luau.UnitTest)
|
|||
tests/TypeVar.test.cpp
|
||||
tests/Variant.test.cpp
|
||||
tests/VisitTypeVar.test.cpp
|
||||
tests/AssemblyBuilderX64.test.cpp
|
||||
tests/main.cpp)
|
||||
endif()
|
||||
|
||||
|
|
|
@ -487,7 +487,6 @@ void* lua_tolightuserdata(lua_State* L, int idx)
|
|||
void* lua_touserdata(lua_State* L, int idx)
|
||||
{
|
||||
StkId o = index2addr(L, idx);
|
||||
// fast-path: check userdata first since it is most likely the expected result
|
||||
if (ttisuserdata(o))
|
||||
return uvalue(o)->data;
|
||||
else if (ttislightuserdata(o))
|
||||
|
|
|
@ -15,8 +15,6 @@
|
|||
#include <intrin.h>
|
||||
#endif
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauFixBuiltinsStackLimit, false)
|
||||
|
||||
// luauF functions implement FASTCALL instruction that performs a direct execution of some builtin functions from the VM
|
||||
// The rule of thumb is that FASTCALL functions can not call user code, yield, fail, or reallocate stack.
|
||||
// If types of the arguments mismatch, luauF_* needs to return -1 and the execution will fall back to the usual call path
|
||||
|
@ -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)
|
||||
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;
|
||||
for (int i = 0; i < n; ++i)
|
||||
|
|
|
@ -3,7 +3,4 @@
|
|||
#pragma once
|
||||
|
||||
// This is a forwarding header for Luau bytecode definition
|
||||
// Luau consists of several components, including compiler (Ast, Compiler) and VM (virtual machine)
|
||||
// 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"
|
||||
#include "Luau/Bytecode.h"
|
||||
|
|
|
@ -7,11 +7,7 @@
|
|||
|
||||
#include "luaconf.h"
|
||||
|
||||
// This is a forwarding header for Luau common definition (assertions, flags)
|
||||
// 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"
|
||||
#include "Luau/Common.h"
|
||||
|
||||
typedef LUAI_USER_ALIGNMENT_T L_Umaxalign;
|
||||
|
||||
|
|
|
@ -245,6 +245,7 @@ void luaD_call(lua_State* L, StkId func, int nResults)
|
|||
if (!oldactive)
|
||||
resetbit(L->stackstate, THREAD_ACTIVEBIT);
|
||||
}
|
||||
|
||||
L->nCcalls--;
|
||||
luaC_checkGC(L);
|
||||
}
|
||||
|
|
|
@ -694,7 +694,7 @@ static void luau_execute(lua_State* L)
|
|||
}
|
||||
else
|
||||
{
|
||||
// slow-path, may invoke Lua calls via __index metamethod
|
||||
// slow-path, may invoke Lua calls via __newindex metamethod
|
||||
L->cachedslot = slot;
|
||||
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++
|
||||
|
@ -704,7 +704,7 @@ static void luau_execute(lua_State* L)
|
|||
}
|
||||
else
|
||||
{
|
||||
// fast-path: user data with C __index TM
|
||||
// fast-path: user data with C __newindex TM
|
||||
const TValue* fn = 0;
|
||||
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
|
||||
{
|
||||
// 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_NEXT();
|
||||
}
|
||||
|
@ -2358,9 +2358,8 @@ static void luau_execute(lua_State* L)
|
|||
// fast-path: ipairs/inext
|
||||
if (cl->env->safeenv && ttistable(ra + 1) && ttisnumber(ra + 2) && nvalue(ra + 2) == 0.0)
|
||||
{
|
||||
if (FFlag::LuauIter)
|
||||
setnilvalue(ra);
|
||||
|
||||
/* ra+1 is already the table */
|
||||
setpvalue(ra + 2, reinterpret_cast<void*>(uintptr_t(0)));
|
||||
}
|
||||
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));
|
||||
|
||||
// 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);
|
||||
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
|
||||
if (cl->env->safeenv && ttistable(ra + 1) && ttisnil(ra + 2))
|
||||
{
|
||||
if (FFlag::LuauIter)
|
||||
setnilvalue(ra);
|
||||
|
||||
/* ra+1 is already the table */
|
||||
setpvalue(ra + 2, reinterpret_cast<void*>(uintptr_t(0)));
|
||||
}
|
||||
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));
|
||||
|
||||
// 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);
|
||||
int index = int(reinterpret_cast<uintptr_t>(pvalue(ra + 2)));
|
||||
|
|
410
tests/AssemblyBuilderX64.test.cpp
Normal file
410
tests/AssemblyBuilderX64.test.cpp
Normal 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();
|
|
@ -14,7 +14,6 @@
|
|||
|
||||
LUAU_FASTFLAG(LuauTraceTypesInNonstrictMode2)
|
||||
LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel)
|
||||
LUAU_FASTFLAG(LuauSeparateTypechecks)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
|
@ -82,8 +81,6 @@ struct ACFixtureImpl : BaseType
|
|||
}
|
||||
|
||||
LoadDefinitionFileResult loadDefinition(const std::string& source)
|
||||
{
|
||||
if (FFlag::LuauSeparateTypechecks)
|
||||
{
|
||||
TypeChecker& typeChecker = this->frontend.typeCheckerForAutocomplete;
|
||||
unfreeze(typeChecker.globalTypes);
|
||||
|
@ -93,11 +90,6 @@ struct ACFixtureImpl : BaseType
|
|||
REQUIRE_MESSAGE(result.success, "loadDefinition: unable to load definition file");
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
return BaseType::loadDefinition(source);
|
||||
}
|
||||
}
|
||||
|
||||
const Position& getPosition(char marker) const
|
||||
{
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -241,9 +241,14 @@ TEST_CASE("Math")
|
|||
|
||||
TEST_CASE("Table")
|
||||
{
|
||||
ScopedFastFlag sff("LuauFixBuiltinsStackLimit", true);
|
||||
|
||||
runConformance("nextvar.lua");
|
||||
runConformance("nextvar.lua", [](lua_State* L) {
|
||||
lua_pushcfunction(L, [](lua_State* L) {
|
||||
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")
|
||||
|
@ -1106,11 +1111,170 @@ TEST_CASE("UserdataApi")
|
|||
TEST_CASE("Iter")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{ "LuauCompileIter", true },
|
||||
{ "LuauIter", true },
|
||||
{"LuauCompileIter", true},
|
||||
{"LuauIter", true},
|
||||
};
|
||||
|
||||
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();
|
||||
|
|
|
@ -136,7 +136,7 @@ function test(a)
|
|||
while a < 0 do
|
||||
a += 1
|
||||
end
|
||||
for i=1,2 do
|
||||
for i=10,1,-1 do
|
||||
a += 1
|
||||
end
|
||||
for i in pairs({}) do
|
||||
|
@ -154,8 +154,8 @@ end
|
|||
const bool args1[] = {false};
|
||||
const bool args2[] = {true};
|
||||
|
||||
CHECK_EQ(50, Luau::Compile::computeCost(model, args1, 1));
|
||||
CHECK_EQ(49, Luau::Compile::computeCost(model, args2, 1));
|
||||
CHECK_EQ(82, Luau::Compile::computeCost(model, args1, 1));
|
||||
CHECK_EQ(79, Luau::Compile::computeCost(model, args2, 1));
|
||||
}
|
||||
|
||||
TEST_CASE("Conditional")
|
||||
|
|
|
@ -116,7 +116,8 @@ TEST_CASE("encode_AstExprGroup")
|
|||
|
||||
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);
|
||||
}
|
||||
|
@ -149,7 +150,8 @@ TEST_CASE("encode_AstExprVarargs")
|
|||
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstExprCall")
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
@ -158,7 +160,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstExprIndexName")
|
|||
{
|
||||
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);
|
||||
}
|
||||
|
@ -167,7 +170,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstExprIndexExpr")
|
|||
{
|
||||
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);
|
||||
}
|
||||
|
@ -176,7 +180,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstExprFunction")
|
|||
{
|
||||
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);
|
||||
}
|
||||
|
@ -185,7 +190,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstExprTable")
|
|||
{
|
||||
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);
|
||||
}
|
||||
|
@ -194,7 +200,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstExprUnary")
|
|||
{
|
||||
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);
|
||||
}
|
||||
|
@ -203,7 +210,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstExprBinary")
|
|||
{
|
||||
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);
|
||||
}
|
||||
|
@ -212,7 +220,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstExprTypeAssertion")
|
|||
{
|
||||
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);
|
||||
}
|
||||
|
@ -239,7 +248,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatIf")
|
|||
{
|
||||
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);
|
||||
}
|
||||
|
@ -248,7 +258,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatWhile")
|
|||
{
|
||||
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);
|
||||
}
|
||||
|
@ -257,7 +268,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatRepeat")
|
|||
{
|
||||
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);
|
||||
}
|
||||
|
@ -266,7 +278,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatBreak")
|
|||
{
|
||||
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);
|
||||
}
|
||||
|
@ -275,7 +288,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatContinue")
|
|||
{
|
||||
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);
|
||||
}
|
||||
|
@ -284,7 +298,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatFor")
|
|||
{
|
||||
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);
|
||||
}
|
||||
|
@ -293,7 +308,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatForIn")
|
|||
{
|
||||
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);
|
||||
}
|
||||
|
@ -302,7 +318,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatCompoundAssign")
|
|||
{
|
||||
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);
|
||||
}
|
||||
|
@ -311,7 +328,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatLocalFunction")
|
|||
{
|
||||
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);
|
||||
}
|
||||
|
@ -320,7 +338,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatTypeAlias")
|
|||
{
|
||||
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);
|
||||
}
|
||||
|
@ -329,7 +348,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatDeclareFunction")
|
|||
{
|
||||
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);
|
||||
}
|
||||
|
@ -349,10 +369,12 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatDeclareClass")
|
|||
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -360,7 +382,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_annotation")
|
|||
{
|
||||
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);
|
||||
}
|
||||
|
@ -372,7 +395,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstTypeError")
|
|||
|
||||
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);
|
||||
}
|
||||
|
@ -386,7 +410,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstTypePackExplicit")
|
|||
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -15,10 +15,7 @@ TEST_SUITE_BEGIN("NonstrictModeTests");
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "function_returns_number_or_string")
|
||||
{
|
||||
ScopedFastFlag sff[]{
|
||||
{"LuauReturnTypeInferenceInNonstrict", true},
|
||||
{"LuauLowerBoundsCalculation", true}
|
||||
};
|
||||
ScopedFastFlag sff[]{{"LuauReturnTypeInferenceInNonstrict", true}, {"LuauLowerBoundsCalculation", true}};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
--!nonstrict
|
||||
|
|
|
@ -44,6 +44,9 @@ void createSomeClasses(TypeChecker& typeChecker)
|
|||
addGlobalBinding(typeChecker, "Unrelated", {unrelatedType});
|
||||
typeChecker.globalScope->exportedTypeBindings["Unrelated"] = TypeFun{{}, unrelatedType};
|
||||
|
||||
for (const auto& [name, ty] : typeChecker.globalScope->exportedTypeBindings)
|
||||
persist(ty.type);
|
||||
|
||||
freeze(arena);
|
||||
}
|
||||
|
||||
|
@ -681,10 +684,7 @@ TEST_CASE_FIXTURE(Fixture, "higher_order_function_with_annotation")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "cyclic_table_is_marked_normal")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{"LuauLowerBoundsCalculation", true},
|
||||
{"LuauNormalizeFlagIsConservative", false}
|
||||
};
|
||||
ScopedFastFlag flags[] = {{"LuauLowerBoundsCalculation", true}, {"LuauNormalizeFlagIsConservative", false}};
|
||||
|
||||
check(R"(
|
||||
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.
|
||||
TEST_CASE_FIXTURE(Fixture, "cyclic_table_is_not_marked_normal")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{"LuauLowerBoundsCalculation", true},
|
||||
{"LuauNormalizeFlagIsConservative", true}
|
||||
};
|
||||
ScopedFastFlag flags[] = {{"LuauLowerBoundsCalculation", true}, {"LuauNormalizeFlagIsConservative", true}};
|
||||
|
||||
check(R"(
|
||||
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();
|
||||
|
|
|
@ -2622,4 +2622,15 @@ type Z<T> = { a: string | T..., b: number }
|
|||
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();
|
||||
|
|
|
@ -24,7 +24,7 @@ struct LimitFixture : BuiltinsFixture
|
|||
#endif
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
template<typename T>
|
||||
bool hasError(const CheckResult& result, T* = nullptr)
|
||||
{
|
||||
auto it = std::find_if(result.errors.begin(), result.errors.end(), [](const TypeError& a) {
|
||||
|
|
|
@ -33,6 +33,9 @@ struct ToDotClassFixture : Fixture
|
|||
};
|
||||
typeChecker.globalScope->exportedTypeBindings["ChildClass"] = TypeFun{{}, childClassInstanceType};
|
||||
|
||||
for (const auto& [name, ty] : typeChecker.globalScope->exportedTypeBindings)
|
||||
persist(ty.type);
|
||||
|
||||
freeze(arena);
|
||||
}
|
||||
};
|
||||
|
@ -425,13 +428,14 @@ n1 -> n2;
|
|||
n2 [label="SingletonTypeVar string: hi"];
|
||||
n1 -> n3;
|
||||
)"
|
||||
"n3 [label=\"SingletonTypeVar string: \\\"hello\\\"\"];"
|
||||
R"(
|
||||
"n3 [label=\"SingletonTypeVar string: \\\"hello\\\"\"];"
|
||||
R"(
|
||||
n1 -> n4;
|
||||
n4 [label="SingletonTypeVar boolean: true"];
|
||||
n1 -> n5;
|
||||
n5 [label="SingletonTypeVar boolean: false"];
|
||||
})", toDot(requireType("x"), opts));
|
||||
})",
|
||||
toDot(requireType("x"), opts));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -600,7 +600,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "select_with_decimal_argument_is_rounded_down
|
|||
}
|
||||
|
||||
// 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"(
|
||||
do end
|
||||
|
@ -612,7 +612,9 @@ TEST_CASE_FIXTURE(Fixture, "bad_select_should_not_crash")
|
|||
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")
|
||||
|
@ -877,10 +879,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "dont_add_definitions_to_persistent_types")
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "assert_removes_falsy_types")
|
||||
{
|
||||
ScopedFastFlag sff[]{
|
||||
{"LuauWidenIfSupertypeIsFree2", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: (number | boolean)?)
|
||||
return assert(x)
|
||||
|
@ -896,10 +894,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "assert_removes_falsy_types")
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "assert_removes_falsy_types2")
|
||||
{
|
||||
ScopedFastFlag sff[]{
|
||||
{"LuauWidenIfSupertypeIsFree2", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: (number | boolean)?): number | true
|
||||
return assert(x)
|
||||
|
|
|
@ -1001,7 +1001,7 @@ TEST_CASE_FIXTURE(Fixture, "no_stack_overflow_from_quantifying")
|
|||
type t0 = t0 | {}
|
||||
)");
|
||||
|
||||
CHECK_LE(0, result.errors.size());
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
|
||||
std::optional<TypeFun> t0 = getMainModule()->getModuleScope()->lookupType("t0");
|
||||
REQUIRE(t0);
|
||||
|
|
|
@ -443,7 +443,7 @@ TEST_CASE_FIXTURE(Fixture, "no_stack_overflow_from_flattenintersection")
|
|||
until _(_)(_)._
|
||||
)");
|
||||
|
||||
CHECK_LE(0, result.errors.size());
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -249,7 +249,7 @@ local ModuleA = require(game.A)
|
|||
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"(
|
||||
export type Type = { unrelated: boolean }
|
||||
|
@ -264,7 +264,7 @@ function x:Destroy(): () end
|
|||
)";
|
||||
|
||||
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")
|
||||
|
|
|
@ -409,14 +409,15 @@ TEST_CASE_FIXTURE(Fixture, "normalization_fails_on_certain_kinds_of_cyclic_table
|
|||
}
|
||||
|
||||
// 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"(
|
||||
local function f(): () end
|
||||
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);
|
||||
// CHECK_EQ("boolean", toString(requireType("ok")));
|
||||
// CHECK_EQ("any", toString(requireType("res")));
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
#include "doctest.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauWeakEqConstraint)
|
||||
LUAU_FASTFLAG(LuauLowerBoundsCalculation)
|
||||
|
||||
using namespace Luau;
|
||||
|
@ -77,6 +76,10 @@ struct RefinementClassFixture : Fixture
|
|||
typeChecker.globalScope->exportedTypeBindings["Instance"] = TypeFun{{}, inst};
|
||||
typeChecker.globalScope->exportedTypeBindings["Folder"] = TypeFun{{}, folder};
|
||||
typeChecker.globalScope->exportedTypeBindings["Part"] = TypeFun{{}, part};
|
||||
|
||||
for (const auto& [name, ty] : typeChecker.globalScope->exportedTypeBindings)
|
||||
persist(ty.type);
|
||||
|
||||
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")
|
||||
{
|
||||
ScopedFastFlag sff2{"LuauWeakEqConstraint", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(a, b: string?)
|
||||
if a == b then
|
||||
|
|
|
@ -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")
|
||||
{
|
||||
ScopedFastFlag sff[]{
|
||||
{"LuauWidenIfSupertypeIsFree2", true},
|
||||
{"LuauWeakEqConstraint", false},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function foo(f, x)
|
||||
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")
|
||||
{
|
||||
ScopedFastFlag sff[]{
|
||||
{"LuauWidenIfSupertypeIsFree2", true},
|
||||
{"LuauWeakEqConstraint", false},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function foo(f, x): "hello"? -- anyone there?
|
||||
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")
|
||||
{
|
||||
ScopedFastFlag sff[]{
|
||||
{"LuauWidenIfSupertypeIsFree2", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local foo: "foo" = "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")
|
||||
{
|
||||
ScopedFastFlag sff[]{
|
||||
{"LuauWidenIfSupertypeIsFree2", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type Cat = {tag: "Cat", meows: 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")
|
||||
{
|
||||
ScopedFastFlag sff[]{
|
||||
{"LuauWidenIfSupertypeIsFree2", true},
|
||||
{"LuauWeakEqConstraint", true},
|
||||
};
|
||||
ScopedFastFlag sff{"LuauLowerBoundsCalculation", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
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")
|
||||
{
|
||||
ScopedFastFlag sff[]{
|
||||
{"LuauWidenIfSupertypeIsFree2", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function foo(my_enum: "A" | "B") end
|
||||
)");
|
||||
|
|
|
@ -1891,7 +1891,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "quantifying_a_bound_var_works")
|
|||
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"(
|
||||
--!strict
|
||||
|
@ -1920,7 +1920,7 @@ TEST_CASE_FIXTURE(Fixture, "less_exponential_blowup_please")
|
|||
newData:First()
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
}
|
||||
|
||||
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")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauDifferentOrderOfUnificationDoesntMatter2", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
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")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauDifferentOrderOfUnificationDoesntMatter2", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
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({})");
|
||||
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")
|
||||
|
@ -3006,4 +3003,30 @@ TEST_CASE_FIXTURE(Fixture, "expected_indexer_value_type_extra_2")
|
|||
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();
|
||||
|
|
|
@ -681,7 +681,7 @@ TEST_CASE_FIXTURE(Fixture, "no_stack_overflow_from_isoptional")
|
|||
_(nil)
|
||||
)");
|
||||
|
||||
CHECK_LE(0, result.errors.size());
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
|
||||
std::optional<TypeFun> t0 = getMainModule()->getModuleScope()->lookupType("t0");
|
||||
REQUIRE(t0);
|
||||
|
@ -693,7 +693,7 @@ TEST_CASE_FIXTURE(Fixture, "no_stack_overflow_from_isoptional")
|
|||
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"(
|
||||
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"(
|
||||
--!nonstrict
|
||||
|
@ -737,7 +737,7 @@ TEST_CASE_FIXTURE(Fixture, "no_heap_use_after_free_error")
|
|||
end
|
||||
)");
|
||||
|
||||
CHECK_LE(0, result.errors.size());
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
}
|
||||
|
||||
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[] = {
|
||||
{"LuauLowerBoundsCalculation", true},
|
||||
{"LuauDifferentOrderOfUnificationDoesntMatter2", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
|
|
|
@ -939,7 +939,7 @@ until _
|
|||
)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "detect_cyclic_typepacks")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "detect_cyclic_typepacks")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
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"(
|
||||
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
|
||||
)");
|
||||
|
||||
CHECK_LE(0, result.errors.size());
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -357,8 +357,7 @@ local b: (T, T, T) -> T
|
|||
TypeId bType = requireType("b");
|
||||
|
||||
VisitCountTracker tester;
|
||||
DenseHashSet<void*> seen{nullptr};
|
||||
DEPRECATED_visitTypeVarOnce(bType, tester, seen);
|
||||
tester.traverse(bType);
|
||||
|
||||
for (auto [_, count] : tester.tyVisits)
|
||||
CHECK_EQ(count, 1);
|
||||
|
|
|
@ -8,12 +8,10 @@
|
|||
|
||||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauUseVisitRecursionLimit)
|
||||
LUAU_FASTINT(LuauVisitRecursionLimit)
|
||||
|
||||
struct VisitTypeVarFixture : Fixture
|
||||
{
|
||||
ScopedFastFlag flag1 = {"LuauUseVisitRecursionLimit", true};
|
||||
ScopedFastFlag flag2 = {"LuauRecursionLimitException", true};
|
||||
};
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
||||
-- 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')
|
||||
|
|
|
@ -567,4 +567,29 @@ do
|
|||
assert(not ok)
|
||||
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"
|
||||
|
|
45
tests/conformance/userdata.lua
Normal file
45
tests/conformance/userdata.lua
Normal 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'
|
50
tools/natvis/CodeGen.natvis
Normal file
50
tools/natvis/CodeGen.natvis
Normal 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 && index == 31">noreg</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::SizeX64::none && index == 0">rip</DisplayString>
|
||||
|
||||
<DisplayString Condition="size == Luau::CodeGen::SizeX64::byte && index == 0">al</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::SizeX64::byte && index == 1">cl</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::SizeX64::byte && index == 2">dl</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::SizeX64::byte && index == 3">bl</DisplayString>
|
||||
|
||||
<DisplayString Condition="size == Luau::CodeGen::SizeX64::dword && index == 0">eax</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::SizeX64::dword && index == 1">ecx</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::SizeX64::dword && index == 2">edx</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::SizeX64::dword && index == 3">ebx</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::SizeX64::dword && index == 4">esp</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::SizeX64::dword && index == 5">ebp</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::SizeX64::dword && index == 6">esi</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::SizeX64::dword && index == 7">edi</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::SizeX64::dword && index >= 8">e{(int)index,d}d</DisplayString>
|
||||
|
||||
<DisplayString Condition="size == Luau::CodeGen::SizeX64::qword && index == 0">rax</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::SizeX64::qword && index == 1">rcx</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::SizeX64::qword && index == 2">rdx</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::SizeX64::qword && index == 3">rbx</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::SizeX64::qword && index == 4">rsp</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::SizeX64::qword && index == 5">rbp</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::SizeX64::qword && index == 6">rsi</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::SizeX64::qword && index == 7">rdi</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::SizeX64::qword && index >= 8">r{(int)index,d}</DisplayString>
|
||||
|
||||
<DisplayString Condition="size == Luau::CodeGen::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
76
tools/patchtests.py
Normal 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)
|
Loading…
Reference in a new issue