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:
Andy Friesen 2023-01-13 14:10:01 -08:00 committed by GitHub
parent 0af10417cd
commit a5c6a38b10
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
54 changed files with 2186 additions and 823 deletions

View File

@ -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)
{

View File

@ -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;

View File

@ -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;

View File

@ -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)))
return {{{"then", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword};
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)

View File

@ -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-
@ -277,20 +275,10 @@ struct StringifierState
private:
void emitIndentation()
{
if (!FFlag::LuauLineBreaksDetermineIndents)
{
if (!opts.DEPRECATED_indent)
return;
if (!opts.useLineBreaks)
return;
emit(std::string(indentation, ' '));
}
else
{
if (!opts.useLineBreaks)
return;
emit(std::string(indentation, ' '));
}
emit(std::string(indentation, ' '));
}
};
@ -781,11 +769,8 @@ struct TypeStringifier
if (results.size() > 1)
s = ")?";
if (FFlag::LuauSerializeNilUnionAsNil)
{
if (!hasNonNilDisjunct)
s = "nil";
}
if (!hasNonNilDisjunct)
s = "nil";
state.emit(s);
}

View File

@ -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};
}
}

View File

@ -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);

View File

@ -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);

View File

@ -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)
{

View File

@ -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;

View File

@ -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)

View File

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

View File

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

View File

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

View File

@ -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");

View File

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

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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);

View File

@ -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
)

View File

@ -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);

View File

@ -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;

View File

@ -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)

View File

@ -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);

View File

@ -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");
size_t length;
const char* string = luaL_tolstring(L, arg, &length);
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;

View File

@ -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);

View File

@ -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);
}

View File

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

View File

@ -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);

View File

@ -15,6 +15,7 @@
LUAU_FASTFLAG(LuauTraceTypesInNonstrictMode2)
LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel)
LUAU_FASTFLAG(LuauFixAutocompleteInIf)
using namespace Luau;
@ -789,14 +790,30 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_if_middle_keywords")
CHECK_EQ(ac2.entryMap.count("end"), 0);
CHECK_EQ(ac2.context, AutocompleteContext::Keyword);
check(R"(
if x t@1
)");
if (FFlag::LuauFixAutocompleteInIf)
{
check(R"(
if x t@1
)");
auto ac3 = autocomplete('1');
CHECK_EQ(1, ac3.entryMap.size());
CHECK_EQ(ac3.entryMap.count("then"), 1);
CHECK_EQ(ac3.context, AutocompleteContext::Keyword);
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
)");
auto ac3 = autocomplete('1');
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

View File

@ -297,6 +297,8 @@ TEST_CASE("Clear")
TEST_CASE("Strings")
{
ScopedFastFlag luauStringFormatAnyFix{"LuauStringFormatAnyFix", true};
runConformance("strings.lua");
}

View File

@ -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 '>'");

View File

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

View File

@ -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();

View File

@ -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)

View File

@ -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"

View File

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

View File

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

View File

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

View File

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

View File

@ -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)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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