mirror of
https://github.com/luau-lang/luau.git
synced 2024-11-15 14:25:44 +08:00
Sync to upstream/release/559 (#804)
* Fix autocompletion of if-then-else expressions * Fix a potential crash surrounding improper use of `%*` in a string format specifier * All Python scripts now invoke Python via `python3` rather than `python`. * Improved error handling for string interpolation with too many arguments. Co-authored-by: Arseny Kapoulkine <arseny.kapoulkine@gmail.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
This commit is contained in:
parent
0af10417cd
commit
a5c6a38b10
@ -57,11 +57,7 @@ struct AndPredicate
|
||||
PredicateVec lhs;
|
||||
PredicateVec rhs;
|
||||
|
||||
AndPredicate(PredicateVec&& lhs, PredicateVec&& rhs)
|
||||
: lhs(std::move(lhs))
|
||||
, rhs(std::move(rhs))
|
||||
{
|
||||
}
|
||||
AndPredicate(PredicateVec&& lhs, PredicateVec&& rhs);
|
||||
};
|
||||
|
||||
struct OrPredicate
|
||||
@ -69,11 +65,7 @@ struct OrPredicate
|
||||
PredicateVec lhs;
|
||||
PredicateVec rhs;
|
||||
|
||||
OrPredicate(PredicateVec&& lhs, PredicateVec&& rhs)
|
||||
: lhs(std::move(lhs))
|
||||
, rhs(std::move(rhs))
|
||||
{
|
||||
}
|
||||
OrPredicate(PredicateVec&& lhs, PredicateVec&& rhs);
|
||||
};
|
||||
|
||||
struct NotPredicate
|
||||
@ -81,6 +73,19 @@ struct NotPredicate
|
||||
PredicateVec predicates;
|
||||
};
|
||||
|
||||
// Outside definition works around clang 15 issue where vector instantiation is triggered while Predicate is still incomplete
|
||||
inline AndPredicate::AndPredicate(PredicateVec&& lhs, PredicateVec&& rhs)
|
||||
: lhs(std::move(lhs))
|
||||
, rhs(std::move(rhs))
|
||||
{
|
||||
}
|
||||
|
||||
inline OrPredicate::OrPredicate(PredicateVec&& lhs, PredicateVec&& rhs)
|
||||
: lhs(std::move(lhs))
|
||||
, rhs(std::move(rhs))
|
||||
{
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
const T* get(const Predicate& predicate)
|
||||
{
|
||||
|
@ -45,7 +45,6 @@ struct ToStringOptions
|
||||
bool hideTableKind = false; // If true, all tables will be surrounded with plain '{}'
|
||||
bool hideNamedFunctionTypeParameters = false; // If true, type parameters of functions will be hidden at top-level.
|
||||
bool hideFunctionSelfArgument = false; // If true, `self: X` will be omitted from the function signature if the function has self
|
||||
bool DEPRECATED_indent = false; // TODO Deprecated field, prune when clipping flag FFlagLuauLineBreaksDeterminIndents
|
||||
size_t maxTableLength = size_t(FInt::LuauTableTypeMaximumStringifierLength); // Only applied to TableTypes
|
||||
size_t maxTypeLength = size_t(FInt::LuauTypeMaximumStringifierLength);
|
||||
ToStringNameMap nameMap;
|
||||
|
@ -27,8 +27,8 @@ private:
|
||||
DenseHashMap<TypeId, TypeId> cachedTypes{nullptr};
|
||||
DenseHashMap<TypePackId, TypePackId> cachedTypePacks{nullptr};
|
||||
|
||||
std::optional<TypeId> reduceImpl(TypeId ty);
|
||||
std::optional<TypePackId> reduceImpl(TypePackId tp);
|
||||
std::pair<std::optional<TypeId>, bool> reduceImpl(TypeId ty);
|
||||
std::pair<std::optional<TypePackId>, bool> reduceImpl(TypePackId tp);
|
||||
|
||||
// Computes an *estimated length* of the cartesian product of the given type.
|
||||
size_t cartesianProductSize(TypeId ty) const;
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include <utility>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauCompleteTableKeysBetter, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauFixAutocompleteInIf, false);
|
||||
|
||||
static const std::unordered_set<std::string> kStatementStartingKeywords = {
|
||||
"while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"};
|
||||
@ -1487,8 +1488,22 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
|
||||
return {{{"then", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword};
|
||||
}
|
||||
else if (AstStatIf* statIf = extractStat<AstStatIf>(ancestry);
|
||||
statIf && (!statIf->thenLocation || statIf->thenLocation->containsClosed(position)))
|
||||
statIf && (!statIf->thenLocation || statIf->thenLocation->containsClosed(position)) &&
|
||||
(!FFlag::LuauFixAutocompleteInIf || (statIf->condition && !statIf->condition->location.containsClosed(position))))
|
||||
{
|
||||
if (FFlag::LuauFixAutocompleteInIf)
|
||||
{
|
||||
AutocompleteEntryMap ret;
|
||||
ret["then"] = {AutocompleteEntryKind::Keyword};
|
||||
ret["and"] = {AutocompleteEntryKind::Keyword};
|
||||
ret["or"] = {AutocompleteEntryKind::Keyword};
|
||||
return {std::move(ret), ancestry, AutocompleteContext::Keyword};
|
||||
}
|
||||
else
|
||||
{
|
||||
return {{{"then", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword};
|
||||
}
|
||||
}
|
||||
else if (AstStatRepeat* statRepeat = node->as<AstStatRepeat>(); statRepeat && statRepeat->condition->is<AstExprError>())
|
||||
return autocompleteExpression(sourceModule, *module, builtinTypes, typeArena, ancestry, position);
|
||||
else if (AstStatRepeat* statRepeat = extractStat<AstStatRepeat>(ancestry); statRepeat)
|
||||
|
@ -15,10 +15,8 @@
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
LUAU_FASTFLAG(LuauUnknownAndNeverType)
|
||||
LUAU_FASTFLAGVARIABLE(LuauLineBreaksDetermineIndents, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFunctionReturnStringificationFixup, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUnseeArrayTtv, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSerializeNilUnionAsNil, false)
|
||||
|
||||
/*
|
||||
* Prefix generic typenames with gen-
|
||||
@ -276,22 +274,12 @@ struct StringifierState
|
||||
|
||||
private:
|
||||
void emitIndentation()
|
||||
{
|
||||
if (!FFlag::LuauLineBreaksDetermineIndents)
|
||||
{
|
||||
if (!opts.DEPRECATED_indent)
|
||||
return;
|
||||
|
||||
emit(std::string(indentation, ' '));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!opts.useLineBreaks)
|
||||
return;
|
||||
|
||||
emit(std::string(indentation, ' '));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct TypeStringifier
|
||||
@ -781,11 +769,8 @@ struct TypeStringifier
|
||||
if (results.size() > 1)
|
||||
s = ")?";
|
||||
|
||||
if (FFlag::LuauSerializeNilUnionAsNil)
|
||||
{
|
||||
if (!hasNonNilDisjunct)
|
||||
s = "nil";
|
||||
}
|
||||
|
||||
state.emit(s);
|
||||
}
|
||||
|
@ -51,6 +51,16 @@ struct TypeReducer
|
||||
NotNull<BuiltinTypes> builtinTypes;
|
||||
NotNull<InternalErrorReporter> handle;
|
||||
|
||||
std::unordered_map<TypeId, TypeId> copies;
|
||||
std::deque<const void*> seen;
|
||||
int depth = 0;
|
||||
|
||||
// When we encounter _any type_ that which is usually mutated in-place, we need to not cache the result.
|
||||
// e.g. `'a & {} T` may have an upper bound constraint `{}` placed upon `'a`, but this constraint was not
|
||||
// known when we decided to reduce this intersection type. By not caching, we'll always be forced to perform
|
||||
// the reduction calculus over again.
|
||||
bool cacheOk = true;
|
||||
|
||||
TypeId reduce(TypeId ty);
|
||||
TypePackId reduce(TypePackId tp);
|
||||
|
||||
@ -60,13 +70,11 @@ struct TypeReducer
|
||||
TypeId functionType(TypeId ty);
|
||||
TypeId negationType(TypeId ty);
|
||||
|
||||
std::deque<const void*> seen;
|
||||
int depth = 0;
|
||||
|
||||
RecursionGuard guard(TypeId ty);
|
||||
RecursionGuard guard(TypePackId tp);
|
||||
|
||||
std::unordered_map<TypeId, TypeId> copies;
|
||||
void checkCacheable(TypeId ty);
|
||||
void checkCacheable(TypePackId tp);
|
||||
|
||||
template<typename T>
|
||||
LUAU_NOINLINE std::pair<TypeId, T*> copy(TypeId ty, const T* t)
|
||||
@ -153,6 +161,7 @@ TypeId TypeReducer::reduce(TypeId ty)
|
||||
return ty;
|
||||
|
||||
RecursionGuard rg = guard(ty);
|
||||
checkCacheable(ty);
|
||||
|
||||
if (auto i = get<IntersectionType>(ty))
|
||||
return foldl<IntersectionType>(begin(i), end(i), &TypeReducer::intersectionType);
|
||||
@ -176,6 +185,7 @@ TypePackId TypeReducer::reduce(TypePackId tp)
|
||||
return tp;
|
||||
|
||||
RecursionGuard rg = guard(tp);
|
||||
checkCacheable(tp);
|
||||
|
||||
TypePackIterator it = begin(tp);
|
||||
|
||||
@ -213,6 +223,14 @@ std::optional<TypeId> TypeReducer::intersectionType(TypeId left, TypeId right)
|
||||
return right; // any & T ~ T
|
||||
else if (get<AnyType>(right))
|
||||
return left; // T & any ~ T
|
||||
else if (get<FreeType>(left))
|
||||
return std::nullopt; // 'a & T ~ 'a & T
|
||||
else if (get<FreeType>(right))
|
||||
return std::nullopt; // T & 'a ~ T & 'a
|
||||
else if (get<GenericType>(left))
|
||||
return std::nullopt; // G & T ~ G & T
|
||||
else if (get<GenericType>(right))
|
||||
return std::nullopt; // T & G ~ T & G
|
||||
else if (get<ErrorType>(left))
|
||||
return std::nullopt; // error & T ~ error & T
|
||||
else if (get<ErrorType>(right))
|
||||
@ -701,6 +719,32 @@ RecursionGuard TypeReducer::guard(TypePackId tp)
|
||||
return RecursionGuard{&depth, FInt::LuauTypeReductionRecursionLimit, &seen};
|
||||
}
|
||||
|
||||
void TypeReducer::checkCacheable(TypeId ty)
|
||||
{
|
||||
if (!cacheOk)
|
||||
return;
|
||||
|
||||
ty = follow(ty);
|
||||
|
||||
// Only does shallow check, the TypeReducer itself already does deep traversal.
|
||||
if (get<FreeType>(ty) || get<BlockedType>(ty) || get<PendingExpansionType>(ty))
|
||||
cacheOk = false;
|
||||
else if (auto tt = get<TableType>(ty); tt && (tt->state == TableState::Free || tt->state == TableState::Unsealed))
|
||||
cacheOk = false;
|
||||
}
|
||||
|
||||
void TypeReducer::checkCacheable(TypePackId tp)
|
||||
{
|
||||
if (!cacheOk)
|
||||
return;
|
||||
|
||||
tp = follow(tp);
|
||||
|
||||
// Only does shallow check, the TypeReducer itself already does deep traversal.
|
||||
if (get<FreeTypePack>(tp) || get<BlockedTypePack>(tp))
|
||||
cacheOk = false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TypeReduction::TypeReduction(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, NotNull<InternalErrorReporter> handle)
|
||||
@ -715,13 +759,11 @@ std::optional<TypeId> TypeReduction::reduce(TypeId ty)
|
||||
if (auto found = cachedTypes.find(ty))
|
||||
return *found;
|
||||
|
||||
if (auto reduced = reduceImpl(ty))
|
||||
{
|
||||
cachedTypes[ty] = *reduced;
|
||||
return *reduced;
|
||||
}
|
||||
auto [reducedTy, cacheOk] = reduceImpl(ty);
|
||||
if (cacheOk)
|
||||
cachedTypes[ty] = *reducedTy;
|
||||
|
||||
return std::nullopt;
|
||||
return reducedTy;
|
||||
}
|
||||
|
||||
std::optional<TypePackId> TypeReduction::reduce(TypePackId tp)
|
||||
@ -729,50 +771,48 @@ std::optional<TypePackId> TypeReduction::reduce(TypePackId tp)
|
||||
if (auto found = cachedTypePacks.find(tp))
|
||||
return *found;
|
||||
|
||||
if (auto reduced = reduceImpl(tp))
|
||||
{
|
||||
cachedTypePacks[tp] = *reduced;
|
||||
return *reduced;
|
||||
}
|
||||
auto [reducedTp, cacheOk] = reduceImpl(tp);
|
||||
if (cacheOk)
|
||||
cachedTypePacks[tp] = *reducedTp;
|
||||
|
||||
return std::nullopt;
|
||||
return reducedTp;
|
||||
}
|
||||
|
||||
std::optional<TypeId> TypeReduction::reduceImpl(TypeId ty)
|
||||
std::pair<std::optional<TypeId>, bool> TypeReduction::reduceImpl(TypeId ty)
|
||||
{
|
||||
if (FFlag::DebugLuauDontReduceTypes)
|
||||
return ty;
|
||||
return {ty, false};
|
||||
|
||||
if (hasExceededCartesianProductLimit(ty))
|
||||
return std::nullopt;
|
||||
return {std::nullopt, false};
|
||||
|
||||
try
|
||||
{
|
||||
TypeReducer reducer{arena, builtinTypes, handle};
|
||||
return reducer.reduce(ty);
|
||||
return {reducer.reduce(ty), reducer.cacheOk};
|
||||
}
|
||||
catch (const RecursionLimitException&)
|
||||
{
|
||||
return std::nullopt;
|
||||
return {std::nullopt, false};
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<TypePackId> TypeReduction::reduceImpl(TypePackId tp)
|
||||
std::pair<std::optional<TypePackId>, bool> TypeReduction::reduceImpl(TypePackId tp)
|
||||
{
|
||||
if (FFlag::DebugLuauDontReduceTypes)
|
||||
return tp;
|
||||
return {tp, false};
|
||||
|
||||
if (hasExceededCartesianProductLimit(tp))
|
||||
return std::nullopt;
|
||||
return {std::nullopt, false};
|
||||
|
||||
try
|
||||
{
|
||||
TypeReducer reducer{arena, builtinTypes, handle};
|
||||
return reducer.reduce(tp);
|
||||
return {reducer.reduce(tp), reducer.cacheOk};
|
||||
}
|
||||
catch (const RecursionLimitException&)
|
||||
{
|
||||
return std::nullopt;
|
||||
return {std::nullopt, false};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -14,11 +14,6 @@
|
||||
LUAU_FASTINTVARIABLE(LuauRecursionLimit, 1000)
|
||||
LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauFixNamedFunctionParse, false)
|
||||
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuaReportParseWrongNamedType, false)
|
||||
|
||||
bool lua_telemetry_parsed_named_non_function_type = false;
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauErrorDoubleHexPrefix, false)
|
||||
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuaReportParseIntegerIssues, false)
|
||||
|
||||
@ -1423,7 +1418,7 @@ AstTypeOrPack Parser::parseFunctionTypeAnnotation(bool allowPack)
|
||||
|
||||
AstArray<AstType*> paramTypes = copy(params);
|
||||
|
||||
if (FFlag::LuauFixNamedFunctionParse && !names.empty())
|
||||
if (!names.empty())
|
||||
forceFunctionType = true;
|
||||
|
||||
bool returnTypeIntroducer = lexer.current().type == Lexeme::SkinnyArrow || lexer.current().type == ':';
|
||||
@ -1431,9 +1426,6 @@ AstTypeOrPack Parser::parseFunctionTypeAnnotation(bool allowPack)
|
||||
// Not a function at all. Just a parenthesized type. Or maybe a type pack with a single element
|
||||
if (params.size() == 1 && !varargAnnotation && !forceFunctionType && !returnTypeIntroducer)
|
||||
{
|
||||
if (DFFlag::LuaReportParseWrongNamedType && !names.empty())
|
||||
lua_telemetry_parsed_named_non_function_type = true;
|
||||
|
||||
if (allowPack)
|
||||
return {{}, allocator.alloc<AstTypePackExplicit>(begin.location, AstTypeList{paramTypes, nullptr})};
|
||||
else
|
||||
@ -1441,12 +1433,7 @@ AstTypeOrPack Parser::parseFunctionTypeAnnotation(bool allowPack)
|
||||
}
|
||||
|
||||
if (!forceFunctionType && !returnTypeIntroducer && allowPack)
|
||||
{
|
||||
if (DFFlag::LuaReportParseWrongNamedType && !names.empty())
|
||||
lua_telemetry_parsed_named_non_function_type = true;
|
||||
|
||||
return {{}, allocator.alloc<AstTypePackExplicit>(begin.location, AstTypeList{paramTypes, varargAnnotation})};
|
||||
}
|
||||
|
||||
AstArray<std::optional<AstArgumentName>> paramNames = copy(names);
|
||||
|
||||
|
@ -78,6 +78,7 @@ public:
|
||||
|
||||
void test(OperandX64 lhs, OperandX64 rhs);
|
||||
void lea(OperandX64 lhs, OperandX64 rhs);
|
||||
void setcc(ConditionX64 cond, OperandX64 op);
|
||||
|
||||
void push(OperandX64 op);
|
||||
void pop(OperandX64 op);
|
||||
@ -189,8 +190,8 @@ private:
|
||||
const char* name, OperandX64 dst, OperandX64 src1, OperandX64 src2, uint8_t imm8, 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 placeRegAndModRegMem(OperandX64 lhs, OperandX64 rhs, int32_t extraCodeBytes = 0);
|
||||
void placeModRegMem(OperandX64 rhs, uint8_t regop, int32_t extraCodeBytes = 0);
|
||||
void placeRex(RegisterX64 op);
|
||||
void placeRex(OperandX64 op);
|
||||
void placeRexNoW(OperandX64 op);
|
||||
|
@ -17,9 +17,13 @@ static const uint8_t codeForCondition[] = {
|
||||
0x0, 0x1, 0x2, 0x3, 0x2, 0x6, 0x7, 0x3, 0x4, 0xc, 0xe, 0xf, 0xd, 0x3, 0x7, 0x6, 0x2, 0x5, 0xd, 0xf, 0xe, 0xc, 0x4, 0x5, 0xa, 0xb};
|
||||
static_assert(sizeof(codeForCondition) / sizeof(codeForCondition[0]) == size_t(ConditionX64::Count), "all conditions have to be covered");
|
||||
|
||||
static const char* textForCondition[] = {"jo", "jno", "jc", "jnc", "jb", "jbe", "ja", "jae", "je", "jl", "jle", "jg", "jge", "jnb", "jnbe", "jna",
|
||||
static const char* jccTextForCondition[] = {"jo", "jno", "jc", "jnc", "jb", "jbe", "ja", "jae", "je", "jl", "jle", "jg", "jge", "jnb", "jnbe", "jna",
|
||||
"jnae", "jne", "jnl", "jnle", "jng", "jnge", "jz", "jnz", "jp", "jnp"};
|
||||
static_assert(sizeof(textForCondition) / sizeof(textForCondition[0]) == size_t(ConditionX64::Count), "all conditions have to be covered");
|
||||
static_assert(sizeof(jccTextForCondition) / sizeof(jccTextForCondition[0]) == size_t(ConditionX64::Count), "all conditions have to be covered");
|
||||
|
||||
static const char* setccTextForCondition[] = {"seto", "setno", "setc", "setnc", "setb", "setbe", "seta", "setae", "sete", "setl", "setle", "setg",
|
||||
"setge", "setnb", "setnbe", "setna", "setnae", "setne", "setnl", "setnle", "setng", "setnge", "setz", "setnz", "setp", "setnp"};
|
||||
static_assert(sizeof(setccTextForCondition) / sizeof(setccTextForCondition[0]) == size_t(ConditionX64::Count), "all conditions have to be covered");
|
||||
|
||||
#define OP_PLUS_REG(op, reg) ((op) + (reg & 0x7))
|
||||
#define OP_PLUS_CC(op, cc) ((op) + uint8_t(cc))
|
||||
@ -169,7 +173,7 @@ void AssemblyBuilderX64::mov(OperandX64 lhs, OperandX64 rhs)
|
||||
if (size == SizeX64::byte)
|
||||
{
|
||||
place(0xc6);
|
||||
placeModRegMem(lhs, 0);
|
||||
placeModRegMem(lhs, 0, /*extraCodeBytes=*/1);
|
||||
placeImm8(rhs.imm);
|
||||
}
|
||||
else
|
||||
@ -177,7 +181,7 @@ void AssemblyBuilderX64::mov(OperandX64 lhs, OperandX64 rhs)
|
||||
LUAU_ASSERT(size == SizeX64::dword || size == SizeX64::qword);
|
||||
|
||||
place(0xc7);
|
||||
placeModRegMem(lhs, 0);
|
||||
placeModRegMem(lhs, 0, /*extraCodeBytes=*/4);
|
||||
placeImm32(rhs.imm);
|
||||
}
|
||||
}
|
||||
@ -304,13 +308,13 @@ void AssemblyBuilderX64::imul(OperandX64 dst, OperandX64 lhs, int32_t rhs)
|
||||
if (int8_t(rhs) == rhs)
|
||||
{
|
||||
place(0x6b);
|
||||
placeRegAndModRegMem(dst, lhs);
|
||||
placeRegAndModRegMem(dst, lhs, /*extraCodeBytes=*/1);
|
||||
placeImm8(rhs);
|
||||
}
|
||||
else
|
||||
{
|
||||
place(0x69);
|
||||
placeRegAndModRegMem(dst, lhs);
|
||||
placeRegAndModRegMem(dst, lhs, /*extraCodeBytes=*/4);
|
||||
placeImm32(rhs);
|
||||
}
|
||||
|
||||
@ -366,9 +370,24 @@ void AssemblyBuilderX64::ret()
|
||||
commit();
|
||||
}
|
||||
|
||||
void AssemblyBuilderX64::setcc(ConditionX64 cond, OperandX64 op)
|
||||
{
|
||||
SizeX64 size = op.cat == CategoryX64::reg ? op.base.size : op.memSize;
|
||||
LUAU_ASSERT(size == SizeX64::byte);
|
||||
|
||||
if (logText)
|
||||
log(setccTextForCondition[size_t(cond)], op);
|
||||
|
||||
placeRex(op);
|
||||
place(0x0f);
|
||||
place(0x90 | codeForCondition[size_t(cond)]);
|
||||
placeModRegMem(op, 0);
|
||||
commit();
|
||||
}
|
||||
|
||||
void AssemblyBuilderX64::jcc(ConditionX64 cond, Label& label)
|
||||
{
|
||||
placeJcc(textForCondition[size_t(cond)], label, codeForCondition[size_t(cond)]);
|
||||
placeJcc(jccTextForCondition[size_t(cond)], label, codeForCondition[size_t(cond)]);
|
||||
}
|
||||
|
||||
void AssemblyBuilderX64::jmp(Label& label)
|
||||
@ -866,7 +885,7 @@ void AssemblyBuilderX64::placeBinaryRegMemAndImm(OperandX64 lhs, OperandX64 rhs,
|
||||
if (size == SizeX64::byte)
|
||||
{
|
||||
place(code8);
|
||||
placeModRegMem(lhs, opreg);
|
||||
placeModRegMem(lhs, opreg, /*extraCodeBytes=*/1);
|
||||
placeImm8(rhs.imm);
|
||||
}
|
||||
else
|
||||
@ -876,13 +895,13 @@ void AssemblyBuilderX64::placeBinaryRegMemAndImm(OperandX64 lhs, OperandX64 rhs,
|
||||
if (int8_t(rhs.imm) == rhs.imm && code != codeImm8)
|
||||
{
|
||||
place(codeImm8);
|
||||
placeModRegMem(lhs, opreg);
|
||||
placeModRegMem(lhs, opreg, /*extraCodeBytes=*/1);
|
||||
placeImm8(rhs.imm);
|
||||
}
|
||||
else
|
||||
{
|
||||
place(code);
|
||||
placeModRegMem(lhs, opreg);
|
||||
placeModRegMem(lhs, opreg, /*extraCodeBytes=*/4);
|
||||
placeImm32(rhs.imm);
|
||||
}
|
||||
}
|
||||
@ -950,7 +969,7 @@ void AssemblyBuilderX64::placeShift(const char* name, OperandX64 lhs, OperandX64
|
||||
LUAU_ASSERT(int8_t(rhs.imm) == rhs.imm);
|
||||
|
||||
place(size == SizeX64::byte ? 0xc0 : 0xc1);
|
||||
placeModRegMem(lhs, opreg);
|
||||
placeModRegMem(lhs, opreg, /*extraCodeBytes=*/1);
|
||||
placeImm8(rhs.imm);
|
||||
}
|
||||
else
|
||||
@ -1042,7 +1061,7 @@ void AssemblyBuilderX64::placeAvx(
|
||||
|
||||
placeVex(dst, src1, src2, setW, mode, prefix);
|
||||
place(code);
|
||||
placeRegAndModRegMem(dst, src2);
|
||||
placeRegAndModRegMem(dst, src2, /*extraCodeBytes=*/1);
|
||||
placeImm8(imm8);
|
||||
|
||||
commit();
|
||||
@ -1118,14 +1137,14 @@ static uint8_t getScaleEncoding(uint8_t scale)
|
||||
return scales[scale];
|
||||
}
|
||||
|
||||
void AssemblyBuilderX64::placeRegAndModRegMem(OperandX64 lhs, OperandX64 rhs)
|
||||
void AssemblyBuilderX64::placeRegAndModRegMem(OperandX64 lhs, OperandX64 rhs, int32_t extraCodeBytes)
|
||||
{
|
||||
LUAU_ASSERT(lhs.cat == CategoryX64::reg);
|
||||
|
||||
placeModRegMem(rhs, lhs.base.index);
|
||||
placeModRegMem(rhs, lhs.base.index, extraCodeBytes);
|
||||
}
|
||||
|
||||
void AssemblyBuilderX64::placeModRegMem(OperandX64 rhs, uint8_t regop)
|
||||
void AssemblyBuilderX64::placeModRegMem(OperandX64 rhs, uint8_t regop, int32_t extraCodeBytes)
|
||||
{
|
||||
if (rhs.cat == CategoryX64::reg)
|
||||
{
|
||||
@ -1180,7 +1199,12 @@ void AssemblyBuilderX64::placeModRegMem(OperandX64 rhs, uint8_t regop)
|
||||
else if (base == rip)
|
||||
{
|
||||
place(MOD_RM(0b00, regop, 0b101));
|
||||
placeImm32(-int32_t(getCodeSize() + 4) + rhs.imm);
|
||||
|
||||
// As a reminder: we do (getCodeSize() + 4) here to calculate the offset of the end of the current instruction we are placing.
|
||||
// Since we have already placed all of the instruction bytes for this instruction, we add +4 to account for the imm32 displacement.
|
||||
// Some instructions, however, are encoded such that an additional imm8 byte, or imm32 bytes, is placed after the ModRM byte, thus,
|
||||
// we need to account for that case here as well.
|
||||
placeImm32(-int32_t(getCodeSize() + 4 + extraCodeBytes) + rhs.imm);
|
||||
}
|
||||
else if (base != noreg)
|
||||
{
|
||||
|
@ -91,6 +91,9 @@ static int emitInst(AssemblyBuilderX64& build, NativeState& data, ModuleHelpers&
|
||||
case LOP_SETGLOBAL:
|
||||
emitInstSetGlobal(build, pc, i, next, fallback);
|
||||
break;
|
||||
case LOP_NAMECALL:
|
||||
emitInstNameCall(build, pc, i, proto->k, next, fallback);
|
||||
break;
|
||||
case LOP_CALL:
|
||||
emitInstCall(build, helpers, pc, i);
|
||||
break;
|
||||
@ -270,6 +273,9 @@ static int emitInst(AssemblyBuilderX64& build, NativeState& data, ModuleHelpers&
|
||||
case LOP_CONCAT:
|
||||
emitInstConcat(build, pc, i, next);
|
||||
break;
|
||||
case LOP_COVERAGE:
|
||||
emitInstCoverage(build, i);
|
||||
break;
|
||||
default:
|
||||
emitFallback(build, data, op, i);
|
||||
break;
|
||||
@ -298,6 +304,10 @@ static void emitInstFallback(AssemblyBuilderX64& build, NativeState& data, LuauO
|
||||
case LOP_SETTABLEN:
|
||||
emitInstSetTableNFallback(build, pc, i);
|
||||
break;
|
||||
case LOP_NAMECALL:
|
||||
// TODO: fast-paths that we've handled can be removed from the fallback
|
||||
emitFallback(build, data, op, i);
|
||||
break;
|
||||
case LOP_JUMPIFEQ:
|
||||
emitInstJumpIfEqFallback(build, pc, i, labelarr, /* not_ */ false);
|
||||
break;
|
||||
|
@ -239,6 +239,22 @@ void callCheckGc(AssemblyBuilderX64& build, int pcpos, bool savepc, Label& skip)
|
||||
emitUpdateBase(build);
|
||||
}
|
||||
|
||||
void callGetFastTmOrFallback(AssemblyBuilderX64& build, RegisterX64 table, TMS tm, Label& fallback)
|
||||
{
|
||||
build.mov(rArg1, qword[table + offsetof(Table, metatable)]);
|
||||
build.test(rArg1, rArg1);
|
||||
build.jcc(ConditionX64::Zero, fallback); // no metatable
|
||||
|
||||
build.test(byte[rArg1 + offsetof(Table, tmcache)], 1 << tm);
|
||||
build.jcc(ConditionX64::NotZero, fallback); // no tag method
|
||||
|
||||
// rArg1 is already prepared
|
||||
build.mov(rArg2, tm);
|
||||
build.mov(rax, qword[rState + offsetof(lua_State, global)]);
|
||||
build.mov(rArg3, qword[rax + offsetof(global_State, tmname[tm])]);
|
||||
build.call(qword[rNativeContext + offsetof(NativeContext, luaT_gettm)]);
|
||||
}
|
||||
|
||||
void emitExit(AssemblyBuilderX64& build, bool continueInVm)
|
||||
{
|
||||
if (continueInVm)
|
||||
|
@ -67,8 +67,10 @@ constexpr OperandX64 sArg6 = noreg;
|
||||
constexpr unsigned kTValueSizeLog2 = 4;
|
||||
constexpr unsigned kLuaNodeSizeLog2 = 5;
|
||||
constexpr unsigned kLuaNodeTagMask = 0xf;
|
||||
constexpr unsigned kNextBitOffset = 4;
|
||||
|
||||
constexpr unsigned kOffsetOfLuaNodeTag = 12; // offsetof cannot be used on a bit field
|
||||
constexpr unsigned kOffsetOfLuaNodeNext = 12; // offsetof cannot be used on a bit field
|
||||
constexpr unsigned kOffsetOfInstructionC = 3;
|
||||
|
||||
// Leaf functions that are placed in every module to perform common instruction sequences
|
||||
@ -168,6 +170,12 @@ inline void jumpIfTagIsNot(AssemblyBuilderX64& build, int ri, lua_Type tag, Labe
|
||||
build.jcc(ConditionX64::NotEqual, label);
|
||||
}
|
||||
|
||||
inline void jumpIfTagIsNot(AssemblyBuilderX64& build, RegisterX64 reg, lua_Type tag, Label& label)
|
||||
{
|
||||
build.cmp(dword[reg + offsetof(TValue, tt)], tag);
|
||||
build.jcc(ConditionX64::NotEqual, label);
|
||||
}
|
||||
|
||||
// Note: fallthrough label should be placed after this condition
|
||||
inline void jumpIfFalsy(AssemblyBuilderX64& build, int ri, Label& target, Label& fallthrough)
|
||||
{
|
||||
@ -224,6 +232,13 @@ inline void jumpIfNodeValueTagIs(AssemblyBuilderX64& build, RegisterX64 node, lu
|
||||
build.jcc(ConditionX64::Equal, label);
|
||||
}
|
||||
|
||||
inline void jumpIfNodeHasNext(AssemblyBuilderX64& build, RegisterX64 node, Label& label)
|
||||
{
|
||||
build.mov(ecx, dword[node + offsetof(LuaNode, key) + kOffsetOfLuaNodeNext]);
|
||||
build.shr(ecx, kNextBitOffset);
|
||||
build.jcc(ConditionX64::NotZero, label);
|
||||
}
|
||||
|
||||
inline void jumpIfNodeKeyNotInExpectedSlot(AssemblyBuilderX64& build, RegisterX64 tmp, RegisterX64 node, OperandX64 expectedKey, Label& label)
|
||||
{
|
||||
jumpIfNodeKeyTagIsNot(build, tmp, node, LUA_TSTRING, label);
|
||||
@ -250,6 +265,7 @@ void callBarrierTable(AssemblyBuilderX64& build, RegisterX64 tmp, RegisterX64 ta
|
||||
void callBarrierObject(AssemblyBuilderX64& build, RegisterX64 tmp, RegisterX64 object, int ra, Label& skip);
|
||||
void callBarrierTableFast(AssemblyBuilderX64& build, RegisterX64 table, Label& skip);
|
||||
void callCheckGc(AssemblyBuilderX64& build, int pcpos, bool savepc, Label& skip);
|
||||
void callGetFastTmOrFallback(AssemblyBuilderX64& build, RegisterX64 table, TMS tm, Label& fallback);
|
||||
|
||||
void emitExit(AssemblyBuilderX64& build, bool continueInVm);
|
||||
void emitUpdateBase(AssemblyBuilderX64& build);
|
||||
@ -258,7 +274,6 @@ void emitInterrupt(AssemblyBuilderX64& build, int pcpos);
|
||||
void emitFallback(AssemblyBuilderX64& build, NativeState& data, int op, int pcpos);
|
||||
|
||||
void emitContinueCallInVm(AssemblyBuilderX64& build);
|
||||
void emitExitFromLastReturn(AssemblyBuilderX64& build);
|
||||
|
||||
} // namespace CodeGen
|
||||
} // namespace Luau
|
||||
|
@ -69,6 +69,51 @@ void emitInstMove(AssemblyBuilderX64& build, const Instruction* pc)
|
||||
build.vmovups(luauReg(ra), xmm0);
|
||||
}
|
||||
|
||||
void emitInstNameCall(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, const TValue* k, Label& next, Label& fallback)
|
||||
{
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
int rb = LUAU_INSN_B(*pc);
|
||||
uint32_t aux = pc[1];
|
||||
|
||||
Label secondfpath;
|
||||
|
||||
jumpIfTagIsNot(build, rb, LUA_TTABLE, fallback);
|
||||
|
||||
RegisterX64 table = r8;
|
||||
build.mov(table, luauRegValue(rb));
|
||||
|
||||
// &h->node[tsvalue(kv)->hash & (sizenode(h) - 1)];
|
||||
RegisterX64 node = rdx;
|
||||
build.mov(node, qword[table + offsetof(Table, node)]);
|
||||
build.mov(eax, 1);
|
||||
build.mov(cl, byte[table + offsetof(Table, lsizenode)]);
|
||||
build.shl(eax, cl);
|
||||
build.dec(eax);
|
||||
build.and_(eax, tsvalue(&k[aux])->hash);
|
||||
build.shl(rax, kLuaNodeSizeLog2);
|
||||
build.add(node, rax);
|
||||
|
||||
jumpIfNodeKeyNotInExpectedSlot(build, rax, node, luauConstantValue(aux), secondfpath);
|
||||
|
||||
setLuauReg(build, xmm0, ra + 1, luauReg(rb));
|
||||
setLuauReg(build, xmm0, ra, luauNodeValue(node));
|
||||
build.jmp(next);
|
||||
|
||||
build.setLabel(secondfpath);
|
||||
|
||||
jumpIfNodeHasNext(build, node, fallback);
|
||||
callGetFastTmOrFallback(build, table, TM_INDEX, fallback);
|
||||
jumpIfTagIsNot(build, rax, LUA_TTABLE, fallback);
|
||||
|
||||
build.mov(table, qword[rax + offsetof(TValue, value)]);
|
||||
|
||||
getTableNodeAtCachedSlot(build, rax, node, table, pcpos);
|
||||
jumpIfNodeKeyNotInExpectedSlot(build, rax, node, luauConstantValue(aux), fallback);
|
||||
|
||||
setLuauReg(build, xmm0, ra + 1, luauReg(rb));
|
||||
setLuauReg(build, xmm0, ra, luauNodeValue(node));
|
||||
}
|
||||
|
||||
void emitInstCall(AssemblyBuilderX64& build, ModuleHelpers& helpers, const Instruction* pc, int pcpos)
|
||||
{
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
@ -1627,5 +1672,28 @@ void emitInstConcat(AssemblyBuilderX64& build, const Instruction* pc, int pcpos,
|
||||
callCheckGc(build, pcpos, /* savepc= */ false, next);
|
||||
}
|
||||
|
||||
void emitInstCoverage(AssemblyBuilderX64& build, int pcpos)
|
||||
{
|
||||
build.mov(rcx, sCode);
|
||||
build.add(rcx, pcpos * sizeof(Instruction));
|
||||
|
||||
// hits = LUAU_INSN_E(*pc)
|
||||
build.mov(edx, dword[rcx]);
|
||||
build.sar(edx, 8);
|
||||
|
||||
// hits = (hits < (1 << 23) - 1) ? hits + 1 : hits;
|
||||
build.xor_(eax, eax);
|
||||
build.cmp(edx, (1 << 23) - 1);
|
||||
build.setcc(ConditionX64::NotEqual, al);
|
||||
build.add(edx, eax);
|
||||
|
||||
|
||||
// VM_PATCH_E(pc, hits);
|
||||
build.sal(edx, 8);
|
||||
build.movzx(eax, byte[rcx]);
|
||||
build.or_(eax, edx);
|
||||
build.mov(dword[rcx], eax);
|
||||
}
|
||||
|
||||
} // namespace CodeGen
|
||||
} // namespace Luau
|
||||
|
@ -17,6 +17,7 @@ class AssemblyBuilderX64;
|
||||
enum class ConditionX64 : uint8_t;
|
||||
struct Label;
|
||||
struct ModuleHelpers;
|
||||
struct NativeState;
|
||||
|
||||
void emitInstLoadNil(AssemblyBuilderX64& build, const Instruction* pc);
|
||||
void emitInstLoadB(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr);
|
||||
@ -24,6 +25,7 @@ void emitInstLoadN(AssemblyBuilderX64& build, const Instruction* pc);
|
||||
void emitInstLoadK(AssemblyBuilderX64& build, const Instruction* pc);
|
||||
void emitInstLoadKX(AssemblyBuilderX64& build, const Instruction* pc);
|
||||
void emitInstMove(AssemblyBuilderX64& build, const Instruction* pc);
|
||||
void emitInstNameCall(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, const TValue* k, Label& next, Label& fallback);
|
||||
void emitInstCall(AssemblyBuilderX64& build, ModuleHelpers& helpers, const Instruction* pc, int pcpos);
|
||||
void emitInstReturn(AssemblyBuilderX64& build, ModuleHelpers& helpers, const Instruction* pc, int pcpos);
|
||||
void emitInstJump(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr);
|
||||
@ -84,6 +86,7 @@ void emitInstSetTableKS(AssemblyBuilderX64& build, const Instruction* pc, int pc
|
||||
void emitInstGetGlobal(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label& fallback);
|
||||
void emitInstSetGlobal(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label& next, Label& fallback);
|
||||
void emitInstConcat(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label& next);
|
||||
void emitInstCoverage(AssemblyBuilderX64& build, int pcpos);
|
||||
|
||||
} // namespace CodeGen
|
||||
} // namespace Luau
|
||||
|
@ -594,19 +594,6 @@ const Instruction* execute_LOP_PREPVARARGS(lua_State* L, const Instruction* pc,
|
||||
return pc;
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_COVERAGE(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
[[maybe_unused]] Closure* cl = clvalue(L->ci->func);
|
||||
Instruction insn = *pc++;
|
||||
int hits = LUAU_INSN_E(insn);
|
||||
|
||||
// update hits with saturated add and patch the instruction in place
|
||||
hits = (hits < (1 << 23) - 1) ? hits + 1 : hits;
|
||||
VM_PATCH_E(pc - 1, hits);
|
||||
|
||||
return pc;
|
||||
}
|
||||
|
||||
const Instruction* execute_LOP_BREAK(lua_State* L, const Instruction* pc, StkId base, TValue* k)
|
||||
{
|
||||
LUAU_ASSERT(!"Unsupported deprecated opcode");
|
||||
|
@ -20,5 +20,4 @@ const Instruction* execute_LOP_FORGPREP(lua_State* L, const Instruction* pc, Stk
|
||||
const Instruction* execute_LOP_GETVARARGS(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_DUPCLOSURE(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_PREPVARARGS(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_COVERAGE(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
const Instruction* execute_LOP_BREAK(lua_State* L, const Instruction* pc, StkId base, TValue* k);
|
||||
|
280
CodeGen/src/IrData.h
Normal file
280
CodeGen/src/IrData.h
Normal file
@ -0,0 +1,280 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Label.h"
|
||||
#include "Luau/RegisterX64.h"
|
||||
#include "Luau/RegisterA64.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
namespace CodeGen
|
||||
{
|
||||
|
||||
enum class IrCmd : uint8_t
|
||||
{
|
||||
NOP,
|
||||
|
||||
LOAD_TAG,
|
||||
LOAD_POINTER,
|
||||
LOAD_DOUBLE,
|
||||
LOAD_INT,
|
||||
LOAD_TVALUE,
|
||||
LOAD_NODE_VALUE_TV, // TODO: we should find a way to generalize LOAD_TVALUE
|
||||
LOAD_ENV,
|
||||
|
||||
GET_ARR_ADDR,
|
||||
GET_SLOT_NODE_ADDR,
|
||||
|
||||
STORE_TAG,
|
||||
STORE_POINTER,
|
||||
STORE_DOUBLE,
|
||||
STORE_INT,
|
||||
STORE_TVALUE,
|
||||
STORE_NODE_VALUE_TV, // TODO: we should find a way to generalize STORE_TVALUE
|
||||
|
||||
ADD_INT,
|
||||
SUB_INT,
|
||||
|
||||
ADD_NUM,
|
||||
SUB_NUM,
|
||||
MUL_NUM,
|
||||
DIV_NUM,
|
||||
MOD_NUM,
|
||||
POW_NUM,
|
||||
|
||||
UNM_NUM,
|
||||
|
||||
NOT_ANY, // TODO: boolean specialization will be useful
|
||||
|
||||
JUMP,
|
||||
JUMP_IF_TRUTHY,
|
||||
JUMP_IF_FALSY,
|
||||
JUMP_EQ_TAG,
|
||||
JUMP_EQ_BOOLEAN,
|
||||
JUMP_EQ_POINTER,
|
||||
|
||||
JUMP_CMP_NUM,
|
||||
JUMP_CMP_STR,
|
||||
JUMP_CMP_ANY,
|
||||
|
||||
TABLE_LEN,
|
||||
NEW_TABLE,
|
||||
DUP_TABLE,
|
||||
|
||||
NUM_TO_INDEX,
|
||||
|
||||
// Fallback functions
|
||||
DO_ARITH,
|
||||
DO_LEN,
|
||||
GET_TABLE,
|
||||
SET_TABLE,
|
||||
GET_IMPORT,
|
||||
CONCAT,
|
||||
GET_UPVALUE,
|
||||
SET_UPVALUE,
|
||||
|
||||
// Guards and checks
|
||||
CHECK_TAG,
|
||||
CHECK_READONLY,
|
||||
CHECK_NO_METATABLE,
|
||||
CHECK_SAFE_ENV,
|
||||
CHECK_ARRAY_SIZE,
|
||||
CHECK_SLOT_MATCH,
|
||||
|
||||
// Special operations
|
||||
INTERRUPT,
|
||||
CHECK_GC,
|
||||
BARRIER_OBJ,
|
||||
BARRIER_TABLE_BACK,
|
||||
BARRIER_TABLE_FORWARD,
|
||||
SET_SAVEDPC,
|
||||
CLOSE_UPVALS,
|
||||
|
||||
// While capture is a no-op right now, it might be useful to track register/upvalue lifetimes
|
||||
CAPTURE,
|
||||
|
||||
// Operations that don't have an IR representation yet
|
||||
LOP_SETLIST,
|
||||
LOP_CALL,
|
||||
LOP_RETURN,
|
||||
LOP_FASTCALL,
|
||||
LOP_FASTCALL1,
|
||||
LOP_FASTCALL2,
|
||||
LOP_FASTCALL2K,
|
||||
LOP_FORNPREP,
|
||||
LOP_FORNLOOP,
|
||||
LOP_FORGLOOP,
|
||||
LOP_FORGLOOP_FALLBACK,
|
||||
LOP_FORGPREP_NEXT,
|
||||
LOP_FORGPREP_INEXT,
|
||||
LOP_FORGPREP_XNEXT_FALLBACK,
|
||||
LOP_AND,
|
||||
LOP_ANDK,
|
||||
LOP_OR,
|
||||
LOP_ORK,
|
||||
|
||||
// Operations that have a translation, but use a full instruction fallback
|
||||
FALLBACK_GETGLOBAL,
|
||||
FALLBACK_SETGLOBAL,
|
||||
FALLBACK_GETTABLEKS,
|
||||
FALLBACK_SETTABLEKS,
|
||||
|
||||
// Operations that don't have assembly lowering at all
|
||||
FALLBACK_NAMECALL,
|
||||
FALLBACK_PREPVARARGS,
|
||||
FALLBACK_GETVARARGS,
|
||||
FALLBACK_NEWCLOSURE,
|
||||
FALLBACK_DUPCLOSURE,
|
||||
FALLBACK_FORGPREP,
|
||||
FALLBACK_COVERAGE,
|
||||
};
|
||||
|
||||
enum class IrConstKind : uint8_t
|
||||
{
|
||||
Bool,
|
||||
Int,
|
||||
Uint,
|
||||
Double,
|
||||
Tag,
|
||||
};
|
||||
|
||||
struct IrConst
|
||||
{
|
||||
IrConstKind kind;
|
||||
|
||||
union
|
||||
{
|
||||
bool valueBool;
|
||||
int valueInt;
|
||||
unsigned valueUint;
|
||||
double valueDouble;
|
||||
uint8_t valueTag;
|
||||
};
|
||||
};
|
||||
|
||||
enum class IrCondition : uint8_t
|
||||
{
|
||||
Equal,
|
||||
NotEqual,
|
||||
Less,
|
||||
NotLess,
|
||||
LessEqual,
|
||||
NotLessEqual,
|
||||
Greater,
|
||||
NotGreater,
|
||||
GreaterEqual,
|
||||
NotGreaterEqual,
|
||||
|
||||
UnsignedLess,
|
||||
UnsignedLessEqual,
|
||||
UnsignedGreater,
|
||||
UnsignedGreaterEqual,
|
||||
|
||||
Count
|
||||
};
|
||||
|
||||
enum class IrOpKind : uint32_t
|
||||
{
|
||||
None,
|
||||
|
||||
// To reference a constant value
|
||||
Constant,
|
||||
|
||||
// To specify a condition code
|
||||
Condition,
|
||||
|
||||
// To reference a result of a previous instruction
|
||||
Inst,
|
||||
|
||||
// To reference a basic block in control flow
|
||||
Block,
|
||||
|
||||
// To reference a VM register
|
||||
VmReg,
|
||||
|
||||
// To reference a VM constant
|
||||
VmConst,
|
||||
|
||||
// To reference a VM upvalue
|
||||
VmUpvalue,
|
||||
};
|
||||
|
||||
struct IrOp
|
||||
{
|
||||
IrOpKind kind : 4;
|
||||
uint32_t index : 28;
|
||||
|
||||
IrOp()
|
||||
: kind(IrOpKind::None)
|
||||
, index(0)
|
||||
{
|
||||
}
|
||||
|
||||
IrOp(IrOpKind kind, uint32_t index)
|
||||
: kind(kind)
|
||||
, index(index)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(sizeof(IrOp) == 4);
|
||||
|
||||
struct IrInst
|
||||
{
|
||||
IrCmd cmd;
|
||||
|
||||
// Operands
|
||||
IrOp a;
|
||||
IrOp b;
|
||||
IrOp c;
|
||||
IrOp d;
|
||||
IrOp e;
|
||||
|
||||
uint32_t lastUse = 0;
|
||||
uint16_t useCount = 0;
|
||||
|
||||
// Location of the result (optional)
|
||||
RegisterX64 regX64 = noreg;
|
||||
RegisterA64 regA64{KindA64::none, 0};
|
||||
bool reusedReg = false;
|
||||
};
|
||||
|
||||
enum class IrBlockKind : uint8_t
|
||||
{
|
||||
Bytecode,
|
||||
Fallback,
|
||||
Internal,
|
||||
};
|
||||
|
||||
struct IrBlock
|
||||
{
|
||||
IrBlockKind kind;
|
||||
|
||||
// Start points to an instruction index in a stream
|
||||
// End is implicit
|
||||
uint32_t start;
|
||||
|
||||
Label label;
|
||||
};
|
||||
|
||||
struct BytecodeMapping
|
||||
{
|
||||
uint32_t irLocation;
|
||||
uint32_t asmLocation;
|
||||
};
|
||||
|
||||
struct IrFunction
|
||||
{
|
||||
std::vector<IrBlock> blocks;
|
||||
std::vector<IrInst> instructions;
|
||||
std::vector<IrConst> constants;
|
||||
|
||||
std::vector<BytecodeMapping> bcMapping;
|
||||
};
|
||||
|
||||
} // namespace CodeGen
|
||||
} // namespace Luau
|
379
CodeGen/src/IrDump.cpp
Normal file
379
CodeGen/src/IrDump.cpp
Normal file
@ -0,0 +1,379 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "IrDump.h"
|
||||
|
||||
#include "IrUtils.h"
|
||||
|
||||
#include "lua.h"
|
||||
|
||||
#include <stdarg.h>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
namespace CodeGen
|
||||
{
|
||||
|
||||
static const char* textForCondition[] = {
|
||||
"eq", "not_eq", "lt", "not_lt", "le", "not_le", "gt", "not_gt", "ge", "not_ge", "u_lt", "u_le", "u_gt", "u_ge"};
|
||||
static_assert(sizeof(textForCondition) / sizeof(textForCondition[0]) == size_t(IrCondition::Count), "all conditions have to be covered");
|
||||
|
||||
const int kDetailsAlignColumn = 60;
|
||||
|
||||
LUAU_PRINTF_ATTR(2, 3)
|
||||
static void append(std::string& result, const char* fmt, ...)
|
||||
{
|
||||
char buf[256];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vsnprintf(buf, sizeof(buf), fmt, args);
|
||||
va_end(args);
|
||||
result.append(buf);
|
||||
}
|
||||
|
||||
static const char* getTagName(uint8_t tag)
|
||||
{
|
||||
switch (tag)
|
||||
{
|
||||
case LUA_TNIL:
|
||||
return "tnil";
|
||||
case LUA_TBOOLEAN:
|
||||
return "tboolean";
|
||||
case LUA_TLIGHTUSERDATA:
|
||||
return "tlightuserdata";
|
||||
case LUA_TNUMBER:
|
||||
return "tnumber";
|
||||
case LUA_TVECTOR:
|
||||
return "tvector";
|
||||
case LUA_TSTRING:
|
||||
return "tstring";
|
||||
case LUA_TTABLE:
|
||||
return "ttable";
|
||||
case LUA_TFUNCTION:
|
||||
return "tfunction";
|
||||
case LUA_TUSERDATA:
|
||||
return "tuserdata";
|
||||
case LUA_TTHREAD:
|
||||
return "tthread";
|
||||
default:
|
||||
LUAU_UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
const char* getCmdName(IrCmd cmd)
|
||||
{
|
||||
switch (cmd)
|
||||
{
|
||||
case IrCmd::NOP:
|
||||
return "NOP";
|
||||
case IrCmd::LOAD_TAG:
|
||||
return "LOAD_TAG";
|
||||
case IrCmd::LOAD_POINTER:
|
||||
return "LOAD_POINTER";
|
||||
case IrCmd::LOAD_DOUBLE:
|
||||
return "LOAD_DOUBLE";
|
||||
case IrCmd::LOAD_INT:
|
||||
return "LOAD_INT";
|
||||
case IrCmd::LOAD_TVALUE:
|
||||
return "LOAD_TVALUE";
|
||||
case IrCmd::LOAD_NODE_VALUE_TV:
|
||||
return "LOAD_NODE_VALUE_TV";
|
||||
case IrCmd::LOAD_ENV:
|
||||
return "LOAD_ENV";
|
||||
case IrCmd::GET_ARR_ADDR:
|
||||
return "GET_ARR_ADDR";
|
||||
case IrCmd::GET_SLOT_NODE_ADDR:
|
||||
return "GET_SLOT_NODE_ADDR";
|
||||
case IrCmd::STORE_TAG:
|
||||
return "STORE_TAG";
|
||||
case IrCmd::STORE_POINTER:
|
||||
return "STORE_POINTER";
|
||||
case IrCmd::STORE_DOUBLE:
|
||||
return "STORE_DOUBLE";
|
||||
case IrCmd::STORE_INT:
|
||||
return "STORE_INT";
|
||||
case IrCmd::STORE_TVALUE:
|
||||
return "STORE_TVALUE";
|
||||
case IrCmd::STORE_NODE_VALUE_TV:
|
||||
return "STORE_NODE_VALUE_TV";
|
||||
case IrCmd::ADD_INT:
|
||||
return "ADD_INT";
|
||||
case IrCmd::SUB_INT:
|
||||
return "SUB_INT";
|
||||
case IrCmd::ADD_NUM:
|
||||
return "ADD_NUM";
|
||||
case IrCmd::SUB_NUM:
|
||||
return "SUB_NUM";
|
||||
case IrCmd::MUL_NUM:
|
||||
return "MUL_NUM";
|
||||
case IrCmd::DIV_NUM:
|
||||
return "DIV_NUM";
|
||||
case IrCmd::MOD_NUM:
|
||||
return "MOD_NUM";
|
||||
case IrCmd::POW_NUM:
|
||||
return "POW_NUM";
|
||||
case IrCmd::UNM_NUM:
|
||||
return "UNM_NUM";
|
||||
case IrCmd::NOT_ANY:
|
||||
return "NOT_ANY";
|
||||
case IrCmd::JUMP:
|
||||
return "JUMP";
|
||||
case IrCmd::JUMP_IF_TRUTHY:
|
||||
return "JUMP_IF_TRUTHY";
|
||||
case IrCmd::JUMP_IF_FALSY:
|
||||
return "JUMP_IF_FALSY";
|
||||
case IrCmd::JUMP_EQ_TAG:
|
||||
return "JUMP_EQ_TAG";
|
||||
case IrCmd::JUMP_EQ_BOOLEAN:
|
||||
return "JUMP_EQ_BOOLEAN";
|
||||
case IrCmd::JUMP_EQ_POINTER:
|
||||
return "JUMP_EQ_POINTER";
|
||||
case IrCmd::JUMP_CMP_NUM:
|
||||
return "JUMP_CMP_NUM";
|
||||
case IrCmd::JUMP_CMP_STR:
|
||||
return "JUMP_CMP_STR";
|
||||
case IrCmd::JUMP_CMP_ANY:
|
||||
return "JUMP_CMP_ANY";
|
||||
case IrCmd::TABLE_LEN:
|
||||
return "TABLE_LEN";
|
||||
case IrCmd::NEW_TABLE:
|
||||
return "NEW_TABLE";
|
||||
case IrCmd::DUP_TABLE:
|
||||
return "DUP_TABLE";
|
||||
case IrCmd::NUM_TO_INDEX:
|
||||
return "NUM_TO_INDEX";
|
||||
case IrCmd::DO_ARITH:
|
||||
return "DO_ARITH";
|
||||
case IrCmd::DO_LEN:
|
||||
return "DO_LEN";
|
||||
case IrCmd::GET_TABLE:
|
||||
return "GET_TABLE";
|
||||
case IrCmd::SET_TABLE:
|
||||
return "SET_TABLE";
|
||||
case IrCmd::GET_IMPORT:
|
||||
return "GET_IMPORT";
|
||||
case IrCmd::CONCAT:
|
||||
return "CONCAT";
|
||||
case IrCmd::GET_UPVALUE:
|
||||
return "GET_UPVALUE";
|
||||
case IrCmd::SET_UPVALUE:
|
||||
return "SET_UPVALUE";
|
||||
case IrCmd::CHECK_TAG:
|
||||
return "CHECK_TAG";
|
||||
case IrCmd::CHECK_READONLY:
|
||||
return "CHECK_READONLY";
|
||||
case IrCmd::CHECK_NO_METATABLE:
|
||||
return "CHECK_NO_METATABLE";
|
||||
case IrCmd::CHECK_SAFE_ENV:
|
||||
return "CHECK_SAFE_ENV";
|
||||
case IrCmd::CHECK_ARRAY_SIZE:
|
||||
return "CHECK_ARRAY_SIZE";
|
||||
case IrCmd::CHECK_SLOT_MATCH:
|
||||
return "CHECK_SLOT_MATCH";
|
||||
case IrCmd::INTERRUPT:
|
||||
return "INTERRUPT";
|
||||
case IrCmd::CHECK_GC:
|
||||
return "CHECK_GC";
|
||||
case IrCmd::BARRIER_OBJ:
|
||||
return "BARRIER_OBJ";
|
||||
case IrCmd::BARRIER_TABLE_BACK:
|
||||
return "BARRIER_TABLE_BACK";
|
||||
case IrCmd::BARRIER_TABLE_FORWARD:
|
||||
return "BARRIER_TABLE_FORWARD";
|
||||
case IrCmd::SET_SAVEDPC:
|
||||
return "SET_SAVEDPC";
|
||||
case IrCmd::CLOSE_UPVALS:
|
||||
return "CLOSE_UPVALS";
|
||||
case IrCmd::CAPTURE:
|
||||
return "CAPTURE";
|
||||
case IrCmd::LOP_SETLIST:
|
||||
return "LOP_SETLIST";
|
||||
case IrCmd::LOP_CALL:
|
||||
return "LOP_CALL";
|
||||
case IrCmd::LOP_RETURN:
|
||||
return "LOP_RETURN";
|
||||
case IrCmd::LOP_FASTCALL:
|
||||
return "LOP_FASTCALL";
|
||||
case IrCmd::LOP_FASTCALL1:
|
||||
return "LOP_FASTCALL1";
|
||||
case IrCmd::LOP_FASTCALL2:
|
||||
return "LOP_FASTCALL2";
|
||||
case IrCmd::LOP_FASTCALL2K:
|
||||
return "LOP_FASTCALL2K";
|
||||
case IrCmd::LOP_FORNPREP:
|
||||
return "LOP_FORNPREP";
|
||||
case IrCmd::LOP_FORNLOOP:
|
||||
return "LOP_FORNLOOP";
|
||||
case IrCmd::LOP_FORGLOOP:
|
||||
return "LOP_FORGLOOP";
|
||||
case IrCmd::LOP_FORGLOOP_FALLBACK:
|
||||
return "LOP_FORGLOOP_FALLBACK";
|
||||
case IrCmd::LOP_FORGPREP_NEXT:
|
||||
return "LOP_FORGPREP_NEXT";
|
||||
case IrCmd::LOP_FORGPREP_INEXT:
|
||||
return "LOP_FORGPREP_INEXT";
|
||||
case IrCmd::LOP_FORGPREP_XNEXT_FALLBACK:
|
||||
return "LOP_FORGPREP_XNEXT_FALLBACK";
|
||||
case IrCmd::LOP_AND:
|
||||
return "LOP_AND";
|
||||
case IrCmd::LOP_ANDK:
|
||||
return "LOP_ANDK";
|
||||
case IrCmd::LOP_OR:
|
||||
return "LOP_OR";
|
||||
case IrCmd::LOP_ORK:
|
||||
return "LOP_ORK";
|
||||
case IrCmd::FALLBACK_GETGLOBAL:
|
||||
return "FALLBACK_GETGLOBAL";
|
||||
case IrCmd::FALLBACK_SETGLOBAL:
|
||||
return "FALLBACK_SETGLOBAL";
|
||||
case IrCmd::FALLBACK_GETTABLEKS:
|
||||
return "FALLBACK_GETTABLEKS";
|
||||
case IrCmd::FALLBACK_SETTABLEKS:
|
||||
return "FALLBACK_SETTABLEKS";
|
||||
case IrCmd::FALLBACK_NAMECALL:
|
||||
return "FALLBACK_NAMECALL";
|
||||
case IrCmd::FALLBACK_PREPVARARGS:
|
||||
return "FALLBACK_PREPVARARGS";
|
||||
case IrCmd::FALLBACK_GETVARARGS:
|
||||
return "FALLBACK_GETVARARGS";
|
||||
case IrCmd::FALLBACK_NEWCLOSURE:
|
||||
return "FALLBACK_NEWCLOSURE";
|
||||
case IrCmd::FALLBACK_DUPCLOSURE:
|
||||
return "FALLBACK_DUPCLOSURE";
|
||||
case IrCmd::FALLBACK_FORGPREP:
|
||||
return "FALLBACK_FORGPREP";
|
||||
case IrCmd::FALLBACK_COVERAGE:
|
||||
return "FALLBACK_COVERAGE";
|
||||
}
|
||||
|
||||
LUAU_UNREACHABLE();
|
||||
}
|
||||
|
||||
const char* getBlockKindName(IrBlockKind kind)
|
||||
{
|
||||
switch (kind)
|
||||
{
|
||||
case IrBlockKind::Bytecode:
|
||||
return "bb_bytecode";
|
||||
case IrBlockKind::Fallback:
|
||||
return "bb_fallback";
|
||||
case IrBlockKind::Internal:
|
||||
return "bb";
|
||||
}
|
||||
|
||||
LUAU_UNREACHABLE();
|
||||
}
|
||||
|
||||
void toString(IrToStringContext& ctx, IrInst inst, uint32_t index)
|
||||
{
|
||||
append(ctx.result, " ");
|
||||
|
||||
// Instructions with a result display target virtual register
|
||||
if (hasResult(inst.cmd))
|
||||
append(ctx.result, "%%%u = ", index);
|
||||
|
||||
ctx.result.append(getCmdName(inst.cmd));
|
||||
|
||||
if (inst.a.kind != IrOpKind::None)
|
||||
{
|
||||
append(ctx.result, " ");
|
||||
toString(ctx, inst.a);
|
||||
}
|
||||
|
||||
if (inst.b.kind != IrOpKind::None)
|
||||
{
|
||||
append(ctx.result, ", ");
|
||||
toString(ctx, inst.b);
|
||||
}
|
||||
|
||||
if (inst.c.kind != IrOpKind::None)
|
||||
{
|
||||
append(ctx.result, ", ");
|
||||
toString(ctx, inst.c);
|
||||
}
|
||||
|
||||
if (inst.d.kind != IrOpKind::None)
|
||||
{
|
||||
append(ctx.result, ", ");
|
||||
toString(ctx, inst.d);
|
||||
}
|
||||
|
||||
if (inst.e.kind != IrOpKind::None)
|
||||
{
|
||||
append(ctx.result, ", ");
|
||||
toString(ctx, inst.e);
|
||||
}
|
||||
}
|
||||
|
||||
void toString(IrToStringContext& ctx, IrOp op)
|
||||
{
|
||||
switch (op.kind)
|
||||
{
|
||||
case IrOpKind::None:
|
||||
break;
|
||||
case IrOpKind::Constant:
|
||||
toString(ctx.result, ctx.constants[op.index]);
|
||||
break;
|
||||
case IrOpKind::Condition:
|
||||
LUAU_ASSERT(op.index < uint32_t(IrCondition::Count));
|
||||
ctx.result.append(textForCondition[op.index]);
|
||||
break;
|
||||
case IrOpKind::Inst:
|
||||
append(ctx.result, "%%%u", op.index);
|
||||
break;
|
||||
case IrOpKind::Block:
|
||||
append(ctx.result, "%s_%u", getBlockKindName(ctx.blocks[op.index].kind), op.index);
|
||||
break;
|
||||
case IrOpKind::VmReg:
|
||||
append(ctx.result, "R%u", op.index);
|
||||
break;
|
||||
case IrOpKind::VmConst:
|
||||
append(ctx.result, "K%u", op.index);
|
||||
break;
|
||||
case IrOpKind::VmUpvalue:
|
||||
append(ctx.result, "U%u", op.index);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void toString(std::string& result, IrConst constant)
|
||||
{
|
||||
switch (constant.kind)
|
||||
{
|
||||
case IrConstKind::Bool:
|
||||
append(result, constant.valueBool ? "true" : "false");
|
||||
break;
|
||||
case IrConstKind::Int:
|
||||
append(result, "%di", constant.valueInt);
|
||||
break;
|
||||
case IrConstKind::Uint:
|
||||
append(result, "%uu", constant.valueUint);
|
||||
break;
|
||||
case IrConstKind::Double:
|
||||
append(result, "%.17g", constant.valueDouble);
|
||||
break;
|
||||
case IrConstKind::Tag:
|
||||
result.append(getTagName(constant.valueTag));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void toStringDetailed(IrToStringContext& ctx, IrInst inst, uint32_t index)
|
||||
{
|
||||
size_t start = ctx.result.size();
|
||||
|
||||
toString(ctx, inst, index);
|
||||
|
||||
int pad = kDetailsAlignColumn - int(ctx.result.size() - start);
|
||||
|
||||
if (pad > 0)
|
||||
ctx.result.append(pad, ' ');
|
||||
|
||||
LUAU_ASSERT(inst.useCount == 0 || inst.lastUse != 0);
|
||||
|
||||
if (inst.useCount == 0 && hasSideEffects(inst.cmd))
|
||||
append(ctx.result, "; %%%u, has side-effects\n", index);
|
||||
else
|
||||
append(ctx.result, "; useCount: %d, lastUse: %%%u\n", inst.useCount, inst.lastUse);
|
||||
}
|
||||
|
||||
} // namespace CodeGen
|
||||
} // namespace Luau
|
32
CodeGen/src/IrDump.h
Normal file
32
CodeGen/src/IrDump.h
Normal file
@ -0,0 +1,32 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "IrData.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
namespace CodeGen
|
||||
{
|
||||
|
||||
const char* getCmdName(IrCmd cmd);
|
||||
const char* getBlockKindName(IrBlockKind kind);
|
||||
|
||||
struct IrToStringContext
|
||||
{
|
||||
std::string& result;
|
||||
std::vector<IrBlock>& blocks;
|
||||
std::vector<IrConst>& constants;
|
||||
};
|
||||
|
||||
void toString(IrToStringContext& ctx, IrInst inst, uint32_t index);
|
||||
void toString(IrToStringContext& ctx, IrOp op);
|
||||
|
||||
void toString(std::string& result, IrConst constant);
|
||||
|
||||
void toStringDetailed(IrToStringContext& ctx, IrInst inst, uint32_t index);
|
||||
|
||||
} // namespace CodeGen
|
||||
} // namespace Luau
|
161
CodeGen/src/IrUtils.h
Normal file
161
CodeGen/src/IrUtils.h
Normal file
@ -0,0 +1,161 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Bytecode.h"
|
||||
#include "Luau/Common.h"
|
||||
|
||||
#include "IrData.h"
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
namespace CodeGen
|
||||
{
|
||||
|
||||
inline bool isJumpD(LuauOpcode op)
|
||||
{
|
||||
switch (op)
|
||||
{
|
||||
case LOP_JUMP:
|
||||
case LOP_JUMPIF:
|
||||
case LOP_JUMPIFNOT:
|
||||
case LOP_JUMPIFEQ:
|
||||
case LOP_JUMPIFLE:
|
||||
case LOP_JUMPIFLT:
|
||||
case LOP_JUMPIFNOTEQ:
|
||||
case LOP_JUMPIFNOTLE:
|
||||
case LOP_JUMPIFNOTLT:
|
||||
case LOP_FORNPREP:
|
||||
case LOP_FORNLOOP:
|
||||
case LOP_FORGPREP:
|
||||
case LOP_FORGLOOP:
|
||||
case LOP_FORGPREP_INEXT:
|
||||
case LOP_FORGPREP_NEXT:
|
||||
case LOP_JUMPBACK:
|
||||
case LOP_JUMPXEQKNIL:
|
||||
case LOP_JUMPXEQKB:
|
||||
case LOP_JUMPXEQKN:
|
||||
case LOP_JUMPXEQKS:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
inline bool isSkipC(LuauOpcode op)
|
||||
{
|
||||
switch (op)
|
||||
{
|
||||
case LOP_LOADB:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
inline bool isFastCall(LuauOpcode op)
|
||||
{
|
||||
switch (op)
|
||||
{
|
||||
case LOP_FASTCALL:
|
||||
case LOP_FASTCALL1:
|
||||
case LOP_FASTCALL2:
|
||||
case LOP_FASTCALL2K:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
inline 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 (isFastCall(op))
|
||||
return int(pc + LUAU_INSN_C(insn) + 2);
|
||||
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;
|
||||
}
|
||||
|
||||
inline bool isBlockTerminator(IrCmd cmd)
|
||||
{
|
||||
switch (cmd)
|
||||
{
|
||||
case IrCmd::JUMP:
|
||||
case IrCmd::JUMP_IF_TRUTHY:
|
||||
case IrCmd::JUMP_IF_FALSY:
|
||||
case IrCmd::JUMP_EQ_TAG:
|
||||
case IrCmd::JUMP_EQ_BOOLEAN:
|
||||
case IrCmd::JUMP_EQ_POINTER:
|
||||
case IrCmd::JUMP_CMP_NUM:
|
||||
case IrCmd::JUMP_CMP_STR:
|
||||
case IrCmd::JUMP_CMP_ANY:
|
||||
case IrCmd::LOP_RETURN:
|
||||
case IrCmd::LOP_FORNPREP:
|
||||
case IrCmd::LOP_FORNLOOP:
|
||||
case IrCmd::LOP_FORGLOOP:
|
||||
case IrCmd::LOP_FORGLOOP_FALLBACK:
|
||||
case IrCmd::LOP_FORGPREP_NEXT:
|
||||
case IrCmd::LOP_FORGPREP_INEXT:
|
||||
case IrCmd::LOP_FORGPREP_XNEXT_FALLBACK:
|
||||
case IrCmd::FALLBACK_FORGPREP:
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool hasResult(IrCmd cmd)
|
||||
{
|
||||
switch (cmd)
|
||||
{
|
||||
case IrCmd::LOAD_TAG:
|
||||
case IrCmd::LOAD_POINTER:
|
||||
case IrCmd::LOAD_DOUBLE:
|
||||
case IrCmd::LOAD_INT:
|
||||
case IrCmd::LOAD_TVALUE:
|
||||
case IrCmd::LOAD_NODE_VALUE_TV:
|
||||
case IrCmd::LOAD_ENV:
|
||||
case IrCmd::GET_ARR_ADDR:
|
||||
case IrCmd::GET_SLOT_NODE_ADDR:
|
||||
case IrCmd::ADD_INT:
|
||||
case IrCmd::SUB_INT:
|
||||
case IrCmd::ADD_NUM:
|
||||
case IrCmd::SUB_NUM:
|
||||
case IrCmd::MUL_NUM:
|
||||
case IrCmd::DIV_NUM:
|
||||
case IrCmd::MOD_NUM:
|
||||
case IrCmd::POW_NUM:
|
||||
case IrCmd::UNM_NUM:
|
||||
case IrCmd::NOT_ANY:
|
||||
case IrCmd::TABLE_LEN:
|
||||
case IrCmd::NEW_TABLE:
|
||||
case IrCmd::DUP_TABLE:
|
||||
case IrCmd::NUM_TO_INDEX:
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool hasSideEffects(IrCmd cmd)
|
||||
{
|
||||
// Instructions that don't produce a result most likely have other side-effects to make them useful
|
||||
// Right now, a full switch would mirror the 'hasResult' function, so we use this simple condition
|
||||
return !hasResult(cmd);
|
||||
}
|
||||
|
||||
} // namespace CodeGen
|
||||
} // namespace Luau
|
@ -42,7 +42,6 @@ void initFallbackTable(NativeState& data)
|
||||
CODEGEN_SET_FALLBACK(LOP_GETVARARGS, 0);
|
||||
CODEGEN_SET_FALLBACK(LOP_DUPCLOSURE, 0);
|
||||
CODEGEN_SET_FALLBACK(LOP_PREPVARARGS, 0);
|
||||
CODEGEN_SET_FALLBACK(LOP_COVERAGE, 0);
|
||||
CODEGEN_SET_FALLBACK(LOP_BREAK, 0);
|
||||
|
||||
// Fallbacks that are called from partial implementation of an instruction
|
||||
@ -80,6 +79,8 @@ void initHelperFunctions(NativeState& data)
|
||||
|
||||
data.context.luaF_close = luaF_close;
|
||||
|
||||
data.context.luaT_gettm = luaT_gettm;
|
||||
|
||||
data.context.libm_exp = exp;
|
||||
data.context.libm_pow = pow;
|
||||
data.context.libm_fmod = fmod;
|
||||
|
@ -78,6 +78,8 @@ struct NativeContext
|
||||
|
||||
void (*luaF_close)(lua_State* L, StkId level) = nullptr;
|
||||
|
||||
const TValue* (*luaT_gettm)(Table* events, TMS event, TString* ename) = nullptr;
|
||||
|
||||
double (*libm_exp)(double) = nullptr;
|
||||
double (*libm_pow)(double, double) = nullptr;
|
||||
double (*libm_fmod)(double, double) = nullptr;
|
||||
|
@ -123,6 +123,8 @@ public:
|
||||
static uint32_t getImportId(int32_t id0, int32_t id1);
|
||||
static uint32_t getImportId(int32_t id0, int32_t id1, int32_t id2);
|
||||
|
||||
static int decomposeImportId(uint32_t ids, int32_t& id0, int32_t& id1, int32_t& id2);
|
||||
|
||||
static uint32_t getStringHash(StringRef key);
|
||||
|
||||
static std::string getError(const std::string& message);
|
||||
@ -243,6 +245,7 @@ private:
|
||||
std::vector<DebugUpval> debugUpvals;
|
||||
|
||||
DenseHashMap<StringRef, unsigned int, StringRefHash> stringTable;
|
||||
std::vector<StringRef> debugStrings;
|
||||
|
||||
std::vector<std::pair<uint32_t, uint32_t>> debugRemarks;
|
||||
std::string debugRemarkBuffer;
|
||||
@ -261,6 +264,7 @@ private:
|
||||
void validateVariadic() const;
|
||||
|
||||
std::string dumpCurrentFunction(std::vector<int>& dumpinstoffs) const;
|
||||
void dumpConstant(std::string& result, int k) const;
|
||||
void dumpInstruction(const uint32_t* opcode, std::string& output, int targetLabel) const;
|
||||
|
||||
void writeFunction(std::string& ss, uint32_t id) const;
|
||||
|
@ -318,8 +318,13 @@ unsigned int BytecodeBuilder::addStringTableEntry(StringRef value)
|
||||
|
||||
// note: bytecode serialization format uses 1-based table indices, 0 is reserved to mean nil
|
||||
if (index == 0)
|
||||
{
|
||||
index = uint32_t(stringTable.size());
|
||||
|
||||
if ((dumpFlags & Dump_Code) != 0)
|
||||
debugStrings.push_back(value);
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
@ -870,6 +875,15 @@ uint32_t BytecodeBuilder::getImportId(int32_t id0, int32_t id1, int32_t id2)
|
||||
return (3u << 30) | (id0 << 20) | (id1 << 10) | id2;
|
||||
}
|
||||
|
||||
int BytecodeBuilder::decomposeImportId(uint32_t ids, int32_t& id0, int32_t& id1, int32_t& id2)
|
||||
{
|
||||
int count = ids >> 30;
|
||||
id0 = count > 0 ? int(ids >> 20) & 1023 : -1;
|
||||
id1 = count > 1 ? int(ids >> 10) & 1023 : -1;
|
||||
id2 = count > 2 ? int(ids) & 1023 : -1;
|
||||
return count;
|
||||
}
|
||||
|
||||
uint32_t BytecodeBuilder::getStringHash(StringRef key)
|
||||
{
|
||||
// This hashing algorithm should match luaS_hash defined in VM/lstring.cpp for short inputs; we can't use that code directly to keep compiler and
|
||||
@ -1598,6 +1612,95 @@ void BytecodeBuilder::validateVariadic() const
|
||||
}
|
||||
#endif
|
||||
|
||||
static bool printableStringConstant(const char* str, size_t len)
|
||||
{
|
||||
for (size_t i = 0; i < len; ++i)
|
||||
{
|
||||
if (unsigned(str[i]) < ' ')
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void BytecodeBuilder::dumpConstant(std::string& result, int k) const
|
||||
{
|
||||
LUAU_ASSERT(unsigned(k) < constants.size());
|
||||
const Constant& data = constants[k];
|
||||
|
||||
switch (data.type)
|
||||
{
|
||||
case Constant::Type_Nil:
|
||||
formatAppend(result, "nil");
|
||||
break;
|
||||
case Constant::Type_Boolean:
|
||||
formatAppend(result, "%s", data.valueBoolean ? "true" : "false");
|
||||
break;
|
||||
case Constant::Type_Number:
|
||||
formatAppend(result, "%.17g", data.valueNumber);
|
||||
break;
|
||||
case Constant::Type_String:
|
||||
{
|
||||
const StringRef& str = debugStrings[data.valueString - 1];
|
||||
|
||||
if (printableStringConstant(str.data, str.length))
|
||||
{
|
||||
if (str.length < 32)
|
||||
formatAppend(result, "'%.*s'", int(str.length), str.data);
|
||||
else
|
||||
formatAppend(result, "'%.*s'...", 32, str.data);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Constant::Type_Import:
|
||||
{
|
||||
int id0 = -1, id1 = -1, id2 = -1;
|
||||
if (int count = decomposeImportId(data.valueImport, id0, id1, id2))
|
||||
{
|
||||
{
|
||||
const Constant& id = constants[id0];
|
||||
LUAU_ASSERT(id.type == Constant::Type_String && id.valueString <= debugStrings.size());
|
||||
|
||||
const StringRef& str = debugStrings[id.valueString - 1];
|
||||
formatAppend(result, "%.*s", int(str.length), str.data);
|
||||
}
|
||||
|
||||
if (count > 1)
|
||||
{
|
||||
const Constant& id = constants[id1];
|
||||
LUAU_ASSERT(id.type == Constant::Type_String && id.valueString <= debugStrings.size());
|
||||
|
||||
const StringRef& str = debugStrings[id.valueString - 1];
|
||||
formatAppend(result, ".%.*s", int(str.length), str.data);
|
||||
}
|
||||
|
||||
if (count > 2)
|
||||
{
|
||||
const Constant& id = constants[id2];
|
||||
LUAU_ASSERT(id.type == Constant::Type_String && id.valueString <= debugStrings.size());
|
||||
|
||||
const StringRef& str = debugStrings[id.valueString - 1];
|
||||
formatAppend(result, ".%.*s", int(str.length), str.data);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Constant::Type_Table:
|
||||
formatAppend(result, "{...}");
|
||||
break;
|
||||
case Constant::Type_Closure:
|
||||
{
|
||||
const Function& func = functions[data.valueClosure];
|
||||
|
||||
if (!func.dumpname.empty())
|
||||
formatAppend(result, "'%s'", func.dumpname.c_str());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LUAU_UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
void BytecodeBuilder::dumpInstruction(const uint32_t* code, std::string& result, int targetLabel) const
|
||||
{
|
||||
uint32_t insn = *code++;
|
||||
@ -1620,7 +1723,9 @@ void BytecodeBuilder::dumpInstruction(const uint32_t* code, std::string& result,
|
||||
break;
|
||||
|
||||
case LOP_LOADK:
|
||||
formatAppend(result, "LOADK R%d K%d\n", LUAU_INSN_A(insn), LUAU_INSN_D(insn));
|
||||
formatAppend(result, "LOADK R%d K%d [", LUAU_INSN_A(insn), LUAU_INSN_D(insn));
|
||||
dumpConstant(result, LUAU_INSN_D(insn));
|
||||
result.append("]\n");
|
||||
break;
|
||||
|
||||
case LOP_MOVE:
|
||||
@ -1628,11 +1733,17 @@ void BytecodeBuilder::dumpInstruction(const uint32_t* code, std::string& result,
|
||||
break;
|
||||
|
||||
case LOP_GETGLOBAL:
|
||||
formatAppend(result, "GETGLOBAL R%d K%d\n", LUAU_INSN_A(insn), *code++);
|
||||
formatAppend(result, "GETGLOBAL R%d K%d [", LUAU_INSN_A(insn), *code);
|
||||
dumpConstant(result, *code);
|
||||
result.append("]\n");
|
||||
code++;
|
||||
break;
|
||||
|
||||
case LOP_SETGLOBAL:
|
||||
formatAppend(result, "SETGLOBAL R%d K%d\n", LUAU_INSN_A(insn), *code++);
|
||||
formatAppend(result, "SETGLOBAL R%d K%d [", LUAU_INSN_A(insn), *code);
|
||||
dumpConstant(result, *code);
|
||||
result.append("]\n");
|
||||
code++;
|
||||
break;
|
||||
|
||||
case LOP_GETUPVAL:
|
||||
@ -1648,7 +1759,9 @@ void BytecodeBuilder::dumpInstruction(const uint32_t* code, std::string& result,
|
||||
break;
|
||||
|
||||
case LOP_GETIMPORT:
|
||||
formatAppend(result, "GETIMPORT R%d %d\n", LUAU_INSN_A(insn), LUAU_INSN_D(insn));
|
||||
formatAppend(result, "GETIMPORT R%d %d [", LUAU_INSN_A(insn), LUAU_INSN_D(insn));
|
||||
dumpConstant(result, LUAU_INSN_D(insn));
|
||||
result.append("]\n");
|
||||
code++; // AUX
|
||||
break;
|
||||
|
||||
@ -1661,11 +1774,17 @@ void BytecodeBuilder::dumpInstruction(const uint32_t* code, std::string& result,
|
||||
break;
|
||||
|
||||
case LOP_GETTABLEKS:
|
||||
formatAppend(result, "GETTABLEKS R%d R%d K%d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), *code++);
|
||||
formatAppend(result, "GETTABLEKS R%d R%d K%d [", LUAU_INSN_A(insn), LUAU_INSN_B(insn), *code);
|
||||
dumpConstant(result, *code);
|
||||
result.append("]\n");
|
||||
code++;
|
||||
break;
|
||||
|
||||
case LOP_SETTABLEKS:
|
||||
formatAppend(result, "SETTABLEKS R%d R%d K%d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), *code++);
|
||||
formatAppend(result, "SETTABLEKS R%d R%d K%d [", LUAU_INSN_A(insn), LUAU_INSN_B(insn), *code);
|
||||
dumpConstant(result, *code);
|
||||
result.append("]\n");
|
||||
code++;
|
||||
break;
|
||||
|
||||
case LOP_GETTABLEN:
|
||||
@ -1681,7 +1800,10 @@ void BytecodeBuilder::dumpInstruction(const uint32_t* code, std::string& result,
|
||||
break;
|
||||
|
||||
case LOP_NAMECALL:
|
||||
formatAppend(result, "NAMECALL R%d R%d K%d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), *code++);
|
||||
formatAppend(result, "NAMECALL R%d R%d K%d [", LUAU_INSN_A(insn), LUAU_INSN_B(insn), *code);
|
||||
dumpConstant(result, *code);
|
||||
result.append("]\n");
|
||||
code++;
|
||||
break;
|
||||
|
||||
case LOP_CALL:
|
||||
@ -1753,27 +1875,39 @@ void BytecodeBuilder::dumpInstruction(const uint32_t* code, std::string& result,
|
||||
break;
|
||||
|
||||
case LOP_ADDK:
|
||||
formatAppend(result, "ADDK R%d R%d K%d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn));
|
||||
formatAppend(result, "ADDK R%d R%d K%d [", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn));
|
||||
dumpConstant(result, LUAU_INSN_C(insn));
|
||||
result.append("]\n");
|
||||
break;
|
||||
|
||||
case LOP_SUBK:
|
||||
formatAppend(result, "SUBK R%d R%d K%d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn));
|
||||
formatAppend(result, "SUBK R%d R%d K%d [", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn));
|
||||
dumpConstant(result, LUAU_INSN_C(insn));
|
||||
result.append("]\n");
|
||||
break;
|
||||
|
||||
case LOP_MULK:
|
||||
formatAppend(result, "MULK R%d R%d K%d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn));
|
||||
formatAppend(result, "MULK R%d R%d K%d [", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn));
|
||||
dumpConstant(result, LUAU_INSN_C(insn));
|
||||
result.append("]\n");
|
||||
break;
|
||||
|
||||
case LOP_DIVK:
|
||||
formatAppend(result, "DIVK R%d R%d K%d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn));
|
||||
formatAppend(result, "DIVK R%d R%d K%d [", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn));
|
||||
dumpConstant(result, LUAU_INSN_C(insn));
|
||||
result.append("]\n");
|
||||
break;
|
||||
|
||||
case LOP_MODK:
|
||||
formatAppend(result, "MODK R%d R%d K%d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn));
|
||||
formatAppend(result, "MODK R%d R%d K%d [", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn));
|
||||
dumpConstant(result, LUAU_INSN_C(insn));
|
||||
result.append("]\n");
|
||||
break;
|
||||
|
||||
case LOP_POWK:
|
||||
formatAppend(result, "POWK R%d R%d K%d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn));
|
||||
formatAppend(result, "POWK R%d R%d K%d [", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn));
|
||||
dumpConstant(result, LUAU_INSN_C(insn));
|
||||
result.append("]\n");
|
||||
break;
|
||||
|
||||
case LOP_AND:
|
||||
@ -1785,11 +1919,15 @@ void BytecodeBuilder::dumpInstruction(const uint32_t* code, std::string& result,
|
||||
break;
|
||||
|
||||
case LOP_ANDK:
|
||||
formatAppend(result, "ANDK R%d R%d K%d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn));
|
||||
formatAppend(result, "ANDK R%d R%d K%d [", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn));
|
||||
dumpConstant(result, LUAU_INSN_C(insn));
|
||||
result.append("]\n");
|
||||
break;
|
||||
|
||||
case LOP_ORK:
|
||||
formatAppend(result, "ORK R%d R%d K%d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn));
|
||||
formatAppend(result, "ORK R%d R%d K%d [", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn));
|
||||
dumpConstant(result, LUAU_INSN_C(insn));
|
||||
result.append("]\n");
|
||||
break;
|
||||
|
||||
case LOP_CONCAT:
|
||||
@ -1850,7 +1988,9 @@ void BytecodeBuilder::dumpInstruction(const uint32_t* code, std::string& result,
|
||||
break;
|
||||
|
||||
case LOP_DUPCLOSURE:
|
||||
formatAppend(result, "DUPCLOSURE R%d K%d\n", LUAU_INSN_A(insn), LUAU_INSN_D(insn));
|
||||
formatAppend(result, "DUPCLOSURE R%d K%d [", LUAU_INSN_A(insn), LUAU_INSN_D(insn));
|
||||
dumpConstant(result, LUAU_INSN_D(insn));
|
||||
result.append("]\n");
|
||||
break;
|
||||
|
||||
case LOP_BREAK:
|
||||
@ -1862,7 +2002,10 @@ void BytecodeBuilder::dumpInstruction(const uint32_t* code, std::string& result,
|
||||
break;
|
||||
|
||||
case LOP_LOADKX:
|
||||
formatAppend(result, "LOADKX R%d K%d\n", LUAU_INSN_A(insn), *code++);
|
||||
formatAppend(result, "LOADKX R%d K%d [", LUAU_INSN_A(insn), *code);
|
||||
dumpConstant(result, *code);
|
||||
result.append("]\n");
|
||||
code++;
|
||||
break;
|
||||
|
||||
case LOP_JUMPX:
|
||||
@ -1876,18 +2019,18 @@ void BytecodeBuilder::dumpInstruction(const uint32_t* code, std::string& result,
|
||||
case LOP_FASTCALL1:
|
||||
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 L%d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), aux, targetLabel);
|
||||
formatAppend(result, "FASTCALL2 %d R%d R%d L%d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), *code, targetLabel);
|
||||
code++;
|
||||
break;
|
||||
}
|
||||
|
||||
case LOP_FASTCALL2K:
|
||||
{
|
||||
uint32_t aux = *code++;
|
||||
formatAppend(result, "FASTCALL2K %d R%d K%d L%d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), aux, targetLabel);
|
||||
formatAppend(result, "FASTCALL2K %d R%d K%d L%d [", LUAU_INSN_A(insn), LUAU_INSN_B(insn), *code, targetLabel);
|
||||
dumpConstant(result, *code);
|
||||
result.append("]\n");
|
||||
code++;
|
||||
break;
|
||||
}
|
||||
|
||||
case LOP_COVERAGE:
|
||||
formatAppend(result, "COVERAGE\n");
|
||||
@ -1910,12 +2053,16 @@ void BytecodeBuilder::dumpInstruction(const uint32_t* code, std::string& result,
|
||||
break;
|
||||
|
||||
case LOP_JUMPXEQKN:
|
||||
formatAppend(result, "JUMPXEQKN R%d K%d L%d%s\n", LUAU_INSN_A(insn), *code & 0xffffff, targetLabel, *code >> 31 ? " NOT" : "");
|
||||
formatAppend(result, "JUMPXEQKN R%d K%d L%d%s [", LUAU_INSN_A(insn), *code & 0xffffff, targetLabel, *code >> 31 ? " NOT" : "");
|
||||
dumpConstant(result, *code & 0xffffff);
|
||||
result.append("]\n");
|
||||
code++;
|
||||
break;
|
||||
|
||||
case LOP_JUMPXEQKS:
|
||||
formatAppend(result, "JUMPXEQKS R%d K%d L%d%s\n", LUAU_INSN_A(insn), *code & 0xffffff, targetLabel, *code >> 31 ? " NOT" : "");
|
||||
formatAppend(result, "JUMPXEQKS R%d K%d L%d%s [", LUAU_INSN_A(insn), *code & 0xffffff, targetLabel, *code >> 31 ? " NOT" : "");
|
||||
dumpConstant(result, *code & 0xffffff);
|
||||
result.append("]\n");
|
||||
code++;
|
||||
break;
|
||||
|
||||
|
@ -28,6 +28,7 @@ LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5)
|
||||
LUAU_FASTFLAG(LuauInterpolatedStringBaseSupport)
|
||||
LUAU_FASTFLAGVARIABLE(LuauMultiAssignmentConflictFix, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSelfAssignmentSkip, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCompileInterpStringLimit, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -1580,7 +1581,8 @@ struct Compiler
|
||||
|
||||
RegScope rs(this);
|
||||
|
||||
uint8_t baseReg = allocReg(expr, uint8_t(2 + expr->expressions.size));
|
||||
uint8_t baseReg = FFlag::LuauCompileInterpStringLimit ? allocReg(expr, unsigned(2 + expr->expressions.size))
|
||||
: allocReg(expr, uint8_t(2 + expr->expressions.size));
|
||||
|
||||
emitLoadK(baseReg, formatStringIndex);
|
||||
|
||||
|
@ -82,6 +82,7 @@ target_sources(Luau.CodeGen PRIVATE
|
||||
CodeGen/src/EmitCommonX64.cpp
|
||||
CodeGen/src/EmitInstructionX64.cpp
|
||||
CodeGen/src/Fallbacks.cpp
|
||||
CodeGen/src/IrDump.cpp
|
||||
CodeGen/src/NativeState.cpp
|
||||
CodeGen/src/UnwindBuilderDwarf2.cpp
|
||||
CodeGen/src/UnwindBuilderWin.cpp
|
||||
@ -95,6 +96,9 @@ target_sources(Luau.CodeGen PRIVATE
|
||||
CodeGen/src/EmitInstructionX64.h
|
||||
CodeGen/src/Fallbacks.h
|
||||
CodeGen/src/FallbacksProlog.h
|
||||
CodeGen/src/IrDump.h
|
||||
CodeGen/src/IrData.h
|
||||
CodeGen/src/IrUtils.h
|
||||
CodeGen/src/NativeState.h
|
||||
)
|
||||
|
||||
|
@ -91,13 +91,13 @@ typedef struct luaL_Buffer luaL_Buffer;
|
||||
// all the buffer users we have in Luau match this pattern, but it's something to keep in mind for new uses of buffers
|
||||
|
||||
#define luaL_addchar(B, c) ((void)((B)->p < (B)->end || luaL_extendbuffer(B, 1, -1)), (*(B)->p++ = (char)(c)))
|
||||
#define luaL_addstring(B, s) luaL_addlstring(B, s, strlen(s))
|
||||
#define luaL_addstring(B, s) luaL_addlstring(B, s, strlen(s), -1)
|
||||
|
||||
LUALIB_API void luaL_buffinit(lua_State* L, luaL_Buffer* B);
|
||||
LUALIB_API char* luaL_buffinitsize(lua_State* L, luaL_Buffer* B, size_t size);
|
||||
LUALIB_API char* luaL_extendbuffer(luaL_Buffer* B, size_t additionalsize, int boxloc);
|
||||
LUALIB_API void luaL_reservebuffer(luaL_Buffer* B, size_t size, int boxloc);
|
||||
LUALIB_API void luaL_addlstring(luaL_Buffer* B, const char* s, size_t l);
|
||||
LUALIB_API void luaL_addlstring(luaL_Buffer* B, const char* s, size_t l, int boxloc);
|
||||
LUALIB_API void luaL_addvalue(luaL_Buffer* B);
|
||||
LUALIB_API void luaL_pushresult(luaL_Buffer* B);
|
||||
LUALIB_API void luaL_pushresultsize(luaL_Buffer* B, size_t size);
|
||||
|
@ -414,10 +414,10 @@ void luaL_reservebuffer(luaL_Buffer* B, size_t size, int boxloc)
|
||||
luaL_extendbuffer(B, size - (B->end - B->p), boxloc);
|
||||
}
|
||||
|
||||
void luaL_addlstring(luaL_Buffer* B, const char* s, size_t len)
|
||||
void luaL_addlstring(luaL_Buffer* B, const char* s, size_t len, int boxloc)
|
||||
{
|
||||
if (size_t(B->end - B->p) < len)
|
||||
luaL_extendbuffer(B, len - (B->end - B->p), -1);
|
||||
luaL_extendbuffer(B, len - (B->end - B->p), boxloc);
|
||||
|
||||
memcpy(B->p, s, len);
|
||||
B->p += len;
|
||||
|
@ -137,7 +137,7 @@ static int db_traceback(lua_State* L)
|
||||
*--lineptr = '0' + (r % 10);
|
||||
|
||||
luaL_addchar(&buf, ':');
|
||||
luaL_addlstring(&buf, lineptr, lineend - lineptr);
|
||||
luaL_addlstring(&buf, lineptr, lineend - lineptr, -1);
|
||||
}
|
||||
|
||||
if (ar.name)
|
||||
|
@ -151,7 +151,7 @@ static int os_date(lua_State* L)
|
||||
char buff[200]; // should be big enough for any conversion result
|
||||
cc[1] = *(++s);
|
||||
reslen = strftime(buff, sizeof(buff), cc, stm);
|
||||
luaL_addlstring(&b, buff, reslen);
|
||||
luaL_addlstring(&b, buff, reslen, -1);
|
||||
}
|
||||
}
|
||||
luaL_pushresult(&b);
|
||||
|
@ -8,6 +8,8 @@
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauStringFormatAnyFix, false)
|
||||
|
||||
// macro to `unsign' a character
|
||||
#define uchar(c) ((unsigned char)(c))
|
||||
|
||||
@ -771,7 +773,7 @@ static void add_s(MatchState* ms, luaL_Buffer* b, const char* s, const char* e)
|
||||
luaL_addchar(b, news[i]);
|
||||
}
|
||||
else if (news[i] == '0')
|
||||
luaL_addlstring(b, s, e - s);
|
||||
luaL_addlstring(b, s, e - s, -1);
|
||||
else
|
||||
{
|
||||
push_onecapture(ms, news[i] - '1', s, e);
|
||||
@ -854,7 +856,7 @@ static int str_gsub(lua_State* L)
|
||||
if (anchor)
|
||||
break;
|
||||
}
|
||||
luaL_addlstring(&b, src, ms.src_end - src);
|
||||
luaL_addlstring(&b, src, ms.src_end - src, -1);
|
||||
luaL_pushresult(&b);
|
||||
lua_pushinteger(L, n); // number of substitutions
|
||||
return 2;
|
||||
@ -891,12 +893,12 @@ static void addquoted(lua_State* L, luaL_Buffer* b, int arg)
|
||||
}
|
||||
case '\r':
|
||||
{
|
||||
luaL_addlstring(b, "\\r", 2);
|
||||
luaL_addlstring(b, "\\r", 2, -1);
|
||||
break;
|
||||
}
|
||||
case '\0':
|
||||
{
|
||||
luaL_addlstring(b, "\\000", 4);
|
||||
luaL_addlstring(b, "\\000", 4, -1);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@ -1012,7 +1014,7 @@ static int str_format(lua_State* L)
|
||||
case 'q':
|
||||
{
|
||||
addquoted(L, &b, arg);
|
||||
continue; // skip the 'addsize' at the end
|
||||
continue; // skip the 'luaL_addlstring' at the end
|
||||
}
|
||||
case 's':
|
||||
{
|
||||
@ -1024,7 +1026,7 @@ static int str_format(lua_State* L)
|
||||
keep original string */
|
||||
lua_pushvalue(L, arg);
|
||||
luaL_addvalue(&b);
|
||||
continue; // skip the `addsize' at the end
|
||||
continue; // skip the `luaL_addlstring' at the end
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -1037,18 +1039,30 @@ static int str_format(lua_State* L)
|
||||
if (formatItemSize != 1)
|
||||
luaL_error(L, "'%%*' does not take a form");
|
||||
|
||||
if (FFlag::LuauStringFormatAnyFix)
|
||||
{
|
||||
size_t length;
|
||||
const char* string = luaL_tolstring(L, arg, &length);
|
||||
|
||||
luaL_addlstring(&b, string, length);
|
||||
continue; // skip the `addsize' at the end
|
||||
luaL_addlstring(&b, string, length, -2);
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t length;
|
||||
const char* string = luaL_tolstring(L, arg, &length);
|
||||
|
||||
luaL_addlstring(&b, string, length, -1);
|
||||
}
|
||||
|
||||
continue; // skip the `luaL_addlstring' at the end
|
||||
}
|
||||
default:
|
||||
{ // also treat cases `pnLlh'
|
||||
luaL_error(L, "invalid option '%%%c' to 'format'", *(strfrmt - 1));
|
||||
}
|
||||
}
|
||||
luaL_addlstring(&b, buff, strlen(buff));
|
||||
luaL_addlstring(&b, buff, strlen(buff), -1);
|
||||
}
|
||||
}
|
||||
luaL_pushresult(&b);
|
||||
@ -1360,7 +1374,7 @@ static void packint(luaL_Buffer* b, unsigned long long n, int islittle, int size
|
||||
for (i = SZINT; i < size; i++) // correct extra bytes
|
||||
buff[islittle ? i : size - 1 - i] = (char)MC;
|
||||
}
|
||||
luaL_addlstring(b, buff, size); // add result to buffer
|
||||
luaL_addlstring(b, buff, size, -1); // add result to buffer
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1434,7 +1448,7 @@ static int str_pack(lua_State* L)
|
||||
u.n = n;
|
||||
// move 'u' to final result, correcting endianness if needed
|
||||
copywithendian(buff, u.buff, size, h.islittle);
|
||||
luaL_addlstring(&b, buff, size);
|
||||
luaL_addlstring(&b, buff, size, -1);
|
||||
break;
|
||||
}
|
||||
case Kchar:
|
||||
@ -1442,7 +1456,7 @@ static int str_pack(lua_State* L)
|
||||
size_t len;
|
||||
const char* s = luaL_checklstring(L, arg, &len);
|
||||
luaL_argcheck(L, len <= (size_t)size, arg, "string longer than given size");
|
||||
luaL_addlstring(&b, s, len); // add string
|
||||
luaL_addlstring(&b, s, len, -1); // add string
|
||||
while (len++ < (size_t)size) // pad extra space
|
||||
luaL_addchar(&b, LUAL_PACKPADBYTE);
|
||||
break;
|
||||
@ -1453,7 +1467,7 @@ static int str_pack(lua_State* L)
|
||||
const char* s = luaL_checklstring(L, arg, &len);
|
||||
luaL_argcheck(L, size >= (int)sizeof(size_t) || len < ((size_t)1 << (size * NB)), arg, "string length does not fit in given size");
|
||||
packint(&b, len, h.islittle, size, 0); // pack length
|
||||
luaL_addlstring(&b, s, len);
|
||||
luaL_addlstring(&b, s, len, -1);
|
||||
totalsize += len;
|
||||
break;
|
||||
}
|
||||
@ -1462,7 +1476,7 @@ static int str_pack(lua_State* L)
|
||||
size_t len;
|
||||
const char* s = luaL_checklstring(L, arg, &len);
|
||||
luaL_argcheck(L, strlen(s) == len, arg, "string contains zeros");
|
||||
luaL_addlstring(&b, s, len);
|
||||
luaL_addlstring(&b, s, len, -1);
|
||||
luaL_addchar(&b, '\0'); // add zero at the end
|
||||
totalsize += len + 1;
|
||||
break;
|
||||
|
@ -238,7 +238,7 @@ static int tconcat(lua_State* L)
|
||||
for (; i < last; i++)
|
||||
{
|
||||
addfield(L, &b, i);
|
||||
luaL_addlstring(&b, sep, lsep);
|
||||
luaL_addlstring(&b, sep, lsep, -1);
|
||||
}
|
||||
if (i == last) // add last value (if interval was not empty)
|
||||
addfield(L, &b, i);
|
||||
|
@ -175,7 +175,7 @@ static int utfchar(lua_State* L)
|
||||
for (int i = 1; i <= n; i++)
|
||||
{
|
||||
int l = buffutfchar(L, i, buff, &charstr);
|
||||
luaL_addlstring(&b, charstr, l);
|
||||
luaL_addlstring(&b, charstr, l, -1);
|
||||
}
|
||||
luaL_pushresult(&b);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/python3
|
||||
# This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
import argparse
|
||||
import os
|
||||
|
@ -243,6 +243,12 @@ TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "FormsOfLea")
|
||||
SINGLE_COMPARE(lea(rax, addr[r13 + r12 * 4 + 4]), 0x4b, 0x8d, 0x44, 0xa5, 0x04);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "FormsOfSetcc")
|
||||
{
|
||||
SINGLE_COMPARE(setcc(ConditionX64::NotEqual, bl), 0x0f, 0x95, 0xc3);
|
||||
SINGLE_COMPARE(setcc(ConditionX64::BelowEqual, byte[rcx]), 0x0f, 0x96, 0x01);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "FormsOfAbsoluteJumps")
|
||||
{
|
||||
SINGLE_COMPARE(jmp(rax), 0xff, 0xe0);
|
||||
|
@ -15,6 +15,7 @@
|
||||
|
||||
LUAU_FASTFLAG(LuauTraceTypesInNonstrictMode2)
|
||||
LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel)
|
||||
LUAU_FASTFLAG(LuauFixAutocompleteInIf)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
@ -789,6 +790,21 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_if_middle_keywords")
|
||||
CHECK_EQ(ac2.entryMap.count("end"), 0);
|
||||
CHECK_EQ(ac2.context, AutocompleteContext::Keyword);
|
||||
|
||||
if (FFlag::LuauFixAutocompleteInIf)
|
||||
{
|
||||
check(R"(
|
||||
if x t@1
|
||||
)");
|
||||
|
||||
auto ac3 = autocomplete('1');
|
||||
CHECK_EQ(3, ac3.entryMap.size());
|
||||
CHECK_EQ(ac3.entryMap.count("then"), 1);
|
||||
CHECK_EQ(ac3.entryMap.count("and"), 1);
|
||||
CHECK_EQ(ac3.entryMap.count("or"), 1);
|
||||
CHECK_EQ(ac3.context, AutocompleteContext::Keyword);
|
||||
}
|
||||
else
|
||||
{
|
||||
check(R"(
|
||||
if x t@1
|
||||
)");
|
||||
@ -797,6 +813,7 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_if_middle_keywords")
|
||||
CHECK_EQ(1, ac3.entryMap.size());
|
||||
CHECK_EQ(ac3.entryMap.count("then"), 1);
|
||||
CHECK_EQ(ac3.context, AutocompleteContext::Keyword);
|
||||
}
|
||||
|
||||
check(R"(
|
||||
if x then
|
||||
@ -839,6 +856,23 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_if_middle_keywords")
|
||||
CHECK_EQ(ac5.entryMap.count("elseif"), 0);
|
||||
CHECK_EQ(ac5.entryMap.count("end"), 0);
|
||||
CHECK_EQ(ac5.context, AutocompleteContext::Statement);
|
||||
|
||||
if (FFlag::LuauFixAutocompleteInIf)
|
||||
{
|
||||
check(R"(
|
||||
if t@1
|
||||
)");
|
||||
|
||||
auto ac6 = autocomplete('1');
|
||||
CHECK_EQ(ac6.entryMap.count("true"), 1);
|
||||
CHECK_EQ(ac6.entryMap.count("false"), 1);
|
||||
CHECK_EQ(ac6.entryMap.count("then"), 0);
|
||||
CHECK_EQ(ac6.entryMap.count("function"), 1);
|
||||
CHECK_EQ(ac6.entryMap.count("else"), 0);
|
||||
CHECK_EQ(ac6.entryMap.count("elseif"), 0);
|
||||
CHECK_EQ(ac6.entryMap.count("end"), 0);
|
||||
CHECK_EQ(ac6.context, AutocompleteContext::Expression);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "autocomplete_until_in_repeat")
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -297,6 +297,8 @@ TEST_CASE("Clear")
|
||||
|
||||
TEST_CASE("Strings")
|
||||
{
|
||||
ScopedFastFlag luauStringFormatAnyFix{"LuauStringFormatAnyFix", true};
|
||||
|
||||
runConformance("strings.lua");
|
||||
}
|
||||
|
||||
|
@ -2177,8 +2177,6 @@ type C<X...> = Packed<(number, X...)>
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "invalid_type_forms")
|
||||
{
|
||||
ScopedFastFlag luauFixNamedFunctionParse{"LuauFixNamedFunctionParse", true};
|
||||
|
||||
matchParseError("type A = (b: number)", "Expected '->' when parsing function type, got <eof>");
|
||||
matchParseError("type P<T...> = () -> T... type B = P<(x: number, y: string)>", "Expected '->' when parsing function type, got '>'");
|
||||
matchParseError("type F<T... = (a: string)> = (T...) -> ()", "Expected '->' when parsing function type, got '>'");
|
||||
|
@ -83,7 +83,6 @@ TEST_CASE_FIXTURE(Fixture, "table_respects_use_line_break")
|
||||
|
||||
ToStringOptions opts;
|
||||
opts.useLineBreaks = true;
|
||||
opts.DEPRECATED_indent = true;
|
||||
|
||||
//clang-format off
|
||||
CHECK_EQ("{|\n"
|
||||
@ -97,7 +96,6 @@ TEST_CASE_FIXTURE(Fixture, "table_respects_use_line_break")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "nil_or_nil_is_nil_not_question_mark")
|
||||
{
|
||||
ScopedFastFlag sff("LuauSerializeNilUnionAsNil", true);
|
||||
CheckResult result = check(R"(
|
||||
type nil_ty = nil | nil
|
||||
local a : nil_ty = nil
|
||||
@ -109,7 +107,6 @@ TEST_CASE_FIXTURE(Fixture, "nil_or_nil_is_nil_not_question_mark")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "long_disjunct_of_nil_is_nil_not_question_mark")
|
||||
{
|
||||
ScopedFastFlag sff("LuauSerializeNilUnionAsNil", true);
|
||||
CheckResult result = check(R"(
|
||||
type nil_ty = nil | nil | nil | nil | nil
|
||||
local a : nil_ty = nil
|
||||
|
@ -88,6 +88,96 @@ TEST_CASE_FIXTURE(ReductionFixture, "cartesian_product_is_zero")
|
||||
CHECK(ty);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ReductionFixture, "stress_test_recursion_limits")
|
||||
{
|
||||
TypeId ty = arena.addType(IntersectionType{{builtinTypes->numberType, builtinTypes->stringType}});
|
||||
for (size_t i = 0; i < 20'000; ++i)
|
||||
{
|
||||
TableType table;
|
||||
table.state = TableState::Sealed;
|
||||
table.props["x"] = {ty};
|
||||
ty = arena.addType(IntersectionType{{arena.addType(table), arena.addType(table)}});
|
||||
}
|
||||
|
||||
CHECK(!reduction.reduce(ty));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ReductionFixture, "caching")
|
||||
{
|
||||
SUBCASE("free_tables")
|
||||
{
|
||||
TypeId ty1 = arena.addType(TableType{});
|
||||
getMutable<TableType>(ty1)->state = TableState::Free;
|
||||
getMutable<TableType>(ty1)->props["x"] = {builtinTypes->stringType};
|
||||
|
||||
TypeId ty2 = arena.addType(TableType{});
|
||||
getMutable<TableType>(ty2)->state = TableState::Sealed;
|
||||
|
||||
TypeId intersectionTy = arena.addType(IntersectionType{{ty1, ty2}});
|
||||
|
||||
ToStringOptions opts;
|
||||
opts.exhaustive = true;
|
||||
|
||||
CHECK("{- x: string -} & {| |}" == toString(reductionof(intersectionTy)));
|
||||
|
||||
getMutable<TableType>(ty1)->state = TableState::Sealed;
|
||||
CHECK("{| x: string |}" == toString(reductionof(intersectionTy)));
|
||||
}
|
||||
|
||||
SUBCASE("unsealed_tables")
|
||||
{
|
||||
TypeId ty1 = arena.addType(TableType{});
|
||||
getMutable<TableType>(ty1)->state = TableState::Unsealed;
|
||||
getMutable<TableType>(ty1)->props["x"] = {builtinTypes->stringType};
|
||||
|
||||
TypeId ty2 = arena.addType(TableType{});
|
||||
getMutable<TableType>(ty2)->state = TableState::Sealed;
|
||||
|
||||
TypeId intersectionTy = arena.addType(IntersectionType{{ty1, ty2}});
|
||||
|
||||
ToStringOptions opts;
|
||||
opts.exhaustive = true;
|
||||
|
||||
CHECK("{| x: string |}" == toString(reductionof(intersectionTy)));
|
||||
|
||||
getMutable<TableType>(ty1)->state = TableState::Sealed;
|
||||
CHECK("{| x: string |}" == toString(reductionof(intersectionTy)));
|
||||
}
|
||||
|
||||
SUBCASE("free_types")
|
||||
{
|
||||
TypeId ty1 = arena.freshType(nullptr);
|
||||
TypeId ty2 = arena.addType(TableType{});
|
||||
getMutable<TableType>(ty2)->state = TableState::Sealed;
|
||||
|
||||
TypeId intersectionTy = arena.addType(IntersectionType{{ty1, ty2}});
|
||||
|
||||
ToStringOptions opts;
|
||||
opts.exhaustive = true;
|
||||
|
||||
CHECK("a & {| |}" == toString(reductionof(intersectionTy)));
|
||||
|
||||
*asMutable(ty1) = BoundType{ty2};
|
||||
CHECK("{| |}" == toString(reductionof(intersectionTy)));
|
||||
}
|
||||
|
||||
SUBCASE("we_can_see_that_the_cache_works_if_we_mutate_a_normally_not_mutated_type")
|
||||
{
|
||||
TypeId ty1 = arena.addType(BoundType{builtinTypes->stringType});
|
||||
TypeId ty2 = builtinTypes->numberType;
|
||||
|
||||
TypeId intersectionTy = arena.addType(IntersectionType{{ty1, ty2}});
|
||||
|
||||
ToStringOptions opts;
|
||||
opts.exhaustive = true;
|
||||
|
||||
CHECK("never" == toString(reductionof(intersectionTy))); // Bound<string> & number ~ never
|
||||
|
||||
*asMutable(ty1) = BoundType{ty2};
|
||||
CHECK("never" == toString(reductionof(intersectionTy))); // Bound<number> & number ~ number, but the cache is `never`.
|
||||
}
|
||||
} // caching
|
||||
|
||||
TEST_CASE_FIXTURE(ReductionFixture, "intersections_without_negations")
|
||||
{
|
||||
SUBCASE("string_and_string")
|
||||
@ -359,6 +449,34 @@ TEST_CASE_FIXTURE(ReductionFixture, "intersections_without_negations")
|
||||
TypeId ty = reductionof("{ p: string } & { p: string, [string]: number }");
|
||||
CHECK("{| [string]: number, p: string |}" == toString(ty));
|
||||
}
|
||||
|
||||
SUBCASE("fresh_type_and_string")
|
||||
{
|
||||
TypeId freshTy = arena.freshType(nullptr);
|
||||
TypeId ty = reductionof(arena.addType(IntersectionType{{freshTy, builtinTypes->stringType}}));
|
||||
CHECK("a & string" == toString(ty));
|
||||
}
|
||||
|
||||
SUBCASE("string_and_fresh_type")
|
||||
{
|
||||
TypeId freshTy = arena.freshType(nullptr);
|
||||
TypeId ty = reductionof(arena.addType(IntersectionType{{builtinTypes->stringType, freshTy}}));
|
||||
CHECK("a & string" == toString(ty));
|
||||
}
|
||||
|
||||
SUBCASE("generic_and_string")
|
||||
{
|
||||
TypeId genericTy = arena.addType(GenericType{"G"});
|
||||
TypeId ty = reductionof(arena.addType(IntersectionType{{genericTy, builtinTypes->stringType}}));
|
||||
CHECK("G & string" == toString(ty));
|
||||
}
|
||||
|
||||
SUBCASE("string_and_generic")
|
||||
{
|
||||
TypeId genericTy = arena.addType(GenericType{"G"});
|
||||
TypeId ty = reductionof(arena.addType(IntersectionType{{builtinTypes->stringType, genericTy}}));
|
||||
CHECK("G & string" == toString(ty));
|
||||
}
|
||||
} // intersections_without_negations
|
||||
|
||||
TEST_CASE_FIXTURE(ReductionFixture, "intersections_with_negations")
|
||||
@ -1232,18 +1350,4 @@ TEST_CASE_FIXTURE(ReductionFixture, "cycles")
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ReductionFixture, "stress_test_recursion_limits")
|
||||
{
|
||||
TypeId ty = arena.addType(IntersectionType{{builtinTypes->numberType, builtinTypes->stringType}});
|
||||
for (size_t i = 0; i < 20'000; ++i)
|
||||
{
|
||||
TableType table;
|
||||
table.state = TableState::Sealed;
|
||||
table.props["x"] = {ty};
|
||||
ty = arena.addType(IntersectionType{{arena.addType(table), arena.addType(table)}});
|
||||
}
|
||||
|
||||
CHECK(!reduction.reduce(ty));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -164,6 +164,16 @@ local ud = newproxy(true)
|
||||
getmetatable(ud).__tostring = function() return "good" end
|
||||
assert(string.format("%*", ud) == "good")
|
||||
|
||||
assert(string.format(string.rep("%*", 100), table.unpack(table.create(100, 1))) == string.rep("1", 100))
|
||||
|
||||
do
|
||||
local a = "1234567890"
|
||||
a = string.format("%*%*%*%*%*", a, a, a, a, a)
|
||||
a = string.format("%*%*%*%*%*", a, a, a, a, a)
|
||||
a = string.format("%*%*%*%*%*", a, a, a, a, a)
|
||||
assert(a == string.rep("1234567890", 125))
|
||||
end
|
||||
|
||||
assert(pcall(function()
|
||||
string.format("%#*", "bad form")
|
||||
end) == false)
|
||||
|
@ -686,4 +686,18 @@ do
|
||||
assert(pcall(table.clear, table.freeze({})) == false)
|
||||
end
|
||||
|
||||
-- check that namecall lookup doesn't give up on entries missing from cached slot position
|
||||
do
|
||||
for i = 1,10 do
|
||||
local t = setmetatable({}, { __index = { foo = 1 }})
|
||||
|
||||
assert(t.foo == 1)
|
||||
|
||||
t[-i] = 2
|
||||
t.foo = function(t, i) return -i end
|
||||
|
||||
assert(t:foo(i) == -i)
|
||||
end
|
||||
end
|
||||
|
||||
return"OK"
|
||||
|
@ -2,8 +2,6 @@ AnnotationTests.corecursive_types_error_on_tight_loop
|
||||
AnnotationTests.duplicate_type_param_name
|
||||
AnnotationTests.for_loop_counter_annotation_is_checked
|
||||
AnnotationTests.generic_aliases_are_cloned_properly
|
||||
AnnotationTests.instantiation_clone_has_to_follow
|
||||
AnnotationTests.luau_print_is_not_special_without_the_flag
|
||||
AnnotationTests.occurs_check_on_cyclic_intersection_type
|
||||
AnnotationTests.occurs_check_on_cyclic_union_type
|
||||
AnnotationTests.too_many_type_params
|
||||
@ -87,6 +85,8 @@ DefinitionTests.class_definition_overload_metamethods
|
||||
DefinitionTests.class_definition_string_props
|
||||
DefinitionTests.declaring_generic_functions
|
||||
DefinitionTests.definition_file_classes
|
||||
DefinitionTests.definitions_symbols_are_generated_for_recursively_referenced_types
|
||||
DefinitionTests.single_class_type_identity_in_global_types
|
||||
FrontendTest.environments
|
||||
FrontendTest.it_should_be_safe_to_stringify_errors_when_full_type_graph_is_discarded
|
||||
FrontendTest.nocheck_cycle_used_by_checked
|
||||
@ -140,6 +140,7 @@ NonstrictModeTests.parameters_having_type_any_are_optional
|
||||
NonstrictModeTests.table_dot_insert_and_recursive_calls
|
||||
NonstrictModeTests.table_props_are_any
|
||||
Normalize.cyclic_table_normalizes_sensibly
|
||||
Normalize.negations_of_classes
|
||||
ParseErrorRecovery.generic_type_list_recovery
|
||||
ParseErrorRecovery.recovery_of_parenthesized_expressions
|
||||
ParserTests.parse_nesting_based_end_detection_failsafe_earlier
|
||||
@ -165,8 +166,10 @@ RefinementTest.call_an_incompatible_function_after_using_typeguard
|
||||
RefinementTest.correctly_lookup_property_whose_base_was_previously_refined2
|
||||
RefinementTest.discriminate_on_properties_of_disjoint_tables_where_that_property_is_true_or_false
|
||||
RefinementTest.discriminate_tag
|
||||
RefinementTest.eliminate_subclasses_of_instance
|
||||
RefinementTest.else_with_no_explicit_expression_should_also_refine_the_tagged_union
|
||||
RefinementTest.falsiness_of_TruthyPredicate_narrows_into_nil
|
||||
RefinementTest.narrow_from_subclasses_of_instance_or_string_or_vector3
|
||||
RefinementTest.narrow_property_of_a_bounded_variable
|
||||
RefinementTest.nonoptional_type_can_narrow_to_nil_if_sense_is_true
|
||||
RefinementTest.refine_a_property_not_to_be_nil_through_an_intersection_table
|
||||
@ -176,6 +179,7 @@ RefinementTest.type_guard_narrowed_into_nothingness
|
||||
RefinementTest.type_narrow_for_all_the_userdata
|
||||
RefinementTest.type_narrow_to_vector
|
||||
RefinementTest.typeguard_cast_free_table_to_vector
|
||||
RefinementTest.typeguard_cast_instance_or_vector3_to_vector
|
||||
RefinementTest.typeguard_in_assert_position
|
||||
RefinementTest.typeguard_narrows_for_table
|
||||
RefinementTest.x_as_any_if_x_is_instance_elseif_x_is_table
|
||||
@ -457,6 +461,10 @@ TypePackTests.type_pack_type_parameters
|
||||
TypePackTests.unify_variadic_tails_in_arguments
|
||||
TypePackTests.unify_variadic_tails_in_arguments_free
|
||||
TypePackTests.variadic_packs
|
||||
TypeReductionTests.discriminable_unions
|
||||
TypeReductionTests.intersections_with_negations
|
||||
TypeReductionTests.negations
|
||||
TypeReductionTests.unions_with_negations
|
||||
TypeSingletons.error_detailed_tagged_union_mismatch_bool
|
||||
TypeSingletons.error_detailed_tagged_union_mismatch_string
|
||||
TypeSingletons.function_call_with_singletons
|
||||
|
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/python3
|
||||
# This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
|
||||
# Given two heap snapshots (A & B), this tool performs reachability analysis on new objects allocated in B
|
||||
|
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/python3
|
||||
# This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
|
||||
# Given a heap snapshot, this tool gathers basic statistics about the allocated objects
|
||||
|
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/python3
|
||||
# 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 split lvmexecute.cpp VM switch into separate functions for use as native code generation fallbacks
|
||||
@ -34,7 +34,7 @@ source = """// This file is part of the Luau programming language and is license
|
||||
function = ""
|
||||
signature = ""
|
||||
|
||||
includeInsts = ["LOP_NEWCLOSURE", "LOP_NAMECALL", "LOP_FORGPREP", "LOP_GETVARARGS", "LOP_DUPCLOSURE", "LOP_PREPVARARGS", "LOP_COVERAGE", "LOP_BREAK", "LOP_GETGLOBAL", "LOP_SETGLOBAL", "LOP_GETTABLEKS", "LOP_SETTABLEKS"]
|
||||
includeInsts = ["LOP_NEWCLOSURE", "LOP_NAMECALL", "LOP_FORGPREP", "LOP_GETVARARGS", "LOP_DUPCLOSURE", "LOP_PREPVARARGS", "LOP_BREAK", "LOP_GETGLOBAL", "LOP_SETGLOBAL", "LOP_GETTABLEKS", "LOP_SETTABLEKS"]
|
||||
|
||||
state = 0
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/python3
|
||||
# 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 generate power tables for Schubfach algorithm (see lnumprint.cpp)
|
||||
|
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/python3
|
||||
# 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
|
||||
|
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/python3
|
||||
# This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
|
||||
# Given a profile dump, this tool generates a flame graph based on the stacks listed in the profile
|
||||
|
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/python3
|
||||
# This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
|
||||
# Given a profile dump, this tool displays top functions based on the stacks listed in the profile
|
||||
|
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/python3
|
||||
# This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
|
||||
# The purpose of this script is to analyze disassembly generated by objdump or
|
||||
|
@ -1,3 +1,4 @@
|
||||
#!/usr/bin/python3
|
||||
# This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
|
||||
import argparse
|
||||
|
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/python3
|
||||
# This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
|
||||
# Given a trace event file, this tool generates a flame graph based on the event scopes present in the file
|
||||
|
Loading…
Reference in New Issue
Block a user