v0.3.8+luau545
This commit is contained in:
parent
66218e74c3
commit
9bd5b63c3c
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "luau0-src"
|
name = "luau0-src"
|
||||||
version = "0.3.7+luau541"
|
version = "0.3.8+luau545"
|
||||||
authors = ["Aleksandr Orlenko <zxteam@protonmail.com>"]
|
authors = ["Aleksandr Orlenko <zxteam@protonmail.com>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
repository = "https://github.com/khvzak/luau-src-rs"
|
repository = "https://github.com/khvzak/luau-src-rs"
|
||||||
|
|
|
@ -134,6 +134,10 @@ public:
|
||||||
{
|
{
|
||||||
return visit((class AstExpr*)node);
|
return visit((class AstExpr*)node);
|
||||||
}
|
}
|
||||||
|
virtual bool visit(class AstExprInterpString* node)
|
||||||
|
{
|
||||||
|
return visit((class AstExpr*)node);
|
||||||
|
}
|
||||||
virtual bool visit(class AstExprError* node)
|
virtual bool visit(class AstExprError* node)
|
||||||
{
|
{
|
||||||
return visit((class AstExpr*)node);
|
return visit((class AstExpr*)node);
|
||||||
|
@ -594,9 +598,9 @@ public:
|
||||||
LUAU_RTTI(AstExprFunction)
|
LUAU_RTTI(AstExprFunction)
|
||||||
|
|
||||||
AstExprFunction(const Location& location, const AstArray<AstGenericType>& generics, const AstArray<AstGenericTypePack>& genericPacks,
|
AstExprFunction(const Location& location, const AstArray<AstGenericType>& generics, const AstArray<AstGenericTypePack>& genericPacks,
|
||||||
AstLocal* self, const AstArray<AstLocal*>& args, std::optional<Location> vararg, AstStatBlock* body, size_t functionDepth,
|
AstLocal* self, const AstArray<AstLocal*>& args, bool vararg, const Location& varargLocation, AstStatBlock* body, size_t functionDepth,
|
||||||
const AstName& debugname, std::optional<AstTypeList> returnAnnotation = {}, AstTypePack* varargAnnotation = nullptr, bool hasEnd = false,
|
const AstName& debugname, const std::optional<AstTypeList>& returnAnnotation = {}, AstTypePack* varargAnnotation = nullptr,
|
||||||
std::optional<Location> argLocation = std::nullopt);
|
bool hasEnd = false, const std::optional<Location>& argLocation = std::nullopt);
|
||||||
|
|
||||||
void visit(AstVisitor* visitor) override;
|
void visit(AstVisitor* visitor) override;
|
||||||
|
|
||||||
|
@ -732,6 +736,22 @@ public:
|
||||||
AstExpr* falseExpr;
|
AstExpr* falseExpr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class AstExprInterpString : public AstExpr
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LUAU_RTTI(AstExprInterpString)
|
||||||
|
|
||||||
|
AstExprInterpString(const Location& location, const AstArray<AstArray<char>>& strings, const AstArray<AstExpr*>& expressions);
|
||||||
|
|
||||||
|
void visit(AstVisitor* visitor) override;
|
||||||
|
|
||||||
|
/// An interpolated string such as `foo{bar}baz` is represented as
|
||||||
|
/// an array of strings for "foo" and "bar", and an array of expressions for "baz".
|
||||||
|
/// `strings` will always have one more element than `expressions`.
|
||||||
|
AstArray<AstArray<char>> strings;
|
||||||
|
AstArray<AstExpr*> expressions;
|
||||||
|
};
|
||||||
|
|
||||||
class AstStatBlock : public AstStat
|
class AstStatBlock : public AstStat
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -1256,6 +1276,16 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
AstName getIdentifier(AstExpr*);
|
AstName getIdentifier(AstExpr*);
|
||||||
|
Location getLocation(const AstTypeList& typeList);
|
||||||
|
|
||||||
|
template<typename T> // AstNode, AstExpr, AstLocal, etc
|
||||||
|
Location getLocation(AstArray<T*> array)
|
||||||
|
{
|
||||||
|
if (0 == array.size)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
return Location{array.data[0]->location.begin, array.data[array.size - 1]->location.end};
|
||||||
|
}
|
||||||
|
|
||||||
#undef LUAU_RTTI
|
#undef LUAU_RTTI
|
||||||
|
|
||||||
|
|
|
@ -61,6 +61,12 @@ struct Lexeme
|
||||||
SkinnyArrow,
|
SkinnyArrow,
|
||||||
DoubleColon,
|
DoubleColon,
|
||||||
|
|
||||||
|
InterpStringBegin,
|
||||||
|
InterpStringMid,
|
||||||
|
InterpStringEnd,
|
||||||
|
// An interpolated string with no expressions (like `x`)
|
||||||
|
InterpStringSimple,
|
||||||
|
|
||||||
AddAssign,
|
AddAssign,
|
||||||
SubAssign,
|
SubAssign,
|
||||||
MulAssign,
|
MulAssign,
|
||||||
|
@ -80,6 +86,8 @@ struct Lexeme
|
||||||
BrokenString,
|
BrokenString,
|
||||||
BrokenComment,
|
BrokenComment,
|
||||||
BrokenUnicode,
|
BrokenUnicode,
|
||||||
|
BrokenInterpDoubleBrace,
|
||||||
|
|
||||||
Error,
|
Error,
|
||||||
|
|
||||||
Reserved_BEGIN,
|
Reserved_BEGIN,
|
||||||
|
@ -208,6 +216,11 @@ private:
|
||||||
Lexeme readLongString(const Position& start, int sep, Lexeme::Type ok, Lexeme::Type broken);
|
Lexeme readLongString(const Position& start, int sep, Lexeme::Type ok, Lexeme::Type broken);
|
||||||
Lexeme readQuotedString();
|
Lexeme readQuotedString();
|
||||||
|
|
||||||
|
Lexeme readInterpolatedStringBegin();
|
||||||
|
Lexeme readInterpolatedStringSection(Position start, Lexeme::Type formatType, Lexeme::Type endType);
|
||||||
|
|
||||||
|
void readBackslashInString();
|
||||||
|
|
||||||
std::pair<AstName, Lexeme::Type> readName();
|
std::pair<AstName, Lexeme::Type> readName();
|
||||||
|
|
||||||
Lexeme readNumber(const Position& start, unsigned int startOffset);
|
Lexeme readNumber(const Position& start, unsigned int startOffset);
|
||||||
|
@ -231,6 +244,14 @@ private:
|
||||||
|
|
||||||
bool skipComments;
|
bool skipComments;
|
||||||
bool readNames;
|
bool readNames;
|
||||||
|
|
||||||
|
enum class BraceType
|
||||||
|
{
|
||||||
|
InterpolatedString,
|
||||||
|
Normal
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<BraceType> braceStack;
|
||||||
};
|
};
|
||||||
|
|
||||||
inline bool isSpace(char ch)
|
inline bool isSpace(char ch)
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
|
|
||||||
#include <initializer_list>
|
#include <initializer_list>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include <tuple>
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -109,8 +110,10 @@ private:
|
||||||
// for namelist in explist do block end |
|
// for namelist in explist do block end |
|
||||||
AstStat* parseFor();
|
AstStat* parseFor();
|
||||||
|
|
||||||
// function funcname funcbody |
|
|
||||||
// funcname ::= Name {`.' Name} [`:' Name]
|
// funcname ::= Name {`.' Name} [`:' Name]
|
||||||
|
AstExpr* parseFunctionName(Location start, bool& hasself, AstName& debugname);
|
||||||
|
|
||||||
|
// function funcname funcbody
|
||||||
AstStat* parseFunctionStat();
|
AstStat* parseFunctionStat();
|
||||||
|
|
||||||
// local function Name funcbody |
|
// local function Name funcbody |
|
||||||
|
@ -135,8 +138,10 @@ private:
|
||||||
// var [`+=' | `-=' | `*=' | `/=' | `%=' | `^=' | `..='] exp
|
// var [`+=' | `-=' | `*=' | `/=' | `%=' | `^=' | `..='] exp
|
||||||
AstStat* parseCompoundAssignment(AstExpr* initial, AstExprBinary::Op op);
|
AstStat* parseCompoundAssignment(AstExpr* initial, AstExprBinary::Op op);
|
||||||
|
|
||||||
// funcbody ::= `(' [parlist] `)' block end
|
std::pair<AstLocal*, AstArray<AstLocal*>> prepareFunctionArguments(const Location& start, bool hasself, const TempVector<Binding>& args);
|
||||||
// parlist ::= namelist [`,' `...'] | `...'
|
|
||||||
|
// funcbodyhead ::= `(' [namelist [`,' `...'] | `...'] `)' [`:` TypeAnnotation]
|
||||||
|
// funcbody ::= funcbodyhead block end
|
||||||
std::pair<AstExprFunction*, AstLocal*> parseFunctionBody(
|
std::pair<AstExprFunction*, AstLocal*> parseFunctionBody(
|
||||||
bool hasself, const Lexeme& matchFunction, const AstName& debugname, const Name* localName);
|
bool hasself, const Lexeme& matchFunction, const AstName& debugname, const Name* localName);
|
||||||
|
|
||||||
|
@ -148,7 +153,7 @@ private:
|
||||||
|
|
||||||
// bindinglist ::= (binding | `...') {`,' bindinglist}
|
// bindinglist ::= (binding | `...') {`,' bindinglist}
|
||||||
// Returns the location of the vararg ..., or std::nullopt if the function is not vararg.
|
// Returns the location of the vararg ..., or std::nullopt if the function is not vararg.
|
||||||
std::pair<std::optional<Location>, AstTypePack*> parseBindingList(TempVector<Binding>& result, bool allowDot3 = false);
|
std::tuple<bool, Location, AstTypePack*> parseBindingList(TempVector<Binding>& result, bool allowDot3 = false);
|
||||||
|
|
||||||
AstType* parseOptionalTypeAnnotation();
|
AstType* parseOptionalTypeAnnotation();
|
||||||
|
|
||||||
|
@ -228,6 +233,9 @@ private:
|
||||||
// TODO: Add grammar rules here?
|
// TODO: Add grammar rules here?
|
||||||
AstExpr* parseIfElseExpr();
|
AstExpr* parseIfElseExpr();
|
||||||
|
|
||||||
|
// stringinterp ::= <INTERP_BEGIN> exp {<INTERP_MID> exp} <INTERP_END>
|
||||||
|
AstExpr* parseInterpString();
|
||||||
|
|
||||||
// Name
|
// Name
|
||||||
std::optional<Name> parseNameOpt(const char* context = nullptr);
|
std::optional<Name> parseNameOpt(const char* context = nullptr);
|
||||||
Name parseName(const char* context = nullptr);
|
Name parseName(const char* context = nullptr);
|
||||||
|
@ -296,6 +304,12 @@ private:
|
||||||
AstExprError* reportExprError(const Location& location, const AstArray<AstExpr*>& expressions, const char* format, ...) LUAU_PRINTF_ATTR(4, 5);
|
AstExprError* reportExprError(const Location& location, const AstArray<AstExpr*>& expressions, const char* format, ...) LUAU_PRINTF_ATTR(4, 5);
|
||||||
AstTypeError* reportTypeAnnotationError(const Location& location, const AstArray<AstType*>& types, bool isMissing, const char* format, ...)
|
AstTypeError* reportTypeAnnotationError(const Location& location, const AstArray<AstType*>& types, bool isMissing, const char* format, ...)
|
||||||
LUAU_PRINTF_ATTR(5, 6);
|
LUAU_PRINTF_ATTR(5, 6);
|
||||||
|
// `parseErrorLocation` is associated with the parser error
|
||||||
|
// `astErrorLocation` is associated with the AstTypeError created
|
||||||
|
// It can be useful to have different error locations so that the parse error can include the next lexeme, while the AstTypeError can precisely
|
||||||
|
// define the location (possibly of zero size) where a type annotation is expected.
|
||||||
|
AstTypeError* reportMissingTypeAnnotationError(const Location& parseErrorLocation, const Location& astErrorLocation, const char* format, ...)
|
||||||
|
LUAU_PRINTF_ATTR(4, 5);
|
||||||
|
|
||||||
AstExpr* reportFunctionArgsError(AstExpr* func, bool self);
|
AstExpr* reportFunctionArgsError(AstExpr* func, bool self);
|
||||||
void reportAmbiguousCallError();
|
void reportAmbiguousCallError();
|
||||||
|
@ -379,6 +393,7 @@ private:
|
||||||
std::vector<unsigned int> matchRecoveryStopOnToken;
|
std::vector<unsigned int> matchRecoveryStopOnToken;
|
||||||
|
|
||||||
std::vector<AstStat*> scratchStat;
|
std::vector<AstStat*> scratchStat;
|
||||||
|
std::vector<AstArray<char>> scratchString;
|
||||||
std::vector<AstExpr*> scratchExpr;
|
std::vector<AstExpr*> scratchExpr;
|
||||||
std::vector<AstExpr*> scratchExprAux;
|
std::vector<AstExpr*> scratchExprAux;
|
||||||
std::vector<AstName> scratchName;
|
std::vector<AstName> scratchName;
|
||||||
|
|
|
@ -35,6 +35,6 @@ bool equalsLower(std::string_view lhs, std::string_view rhs);
|
||||||
|
|
||||||
size_t hashRange(const char* data, size_t size);
|
size_t hashRange(const char* data, size_t size);
|
||||||
|
|
||||||
std::string escape(std::string_view s);
|
std::string escape(std::string_view s, bool escapeForInterpString = false);
|
||||||
bool isIdentifier(std::string_view s);
|
bool isIdentifier(std::string_view s);
|
||||||
} // namespace Luau
|
} // namespace Luau
|
||||||
|
|
|
@ -160,17 +160,17 @@ void AstExprIndexExpr::visit(AstVisitor* visitor)
|
||||||
}
|
}
|
||||||
|
|
||||||
AstExprFunction::AstExprFunction(const Location& location, const AstArray<AstGenericType>& generics, const AstArray<AstGenericTypePack>& genericPacks,
|
AstExprFunction::AstExprFunction(const Location& location, const AstArray<AstGenericType>& generics, const AstArray<AstGenericTypePack>& genericPacks,
|
||||||
AstLocal* self, const AstArray<AstLocal*>& args, std::optional<Location> vararg, AstStatBlock* body, size_t functionDepth,
|
AstLocal* self, const AstArray<AstLocal*>& args, bool vararg, const Location& varargLocation, AstStatBlock* body, size_t functionDepth,
|
||||||
const AstName& debugname, std::optional<AstTypeList> returnAnnotation, AstTypePack* varargAnnotation, bool hasEnd,
|
const AstName& debugname, const std::optional<AstTypeList>& returnAnnotation, AstTypePack* varargAnnotation, bool hasEnd,
|
||||||
std::optional<Location> argLocation)
|
const std::optional<Location>& argLocation)
|
||||||
: AstExpr(ClassIndex(), location)
|
: AstExpr(ClassIndex(), location)
|
||||||
, generics(generics)
|
, generics(generics)
|
||||||
, genericPacks(genericPacks)
|
, genericPacks(genericPacks)
|
||||||
, self(self)
|
, self(self)
|
||||||
, args(args)
|
, args(args)
|
||||||
, returnAnnotation(returnAnnotation)
|
, returnAnnotation(returnAnnotation)
|
||||||
, vararg(vararg.has_value())
|
, vararg(vararg)
|
||||||
, varargLocation(vararg.value_or(Location()))
|
, varargLocation(varargLocation)
|
||||||
, varargAnnotation(varargAnnotation)
|
, varargAnnotation(varargAnnotation)
|
||||||
, body(body)
|
, body(body)
|
||||||
, functionDepth(functionDepth)
|
, functionDepth(functionDepth)
|
||||||
|
@ -349,6 +349,22 @@ AstExprError::AstExprError(const Location& location, const AstArray<AstExpr*>& e
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AstExprInterpString::AstExprInterpString(const Location& location, const AstArray<AstArray<char>>& strings, const AstArray<AstExpr*>& expressions)
|
||||||
|
: AstExpr(ClassIndex(), location)
|
||||||
|
, strings(strings)
|
||||||
|
, expressions(expressions)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AstExprInterpString::visit(AstVisitor* visitor)
|
||||||
|
{
|
||||||
|
if (visitor->visit(this))
|
||||||
|
{
|
||||||
|
for (AstExpr* expr : expressions)
|
||||||
|
expr->visit(visitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void AstExprError::visit(AstVisitor* visitor)
|
void AstExprError::visit(AstVisitor* visitor)
|
||||||
{
|
{
|
||||||
if (visitor->visit(this))
|
if (visitor->visit(this))
|
||||||
|
@ -936,4 +952,17 @@ AstName getIdentifier(AstExpr* node)
|
||||||
return AstName();
|
return AstName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Location getLocation(const AstTypeList& typeList)
|
||||||
|
{
|
||||||
|
Location result;
|
||||||
|
if (typeList.types.size)
|
||||||
|
{
|
||||||
|
result = Location{typeList.types.data[0]->location, typeList.types.data[typeList.types.size - 1]->location};
|
||||||
|
}
|
||||||
|
if (typeList.tailType)
|
||||||
|
result.end = typeList.tailType->location.end;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Luau
|
} // namespace Luau
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
|
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
|
|
||||||
|
LUAU_FASTFLAG(LuauInterpolatedStringBaseSupport)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -89,7 +91,8 @@ Lexeme::Lexeme(const Location& location, Type type, const char* data, size_t siz
|
||||||
, length(unsigned(size))
|
, length(unsigned(size))
|
||||||
, data(data)
|
, data(data)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(type == RawString || type == QuotedString || type == Number || type == Comment || type == BlockComment);
|
LUAU_ASSERT(type == RawString || type == QuotedString || type == InterpStringBegin || type == InterpStringMid || type == InterpStringEnd ||
|
||||||
|
type == InterpStringSimple || type == BrokenInterpDoubleBrace || type == Number || type == Comment || type == BlockComment);
|
||||||
}
|
}
|
||||||
|
|
||||||
Lexeme::Lexeme(const Location& location, Type type, const char* name)
|
Lexeme::Lexeme(const Location& location, Type type, const char* name)
|
||||||
|
@ -160,6 +163,18 @@ std::string Lexeme::toString() const
|
||||||
case QuotedString:
|
case QuotedString:
|
||||||
return data ? format("\"%.*s\"", length, data) : "string";
|
return data ? format("\"%.*s\"", length, data) : "string";
|
||||||
|
|
||||||
|
case InterpStringBegin:
|
||||||
|
return data ? format("`%.*s{", length, data) : "the beginning of an interpolated string";
|
||||||
|
|
||||||
|
case InterpStringMid:
|
||||||
|
return data ? format("}%.*s{", length, data) : "the middle of an interpolated string";
|
||||||
|
|
||||||
|
case InterpStringEnd:
|
||||||
|
return data ? format("}%.*s`", length, data) : "the end of an interpolated string";
|
||||||
|
|
||||||
|
case InterpStringSimple:
|
||||||
|
return data ? format("`%.*s`", length, data) : "interpolated string";
|
||||||
|
|
||||||
case Number:
|
case Number:
|
||||||
return data ? format("'%.*s'", length, data) : "number";
|
return data ? format("'%.*s'", length, data) : "number";
|
||||||
|
|
||||||
|
@ -175,6 +190,9 @@ std::string Lexeme::toString() const
|
||||||
case BrokenComment:
|
case BrokenComment:
|
||||||
return "unfinished comment";
|
return "unfinished comment";
|
||||||
|
|
||||||
|
case BrokenInterpDoubleBrace:
|
||||||
|
return "'{{', which is invalid (did you mean '\\{'?)";
|
||||||
|
|
||||||
case BrokenUnicode:
|
case BrokenUnicode:
|
||||||
if (codepoint)
|
if (codepoint)
|
||||||
{
|
{
|
||||||
|
@ -515,26 +533,9 @@ Lexeme Lexer::readLongString(const Position& start, int sep, Lexeme::Type ok, Le
|
||||||
return Lexeme(Location(start, position()), broken);
|
return Lexeme(Location(start, position()), broken);
|
||||||
}
|
}
|
||||||
|
|
||||||
Lexeme Lexer::readQuotedString()
|
void Lexer::readBackslashInString()
|
||||||
{
|
{
|
||||||
Position start = position();
|
LUAU_ASSERT(peekch() == '\\');
|
||||||
|
|
||||||
char delimiter = peekch();
|
|
||||||
LUAU_ASSERT(delimiter == '\'' || delimiter == '"');
|
|
||||||
consume();
|
|
||||||
|
|
||||||
unsigned int startOffset = offset;
|
|
||||||
|
|
||||||
while (peekch() != delimiter)
|
|
||||||
{
|
|
||||||
switch (peekch())
|
|
||||||
{
|
|
||||||
case 0:
|
|
||||||
case '\r':
|
|
||||||
case '\n':
|
|
||||||
return Lexeme(Location(start, position()), Lexeme::BrokenString);
|
|
||||||
|
|
||||||
case '\\':
|
|
||||||
consume();
|
consume();
|
||||||
switch (peekch())
|
switch (peekch())
|
||||||
{
|
{
|
||||||
|
@ -556,6 +557,29 @@ Lexeme Lexer::readQuotedString()
|
||||||
default:
|
default:
|
||||||
consume();
|
consume();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Lexeme Lexer::readQuotedString()
|
||||||
|
{
|
||||||
|
Position start = position();
|
||||||
|
|
||||||
|
char delimiter = peekch();
|
||||||
|
LUAU_ASSERT(delimiter == '\'' || delimiter == '"');
|
||||||
|
consume();
|
||||||
|
|
||||||
|
unsigned int startOffset = offset;
|
||||||
|
|
||||||
|
while (peekch() != delimiter)
|
||||||
|
{
|
||||||
|
switch (peekch())
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
case '\r':
|
||||||
|
case '\n':
|
||||||
|
return Lexeme(Location(start, position()), Lexeme::BrokenString);
|
||||||
|
|
||||||
|
case '\\':
|
||||||
|
readBackslashInString();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -568,6 +592,70 @@ Lexeme Lexer::readQuotedString()
|
||||||
return Lexeme(Location(start, position()), Lexeme::QuotedString, &buffer[startOffset], offset - startOffset - 1);
|
return Lexeme(Location(start, position()), Lexeme::QuotedString, &buffer[startOffset], offset - startOffset - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Lexeme Lexer::readInterpolatedStringBegin()
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(peekch() == '`');
|
||||||
|
|
||||||
|
Position start = position();
|
||||||
|
consume();
|
||||||
|
|
||||||
|
return readInterpolatedStringSection(start, Lexeme::InterpStringBegin, Lexeme::InterpStringSimple);
|
||||||
|
}
|
||||||
|
|
||||||
|
Lexeme Lexer::readInterpolatedStringSection(Position start, Lexeme::Type formatType, Lexeme::Type endType)
|
||||||
|
{
|
||||||
|
unsigned int startOffset = offset;
|
||||||
|
|
||||||
|
while (peekch() != '`')
|
||||||
|
{
|
||||||
|
switch (peekch())
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
case '\r':
|
||||||
|
case '\n':
|
||||||
|
return Lexeme(Location(start, position()), Lexeme::BrokenString);
|
||||||
|
|
||||||
|
case '\\':
|
||||||
|
// Allow for \u{}, which would otherwise be consumed by looking for {
|
||||||
|
if (peekch(1) == 'u' && peekch(2) == '{')
|
||||||
|
{
|
||||||
|
consume(); // backslash
|
||||||
|
consume(); // u
|
||||||
|
consume(); // {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
readBackslashInString();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '{':
|
||||||
|
{
|
||||||
|
braceStack.push_back(BraceType::InterpolatedString);
|
||||||
|
|
||||||
|
if (peekch(1) == '{')
|
||||||
|
{
|
||||||
|
Lexeme brokenDoubleBrace =
|
||||||
|
Lexeme(Location(start, position()), Lexeme::BrokenInterpDoubleBrace, &buffer[startOffset], offset - startOffset);
|
||||||
|
consume();
|
||||||
|
consume();
|
||||||
|
return brokenDoubleBrace;
|
||||||
|
}
|
||||||
|
|
||||||
|
Lexeme lexemeOutput(Location(start, position()), Lexeme::InterpStringBegin, &buffer[startOffset], offset - startOffset);
|
||||||
|
consume();
|
||||||
|
return lexemeOutput;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
consume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
consume();
|
||||||
|
|
||||||
|
return Lexeme(Location(start, position()), endType, &buffer[startOffset], offset - startOffset - 1);
|
||||||
|
}
|
||||||
|
|
||||||
Lexeme Lexer::readNumber(const Position& start, unsigned int startOffset)
|
Lexeme Lexer::readNumber(const Position& start, unsigned int startOffset)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(isDigit(peekch()));
|
LUAU_ASSERT(isDigit(peekch()));
|
||||||
|
@ -660,6 +748,36 @@ Lexeme Lexer::readNext()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case '{':
|
||||||
|
{
|
||||||
|
consume();
|
||||||
|
|
||||||
|
if (!braceStack.empty())
|
||||||
|
braceStack.push_back(BraceType::Normal);
|
||||||
|
|
||||||
|
return Lexeme(Location(start, 1), '{');
|
||||||
|
}
|
||||||
|
|
||||||
|
case '}':
|
||||||
|
{
|
||||||
|
consume();
|
||||||
|
|
||||||
|
if (braceStack.empty())
|
||||||
|
{
|
||||||
|
return Lexeme(Location(start, 1), '}');
|
||||||
|
}
|
||||||
|
|
||||||
|
const BraceType braceStackTop = braceStack.back();
|
||||||
|
braceStack.pop_back();
|
||||||
|
|
||||||
|
if (braceStackTop != BraceType::InterpolatedString)
|
||||||
|
{
|
||||||
|
return Lexeme(Location(start, 1), '}');
|
||||||
|
}
|
||||||
|
|
||||||
|
return readInterpolatedStringSection(position(), Lexeme::InterpStringMid, Lexeme::InterpStringEnd);
|
||||||
|
}
|
||||||
|
|
||||||
case '=':
|
case '=':
|
||||||
{
|
{
|
||||||
consume();
|
consume();
|
||||||
|
@ -716,6 +834,15 @@ Lexeme Lexer::readNext()
|
||||||
case '\'':
|
case '\'':
|
||||||
return readQuotedString();
|
return readQuotedString();
|
||||||
|
|
||||||
|
case '`':
|
||||||
|
if (FFlag::LuauInterpolatedStringBaseSupport)
|
||||||
|
return readInterpolatedStringBegin();
|
||||||
|
else
|
||||||
|
{
|
||||||
|
consume();
|
||||||
|
return Lexeme(Location(start, 1), '`');
|
||||||
|
}
|
||||||
|
|
||||||
case '.':
|
case '.':
|
||||||
consume();
|
consume();
|
||||||
|
|
||||||
|
@ -817,8 +944,6 @@ Lexeme Lexer::readNext()
|
||||||
|
|
||||||
case '(':
|
case '(':
|
||||||
case ')':
|
case ')':
|
||||||
case '{':
|
|
||||||
case '}':
|
|
||||||
case ']':
|
case ']':
|
||||||
case ';':
|
case ';':
|
||||||
case ',':
|
case ',':
|
||||||
|
|
|
@ -23,10 +23,15 @@ LUAU_FASTFLAGVARIABLE(LuauErrorDoubleHexPrefix, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauLintParseIntegerIssues, false)
|
LUAU_FASTFLAGVARIABLE(LuauLintParseIntegerIssues, false)
|
||||||
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuaReportParseIntegerIssues, false)
|
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuaReportParseIntegerIssues, false)
|
||||||
|
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauInterpolatedStringBaseSupport, false)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauTypeAnnotationLocationChange, false)
|
||||||
|
|
||||||
bool lua_telemetry_parsed_out_of_range_bin_integer = false;
|
bool lua_telemetry_parsed_out_of_range_bin_integer = false;
|
||||||
bool lua_telemetry_parsed_out_of_range_hex_integer = false;
|
bool lua_telemetry_parsed_out_of_range_hex_integer = false;
|
||||||
bool lua_telemetry_parsed_double_prefix_hex_integer = false;
|
bool lua_telemetry_parsed_double_prefix_hex_integer = false;
|
||||||
|
|
||||||
|
#define ERROR_INVALID_INTERP_DOUBLE_BRACE "Double braces are not permitted within interpolated strings. Did you mean '\\{'?"
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -601,16 +606,11 @@ AstStat* Parser::parseFor()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// function funcname funcbody |
|
|
||||||
// funcname ::= Name {`.' Name} [`:' Name]
|
// funcname ::= Name {`.' Name} [`:' Name]
|
||||||
AstStat* Parser::parseFunctionStat()
|
AstExpr* Parser::parseFunctionName(Location start, bool& hasself, AstName& debugname)
|
||||||
{
|
{
|
||||||
Location start = lexer.current().location;
|
if (lexer.current().type == Lexeme::Name)
|
||||||
|
debugname = AstName(lexer.current().name);
|
||||||
Lexeme matchFunction = lexer.current();
|
|
||||||
nextLexeme();
|
|
||||||
|
|
||||||
AstName debugname = (lexer.current().type == Lexeme::Name) ? AstName(lexer.current().name) : AstName();
|
|
||||||
|
|
||||||
// parse funcname into a chain of indexing operators
|
// parse funcname into a chain of indexing operators
|
||||||
AstExpr* expr = parseNameExpr("function name");
|
AstExpr* expr = parseNameExpr("function name");
|
||||||
|
@ -636,8 +636,6 @@ AstStat* Parser::parseFunctionStat()
|
||||||
recursionCounter = recursionCounterOld;
|
recursionCounter = recursionCounterOld;
|
||||||
|
|
||||||
// finish with :
|
// finish with :
|
||||||
bool hasself = false;
|
|
||||||
|
|
||||||
if (lexer.current().type == ':')
|
if (lexer.current().type == ':')
|
||||||
{
|
{
|
||||||
Position opPosition = lexer.current().location.begin;
|
Position opPosition = lexer.current().location.begin;
|
||||||
|
@ -653,6 +651,21 @@ AstStat* Parser::parseFunctionStat()
|
||||||
hasself = true;
|
hasself = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// function funcname funcbody
|
||||||
|
AstStat* Parser::parseFunctionStat()
|
||||||
|
{
|
||||||
|
Location start = lexer.current().location;
|
||||||
|
|
||||||
|
Lexeme matchFunction = lexer.current();
|
||||||
|
nextLexeme();
|
||||||
|
|
||||||
|
bool hasself = false;
|
||||||
|
AstName debugname;
|
||||||
|
AstExpr* expr = parseFunctionName(start, hasself, debugname);
|
||||||
|
|
||||||
matchRecoveryStopOnToken[Lexeme::ReservedEnd]++;
|
matchRecoveryStopOnToken[Lexeme::ReservedEnd]++;
|
||||||
|
|
||||||
AstExprFunction* body = parseFunctionBody(hasself, matchFunction, debugname, nullptr).first;
|
AstExprFunction* body = parseFunctionBody(hasself, matchFunction, debugname, nullptr).first;
|
||||||
|
@ -781,10 +794,11 @@ AstDeclaredClassProp Parser::parseDeclaredClassMethod()
|
||||||
|
|
||||||
TempVector<Binding> args(scratchBinding);
|
TempVector<Binding> args(scratchBinding);
|
||||||
|
|
||||||
std::optional<Location> vararg = std::nullopt;
|
bool vararg = false;
|
||||||
|
Location varargLocation;
|
||||||
AstTypePack* varargAnnotation = nullptr;
|
AstTypePack* varargAnnotation = nullptr;
|
||||||
if (lexer.current().type != ')')
|
if (lexer.current().type != ')')
|
||||||
std::tie(vararg, varargAnnotation) = parseBindingList(args, /* allowDot3 */ true);
|
std::tie(vararg, varargLocation, varargAnnotation) = parseBindingList(args, /* allowDot3 */ true);
|
||||||
|
|
||||||
expectMatchAndConsume(')', matchParen);
|
expectMatchAndConsume(')', matchParen);
|
||||||
|
|
||||||
|
@ -838,11 +852,12 @@ AstStat* Parser::parseDeclaration(const Location& start)
|
||||||
|
|
||||||
TempVector<Binding> args(scratchBinding);
|
TempVector<Binding> args(scratchBinding);
|
||||||
|
|
||||||
std::optional<Location> vararg;
|
bool vararg = false;
|
||||||
|
Location varargLocation;
|
||||||
AstTypePack* varargAnnotation = nullptr;
|
AstTypePack* varargAnnotation = nullptr;
|
||||||
|
|
||||||
if (lexer.current().type != ')')
|
if (lexer.current().type != ')')
|
||||||
std::tie(vararg, varargAnnotation) = parseBindingList(args, /* allowDot3= */ true);
|
std::tie(vararg, varargLocation, varargAnnotation) = parseBindingList(args, /* allowDot3= */ true);
|
||||||
|
|
||||||
expectMatchAndConsume(')', matchParen);
|
expectMatchAndConsume(')', matchParen);
|
||||||
|
|
||||||
|
@ -965,6 +980,21 @@ AstStat* Parser::parseCompoundAssignment(AstExpr* initial, AstExprBinary::Op op)
|
||||||
return allocator.alloc<AstStatCompoundAssign>(Location(initial->location, value->location), op, initial, value);
|
return allocator.alloc<AstStatCompoundAssign>(Location(initial->location, value->location), op, initial, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::pair<AstLocal*, AstArray<AstLocal*>> Parser::prepareFunctionArguments(const Location& start, bool hasself, const TempVector<Binding>& args)
|
||||||
|
{
|
||||||
|
AstLocal* self = nullptr;
|
||||||
|
|
||||||
|
if (hasself)
|
||||||
|
self = pushLocal(Binding(Name(nameSelf, start), nullptr));
|
||||||
|
|
||||||
|
TempVector<AstLocal*> vars(scratchLocal);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < args.size(); ++i)
|
||||||
|
vars.push_back(pushLocal(args[i]));
|
||||||
|
|
||||||
|
return {self, copy(vars)};
|
||||||
|
}
|
||||||
|
|
||||||
// funcbody ::= `(' [parlist] `)' [`:' ReturnType] block end
|
// funcbody ::= `(' [parlist] `)' [`:' ReturnType] block end
|
||||||
// parlist ::= bindinglist [`,' `...'] | `...'
|
// parlist ::= bindinglist [`,' `...'] | `...'
|
||||||
std::pair<AstExprFunction*, AstLocal*> Parser::parseFunctionBody(
|
std::pair<AstExprFunction*, AstLocal*> Parser::parseFunctionBody(
|
||||||
|
@ -979,15 +1009,18 @@ std::pair<AstExprFunction*, AstLocal*> Parser::parseFunctionBody(
|
||||||
|
|
||||||
TempVector<Binding> args(scratchBinding);
|
TempVector<Binding> args(scratchBinding);
|
||||||
|
|
||||||
std::optional<Location> vararg;
|
bool vararg = false;
|
||||||
|
Location varargLocation;
|
||||||
AstTypePack* varargAnnotation = nullptr;
|
AstTypePack* varargAnnotation = nullptr;
|
||||||
|
|
||||||
if (lexer.current().type != ')')
|
if (lexer.current().type != ')')
|
||||||
std::tie(vararg, varargAnnotation) = parseBindingList(args, /* allowDot3= */ true);
|
std::tie(vararg, varargLocation, varargAnnotation) = parseBindingList(args, /* allowDot3= */ true);
|
||||||
|
|
||||||
|
std::optional<Location> argLocation;
|
||||||
|
|
||||||
|
if (matchParen.type == Lexeme::Type('(') && lexer.current().type == Lexeme::Type(')'))
|
||||||
|
argLocation = Location(matchParen.position, lexer.current().location.end);
|
||||||
|
|
||||||
std::optional<Location> argLocation = matchParen.type == Lexeme::Type('(') && lexer.current().type == Lexeme::Type(')')
|
|
||||||
? std::make_optional(Location(matchParen.position, lexer.current().location.end))
|
|
||||||
: std::nullopt;
|
|
||||||
expectMatchAndConsume(')', matchParen, true);
|
expectMatchAndConsume(')', matchParen, true);
|
||||||
|
|
||||||
std::optional<AstTypeList> typelist = parseOptionalReturnTypeAnnotation();
|
std::optional<AstTypeList> typelist = parseOptionalReturnTypeAnnotation();
|
||||||
|
@ -1000,19 +1033,11 @@ std::pair<AstExprFunction*, AstLocal*> Parser::parseFunctionBody(
|
||||||
unsigned int localsBegin = saveLocals();
|
unsigned int localsBegin = saveLocals();
|
||||||
|
|
||||||
Function fun;
|
Function fun;
|
||||||
fun.vararg = vararg.has_value();
|
fun.vararg = vararg;
|
||||||
|
|
||||||
functionStack.push_back(fun);
|
functionStack.emplace_back(fun);
|
||||||
|
|
||||||
AstLocal* self = nullptr;
|
auto [self, vars] = prepareFunctionArguments(start, hasself, args);
|
||||||
|
|
||||||
if (hasself)
|
|
||||||
self = pushLocal(Binding(Name(nameSelf, start), nullptr));
|
|
||||||
|
|
||||||
TempVector<AstLocal*> vars(scratchLocal);
|
|
||||||
|
|
||||||
for (size_t i = 0; i < args.size(); ++i)
|
|
||||||
vars.push_back(pushLocal(args[i]));
|
|
||||||
|
|
||||||
AstStatBlock* body = parseBlock();
|
AstStatBlock* body = parseBlock();
|
||||||
|
|
||||||
|
@ -1024,8 +1049,8 @@ std::pair<AstExprFunction*, AstLocal*> Parser::parseFunctionBody(
|
||||||
|
|
||||||
bool hasEnd = expectMatchEndAndConsume(Lexeme::ReservedEnd, matchFunction);
|
bool hasEnd = expectMatchEndAndConsume(Lexeme::ReservedEnd, matchFunction);
|
||||||
|
|
||||||
return {allocator.alloc<AstExprFunction>(Location(start, end), generics, genericPacks, self, copy(vars), vararg, body, functionStack.size(),
|
return {allocator.alloc<AstExprFunction>(Location(start, end), generics, genericPacks, self, vars, vararg, varargLocation, body,
|
||||||
debugname, typelist, varargAnnotation, hasEnd, argLocation),
|
functionStack.size(), debugname, typelist, varargAnnotation, hasEnd, argLocation),
|
||||||
funLocal};
|
funLocal};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1056,7 +1081,7 @@ Parser::Binding Parser::parseBinding()
|
||||||
}
|
}
|
||||||
|
|
||||||
// bindinglist ::= (binding | `...') [`,' bindinglist]
|
// bindinglist ::= (binding | `...') [`,' bindinglist]
|
||||||
std::pair<std::optional<Location>, AstTypePack*> Parser::parseBindingList(TempVector<Binding>& result, bool allowDot3)
|
std::tuple<bool, Location, AstTypePack*> Parser::parseBindingList(TempVector<Binding>& result, bool allowDot3)
|
||||||
{
|
{
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
|
@ -1072,7 +1097,7 @@ std::pair<std::optional<Location>, AstTypePack*> Parser::parseBindingList(TempVe
|
||||||
tailAnnotation = parseVariadicArgumentAnnotation();
|
tailAnnotation = parseVariadicArgumentAnnotation();
|
||||||
}
|
}
|
||||||
|
|
||||||
return {varargLocation, tailAnnotation};
|
return {true, varargLocation, tailAnnotation};
|
||||||
}
|
}
|
||||||
|
|
||||||
result.push_back(parseBinding());
|
result.push_back(parseBinding());
|
||||||
|
@ -1082,7 +1107,7 @@ std::pair<std::optional<Location>, AstTypePack*> Parser::parseBindingList(TempVe
|
||||||
nextLexeme();
|
nextLexeme();
|
||||||
}
|
}
|
||||||
|
|
||||||
return {std::nullopt, nullptr};
|
return {false, Location(), nullptr};
|
||||||
}
|
}
|
||||||
|
|
||||||
AstType* Parser::parseOptionalTypeAnnotation()
|
AstType* Parser::parseOptionalTypeAnnotation()
|
||||||
|
@ -1540,38 +1565,43 @@ AstTypeOrPack Parser::parseSimpleTypeAnnotation(bool allowPack)
|
||||||
{
|
{
|
||||||
incrementRecursionCounter("type annotation");
|
incrementRecursionCounter("type annotation");
|
||||||
|
|
||||||
Location begin = lexer.current().location;
|
Location start = lexer.current().location;
|
||||||
|
|
||||||
if (lexer.current().type == Lexeme::ReservedNil)
|
if (lexer.current().type == Lexeme::ReservedNil)
|
||||||
{
|
{
|
||||||
nextLexeme();
|
nextLexeme();
|
||||||
return {allocator.alloc<AstTypeReference>(begin, std::nullopt, nameNil), {}};
|
return {allocator.alloc<AstTypeReference>(start, std::nullopt, nameNil), {}};
|
||||||
}
|
}
|
||||||
else if (lexer.current().type == Lexeme::ReservedTrue)
|
else if (lexer.current().type == Lexeme::ReservedTrue)
|
||||||
{
|
{
|
||||||
nextLexeme();
|
nextLexeme();
|
||||||
return {allocator.alloc<AstTypeSingletonBool>(begin, true)};
|
return {allocator.alloc<AstTypeSingletonBool>(start, true)};
|
||||||
}
|
}
|
||||||
else if (lexer.current().type == Lexeme::ReservedFalse)
|
else if (lexer.current().type == Lexeme::ReservedFalse)
|
||||||
{
|
{
|
||||||
nextLexeme();
|
nextLexeme();
|
||||||
return {allocator.alloc<AstTypeSingletonBool>(begin, false)};
|
return {allocator.alloc<AstTypeSingletonBool>(start, false)};
|
||||||
}
|
}
|
||||||
else if (lexer.current().type == Lexeme::RawString || lexer.current().type == Lexeme::QuotedString)
|
else if (lexer.current().type == Lexeme::RawString || lexer.current().type == Lexeme::QuotedString)
|
||||||
{
|
{
|
||||||
if (std::optional<AstArray<char>> value = parseCharArray())
|
if (std::optional<AstArray<char>> value = parseCharArray())
|
||||||
{
|
{
|
||||||
AstArray<char> svalue = *value;
|
AstArray<char> svalue = *value;
|
||||||
return {allocator.alloc<AstTypeSingletonString>(begin, svalue)};
|
return {allocator.alloc<AstTypeSingletonString>(start, svalue)};
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
return {reportTypeAnnotationError(begin, {}, /*isMissing*/ false, "String literal contains malformed escape sequence")};
|
return {reportTypeAnnotationError(start, {}, /*isMissing*/ false, "String literal contains malformed escape sequence")};
|
||||||
|
}
|
||||||
|
else if (lexer.current().type == Lexeme::InterpStringBegin || lexer.current().type == Lexeme::InterpStringSimple)
|
||||||
|
{
|
||||||
|
parseInterpString();
|
||||||
|
|
||||||
|
return {reportTypeAnnotationError(start, {}, /*isMissing*/ false, "Interpolated string literals cannot be used as types")};
|
||||||
}
|
}
|
||||||
else if (lexer.current().type == Lexeme::BrokenString)
|
else if (lexer.current().type == Lexeme::BrokenString)
|
||||||
{
|
{
|
||||||
Location location = lexer.current().location;
|
|
||||||
nextLexeme();
|
nextLexeme();
|
||||||
return {reportTypeAnnotationError(location, {}, /*isMissing*/ false, "Malformed string")};
|
return {reportTypeAnnotationError(start, {}, /*isMissing*/ false, "Malformed string")};
|
||||||
}
|
}
|
||||||
else if (lexer.current().type == Lexeme::Name)
|
else if (lexer.current().type == Lexeme::Name)
|
||||||
{
|
{
|
||||||
|
@ -1602,7 +1632,7 @@ AstTypeOrPack Parser::parseSimpleTypeAnnotation(bool allowPack)
|
||||||
|
|
||||||
expectMatchAndConsume(')', typeofBegin);
|
expectMatchAndConsume(')', typeofBegin);
|
||||||
|
|
||||||
return {allocator.alloc<AstTypeTypeof>(Location(begin, end), expr), {}};
|
return {allocator.alloc<AstTypeTypeof>(Location(start, end), expr), {}};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool hasParameters = false;
|
bool hasParameters = false;
|
||||||
|
@ -1616,7 +1646,7 @@ AstTypeOrPack Parser::parseSimpleTypeAnnotation(bool allowPack)
|
||||||
|
|
||||||
Location end = lexer.previousLocation();
|
Location end = lexer.previousLocation();
|
||||||
|
|
||||||
return {allocator.alloc<AstTypeReference>(Location(begin, end), prefix, name.name, hasParameters, parameters), {}};
|
return {allocator.alloc<AstTypeReference>(Location(start, end), prefix, name.name, hasParameters, parameters), {}};
|
||||||
}
|
}
|
||||||
else if (lexer.current().type == '{')
|
else if (lexer.current().type == '{')
|
||||||
{
|
{
|
||||||
|
@ -1628,16 +1658,27 @@ AstTypeOrPack Parser::parseSimpleTypeAnnotation(bool allowPack)
|
||||||
}
|
}
|
||||||
else if (lexer.current().type == Lexeme::ReservedFunction)
|
else if (lexer.current().type == Lexeme::ReservedFunction)
|
||||||
{
|
{
|
||||||
Location location = lexer.current().location;
|
|
||||||
|
|
||||||
nextLexeme();
|
nextLexeme();
|
||||||
|
|
||||||
return {reportTypeAnnotationError(location, {}, /*isMissing*/ false,
|
return {reportTypeAnnotationError(start, {}, /*isMissing*/ false,
|
||||||
"Using 'function' as a type annotation is not supported, consider replacing with a function type annotation e.g. '(...any) -> "
|
"Using 'function' as a type annotation is not supported, consider replacing with a function type annotation e.g. '(...any) -> "
|
||||||
"...any'"),
|
"...any'"),
|
||||||
{}};
|
{}};
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
if (FFlag::LuauTypeAnnotationLocationChange)
|
||||||
|
{
|
||||||
|
// For a missing type annotation, capture 'space' between last token and the next one
|
||||||
|
Location astErrorlocation(lexer.previousLocation().end, start.begin);
|
||||||
|
// The parse error includes the next lexeme to make it easier to display where the error is (e.g. in an IDE or a CLI error message).
|
||||||
|
// Including the current lexeme also makes the parse error consistent with other parse errors returned by Luau.
|
||||||
|
Location parseErrorLocation(lexer.previousLocation().end, start.end);
|
||||||
|
return {
|
||||||
|
reportMissingTypeAnnotationError(parseErrorLocation, astErrorlocation, "Expected type, got %s", lexer.current().toString().c_str()),
|
||||||
|
{}};
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
Location location = lexer.current().location;
|
Location location = lexer.current().location;
|
||||||
|
|
||||||
|
@ -1647,6 +1688,7 @@ AstTypeOrPack Parser::parseSimpleTypeAnnotation(bool allowPack)
|
||||||
return {reportTypeAnnotationError(location, {}, /*isMissing*/ true, "Expected type, got %s", lexer.current().toString().c_str()), {}};
|
return {reportTypeAnnotationError(location, {}, /*isMissing*/ true, "Expected type, got %s", lexer.current().toString().c_str()), {}};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
AstTypePack* Parser::parseVariadicArgumentAnnotation()
|
AstTypePack* Parser::parseVariadicArgumentAnnotation()
|
||||||
{
|
{
|
||||||
|
@ -2215,15 +2257,25 @@ AstExpr* Parser::parseSimpleExpr()
|
||||||
{
|
{
|
||||||
return parseNumber();
|
return parseNumber();
|
||||||
}
|
}
|
||||||
else if (lexer.current().type == Lexeme::RawString || lexer.current().type == Lexeme::QuotedString)
|
else if (lexer.current().type == Lexeme::RawString || lexer.current().type == Lexeme::QuotedString ||
|
||||||
|
(FFlag::LuauInterpolatedStringBaseSupport && lexer.current().type == Lexeme::InterpStringSimple))
|
||||||
{
|
{
|
||||||
return parseString();
|
return parseString();
|
||||||
}
|
}
|
||||||
|
else if (FFlag::LuauInterpolatedStringBaseSupport && lexer.current().type == Lexeme::InterpStringBegin)
|
||||||
|
{
|
||||||
|
return parseInterpString();
|
||||||
|
}
|
||||||
else if (lexer.current().type == Lexeme::BrokenString)
|
else if (lexer.current().type == Lexeme::BrokenString)
|
||||||
{
|
{
|
||||||
nextLexeme();
|
nextLexeme();
|
||||||
return reportExprError(start, {}, "Malformed string");
|
return reportExprError(start, {}, "Malformed string");
|
||||||
}
|
}
|
||||||
|
else if (lexer.current().type == Lexeme::BrokenInterpDoubleBrace)
|
||||||
|
{
|
||||||
|
nextLexeme();
|
||||||
|
return reportExprError(start, {}, ERROR_INVALID_INTERP_DOUBLE_BRACE);
|
||||||
|
}
|
||||||
else if (lexer.current().type == Lexeme::Dot3)
|
else if (lexer.current().type == Lexeme::Dot3)
|
||||||
{
|
{
|
||||||
if (functionStack.back().vararg)
|
if (functionStack.back().vararg)
|
||||||
|
@ -2614,11 +2666,12 @@ AstArray<AstTypeOrPack> Parser::parseTypeParams()
|
||||||
|
|
||||||
std::optional<AstArray<char>> Parser::parseCharArray()
|
std::optional<AstArray<char>> Parser::parseCharArray()
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(lexer.current().type == Lexeme::QuotedString || lexer.current().type == Lexeme::RawString);
|
LUAU_ASSERT(lexer.current().type == Lexeme::QuotedString || lexer.current().type == Lexeme::RawString ||
|
||||||
|
lexer.current().type == Lexeme::InterpStringSimple);
|
||||||
|
|
||||||
scratchData.assign(lexer.current().data, lexer.current().length);
|
scratchData.assign(lexer.current().data, lexer.current().length);
|
||||||
|
|
||||||
if (lexer.current().type == Lexeme::QuotedString)
|
if (lexer.current().type == Lexeme::QuotedString || lexer.current().type == Lexeme::InterpStringSimple)
|
||||||
{
|
{
|
||||||
if (!Lexer::fixupQuotedString(scratchData))
|
if (!Lexer::fixupQuotedString(scratchData))
|
||||||
{
|
{
|
||||||
|
@ -2645,6 +2698,67 @@ AstExpr* Parser::parseString()
|
||||||
return reportExprError(location, {}, "String literal contains malformed escape sequence");
|
return reportExprError(location, {}, "String literal contains malformed escape sequence");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AstExpr* Parser::parseInterpString()
|
||||||
|
{
|
||||||
|
TempVector<AstArray<char>> strings(scratchString);
|
||||||
|
TempVector<AstExpr*> expressions(scratchExpr);
|
||||||
|
|
||||||
|
Location startLocation = lexer.current().location;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
Lexeme currentLexeme = lexer.current();
|
||||||
|
LUAU_ASSERT(currentLexeme.type == Lexeme::InterpStringBegin || currentLexeme.type == Lexeme::InterpStringMid ||
|
||||||
|
currentLexeme.type == Lexeme::InterpStringEnd || currentLexeme.type == Lexeme::InterpStringSimple);
|
||||||
|
|
||||||
|
Location location = currentLexeme.location;
|
||||||
|
|
||||||
|
Location startOfBrace = Location(location.end, 1);
|
||||||
|
|
||||||
|
scratchData.assign(currentLexeme.data, currentLexeme.length);
|
||||||
|
|
||||||
|
if (!Lexer::fixupQuotedString(scratchData))
|
||||||
|
{
|
||||||
|
nextLexeme();
|
||||||
|
return reportExprError(startLocation, {}, "Interpolated string literal contains malformed escape sequence");
|
||||||
|
}
|
||||||
|
|
||||||
|
AstArray<char> chars = copy(scratchData);
|
||||||
|
|
||||||
|
nextLexeme();
|
||||||
|
|
||||||
|
strings.push_back(chars);
|
||||||
|
|
||||||
|
if (currentLexeme.type == Lexeme::InterpStringEnd || currentLexeme.type == Lexeme::InterpStringSimple)
|
||||||
|
{
|
||||||
|
AstArray<AstArray<char>> stringsArray = copy(strings);
|
||||||
|
AstArray<AstExpr*> expressionsArray = copy(expressions);
|
||||||
|
|
||||||
|
return allocator.alloc<AstExprInterpString>(startLocation, stringsArray, expressionsArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
AstExpr* expression = parseExpr();
|
||||||
|
|
||||||
|
expressions.push_back(expression);
|
||||||
|
|
||||||
|
switch (lexer.current().type)
|
||||||
|
{
|
||||||
|
case Lexeme::InterpStringBegin:
|
||||||
|
case Lexeme::InterpStringMid:
|
||||||
|
case Lexeme::InterpStringEnd:
|
||||||
|
break;
|
||||||
|
case Lexeme::BrokenInterpDoubleBrace:
|
||||||
|
nextLexeme();
|
||||||
|
return reportExprError(location, {}, ERROR_INVALID_INTERP_DOUBLE_BRACE);
|
||||||
|
case Lexeme::BrokenString:
|
||||||
|
nextLexeme();
|
||||||
|
return reportExprError(location, {}, "Malformed interpolated string, did you forget to add a '}'?");
|
||||||
|
default:
|
||||||
|
return reportExprError(location, {}, "Malformed interpolated string, got %s", lexer.current().toString().c_str());
|
||||||
|
}
|
||||||
|
} while (true);
|
||||||
|
}
|
||||||
|
|
||||||
AstExpr* Parser::parseNumber()
|
AstExpr* Parser::parseNumber()
|
||||||
{
|
{
|
||||||
Location start = lexer.current().location;
|
Location start = lexer.current().location;
|
||||||
|
@ -2870,8 +2984,7 @@ bool Parser::expectMatchEndAndConsume(Lexeme::Type type, const MatchLexeme& begi
|
||||||
{
|
{
|
||||||
// If the token matches on a different line and a different column, it suggests misleading indentation
|
// If the token matches on a different line and a different column, it suggests misleading indentation
|
||||||
// This can be used to pinpoint the problem location for a possible future *actual* mismatch
|
// This can be used to pinpoint the problem location for a possible future *actual* mismatch
|
||||||
if (lexer.current().location.begin.line != begin.position.line &&
|
if (lexer.current().location.begin.line != begin.position.line && lexer.current().location.begin.column != begin.position.column &&
|
||||||
lexer.current().location.begin.column != begin.position.column &&
|
|
||||||
endMismatchSuspect.position.line < begin.position.line) // Only replace the previous suspect with more recent suspects
|
endMismatchSuspect.position.line < begin.position.line) // Only replace the previous suspect with more recent suspects
|
||||||
{
|
{
|
||||||
endMismatchSuspect = begin;
|
endMismatchSuspect = begin;
|
||||||
|
@ -3005,6 +3118,13 @@ AstExprError* Parser::reportExprError(const Location& location, const AstArray<A
|
||||||
|
|
||||||
AstTypeError* Parser::reportTypeAnnotationError(const Location& location, const AstArray<AstType*>& types, bool isMissing, const char* format, ...)
|
AstTypeError* Parser::reportTypeAnnotationError(const Location& location, const AstArray<AstType*>& types, bool isMissing, const char* format, ...)
|
||||||
{
|
{
|
||||||
|
if (FFlag::LuauTypeAnnotationLocationChange)
|
||||||
|
{
|
||||||
|
// Missing type annotations should be using `reportMissingTypeAnnotationError` when LuauTypeAnnotationLocationChange is enabled
|
||||||
|
// Note: `isMissing` can be removed once FFlag::LuauTypeAnnotationLocationChange is removed since it will always be true.
|
||||||
|
LUAU_ASSERT(!isMissing);
|
||||||
|
}
|
||||||
|
|
||||||
va_list args;
|
va_list args;
|
||||||
va_start(args, format);
|
va_start(args, format);
|
||||||
report(location, format, args);
|
report(location, format, args);
|
||||||
|
@ -3013,6 +3133,18 @@ AstTypeError* Parser::reportTypeAnnotationError(const Location& location, const
|
||||||
return allocator.alloc<AstTypeError>(location, types, isMissing, unsigned(parseErrors.size() - 1));
|
return allocator.alloc<AstTypeError>(location, types, isMissing, unsigned(parseErrors.size() - 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AstTypeError* Parser::reportMissingTypeAnnotationError(const Location& parseErrorLocation, const Location& astErrorLocation, const char* format, ...)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(FFlag::LuauTypeAnnotationLocationChange);
|
||||||
|
|
||||||
|
va_list args;
|
||||||
|
va_start(args, format);
|
||||||
|
report(parseErrorLocation, format, args);
|
||||||
|
va_end(args);
|
||||||
|
|
||||||
|
return allocator.alloc<AstTypeError>(astErrorLocation, AstArray<AstType*>{}, true, unsigned(parseErrors.size() - 1));
|
||||||
|
}
|
||||||
|
|
||||||
void Parser::nextLexeme()
|
void Parser::nextLexeme()
|
||||||
{
|
{
|
||||||
Lexeme::Type type = lexer.next(/* skipComments= */ false, true).type;
|
Lexeme::Type type = lexer.next(/* skipComments= */ false, true).type;
|
||||||
|
|
|
@ -230,19 +230,25 @@ bool isIdentifier(std::string_view s)
|
||||||
return (s.find_first_not_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890_") == std::string::npos);
|
return (s.find_first_not_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890_") == std::string::npos);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string escape(std::string_view s)
|
std::string escape(std::string_view s, bool escapeForInterpString)
|
||||||
{
|
{
|
||||||
std::string r;
|
std::string r;
|
||||||
r.reserve(s.size() + 50); // arbitrary number to guess how many characters we'll be inserting
|
r.reserve(s.size() + 50); // arbitrary number to guess how many characters we'll be inserting
|
||||||
|
|
||||||
for (uint8_t c : s)
|
for (uint8_t c : s)
|
||||||
{
|
{
|
||||||
if (c >= ' ' && c != '\\' && c != '\'' && c != '\"')
|
if (c >= ' ' && c != '\\' && c != '\'' && c != '\"' && c != '`' && c != '{')
|
||||||
r += c;
|
r += c;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
r += '\\';
|
r += '\\';
|
||||||
|
|
||||||
|
if (escapeForInterpString && (c == '`' || c == '{'))
|
||||||
|
{
|
||||||
|
r += c;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
switch (c)
|
switch (c)
|
||||||
{
|
{
|
||||||
case '\a':
|
case '\a':
|
||||||
|
|
|
@ -289,17 +289,19 @@ enum LuauOpcode
|
||||||
// the first variable is then copied into index; generator/state are immutable, index isn't visible to user code
|
// the first variable is then copied into index; generator/state are immutable, index isn't visible to user code
|
||||||
LOP_FORGLOOP,
|
LOP_FORGLOOP,
|
||||||
|
|
||||||
// FORGPREP_INEXT/FORGLOOP_INEXT: FORGLOOP with 2 output variables (no AUX encoding), assuming generator is luaB_inext
|
// FORGPREP_INEXT: prepare FORGLOOP with 2 output variables (no AUX encoding), assuming generator is luaB_inext, and jump to FORGLOOP
|
||||||
// FORGPREP_INEXT prepares the index variable and jumps to FORGLOOP_INEXT
|
// A: target register (see FORGLOOP for register layout)
|
||||||
// FORGLOOP_INEXT has identical encoding and semantics to FORGLOOP (except for AUX encoding)
|
|
||||||
LOP_FORGPREP_INEXT,
|
LOP_FORGPREP_INEXT,
|
||||||
LOP_FORGLOOP_INEXT,
|
|
||||||
|
|
||||||
// FORGPREP_NEXT/FORGLOOP_NEXT: FORGLOOP with 2 output variables (no AUX encoding), assuming generator is luaB_next
|
// removed in v3
|
||||||
// FORGPREP_NEXT prepares the index variable and jumps to FORGLOOP_NEXT
|
LOP_DEP_FORGLOOP_INEXT,
|
||||||
// FORGLOOP_NEXT has identical encoding and semantics to FORGLOOP (except for AUX encoding)
|
|
||||||
|
// FORGPREP_NEXT: prepare FORGLOOP with 2 output variables (no AUX encoding), assuming generator is luaB_next, and jump to FORGLOOP
|
||||||
|
// A: target register (see FORGLOOP for register layout)
|
||||||
LOP_FORGPREP_NEXT,
|
LOP_FORGPREP_NEXT,
|
||||||
LOP_FORGLOOP_NEXT,
|
|
||||||
|
// removed in v3
|
||||||
|
LOP_DEP_FORGLOOP_NEXT,
|
||||||
|
|
||||||
// GETVARARGS: copy variables into the target register from vararg storage for current function
|
// GETVARARGS: copy variables into the target register from vararg storage for current function
|
||||||
// A: target register
|
// A: target register
|
||||||
|
@ -343,12 +345,9 @@ enum LuauOpcode
|
||||||
// B: source register (for VAL/REF) or upvalue index (for UPVAL/UPREF)
|
// B: source register (for VAL/REF) or upvalue index (for UPVAL/UPREF)
|
||||||
LOP_CAPTURE,
|
LOP_CAPTURE,
|
||||||
|
|
||||||
// JUMPIFEQK, JUMPIFNOTEQK: jumps to target offset if the comparison with constant is true (or false, for NOT variants)
|
// removed in v3
|
||||||
// A: source register 1
|
LOP_DEP_JUMPIFEQK,
|
||||||
// D: jump offset (-32768..32767; 0 means "next instruction" aka "don't jump")
|
LOP_DEP_JUMPIFNOTEQK,
|
||||||
// AUX: constant table index
|
|
||||||
LOP_JUMPIFEQK,
|
|
||||||
LOP_JUMPIFNOTEQK,
|
|
||||||
|
|
||||||
// FASTCALL1: perform a fast call of a built-in function using 1 register argument
|
// FASTCALL1: perform a fast call of a built-in function using 1 register argument
|
||||||
// A: builtin function id (see LuauBuiltinFunction)
|
// A: builtin function id (see LuauBuiltinFunction)
|
||||||
|
@ -412,9 +411,9 @@ enum LuauOpcode
|
||||||
enum LuauBytecodeTag
|
enum LuauBytecodeTag
|
||||||
{
|
{
|
||||||
// Bytecode version; runtime supports [MIN, MAX], compiler emits TARGET by default but may emit a higher version when flags are enabled
|
// Bytecode version; runtime supports [MIN, MAX], compiler emits TARGET by default but may emit a higher version when flags are enabled
|
||||||
LBC_VERSION_MIN = 2,
|
LBC_VERSION_MIN = 3,
|
||||||
LBC_VERSION_MAX = 3,
|
LBC_VERSION_MAX = 3,
|
||||||
LBC_VERSION_TARGET = 2,
|
LBC_VERSION_TARGET = 3,
|
||||||
// Types of constant table entries
|
// Types of constant table entries
|
||||||
LBC_CONSTANT_NIL = 0,
|
LBC_CONSTANT_NIL = 0,
|
||||||
LBC_CONSTANT_BOOLEAN,
|
LBC_CONSTANT_BOOLEAN,
|
||||||
|
|
|
@ -12,7 +12,9 @@ inline bool isFlagExperimental(const char* flag)
|
||||||
// or critical bugs that are found after the code has been submitted.
|
// or critical bugs that are found after the code has been submitted.
|
||||||
static const char* kList[] = {
|
static const char* kList[] = {
|
||||||
"LuauLowerBoundsCalculation",
|
"LuauLowerBoundsCalculation",
|
||||||
nullptr, // makes sure we always have at least one entry
|
"LuauInterpolatedStringBaseSupport",
|
||||||
|
// makes sure we always have at least one entry
|
||||||
|
nullptr,
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const char* item : kList)
|
for (const char* item : kList)
|
||||||
|
|
|
@ -6,8 +6,6 @@
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauCompileBytecodeV3, false)
|
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -75,8 +73,6 @@ static int getOpLength(LuauOpcode op)
|
||||||
case LOP_SETLIST:
|
case LOP_SETLIST:
|
||||||
case LOP_FORGLOOP:
|
case LOP_FORGLOOP:
|
||||||
case LOP_LOADKX:
|
case LOP_LOADKX:
|
||||||
case LOP_JUMPIFEQK:
|
|
||||||
case LOP_JUMPIFNOTEQK:
|
|
||||||
case LOP_FASTCALL2:
|
case LOP_FASTCALL2:
|
||||||
case LOP_FASTCALL2K:
|
case LOP_FASTCALL2K:
|
||||||
case LOP_JUMPXEQKNIL:
|
case LOP_JUMPXEQKNIL:
|
||||||
|
@ -108,12 +104,8 @@ inline bool isJumpD(LuauOpcode op)
|
||||||
case LOP_FORGPREP:
|
case LOP_FORGPREP:
|
||||||
case LOP_FORGLOOP:
|
case LOP_FORGLOOP:
|
||||||
case LOP_FORGPREP_INEXT:
|
case LOP_FORGPREP_INEXT:
|
||||||
case LOP_FORGLOOP_INEXT:
|
|
||||||
case LOP_FORGPREP_NEXT:
|
case LOP_FORGPREP_NEXT:
|
||||||
case LOP_FORGLOOP_NEXT:
|
|
||||||
case LOP_JUMPBACK:
|
case LOP_JUMPBACK:
|
||||||
case LOP_JUMPIFEQK:
|
|
||||||
case LOP_JUMPIFNOTEQK:
|
|
||||||
case LOP_JUMPXEQKNIL:
|
case LOP_JUMPXEQKNIL:
|
||||||
case LOP_JUMPXEQKB:
|
case LOP_JUMPXEQKB:
|
||||||
case LOP_JUMPXEQKN:
|
case LOP_JUMPXEQKN:
|
||||||
|
@ -1079,9 +1071,6 @@ std::string BytecodeBuilder::getError(const std::string& message)
|
||||||
|
|
||||||
uint8_t BytecodeBuilder::getVersion()
|
uint8_t BytecodeBuilder::getVersion()
|
||||||
{
|
{
|
||||||
if (FFlag::LuauCompileBytecodeV3)
|
|
||||||
return 3;
|
|
||||||
|
|
||||||
// This function usually returns LBC_VERSION_TARGET but may sometimes return a higher number (within LBC_VERSION_MIN/MAX) under fast flags
|
// This function usually returns LBC_VERSION_TARGET but may sometimes return a higher number (within LBC_VERSION_MIN/MAX) under fast flags
|
||||||
return LBC_VERSION_TARGET;
|
return LBC_VERSION_TARGET;
|
||||||
}
|
}
|
||||||
|
@ -1252,13 +1241,6 @@ void BytecodeBuilder::validate() const
|
||||||
VJUMP(LUAU_INSN_D(insn));
|
VJUMP(LUAU_INSN_D(insn));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case LOP_JUMPIFEQK:
|
|
||||||
case LOP_JUMPIFNOTEQK:
|
|
||||||
VREG(LUAU_INSN_A(insn));
|
|
||||||
VCONSTANY(insns[i + 1]);
|
|
||||||
VJUMP(LUAU_INSN_D(insn));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case LOP_JUMPXEQKNIL:
|
case LOP_JUMPXEQKNIL:
|
||||||
case LOP_JUMPXEQKB:
|
case LOP_JUMPXEQKB:
|
||||||
VREG(LUAU_INSN_A(insn));
|
VREG(LUAU_INSN_A(insn));
|
||||||
|
@ -1365,9 +1347,7 @@ void BytecodeBuilder::validate() const
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case LOP_FORGPREP_INEXT:
|
case LOP_FORGPREP_INEXT:
|
||||||
case LOP_FORGLOOP_INEXT:
|
|
||||||
case LOP_FORGPREP_NEXT:
|
case LOP_FORGPREP_NEXT:
|
||||||
case LOP_FORGLOOP_NEXT:
|
|
||||||
VREG(LUAU_INSN_A(insn) + 4); // forg loop protocol: A, A+1, A+2 are used for iteration protocol; A+3, A+4 are loop variables
|
VREG(LUAU_INSN_A(insn) + 4); // forg loop protocol: A, A+1, A+2 are used for iteration protocol; A+3, A+4 are loop variables
|
||||||
VJUMP(LUAU_INSN_D(insn));
|
VJUMP(LUAU_INSN_D(insn));
|
||||||
break;
|
break;
|
||||||
|
@ -1733,18 +1713,10 @@ void BytecodeBuilder::dumpInstruction(const uint32_t* code, std::string& result,
|
||||||
formatAppend(result, "FORGPREP_INEXT R%d L%d\n", LUAU_INSN_A(insn), targetLabel);
|
formatAppend(result, "FORGPREP_INEXT R%d L%d\n", LUAU_INSN_A(insn), targetLabel);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case LOP_FORGLOOP_INEXT:
|
|
||||||
formatAppend(result, "FORGLOOP_INEXT R%d L%d\n", LUAU_INSN_A(insn), targetLabel);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case LOP_FORGPREP_NEXT:
|
case LOP_FORGPREP_NEXT:
|
||||||
formatAppend(result, "FORGPREP_NEXT R%d L%d\n", LUAU_INSN_A(insn), targetLabel);
|
formatAppend(result, "FORGPREP_NEXT R%d L%d\n", LUAU_INSN_A(insn), targetLabel);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case LOP_FORGLOOP_NEXT:
|
|
||||||
formatAppend(result, "FORGLOOP_NEXT R%d L%d\n", LUAU_INSN_A(insn), targetLabel);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case LOP_GETVARARGS:
|
case LOP_GETVARARGS:
|
||||||
formatAppend(result, "GETVARARGS R%d %d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn) - 1);
|
formatAppend(result, "GETVARARGS R%d %d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn) - 1);
|
||||||
break;
|
break;
|
||||||
|
@ -1802,14 +1774,6 @@ void BytecodeBuilder::dumpInstruction(const uint32_t* code, std::string& result,
|
||||||
LUAU_INSN_A(insn) == LCT_UPVAL ? 'U' : 'R', LUAU_INSN_B(insn));
|
LUAU_INSN_A(insn) == LCT_UPVAL ? 'U' : 'R', LUAU_INSN_B(insn));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case LOP_JUMPIFEQK:
|
|
||||||
formatAppend(result, "JUMPIFEQK R%d K%d L%d\n", LUAU_INSN_A(insn), *code++, targetLabel);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case LOP_JUMPIFNOTEQK:
|
|
||||||
formatAppend(result, "JUMPIFNOTEQK R%d K%d L%d\n", LUAU_INSN_A(insn), *code++, targetLabel);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case LOP_JUMPXEQKNIL:
|
case LOP_JUMPXEQKNIL:
|
||||||
formatAppend(result, "JUMPXEQKNIL R%d L%d%s\n", LUAU_INSN_A(insn), targetLabel, *code >> 31 ? " NOT" : "");
|
formatAppend(result, "JUMPXEQKNIL R%d L%d%s\n", LUAU_INSN_A(insn), targetLabel, *code >> 31 ? " NOT" : "");
|
||||||
code++;
|
code++;
|
||||||
|
|
|
@ -14,6 +14,8 @@
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <bitset>
|
#include <bitset>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
||||||
LUAU_FASTINTVARIABLE(LuauCompileLoopUnrollThreshold, 25)
|
LUAU_FASTINTVARIABLE(LuauCompileLoopUnrollThreshold, 25)
|
||||||
|
@ -23,11 +25,7 @@ LUAU_FASTINTVARIABLE(LuauCompileInlineThreshold, 25)
|
||||||
LUAU_FASTINTVARIABLE(LuauCompileInlineThresholdMaxBoost, 300)
|
LUAU_FASTINTVARIABLE(LuauCompileInlineThresholdMaxBoost, 300)
|
||||||
LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5)
|
LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5)
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauCompileXEQ, false)
|
LUAU_FASTFLAG(LuauInterpolatedStringBaseSupport)
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauCompileOptimalAssignment, false)
|
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauCompileExtractK, false)
|
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -402,25 +400,19 @@ struct Compiler
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void compileExprFastcallN(AstExprCall* expr, uint8_t target, uint8_t targetCount, bool targetTop, bool multRet, uint8_t regs, int bfid, int bfK = -1)
|
void compileExprFastcallN(
|
||||||
|
AstExprCall* expr, uint8_t target, uint8_t targetCount, bool targetTop, bool multRet, uint8_t regs, int bfid, int bfK = -1)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(!expr->self);
|
LUAU_ASSERT(!expr->self);
|
||||||
LUAU_ASSERT(expr->args.size >= 1);
|
LUAU_ASSERT(expr->args.size >= 1);
|
||||||
LUAU_ASSERT(expr->args.size <= 2 || (bfid == LBF_BIT32_EXTRACTK && expr->args.size == 3));
|
LUAU_ASSERT(expr->args.size <= 2 || (bfid == LBF_BIT32_EXTRACTK && expr->args.size == 3));
|
||||||
LUAU_ASSERT(bfid == LBF_BIT32_EXTRACTK ? bfK >= 0 : bfK < 0);
|
LUAU_ASSERT(bfid == LBF_BIT32_EXTRACTK ? bfK >= 0 : bfK < 0);
|
||||||
|
|
||||||
LuauOpcode opc = expr->args.size == 1 ? LOP_FASTCALL1 : LOP_FASTCALL2;
|
LuauOpcode opc = expr->args.size == 1 ? LOP_FASTCALL1 : (bfK >= 0 || isConstant(expr->args.data[1])) ? LOP_FASTCALL2K : LOP_FASTCALL2;
|
||||||
|
|
||||||
if (FFlag::LuauCompileExtractK)
|
|
||||||
{
|
|
||||||
opc = expr->args.size == 1 ? LOP_FASTCALL1 : (bfK >= 0 || isConstant(expr->args.data[1])) ? LOP_FASTCALL2K : LOP_FASTCALL2;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t args[3] = {};
|
uint32_t args[3] = {};
|
||||||
|
|
||||||
for (size_t i = 0; i < expr->args.size; ++i)
|
for (size_t i = 0; i < expr->args.size; ++i)
|
||||||
{
|
|
||||||
if (FFlag::LuauCompileExtractK)
|
|
||||||
{
|
{
|
||||||
if (i > 0 && opc == LOP_FASTCALL2K)
|
if (i > 0 && opc == LOP_FASTCALL2K)
|
||||||
{
|
{
|
||||||
|
@ -429,20 +421,8 @@ struct Compiler
|
||||||
CompileError::raise(expr->location, "Exceeded constant limit; simplify the code to compile");
|
CompileError::raise(expr->location, "Exceeded constant limit; simplify the code to compile");
|
||||||
|
|
||||||
args[i] = cid;
|
args[i] = cid;
|
||||||
continue; // TODO: remove this and change if below to else if
|
|
||||||
}
|
}
|
||||||
}
|
else if (int reg = getExprLocalReg(expr->args.data[i]); reg >= 0)
|
||||||
else if (i > 0)
|
|
||||||
{
|
|
||||||
if (int32_t cid = getConstantIndex(expr->args.data[i]); cid >= 0)
|
|
||||||
{
|
|
||||||
opc = LOP_FASTCALL2K;
|
|
||||||
args[i] = cid;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (int reg = getExprLocalReg(expr->args.data[i]); reg >= 0)
|
|
||||||
{
|
{
|
||||||
args[i] = uint8_t(reg);
|
args[i] = uint8_t(reg);
|
||||||
}
|
}
|
||||||
|
@ -463,26 +443,12 @@ struct Compiler
|
||||||
// Note, as with other instructions that immediately follow FASTCALL, these are normally not executed and are used as a fallback for
|
// Note, as with other instructions that immediately follow FASTCALL, these are normally not executed and are used as a fallback for
|
||||||
// these FASTCALL variants.
|
// these FASTCALL variants.
|
||||||
for (size_t i = 0; i < expr->args.size; ++i)
|
for (size_t i = 0; i < expr->args.size; ++i)
|
||||||
{
|
|
||||||
if (FFlag::LuauCompileExtractK)
|
|
||||||
{
|
{
|
||||||
if (i > 0 && opc == LOP_FASTCALL2K)
|
if (i > 0 && opc == LOP_FASTCALL2K)
|
||||||
emitLoadK(uint8_t(regs + 1 + i), args[i]);
|
emitLoadK(uint8_t(regs + 1 + i), args[i]);
|
||||||
else if (args[i] != regs + 1 + i)
|
else if (args[i] != regs + 1 + i)
|
||||||
bytecode.emitABC(LOP_MOVE, uint8_t(regs + 1 + i), uint8_t(args[i]), 0);
|
bytecode.emitABC(LOP_MOVE, uint8_t(regs + 1 + i), uint8_t(args[i]), 0);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
if (i > 0 && opc == LOP_FASTCALL2K)
|
|
||||||
{
|
|
||||||
emitLoadK(uint8_t(regs + 1 + i), args[i]);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args[i] != regs + 1 + i)
|
|
||||||
bytecode.emitABC(LOP_MOVE, uint8_t(regs + 1 + i), uint8_t(args[i]), 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// note, these instructions are normally not executed and are used as a fallback for FASTCALL
|
// note, these instructions are normally not executed and are used as a fallback for FASTCALL
|
||||||
// we can't use TempTop variant here because we need to make sure the arguments we already computed aren't overwritten
|
// we can't use TempTop variant here because we need to make sure the arguments we already computed aren't overwritten
|
||||||
|
@ -754,7 +720,7 @@ struct Compiler
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optimization: for bit32.extract with constant in-range f/w we compile using FASTCALL2K and a special builtin
|
// Optimization: for bit32.extract with constant in-range f/w we compile using FASTCALL2K and a special builtin
|
||||||
if (FFlag::LuauCompileExtractK && bfid == LBF_BIT32_EXTRACT && expr->args.size == 3 && isConstant(expr->args.data[1]) && isConstant(expr->args.data[2]))
|
if (bfid == LBF_BIT32_EXTRACT && expr->args.size == 3 && isConstant(expr->args.data[1]) && isConstant(expr->args.data[2]))
|
||||||
{
|
{
|
||||||
Constant fc = getConstant(expr->args.data[1]);
|
Constant fc = getConstant(expr->args.data[1]);
|
||||||
Constant wc = getConstant(expr->args.data[2]);
|
Constant wc = getConstant(expr->args.data[2]);
|
||||||
|
@ -1076,8 +1042,6 @@ struct Compiler
|
||||||
std::swap(left, right);
|
std::swap(left, right);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FFlag::LuauCompileXEQ)
|
|
||||||
{
|
|
||||||
uint8_t rl = compileExprAuto(left, rs);
|
uint8_t rl = compileExprAuto(left, rs);
|
||||||
|
|
||||||
if (isEq && operandIsConstant)
|
if (isEq && operandIsConstant)
|
||||||
|
@ -1147,42 +1111,6 @@ struct Compiler
|
||||||
return jumpLabel;
|
return jumpLabel;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
LuauOpcode opc = getJumpOpCompare(expr->op, not_);
|
|
||||||
|
|
||||||
uint8_t rl = compileExprAuto(left, rs);
|
|
||||||
int32_t rr = -1;
|
|
||||||
|
|
||||||
if (isEq && operandIsConstant)
|
|
||||||
{
|
|
||||||
if (opc == LOP_JUMPIFEQ)
|
|
||||||
opc = LOP_JUMPIFEQK;
|
|
||||||
else if (opc == LOP_JUMPIFNOTEQ)
|
|
||||||
opc = LOP_JUMPIFNOTEQK;
|
|
||||||
|
|
||||||
rr = getConstantIndex(right);
|
|
||||||
LUAU_ASSERT(rr >= 0);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
rr = compileExprAuto(right, rs);
|
|
||||||
|
|
||||||
size_t jumpLabel = bytecode.emitLabel();
|
|
||||||
|
|
||||||
if (expr->op == AstExprBinary::CompareGt || expr->op == AstExprBinary::CompareGe)
|
|
||||||
{
|
|
||||||
bytecode.emitAD(opc, uint8_t(rr), 0);
|
|
||||||
bytecode.emitAux(rl);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
bytecode.emitAD(opc, rl, 0);
|
|
||||||
bytecode.emitAux(rr);
|
|
||||||
}
|
|
||||||
|
|
||||||
return jumpLabel;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t getConstantNumber(AstExpr* node)
|
int32_t getConstantNumber(AstExpr* node)
|
||||||
{
|
{
|
||||||
|
@ -1585,6 +1513,76 @@ struct Compiler
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void compileExprInterpString(AstExprInterpString* expr, uint8_t target, bool targetTemp)
|
||||||
|
{
|
||||||
|
size_t formatCapacity = 0;
|
||||||
|
for (AstArray<char> string : expr->strings)
|
||||||
|
{
|
||||||
|
formatCapacity += string.size + std::count(string.data, string.data + string.size, '%');
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string formatString;
|
||||||
|
formatString.reserve(formatCapacity);
|
||||||
|
|
||||||
|
size_t stringsLeft = expr->strings.size;
|
||||||
|
|
||||||
|
for (AstArray<char> string : expr->strings)
|
||||||
|
{
|
||||||
|
if (memchr(string.data, '%', string.size))
|
||||||
|
{
|
||||||
|
for (size_t characterIndex = 0; characterIndex < string.size; ++characterIndex)
|
||||||
|
{
|
||||||
|
char character = string.data[characterIndex];
|
||||||
|
formatString.push_back(character);
|
||||||
|
|
||||||
|
if (character == '%')
|
||||||
|
formatString.push_back('%');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
formatString.append(string.data, string.size);
|
||||||
|
|
||||||
|
stringsLeft--;
|
||||||
|
|
||||||
|
if (stringsLeft > 0)
|
||||||
|
formatString += "%*";
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t formatStringSize = formatString.size();
|
||||||
|
|
||||||
|
// We can't use formatStringRef.data() directly, because short strings don't have their data
|
||||||
|
// pinned in memory, so when interpFormatStrings grows, these pointers will move and become invalid.
|
||||||
|
std::unique_ptr<char[]> formatStringPtr(new char[formatStringSize]);
|
||||||
|
memcpy(formatStringPtr.get(), formatString.data(), formatStringSize);
|
||||||
|
|
||||||
|
AstArray<char> formatStringArray{formatStringPtr.get(), formatStringSize};
|
||||||
|
interpStrings.emplace_back(std::move(formatStringPtr)); // invalidates formatStringPtr, but keeps formatStringArray intact
|
||||||
|
|
||||||
|
int32_t formatStringIndex = bytecode.addConstantString(sref(formatStringArray));
|
||||||
|
if (formatStringIndex < 0)
|
||||||
|
CompileError::raise(expr->location, "Exceeded constant limit; simplify the code to compile");
|
||||||
|
|
||||||
|
RegScope rs(this);
|
||||||
|
|
||||||
|
uint8_t baseReg = allocReg(expr, uint8_t(2 + expr->expressions.size));
|
||||||
|
|
||||||
|
emitLoadK(baseReg, formatStringIndex);
|
||||||
|
|
||||||
|
for (size_t index = 0; index < expr->expressions.size; ++index)
|
||||||
|
compileExprTempTop(expr->expressions.data[index], uint8_t(baseReg + 2 + index));
|
||||||
|
|
||||||
|
BytecodeBuilder::StringRef formatMethod = sref(AstName("format"));
|
||||||
|
|
||||||
|
int32_t formatMethodIndex = bytecode.addConstantString(formatMethod);
|
||||||
|
if (formatMethodIndex < 0)
|
||||||
|
CompileError::raise(expr->location, "Exceeded constant limit; simplify the code to compile");
|
||||||
|
|
||||||
|
bytecode.emitABC(LOP_NAMECALL, baseReg, baseReg, uint8_t(BytecodeBuilder::getStringHash(formatMethod)));
|
||||||
|
bytecode.emitAux(formatMethodIndex);
|
||||||
|
bytecode.emitABC(LOP_CALL, baseReg, uint8_t(expr->expressions.size + 2), 2);
|
||||||
|
bytecode.emitABC(LOP_MOVE, target, baseReg, 0);
|
||||||
|
}
|
||||||
|
|
||||||
static uint8_t encodeHashSize(unsigned int hashSize)
|
static uint8_t encodeHashSize(unsigned int hashSize)
|
||||||
{
|
{
|
||||||
size_t hashSizeLog2 = 0;
|
size_t hashSizeLog2 = 0;
|
||||||
|
@ -2059,6 +2057,10 @@ struct Compiler
|
||||||
{
|
{
|
||||||
compileExprIfElse(expr, target, targetTemp);
|
compileExprIfElse(expr, target, targetTemp);
|
||||||
}
|
}
|
||||||
|
else if (AstExprInterpString* interpString = node->as<AstExprInterpString>(); FFlag::LuauInterpolatedStringBaseSupport && interpString)
|
||||||
|
{
|
||||||
|
compileExprInterpString(interpString, target, targetTemp);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(!"Unknown expression type");
|
LUAU_ASSERT(!"Unknown expression type");
|
||||||
|
@ -2901,62 +2903,6 @@ struct Compiler
|
||||||
loops.pop_back();
|
loops.pop_back();
|
||||||
}
|
}
|
||||||
|
|
||||||
void resolveAssignConflicts(AstStat* stat, std::vector<LValue>& vars)
|
|
||||||
{
|
|
||||||
LUAU_ASSERT(!FFlag::LuauCompileOptimalAssignment);
|
|
||||||
|
|
||||||
// regsUsed[i] is true if we have assigned the register during earlier assignments
|
|
||||||
// regsRemap[i] is set to the register where the original (pre-assignment) copy was made
|
|
||||||
// note: regsRemap is uninitialized intentionally to speed small assignments up; regsRemap[i] is valid iff regsUsed[i]
|
|
||||||
std::bitset<256> regsUsed;
|
|
||||||
uint8_t regsRemap[256];
|
|
||||||
|
|
||||||
for (size_t i = 0; i < vars.size(); ++i)
|
|
||||||
{
|
|
||||||
LValue& li = vars[i];
|
|
||||||
|
|
||||||
if (li.kind == LValue::Kind_Local)
|
|
||||||
{
|
|
||||||
if (!regsUsed[li.reg])
|
|
||||||
{
|
|
||||||
regsUsed[li.reg] = true;
|
|
||||||
regsRemap[li.reg] = li.reg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (li.kind == LValue::Kind_IndexName || li.kind == LValue::Kind_IndexNumber || li.kind == LValue::Kind_IndexExpr)
|
|
||||||
{
|
|
||||||
// we're looking for assignments before this one that invalidate any of the registers involved
|
|
||||||
if (regsUsed[li.reg])
|
|
||||||
{
|
|
||||||
// the register may have been evacuated previously, but if it wasn't - move it now
|
|
||||||
if (regsRemap[li.reg] == li.reg)
|
|
||||||
{
|
|
||||||
uint8_t reg = allocReg(stat, 1);
|
|
||||||
bytecode.emitABC(LOP_MOVE, reg, li.reg, 0);
|
|
||||||
|
|
||||||
regsRemap[li.reg] = reg;
|
|
||||||
}
|
|
||||||
|
|
||||||
li.reg = regsRemap[li.reg];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (li.kind == LValue::Kind_IndexExpr && regsUsed[li.index])
|
|
||||||
{
|
|
||||||
// the register may have been evacuated previously, but if it wasn't - move it now
|
|
||||||
if (regsRemap[li.index] == li.index)
|
|
||||||
{
|
|
||||||
uint8_t reg = allocReg(stat, 1);
|
|
||||||
bytecode.emitABC(LOP_MOVE, reg, li.index, 0);
|
|
||||||
|
|
||||||
regsRemap[li.index] = reg;
|
|
||||||
}
|
|
||||||
|
|
||||||
li.index = regsRemap[li.index];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Assignment
|
struct Assignment
|
||||||
{
|
{
|
||||||
LValue lvalue;
|
LValue lvalue;
|
||||||
|
@ -2965,6 +2911,18 @@ struct Compiler
|
||||||
uint8_t valueReg = kInvalidReg;
|
uint8_t valueReg = kInvalidReg;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// This function analyzes assignments and marks assignment conflicts: cases when a variable is assigned on lhs
|
||||||
|
// but subsequently used on the rhs, assuming assignments are performed in order. Note that it's also possible
|
||||||
|
// for a variable to conflict on the lhs, if it's used in an lvalue expression after it's assigned.
|
||||||
|
// When conflicts are found, Assignment::conflictReg is allocated and that's where assignment is performed instead,
|
||||||
|
// until the final fixup in compileStatAssign. Assignment::valueReg is allocated by compileStatAssign as well.
|
||||||
|
//
|
||||||
|
// Per Lua manual, section 3.3.3 (Assignments), the proper assignment order is only guaranteed to hold for syntactic access:
|
||||||
|
//
|
||||||
|
// Note that this guarantee covers only accesses syntactically inside the assignment statement. If a function or a metamethod called
|
||||||
|
// during the assignment changes the value of a variable, Lua gives no guarantees about the order of that access.
|
||||||
|
//
|
||||||
|
// As such, we currently don't check if an assigned local is captured, which may mean it gets reassigned during a function call.
|
||||||
void resolveAssignConflicts(AstStat* stat, std::vector<Assignment>& vars, const AstArray<AstExpr*>& values)
|
void resolveAssignConflicts(AstStat* stat, std::vector<Assignment>& vars, const AstArray<AstExpr*>& values)
|
||||||
{
|
{
|
||||||
struct Visitor : AstVisitor
|
struct Visitor : AstVisitor
|
||||||
|
@ -3056,8 +3014,6 @@ struct Compiler
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FFlag::LuauCompileOptimalAssignment)
|
|
||||||
{
|
|
||||||
// compute all l-values: note that this doesn't assign anything yet but it allocates registers and computes complex expressions on the
|
// compute all l-values: note that this doesn't assign anything yet but it allocates registers and computes complex expressions on the
|
||||||
// left hand side - for example, in "a[expr] = foo" expr will get evaluated here
|
// left hand side - for example, in "a[expr] = foo" expr will get evaluated here
|
||||||
std::vector<Assignment> vars(stat->vars.size);
|
std::vector<Assignment> vars(stat->vars.size);
|
||||||
|
@ -3135,33 +3091,6 @@ struct Compiler
|
||||||
bytecode.emitABC(LOP_MOVE, var.lvalue.reg, var.valueReg, 0);
|
bytecode.emitABC(LOP_MOVE, var.lvalue.reg, var.valueReg, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
// compute all l-values: note that this doesn't assign anything yet but it allocates registers and computes complex expressions on the
|
|
||||||
// left hand side for example, in "a[expr] = foo" expr will get evaluated here
|
|
||||||
std::vector<LValue> vars(stat->vars.size);
|
|
||||||
|
|
||||||
for (size_t i = 0; i < stat->vars.size; ++i)
|
|
||||||
vars[i] = compileLValue(stat->vars.data[i], rs);
|
|
||||||
|
|
||||||
// perform conflict resolution: if any lvalue refers to a local reg that will be reassigned before that, we save the local variable in a
|
|
||||||
// temporary reg
|
|
||||||
resolveAssignConflicts(stat, vars);
|
|
||||||
|
|
||||||
// compute values into temporaries
|
|
||||||
uint8_t regs = allocReg(stat, unsigned(stat->vars.size));
|
|
||||||
|
|
||||||
compileExprListTemp(stat->values, regs, uint8_t(stat->vars.size), /* targetTop= */ true);
|
|
||||||
|
|
||||||
// assign variables that have associated values; note that if we have fewer values than variables, we'll assign nil because
|
|
||||||
// compileExprListTemp will generate nils
|
|
||||||
for (size_t i = 0; i < stat->vars.size; ++i)
|
|
||||||
{
|
|
||||||
setDebugLine(stat->vars.data[i]);
|
|
||||||
compileAssign(vars[i], uint8_t(regs + i));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void compileStatCompoundAssign(AstStatCompoundAssign* stat)
|
void compileStatCompoundAssign(AstStatCompoundAssign* stat)
|
||||||
{
|
{
|
||||||
|
@ -3528,14 +3457,6 @@ struct Compiler
|
||||||
return uint8_t(top);
|
return uint8_t(top);
|
||||||
}
|
}
|
||||||
|
|
||||||
void reserveReg(AstNode* node, unsigned int count)
|
|
||||||
{
|
|
||||||
if (regTop + count > kMaxRegisterCount)
|
|
||||||
CompileError::raise(node->location, "Out of registers when trying to allocate %d registers: exceeded limit %d", count, kMaxRegisterCount);
|
|
||||||
|
|
||||||
stackSize = std::max(stackSize, regTop + count);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setDebugLine(AstNode* node)
|
void setDebugLine(AstNode* node)
|
||||||
{
|
{
|
||||||
if (options.debugLevel >= 1)
|
if (options.debugLevel >= 1)
|
||||||
|
@ -3808,6 +3729,7 @@ struct Compiler
|
||||||
std::vector<Loop> loops;
|
std::vector<Loop> loops;
|
||||||
std::vector<InlineFrame> inlineFrames;
|
std::vector<InlineFrame> inlineFrames;
|
||||||
std::vector<Capture> captures;
|
std::vector<Capture> captures;
|
||||||
|
std::vector<std::unique_ptr<char[]>> interpStrings;
|
||||||
};
|
};
|
||||||
|
|
||||||
void compileOrThrow(BytecodeBuilder& bytecode, const ParseResult& parseResult, const AstNameTable& names, const CompileOptions& inputOptions)
|
void compileOrThrow(BytecodeBuilder& bytecode, const ParseResult& parseResult, const AstNameTable& names, const CompileOptions& inputOptions)
|
||||||
|
@ -3866,7 +3788,8 @@ void compileOrThrow(BytecodeBuilder& bytecode, const ParseResult& parseResult, c
|
||||||
compiler.compileFunction(expr);
|
compiler.compileFunction(expr);
|
||||||
|
|
||||||
AstExprFunction main(root->location, /*generics= */ AstArray<AstGenericType>(), /*genericPacks= */ AstArray<AstGenericTypePack>(),
|
AstExprFunction main(root->location, /*generics= */ AstArray<AstGenericType>(), /*genericPacks= */ AstArray<AstGenericTypePack>(),
|
||||||
/* self= */ nullptr, AstArray<AstLocal*>(), /* vararg= */ Luau::Location(), root, /* functionDepth= */ 0, /* debugname= */ AstName());
|
/* self= */ nullptr, AstArray<AstLocal*>(), /* vararg= */ true, /* varargLocation= */ Luau::Location(), root, /* functionDepth= */ 0,
|
||||||
|
/* debugname= */ AstName());
|
||||||
uint32_t mainid = compiler.compileFunction(&main);
|
uint32_t mainid = compiler.compileFunction(&main);
|
||||||
|
|
||||||
const Compiler::Function* mainf = compiler.functions.find(&main);
|
const Compiler::Function* mainf = compiler.functions.find(&main);
|
||||||
|
|
|
@ -349,6 +349,11 @@ struct ConstantVisitor : AstVisitor
|
||||||
if (cond.type != Constant::Type_Unknown)
|
if (cond.type != Constant::Type_Unknown)
|
||||||
result = cond.isTruthful() ? trueExpr : falseExpr;
|
result = cond.isTruthful() ? trueExpr : falseExpr;
|
||||||
}
|
}
|
||||||
|
else if (AstExprInterpString* expr = node->as<AstExprInterpString>())
|
||||||
|
{
|
||||||
|
for (AstExpr* expression : expr->expressions)
|
||||||
|
analyze(expression);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(!"Unknown expression type");
|
LUAU_ASSERT(!"Unknown expression type");
|
||||||
|
|
|
@ -215,6 +215,16 @@ struct CostVisitor : AstVisitor
|
||||||
{
|
{
|
||||||
return model(expr->condition) + model(expr->trueExpr) + model(expr->falseExpr) + 2;
|
return model(expr->condition) + model(expr->trueExpr) + model(expr->falseExpr) + 2;
|
||||||
}
|
}
|
||||||
|
else if (AstExprInterpString* expr = node->as<AstExprInterpString>())
|
||||||
|
{
|
||||||
|
// Baseline cost of string.format
|
||||||
|
Cost cost = 3;
|
||||||
|
|
||||||
|
for (AstExpr* innerExpression : expr->expressions)
|
||||||
|
cost += model(innerExpression);
|
||||||
|
|
||||||
|
return cost;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(!"Unknown expression type");
|
LUAU_ASSERT(!"Unknown expression type");
|
||||||
|
|
|
@ -35,6 +35,15 @@ enum lua_Status
|
||||||
LUA_BREAK, // yielded for a debug breakpoint
|
LUA_BREAK, // yielded for a debug breakpoint
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum lua_CoStatus
|
||||||
|
{
|
||||||
|
LUA_CORUN = 0, // running
|
||||||
|
LUA_COSUS, // suspended
|
||||||
|
LUA_CONOR, // 'normal' (it resumed another coroutine)
|
||||||
|
LUA_COFIN, // finished
|
||||||
|
LUA_COERR, // finished with error
|
||||||
|
};
|
||||||
|
|
||||||
typedef struct lua_State lua_State;
|
typedef struct lua_State lua_State;
|
||||||
|
|
||||||
typedef int (*lua_CFunction)(lua_State* L);
|
typedef int (*lua_CFunction)(lua_State* L);
|
||||||
|
@ -201,6 +210,7 @@ LUA_API void lua_getfenv(lua_State* L, int idx);
|
||||||
*/
|
*/
|
||||||
LUA_API void lua_settable(lua_State* L, int idx);
|
LUA_API void lua_settable(lua_State* L, int idx);
|
||||||
LUA_API void lua_setfield(lua_State* L, int idx, const char* k);
|
LUA_API void lua_setfield(lua_State* L, int idx, const char* k);
|
||||||
|
LUA_API void lua_rawsetfield(lua_State* L, int idx, const char* k);
|
||||||
LUA_API void lua_rawset(lua_State* L, int idx);
|
LUA_API void lua_rawset(lua_State* L, int idx);
|
||||||
LUA_API void lua_rawseti(lua_State* L, int idx, int n);
|
LUA_API void lua_rawseti(lua_State* L, int idx, int n);
|
||||||
LUA_API int lua_setmetatable(lua_State* L, int objindex);
|
LUA_API int lua_setmetatable(lua_State* L, int objindex);
|
||||||
|
@ -224,6 +234,7 @@ LUA_API int lua_status(lua_State* L);
|
||||||
LUA_API int lua_isyieldable(lua_State* L);
|
LUA_API int lua_isyieldable(lua_State* L);
|
||||||
LUA_API void* lua_getthreaddata(lua_State* L);
|
LUA_API void* lua_getthreaddata(lua_State* L);
|
||||||
LUA_API void lua_setthreaddata(lua_State* L, void* data);
|
LUA_API void lua_setthreaddata(lua_State* L, void* data);
|
||||||
|
LUA_API int lua_costatus(lua_State* L, lua_State* co);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** garbage-collection function and options
|
** garbage-collection function and options
|
||||||
|
|
|
@ -24,14 +24,14 @@
|
||||||
* The caller is expected to handle stack reservation (by using less than LUA_MINSTACK slots or by calling lua_checkstack).
|
* The caller is expected to handle stack reservation (by using less than LUA_MINSTACK slots or by calling lua_checkstack).
|
||||||
* To ensure this is handled correctly, use api_incr_top(L) when pushing values to the stack.
|
* To ensure this is handled correctly, use api_incr_top(L) when pushing values to the stack.
|
||||||
*
|
*
|
||||||
* Functions that push any collectable objects to the stack *should* call luaC_checkthreadsleep. Failure to do this can result
|
* Functions that push any collectable objects to the stack *should* call luaC_threadbarrier. Failure to do this can result
|
||||||
* in stack references that point to dead objects since sleeping threads don't get rescanned.
|
* in stack references that point to dead objects since black threads don't get rescanned.
|
||||||
*
|
*
|
||||||
* Functions that push newly created objects to the stack *should* call luaC_checkGC in addition to luaC_checkthreadsleep.
|
* Functions that push newly created objects to the stack *should* call luaC_checkGC in addition to luaC_threadbarrier.
|
||||||
* Failure to do this can result in OOM since GC may never run.
|
* Failure to do this can result in OOM since GC may never run.
|
||||||
*
|
*
|
||||||
* Note that luaC_checkGC may scan the thread and put it back to sleep; functions that call both before pushing objects must
|
* Note that luaC_checkGC may mark the thread and paint it black; functions that call both before pushing objects must
|
||||||
* therefore call luaC_checkGC before luaC_checkthreadsleep to guarantee the object is pushed to an awake thread.
|
* therefore call luaC_checkGC before luaC_threadbarrier to guarantee the object is pushed to a gray thread.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const char* lua_ident = "$Lua: Lua 5.1.4 Copyright (C) 1994-2008 Lua.org, PUC-Rio $\n"
|
const char* lua_ident = "$Lua: Lua 5.1.4 Copyright (C) 1994-2008 Lua.org, PUC-Rio $\n"
|
||||||
|
@ -152,7 +152,7 @@ void lua_xmove(lua_State* from, lua_State* to, int n)
|
||||||
api_checknelems(from, n);
|
api_checknelems(from, n);
|
||||||
api_check(from, from->global == to->global);
|
api_check(from, from->global == to->global);
|
||||||
api_check(from, to->ci->top - to->top >= n);
|
api_check(from, to->ci->top - to->top >= n);
|
||||||
luaC_checkthreadsleep(to);
|
luaC_threadbarrier(to);
|
||||||
|
|
||||||
StkId ttop = to->top;
|
StkId ttop = to->top;
|
||||||
StkId ftop = from->top - n;
|
StkId ftop = from->top - n;
|
||||||
|
@ -168,7 +168,7 @@ void lua_xmove(lua_State* from, lua_State* to, int n)
|
||||||
void lua_xpush(lua_State* from, lua_State* to, int idx)
|
void lua_xpush(lua_State* from, lua_State* to, int idx)
|
||||||
{
|
{
|
||||||
api_check(from, from->global == to->global);
|
api_check(from, from->global == to->global);
|
||||||
luaC_checkthreadsleep(to);
|
luaC_threadbarrier(to);
|
||||||
setobj2s(to, to->top, index2addr(from, idx));
|
setobj2s(to, to->top, index2addr(from, idx));
|
||||||
api_incr_top(to);
|
api_incr_top(to);
|
||||||
return;
|
return;
|
||||||
|
@ -177,7 +177,7 @@ void lua_xpush(lua_State* from, lua_State* to, int idx)
|
||||||
lua_State* lua_newthread(lua_State* L)
|
lua_State* lua_newthread(lua_State* L)
|
||||||
{
|
{
|
||||||
luaC_checkGC(L);
|
luaC_checkGC(L);
|
||||||
luaC_checkthreadsleep(L);
|
luaC_threadbarrier(L);
|
||||||
lua_State* L1 = luaE_newthread(L);
|
lua_State* L1 = luaE_newthread(L);
|
||||||
setthvalue(L, L->top, L1);
|
setthvalue(L, L->top, L1);
|
||||||
api_incr_top(L);
|
api_incr_top(L);
|
||||||
|
@ -236,7 +236,7 @@ void lua_remove(lua_State* L, int idx)
|
||||||
|
|
||||||
void lua_insert(lua_State* L, int idx)
|
void lua_insert(lua_State* L, int idx)
|
||||||
{
|
{
|
||||||
luaC_checkthreadsleep(L);
|
luaC_threadbarrier(L);
|
||||||
StkId p = index2addr(L, idx);
|
StkId p = index2addr(L, idx);
|
||||||
api_checkvalidindex(L, p);
|
api_checkvalidindex(L, p);
|
||||||
for (StkId q = L->top; q > p; q--)
|
for (StkId q = L->top; q > p; q--)
|
||||||
|
@ -248,7 +248,7 @@ void lua_insert(lua_State* L, int idx)
|
||||||
void lua_replace(lua_State* L, int idx)
|
void lua_replace(lua_State* L, int idx)
|
||||||
{
|
{
|
||||||
api_checknelems(L, 1);
|
api_checknelems(L, 1);
|
||||||
luaC_checkthreadsleep(L);
|
luaC_threadbarrier(L);
|
||||||
StkId o = index2addr(L, idx);
|
StkId o = index2addr(L, idx);
|
||||||
api_checkvalidindex(L, o);
|
api_checkvalidindex(L, o);
|
||||||
if (idx == LUA_ENVIRONINDEX)
|
if (idx == LUA_ENVIRONINDEX)
|
||||||
|
@ -276,7 +276,7 @@ void lua_replace(lua_State* L, int idx)
|
||||||
|
|
||||||
void lua_pushvalue(lua_State* L, int idx)
|
void lua_pushvalue(lua_State* L, int idx)
|
||||||
{
|
{
|
||||||
luaC_checkthreadsleep(L);
|
luaC_threadbarrier(L);
|
||||||
StkId o = index2addr(L, idx);
|
StkId o = index2addr(L, idx);
|
||||||
setobj2s(L, L->top, o);
|
setobj2s(L, L->top, o);
|
||||||
api_incr_top(L);
|
api_incr_top(L);
|
||||||
|
@ -427,7 +427,7 @@ const char* lua_tolstring(lua_State* L, int idx, size_t* len)
|
||||||
StkId o = index2addr(L, idx);
|
StkId o = index2addr(L, idx);
|
||||||
if (!ttisstring(o))
|
if (!ttisstring(o))
|
||||||
{
|
{
|
||||||
luaC_checkthreadsleep(L);
|
luaC_threadbarrier(L);
|
||||||
if (!luaV_tostring(L, o))
|
if (!luaV_tostring(L, o))
|
||||||
{ // conversion failed?
|
{ // conversion failed?
|
||||||
if (len != NULL)
|
if (len != NULL)
|
||||||
|
@ -607,7 +607,7 @@ void lua_pushvector(lua_State* L, float x, float y, float z)
|
||||||
void lua_pushlstring(lua_State* L, const char* s, size_t len)
|
void lua_pushlstring(lua_State* L, const char* s, size_t len)
|
||||||
{
|
{
|
||||||
luaC_checkGC(L);
|
luaC_checkGC(L);
|
||||||
luaC_checkthreadsleep(L);
|
luaC_threadbarrier(L);
|
||||||
setsvalue2s(L, L->top, luaS_newlstr(L, s, len));
|
setsvalue2s(L, L->top, luaS_newlstr(L, s, len));
|
||||||
api_incr_top(L);
|
api_incr_top(L);
|
||||||
return;
|
return;
|
||||||
|
@ -624,7 +624,7 @@ void lua_pushstring(lua_State* L, const char* s)
|
||||||
const char* lua_pushvfstring(lua_State* L, const char* fmt, va_list argp)
|
const char* lua_pushvfstring(lua_State* L, const char* fmt, va_list argp)
|
||||||
{
|
{
|
||||||
luaC_checkGC(L);
|
luaC_checkGC(L);
|
||||||
luaC_checkthreadsleep(L);
|
luaC_threadbarrier(L);
|
||||||
const char* ret = luaO_pushvfstring(L, fmt, argp);
|
const char* ret = luaO_pushvfstring(L, fmt, argp);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -632,7 +632,7 @@ const char* lua_pushvfstring(lua_State* L, const char* fmt, va_list argp)
|
||||||
const char* lua_pushfstringL(lua_State* L, const char* fmt, ...)
|
const char* lua_pushfstringL(lua_State* L, const char* fmt, ...)
|
||||||
{
|
{
|
||||||
luaC_checkGC(L);
|
luaC_checkGC(L);
|
||||||
luaC_checkthreadsleep(L);
|
luaC_threadbarrier(L);
|
||||||
va_list argp;
|
va_list argp;
|
||||||
va_start(argp, fmt);
|
va_start(argp, fmt);
|
||||||
const char* ret = luaO_pushvfstring(L, fmt, argp);
|
const char* ret = luaO_pushvfstring(L, fmt, argp);
|
||||||
|
@ -643,7 +643,7 @@ const char* lua_pushfstringL(lua_State* L, const char* fmt, ...)
|
||||||
void lua_pushcclosurek(lua_State* L, lua_CFunction fn, const char* debugname, int nup, lua_Continuation cont)
|
void lua_pushcclosurek(lua_State* L, lua_CFunction fn, const char* debugname, int nup, lua_Continuation cont)
|
||||||
{
|
{
|
||||||
luaC_checkGC(L);
|
luaC_checkGC(L);
|
||||||
luaC_checkthreadsleep(L);
|
luaC_threadbarrier(L);
|
||||||
api_checknelems(L, nup);
|
api_checknelems(L, nup);
|
||||||
Closure* cl = luaF_newCclosure(L, nup, getcurrenv(L));
|
Closure* cl = luaF_newCclosure(L, nup, getcurrenv(L));
|
||||||
cl->c.f = fn;
|
cl->c.f = fn;
|
||||||
|
@ -674,7 +674,7 @@ void lua_pushlightuserdata(lua_State* L, void* p)
|
||||||
|
|
||||||
int lua_pushthread(lua_State* L)
|
int lua_pushthread(lua_State* L)
|
||||||
{
|
{
|
||||||
luaC_checkthreadsleep(L);
|
luaC_threadbarrier(L);
|
||||||
setthvalue(L, L->top, L);
|
setthvalue(L, L->top, L);
|
||||||
api_incr_top(L);
|
api_incr_top(L);
|
||||||
return L->global->mainthread == L;
|
return L->global->mainthread == L;
|
||||||
|
@ -686,7 +686,7 @@ int lua_pushthread(lua_State* L)
|
||||||
|
|
||||||
int lua_gettable(lua_State* L, int idx)
|
int lua_gettable(lua_State* L, int idx)
|
||||||
{
|
{
|
||||||
luaC_checkthreadsleep(L);
|
luaC_threadbarrier(L);
|
||||||
StkId t = index2addr(L, idx);
|
StkId t = index2addr(L, idx);
|
||||||
api_checkvalidindex(L, t);
|
api_checkvalidindex(L, t);
|
||||||
luaV_gettable(L, t, L->top - 1, L->top - 1);
|
luaV_gettable(L, t, L->top - 1, L->top - 1);
|
||||||
|
@ -695,7 +695,7 @@ int lua_gettable(lua_State* L, int idx)
|
||||||
|
|
||||||
int lua_getfield(lua_State* L, int idx, const char* k)
|
int lua_getfield(lua_State* L, int idx, const char* k)
|
||||||
{
|
{
|
||||||
luaC_checkthreadsleep(L);
|
luaC_threadbarrier(L);
|
||||||
StkId t = index2addr(L, idx);
|
StkId t = index2addr(L, idx);
|
||||||
api_checkvalidindex(L, t);
|
api_checkvalidindex(L, t);
|
||||||
TValue key;
|
TValue key;
|
||||||
|
@ -707,7 +707,7 @@ int lua_getfield(lua_State* L, int idx, const char* k)
|
||||||
|
|
||||||
int lua_rawgetfield(lua_State* L, int idx, const char* k)
|
int lua_rawgetfield(lua_State* L, int idx, const char* k)
|
||||||
{
|
{
|
||||||
luaC_checkthreadsleep(L);
|
luaC_threadbarrier(L);
|
||||||
StkId t = index2addr(L, idx);
|
StkId t = index2addr(L, idx);
|
||||||
api_check(L, ttistable(t));
|
api_check(L, ttistable(t));
|
||||||
TValue key;
|
TValue key;
|
||||||
|
@ -719,7 +719,7 @@ int lua_rawgetfield(lua_State* L, int idx, const char* k)
|
||||||
|
|
||||||
int lua_rawget(lua_State* L, int idx)
|
int lua_rawget(lua_State* L, int idx)
|
||||||
{
|
{
|
||||||
luaC_checkthreadsleep(L);
|
luaC_threadbarrier(L);
|
||||||
StkId t = index2addr(L, idx);
|
StkId t = index2addr(L, idx);
|
||||||
api_check(L, ttistable(t));
|
api_check(L, ttistable(t));
|
||||||
setobj2s(L, L->top - 1, luaH_get(hvalue(t), L->top - 1));
|
setobj2s(L, L->top - 1, luaH_get(hvalue(t), L->top - 1));
|
||||||
|
@ -728,7 +728,7 @@ int lua_rawget(lua_State* L, int idx)
|
||||||
|
|
||||||
int lua_rawgeti(lua_State* L, int idx, int n)
|
int lua_rawgeti(lua_State* L, int idx, int n)
|
||||||
{
|
{
|
||||||
luaC_checkthreadsleep(L);
|
luaC_threadbarrier(L);
|
||||||
StkId t = index2addr(L, idx);
|
StkId t = index2addr(L, idx);
|
||||||
api_check(L, ttistable(t));
|
api_check(L, ttistable(t));
|
||||||
setobj2s(L, L->top, luaH_getnum(hvalue(t), n));
|
setobj2s(L, L->top, luaH_getnum(hvalue(t), n));
|
||||||
|
@ -739,7 +739,7 @@ int lua_rawgeti(lua_State* L, int idx, int n)
|
||||||
void lua_createtable(lua_State* L, int narray, int nrec)
|
void lua_createtable(lua_State* L, int narray, int nrec)
|
||||||
{
|
{
|
||||||
luaC_checkGC(L);
|
luaC_checkGC(L);
|
||||||
luaC_checkthreadsleep(L);
|
luaC_threadbarrier(L);
|
||||||
sethvalue(L, L->top, luaH_new(L, narray, nrec));
|
sethvalue(L, L->top, luaH_new(L, narray, nrec));
|
||||||
api_incr_top(L);
|
api_incr_top(L);
|
||||||
return;
|
return;
|
||||||
|
@ -775,7 +775,7 @@ void lua_setsafeenv(lua_State* L, int objindex, int enabled)
|
||||||
|
|
||||||
int lua_getmetatable(lua_State* L, int objindex)
|
int lua_getmetatable(lua_State* L, int objindex)
|
||||||
{
|
{
|
||||||
luaC_checkthreadsleep(L);
|
luaC_threadbarrier(L);
|
||||||
Table* mt = NULL;
|
Table* mt = NULL;
|
||||||
const TValue* obj = index2addr(L, objindex);
|
const TValue* obj = index2addr(L, objindex);
|
||||||
switch (ttype(obj))
|
switch (ttype(obj))
|
||||||
|
@ -800,7 +800,7 @@ int lua_getmetatable(lua_State* L, int objindex)
|
||||||
|
|
||||||
void lua_getfenv(lua_State* L, int idx)
|
void lua_getfenv(lua_State* L, int idx)
|
||||||
{
|
{
|
||||||
luaC_checkthreadsleep(L);
|
luaC_threadbarrier(L);
|
||||||
StkId o = index2addr(L, idx);
|
StkId o = index2addr(L, idx);
|
||||||
api_checkvalidindex(L, o);
|
api_checkvalidindex(L, o);
|
||||||
switch (ttype(o))
|
switch (ttype(o))
|
||||||
|
@ -845,6 +845,19 @@ void lua_setfield(lua_State* L, int idx, const char* k)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void lua_rawsetfield(lua_State* L, int idx, const char* k)
|
||||||
|
{
|
||||||
|
api_checknelems(L, 1);
|
||||||
|
StkId t = index2addr(L, idx);
|
||||||
|
api_check(L, ttistable(t));
|
||||||
|
if (hvalue(t)->readonly)
|
||||||
|
luaG_runerror(L, "Attempt to modify a readonly table");
|
||||||
|
setobj2t(L, luaH_setstr(L, hvalue(t), luaS_new(L, k)), L->top - 1);
|
||||||
|
luaC_barriert(L, hvalue(t), L->top - 1);
|
||||||
|
L->top--;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
void lua_rawset(lua_State* L, int idx)
|
void lua_rawset(lua_State* L, int idx)
|
||||||
{
|
{
|
||||||
api_checknelems(L, 2);
|
api_checknelems(L, 2);
|
||||||
|
@ -1006,6 +1019,23 @@ int lua_status(lua_State* L)
|
||||||
return L->status;
|
return L->status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int lua_costatus(lua_State* L, lua_State* co)
|
||||||
|
{
|
||||||
|
if (co == L)
|
||||||
|
return LUA_CORUN;
|
||||||
|
if (co->status == LUA_YIELD)
|
||||||
|
return LUA_COSUS;
|
||||||
|
if (co->status == LUA_BREAK)
|
||||||
|
return LUA_CONOR;
|
||||||
|
if (co->status != 0) // some error occurred
|
||||||
|
return LUA_COERR;
|
||||||
|
if (co->ci != co->base_ci) // does it have frames?
|
||||||
|
return LUA_CONOR;
|
||||||
|
if (co->top == co->base)
|
||||||
|
return LUA_COFIN;
|
||||||
|
return LUA_COSUS; // initial state
|
||||||
|
}
|
||||||
|
|
||||||
void* lua_getthreaddata(lua_State* L)
|
void* lua_getthreaddata(lua_State* L)
|
||||||
{
|
{
|
||||||
return L->userdata;
|
return L->userdata;
|
||||||
|
@ -1161,7 +1191,7 @@ l_noret lua_error(lua_State* L)
|
||||||
|
|
||||||
int lua_next(lua_State* L, int idx)
|
int lua_next(lua_State* L, int idx)
|
||||||
{
|
{
|
||||||
luaC_checkthreadsleep(L);
|
luaC_threadbarrier(L);
|
||||||
StkId t = index2addr(L, idx);
|
StkId t = index2addr(L, idx);
|
||||||
api_check(L, ttistable(t));
|
api_check(L, ttistable(t));
|
||||||
int more = luaH_next(L, hvalue(t), L->top - 1);
|
int more = luaH_next(L, hvalue(t), L->top - 1);
|
||||||
|
@ -1180,13 +1210,13 @@ void lua_concat(lua_State* L, int n)
|
||||||
if (n >= 2)
|
if (n >= 2)
|
||||||
{
|
{
|
||||||
luaC_checkGC(L);
|
luaC_checkGC(L);
|
||||||
luaC_checkthreadsleep(L);
|
luaC_threadbarrier(L);
|
||||||
luaV_concat(L, n, cast_int(L->top - L->base) - 1);
|
luaV_concat(L, n, cast_int(L->top - L->base) - 1);
|
||||||
L->top -= (n - 1);
|
L->top -= (n - 1);
|
||||||
}
|
}
|
||||||
else if (n == 0)
|
else if (n == 0)
|
||||||
{ // push empty string
|
{ // push empty string
|
||||||
luaC_checkthreadsleep(L);
|
luaC_threadbarrier(L);
|
||||||
setsvalue2s(L, L->top, luaS_newlstr(L, "", 0));
|
setsvalue2s(L, L->top, luaS_newlstr(L, "", 0));
|
||||||
api_incr_top(L);
|
api_incr_top(L);
|
||||||
}
|
}
|
||||||
|
@ -1198,7 +1228,7 @@ void* lua_newuserdatatagged(lua_State* L, size_t sz, int tag)
|
||||||
{
|
{
|
||||||
api_check(L, unsigned(tag) < LUA_UTAG_LIMIT || tag == UTAG_PROXY);
|
api_check(L, unsigned(tag) < LUA_UTAG_LIMIT || tag == UTAG_PROXY);
|
||||||
luaC_checkGC(L);
|
luaC_checkGC(L);
|
||||||
luaC_checkthreadsleep(L);
|
luaC_threadbarrier(L);
|
||||||
Udata* u = luaU_newudata(L, sz, tag);
|
Udata* u = luaU_newudata(L, sz, tag);
|
||||||
setuvalue(L, L->top, u);
|
setuvalue(L, L->top, u);
|
||||||
api_incr_top(L);
|
api_incr_top(L);
|
||||||
|
@ -1208,7 +1238,7 @@ void* lua_newuserdatatagged(lua_State* L, size_t sz, int tag)
|
||||||
void* lua_newuserdatadtor(lua_State* L, size_t sz, void (*dtor)(void*))
|
void* lua_newuserdatadtor(lua_State* L, size_t sz, void (*dtor)(void*))
|
||||||
{
|
{
|
||||||
luaC_checkGC(L);
|
luaC_checkGC(L);
|
||||||
luaC_checkthreadsleep(L);
|
luaC_threadbarrier(L);
|
||||||
// make sure sz + sizeof(dtor) doesn't overflow; luaU_newdata will reject SIZE_MAX correctly
|
// make sure sz + sizeof(dtor) doesn't overflow; luaU_newdata will reject SIZE_MAX correctly
|
||||||
size_t as = sz < SIZE_MAX - sizeof(dtor) ? sz + sizeof(dtor) : SIZE_MAX;
|
size_t as = sz < SIZE_MAX - sizeof(dtor) ? sz + sizeof(dtor) : SIZE_MAX;
|
||||||
Udata* u = luaU_newudata(L, as, UTAG_IDTOR);
|
Udata* u = luaU_newudata(L, as, UTAG_IDTOR);
|
||||||
|
@ -1244,7 +1274,7 @@ static const char* aux_upvalue(StkId fi, int n, TValue** val)
|
||||||
|
|
||||||
const char* lua_getupvalue(lua_State* L, int funcindex, int n)
|
const char* lua_getupvalue(lua_State* L, int funcindex, int n)
|
||||||
{
|
{
|
||||||
luaC_checkthreadsleep(L);
|
luaC_threadbarrier(L);
|
||||||
TValue* val;
|
TValue* val;
|
||||||
const char* name = aux_upvalue(index2addr(L, funcindex), n, &val);
|
const char* name = aux_upvalue(index2addr(L, funcindex), n, &val);
|
||||||
if (name)
|
if (name)
|
||||||
|
@ -1266,7 +1296,6 @@ const char* lua_setupvalue(lua_State* L, int funcindex, int n)
|
||||||
L->top--;
|
L->top--;
|
||||||
setobj(L, val, L->top);
|
setobj(L, val, L->top);
|
||||||
luaC_barrier(L, clvalue(fi), L->top);
|
luaC_barrier(L, clvalue(fi), L->top);
|
||||||
luaC_upvalbarrier(L, cast_to(UpVal*, NULL), val);
|
|
||||||
}
|
}
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
@ -1336,7 +1365,7 @@ void lua_setuserdatadtor(lua_State* L, int tag, void (*dtor)(lua_State*, void*))
|
||||||
void lua_clonefunction(lua_State* L, int idx)
|
void lua_clonefunction(lua_State* L, int idx)
|
||||||
{
|
{
|
||||||
luaC_checkGC(L);
|
luaC_checkGC(L);
|
||||||
luaC_checkthreadsleep(L);
|
luaC_threadbarrier(L);
|
||||||
StkId p = index2addr(L, idx);
|
StkId p = index2addr(L, idx);
|
||||||
api_check(L, isLfunction(p));
|
api_check(L, isLfunction(p));
|
||||||
Closure* cl = clvalue(p);
|
Closure* cl = clvalue(p);
|
||||||
|
|
|
@ -15,8 +15,6 @@
|
||||||
#include <intrin.h>
|
#include <intrin.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauFasterBit32NoWidth, false)
|
|
||||||
|
|
||||||
// luauF functions implement FASTCALL instruction that performs a direct execution of some builtin functions from the VM
|
// luauF functions implement FASTCALL instruction that performs a direct execution of some builtin functions from the VM
|
||||||
// The rule of thumb is that FASTCALL functions can not call user code, yield, fail, or reallocate stack.
|
// The rule of thumb is that FASTCALL functions can not call user code, yield, fail, or reallocate stack.
|
||||||
// If types of the arguments mismatch, luauF_* needs to return -1 and the execution will fall back to the usual call path
|
// If types of the arguments mismatch, luauF_* needs to return -1 and the execution will fall back to the usual call path
|
||||||
|
@ -602,7 +600,7 @@ static int luauF_btest(lua_State* L, StkId res, TValue* arg0, int nresults, StkI
|
||||||
|
|
||||||
static int luauF_extract(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams)
|
static int luauF_extract(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams)
|
||||||
{
|
{
|
||||||
if (nparams >= (3 - FFlag::LuauFasterBit32NoWidth) && nresults <= 1 && ttisnumber(arg0) && ttisnumber(args))
|
if (nparams >= 2 && nresults <= 1 && ttisnumber(arg0) && ttisnumber(args))
|
||||||
{
|
{
|
||||||
double a1 = nvalue(arg0);
|
double a1 = nvalue(arg0);
|
||||||
double a2 = nvalue(args);
|
double a2 = nvalue(args);
|
||||||
|
@ -693,7 +691,7 @@ static int luauF_lshift(lua_State* L, StkId res, TValue* arg0, int nresults, Stk
|
||||||
|
|
||||||
static int luauF_replace(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams)
|
static int luauF_replace(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams)
|
||||||
{
|
{
|
||||||
if (nparams >= (4 - FFlag::LuauFasterBit32NoWidth) && nresults <= 1 && ttisnumber(arg0) && ttisnumber(args) && ttisnumber(args + 1))
|
if (nparams >= 3 && nresults <= 1 && ttisnumber(arg0) && ttisnumber(args) && ttisnumber(args + 1))
|
||||||
{
|
{
|
||||||
double a1 = nvalue(arg0);
|
double a1 = nvalue(arg0);
|
||||||
double a2 = nvalue(args);
|
double a2 = nvalue(args);
|
||||||
|
|
|
@ -5,38 +5,16 @@
|
||||||
#include "lstate.h"
|
#include "lstate.h"
|
||||||
#include "lvm.h"
|
#include "lvm.h"
|
||||||
|
|
||||||
#define CO_RUN 0 // running
|
|
||||||
#define CO_SUS 1 // suspended
|
|
||||||
#define CO_NOR 2 // 'normal' (it resumed another coroutine)
|
|
||||||
#define CO_DEAD 3
|
|
||||||
|
|
||||||
#define CO_STATUS_ERROR -1
|
#define CO_STATUS_ERROR -1
|
||||||
#define CO_STATUS_BREAK -2
|
#define CO_STATUS_BREAK -2
|
||||||
|
|
||||||
static const char* const statnames[] = {"running", "suspended", "normal", "dead"};
|
static const char* const statnames[] = {"running", "suspended", "normal", "dead", "dead"}; // dead appears twice for LUA_COERR and LUA_COFIN
|
||||||
|
|
||||||
static int auxstatus(lua_State* L, lua_State* co)
|
|
||||||
{
|
|
||||||
if (co == L)
|
|
||||||
return CO_RUN;
|
|
||||||
if (co->status == LUA_YIELD)
|
|
||||||
return CO_SUS;
|
|
||||||
if (co->status == LUA_BREAK)
|
|
||||||
return CO_NOR;
|
|
||||||
if (co->status != 0) // some error occurred
|
|
||||||
return CO_DEAD;
|
|
||||||
if (co->ci != co->base_ci) // does it have frames?
|
|
||||||
return CO_NOR;
|
|
||||||
if (co->top == co->base)
|
|
||||||
return CO_DEAD;
|
|
||||||
return CO_SUS; // initial state
|
|
||||||
}
|
|
||||||
|
|
||||||
static int costatus(lua_State* L)
|
static int costatus(lua_State* L)
|
||||||
{
|
{
|
||||||
lua_State* co = lua_tothread(L, 1);
|
lua_State* co = lua_tothread(L, 1);
|
||||||
luaL_argexpected(L, co, 1, "thread");
|
luaL_argexpected(L, co, 1, "thread");
|
||||||
lua_pushstring(L, statnames[auxstatus(L, co)]);
|
lua_pushstring(L, statnames[lua_costatus(L, co)]);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,8 +23,8 @@ static int auxresume(lua_State* L, lua_State* co, int narg)
|
||||||
// error handling for edge cases
|
// error handling for edge cases
|
||||||
if (co->status != LUA_YIELD)
|
if (co->status != LUA_YIELD)
|
||||||
{
|
{
|
||||||
int status = auxstatus(L, co);
|
int status = lua_costatus(L, co);
|
||||||
if (status != CO_SUS)
|
if (status != LUA_COSUS)
|
||||||
{
|
{
|
||||||
lua_pushfstring(L, "cannot resume %s coroutine", statnames[status]);
|
lua_pushfstring(L, "cannot resume %s coroutine", statnames[status]);
|
||||||
return CO_STATUS_ERROR;
|
return CO_STATUS_ERROR;
|
||||||
|
@ -236,8 +214,8 @@ static int coclose(lua_State* L)
|
||||||
lua_State* co = lua_tothread(L, 1);
|
lua_State* co = lua_tothread(L, 1);
|
||||||
luaL_argexpected(L, co, 1, "thread");
|
luaL_argexpected(L, co, 1, "thread");
|
||||||
|
|
||||||
int status = auxstatus(L, co);
|
int status = lua_costatus(L, co);
|
||||||
if (status != CO_DEAD && status != CO_SUS)
|
if (status != LUA_COFIN && status != LUA_COERR && status != LUA_COSUS)
|
||||||
luaL_error(L, "cannot close %s coroutine", statnames[status]);
|
luaL_error(L, "cannot close %s coroutine", statnames[status]);
|
||||||
|
|
||||||
if (co->status == LUA_OK || co->status == LUA_YIELD)
|
if (co->status == LUA_OK || co->status == LUA_YIELD)
|
||||||
|
|
|
@ -12,8 +12,6 @@
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauDebuggerBreakpointHitOnNextBestLine, false);
|
|
||||||
|
|
||||||
static const char* getfuncname(Closure* f);
|
static const char* getfuncname(Closure* f);
|
||||||
|
|
||||||
static int currentpc(lua_State* L, CallInfo* ci)
|
static int currentpc(lua_State* L, CallInfo* ci)
|
||||||
|
@ -44,13 +42,13 @@ int lua_getargument(lua_State* L, int level, int n)
|
||||||
{
|
{
|
||||||
if (n <= fp->numparams)
|
if (n <= fp->numparams)
|
||||||
{
|
{
|
||||||
luaC_checkthreadsleep(L);
|
luaC_threadbarrier(L);
|
||||||
luaA_pushobject(L, ci->base + (n - 1));
|
luaA_pushobject(L, ci->base + (n - 1));
|
||||||
res = 1;
|
res = 1;
|
||||||
}
|
}
|
||||||
else if (fp->is_vararg && n < ci->base - ci->func)
|
else if (fp->is_vararg && n < ci->base - ci->func)
|
||||||
{
|
{
|
||||||
luaC_checkthreadsleep(L);
|
luaC_threadbarrier(L);
|
||||||
luaA_pushobject(L, ci->func + n);
|
luaA_pushobject(L, ci->func + n);
|
||||||
res = 1;
|
res = 1;
|
||||||
}
|
}
|
||||||
|
@ -69,7 +67,7 @@ const char* lua_getlocal(lua_State* L, int level, int n)
|
||||||
const LocVar* var = fp ? luaF_getlocal(fp, n, currentpc(L, ci)) : NULL;
|
const LocVar* var = fp ? luaF_getlocal(fp, n, currentpc(L, ci)) : NULL;
|
||||||
if (var)
|
if (var)
|
||||||
{
|
{
|
||||||
luaC_checkthreadsleep(L);
|
luaC_threadbarrier(L);
|
||||||
luaA_pushobject(L, ci->base + var->reg);
|
luaA_pushobject(L, ci->base + var->reg);
|
||||||
}
|
}
|
||||||
const char* name = var ? getstr(var->varname) : NULL;
|
const char* name = var ? getstr(var->varname) : NULL;
|
||||||
|
@ -185,7 +183,7 @@ int lua_getinfo(lua_State* L, int level, const char* what, lua_Debug* ar)
|
||||||
status = auxgetinfo(L, what, ar, f, ci);
|
status = auxgetinfo(L, what, ar, f, ci);
|
||||||
if (strchr(what, 'f'))
|
if (strchr(what, 'f'))
|
||||||
{
|
{
|
||||||
luaC_checkthreadsleep(L);
|
luaC_threadbarrier(L);
|
||||||
setclvalue(L, L->top, f);
|
setclvalue(L, L->top, f);
|
||||||
incr_top(L);
|
incr_top(L);
|
||||||
}
|
}
|
||||||
|
@ -436,31 +434,19 @@ static int getnextline(Proto* p, int line)
|
||||||
}
|
}
|
||||||
|
|
||||||
int lua_breakpoint(lua_State* L, int funcindex, int line, int enabled)
|
int lua_breakpoint(lua_State* L, int funcindex, int line, int enabled)
|
||||||
{
|
|
||||||
int target = -1;
|
|
||||||
|
|
||||||
if (FFlag::LuauDebuggerBreakpointHitOnNextBestLine)
|
|
||||||
{
|
{
|
||||||
const TValue* func = luaA_toobject(L, funcindex);
|
const TValue* func = luaA_toobject(L, funcindex);
|
||||||
api_check(L, ttisfunction(func) && !clvalue(func)->isC);
|
api_check(L, ttisfunction(func) && !clvalue(func)->isC);
|
||||||
|
|
||||||
Proto* p = clvalue(func)->l.p;
|
Proto* p = clvalue(func)->l.p;
|
||||||
// Find line number to add the breakpoint to.
|
// Find line number to add the breakpoint to.
|
||||||
target = getnextline(p, line);
|
int target = getnextline(p, line);
|
||||||
|
|
||||||
if (target != -1)
|
if (target != -1)
|
||||||
{
|
{
|
||||||
// Add breakpoint on the exact line
|
// Add breakpoint on the exact line
|
||||||
luaG_breakpoint(L, p, target, bool(enabled));
|
luaG_breakpoint(L, p, target, bool(enabled));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
const TValue* func = luaA_toobject(L, funcindex);
|
|
||||||
api_check(L, ttisfunction(func) && !clvalue(func)->isC);
|
|
||||||
|
|
||||||
luaG_breakpoint(L, clvalue(func)->l.p, line, bool(enabled));
|
|
||||||
}
|
|
||||||
|
|
||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
|
|
|
@ -158,7 +158,7 @@ l_noret luaD_throw(lua_State* L, int errcode)
|
||||||
static void correctstack(lua_State* L, TValue* oldstack)
|
static void correctstack(lua_State* L, TValue* oldstack)
|
||||||
{
|
{
|
||||||
L->top = (L->top - oldstack) + L->stack;
|
L->top = (L->top - oldstack) + L->stack;
|
||||||
for (UpVal* up = L->openupval; up != NULL; up = up->u.l.threadnext)
|
for (UpVal* up = L->openupval; up != NULL; up = up->u.open.threadnext)
|
||||||
up->v = (up->v - oldstack) + L->stack;
|
up->v = (up->v - oldstack) + L->stack;
|
||||||
for (CallInfo* ci = L->base_ci; ci <= L->ci; ci++)
|
for (CallInfo* ci = L->base_ci; ci <= L->ci; ci++)
|
||||||
{
|
{
|
||||||
|
@ -243,14 +243,14 @@ void luaD_call(lua_State* L, StkId func, int nResults)
|
||||||
{ // is a Lua function?
|
{ // is a Lua function?
|
||||||
L->ci->flags |= LUA_CALLINFO_RETURN; // luau_execute will stop after returning from the stack frame
|
L->ci->flags |= LUA_CALLINFO_RETURN; // luau_execute will stop after returning from the stack frame
|
||||||
|
|
||||||
int oldactive = luaC_threadactive(L);
|
bool oldactive = L->isactive;
|
||||||
l_setbit(L->stackstate, THREAD_ACTIVEBIT);
|
L->isactive = true;
|
||||||
luaC_checkthreadsleep(L);
|
luaC_threadbarrier(L);
|
||||||
|
|
||||||
luau_execute(L); // call it
|
luau_execute(L); // call it
|
||||||
|
|
||||||
if (!oldactive)
|
if (!oldactive)
|
||||||
resetbit(L->stackstate, THREAD_ACTIVEBIT);
|
L->isactive = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
L->nCcalls--;
|
L->nCcalls--;
|
||||||
|
@ -427,7 +427,7 @@ static int resume_error(lua_State* L, const char* msg)
|
||||||
static void resume_finish(lua_State* L, int status)
|
static void resume_finish(lua_State* L, int status)
|
||||||
{
|
{
|
||||||
L->nCcalls = L->baseCcalls;
|
L->nCcalls = L->baseCcalls;
|
||||||
resetbit(L->stackstate, THREAD_ACTIVEBIT);
|
L->isactive = false;
|
||||||
|
|
||||||
if (status != 0)
|
if (status != 0)
|
||||||
{ // error?
|
{ // error?
|
||||||
|
@ -452,9 +452,9 @@ int lua_resume(lua_State* L, lua_State* from, int nargs)
|
||||||
return resume_error(L, "C stack overflow");
|
return resume_error(L, "C stack overflow");
|
||||||
|
|
||||||
L->baseCcalls = ++L->nCcalls;
|
L->baseCcalls = ++L->nCcalls;
|
||||||
l_setbit(L->stackstate, THREAD_ACTIVEBIT);
|
L->isactive = true;
|
||||||
|
|
||||||
luaC_checkthreadsleep(L);
|
luaC_threadbarrier(L);
|
||||||
|
|
||||||
status = luaD_rawrunprotected(L, resume, L->top - nargs);
|
status = luaD_rawrunprotected(L, resume, L->top - nargs);
|
||||||
|
|
||||||
|
@ -481,9 +481,9 @@ int lua_resumeerror(lua_State* L, lua_State* from)
|
||||||
return resume_error(L, "C stack overflow");
|
return resume_error(L, "C stack overflow");
|
||||||
|
|
||||||
L->baseCcalls = ++L->nCcalls;
|
L->baseCcalls = ++L->nCcalls;
|
||||||
l_setbit(L->stackstate, THREAD_ACTIVEBIT);
|
L->isactive = true;
|
||||||
|
|
||||||
luaC_checkthreadsleep(L);
|
luaC_threadbarrier(L);
|
||||||
|
|
||||||
status = LUA_ERRRUN;
|
status = LUA_ERRRUN;
|
||||||
|
|
||||||
|
@ -546,7 +546,7 @@ int luaD_pcall(lua_State* L, Pfunc func, void* u, ptrdiff_t old_top, ptrdiff_t e
|
||||||
{
|
{
|
||||||
unsigned short oldnCcalls = L->nCcalls;
|
unsigned short oldnCcalls = L->nCcalls;
|
||||||
ptrdiff_t old_ci = saveci(L, L->ci);
|
ptrdiff_t old_ci = saveci(L, L->ci);
|
||||||
int oldactive = luaC_threadactive(L);
|
bool oldactive = L->isactive;
|
||||||
int status = luaD_rawrunprotected(L, func, u);
|
int status = luaD_rawrunprotected(L, func, u);
|
||||||
if (status != 0)
|
if (status != 0)
|
||||||
{
|
{
|
||||||
|
@ -560,7 +560,7 @@ int luaD_pcall(lua_State* L, Pfunc func, void* u, ptrdiff_t old_top, ptrdiff_t e
|
||||||
|
|
||||||
// since the call failed with an error, we might have to reset the 'active' thread state
|
// since the call failed with an error, we might have to reset the 'active' thread state
|
||||||
if (!oldactive)
|
if (!oldactive)
|
||||||
resetbit(L->stackstate, THREAD_ACTIVEBIT);
|
L->isactive = false;
|
||||||
|
|
||||||
// Restore nCcalls before calling the debugprotectederror callback which may rely on the proper value to have been restored.
|
// Restore nCcalls before calling the debugprotectederror callback which may rely on the proper value to have been restored.
|
||||||
L->nCcalls = oldnCcalls;
|
L->nCcalls = oldnCcalls;
|
||||||
|
|
|
@ -71,58 +71,38 @@ UpVal* luaF_findupval(lua_State* L, StkId level)
|
||||||
UpVal* p;
|
UpVal* p;
|
||||||
while (*pp != NULL && (p = *pp)->v >= level)
|
while (*pp != NULL && (p = *pp)->v >= level)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(p->v != &p->u.value);
|
LUAU_ASSERT(!isdead(g, obj2gco(p)));
|
||||||
|
LUAU_ASSERT(upisopen(p));
|
||||||
if (p->v == level)
|
if (p->v == level)
|
||||||
{ // found a corresponding upvalue?
|
|
||||||
if (isdead(g, obj2gco(p))) // is it dead?
|
|
||||||
changewhite(obj2gco(p)); // resurrect it
|
|
||||||
return p;
|
return p;
|
||||||
|
|
||||||
|
pp = &p->u.open.threadnext;
|
||||||
}
|
}
|
||||||
|
|
||||||
pp = &p->u.l.threadnext;
|
LUAU_ASSERT(L->isactive);
|
||||||
}
|
LUAU_ASSERT(!isblack(obj2gco(L))); // we don't use luaC_threadbarrier because active threads never turn black
|
||||||
|
|
||||||
UpVal* uv = luaM_newgco(L, UpVal, sizeof(UpVal), L->activememcat); // not found: create a new one
|
UpVal* uv = luaM_newgco(L, UpVal, sizeof(UpVal), L->activememcat); // not found: create a new one
|
||||||
uv->tt = LUA_TUPVAL;
|
luaC_init(L, uv, LUA_TUPVAL);
|
||||||
uv->marked = luaC_white(g);
|
uv->markedopen = 0;
|
||||||
uv->memcat = L->activememcat;
|
|
||||||
uv->v = level; // current value lives in the stack
|
uv->v = level; // current value lives in the stack
|
||||||
|
|
||||||
// chain the upvalue in the threads open upvalue list at the proper position
|
// chain the upvalue in the threads open upvalue list at the proper position
|
||||||
UpVal* next = *pp;
|
uv->u.open.threadnext = *pp;
|
||||||
uv->u.l.threadnext = next;
|
|
||||||
uv->u.l.threadprev = pp;
|
|
||||||
if (next)
|
|
||||||
next->u.l.threadprev = &uv->u.l.threadnext;
|
|
||||||
|
|
||||||
*pp = uv;
|
*pp = uv;
|
||||||
|
|
||||||
// double link the upvalue in the global open upvalue list
|
// double link the upvalue in the global open upvalue list
|
||||||
uv->u.l.prev = &g->uvhead;
|
uv->u.open.prev = &g->uvhead;
|
||||||
uv->u.l.next = g->uvhead.u.l.next;
|
uv->u.open.next = g->uvhead.u.open.next;
|
||||||
uv->u.l.next->u.l.prev = uv;
|
uv->u.open.next->u.open.prev = uv;
|
||||||
g->uvhead.u.l.next = uv;
|
g->uvhead.u.open.next = uv;
|
||||||
LUAU_ASSERT(uv->u.l.next->u.l.prev == uv && uv->u.l.prev->u.l.next == uv);
|
LUAU_ASSERT(uv->u.open.next->u.open.prev == uv && uv->u.open.prev->u.open.next == uv);
|
||||||
|
|
||||||
return uv;
|
return uv;
|
||||||
}
|
}
|
||||||
void luaF_unlinkupval(UpVal* uv)
|
|
||||||
{
|
|
||||||
// unlink upvalue from the global open upvalue list
|
|
||||||
LUAU_ASSERT(uv->u.l.next->u.l.prev == uv && uv->u.l.prev->u.l.next == uv);
|
|
||||||
uv->u.l.next->u.l.prev = uv->u.l.prev;
|
|
||||||
uv->u.l.prev->u.l.next = uv->u.l.next;
|
|
||||||
|
|
||||||
// unlink upvalue from the thread open upvalue list
|
|
||||||
*uv->u.l.threadprev = uv->u.l.threadnext;
|
|
||||||
|
|
||||||
if (UpVal* next = uv->u.l.threadnext)
|
|
||||||
next->u.l.threadprev = uv->u.l.threadprev;
|
|
||||||
}
|
|
||||||
|
|
||||||
void luaF_freeupval(lua_State* L, UpVal* uv, lua_Page* page)
|
void luaF_freeupval(lua_State* L, UpVal* uv, lua_Page* page)
|
||||||
{
|
{
|
||||||
if (uv->v != &uv->u.value) // is it open?
|
|
||||||
luaF_unlinkupval(uv); // remove from open list
|
|
||||||
luaM_freegco(L, uv, sizeof(UpVal), uv->memcat, page); // free upvalue
|
luaM_freegco(L, uv, sizeof(UpVal), uv->memcat, page); // free upvalue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,24 +113,29 @@ void luaF_close(lua_State* L, StkId level)
|
||||||
while (L->openupval != NULL && (uv = L->openupval)->v >= level)
|
while (L->openupval != NULL && (uv = L->openupval)->v >= level)
|
||||||
{
|
{
|
||||||
GCObject* o = obj2gco(uv);
|
GCObject* o = obj2gco(uv);
|
||||||
LUAU_ASSERT(!isblack(o) && uv->v != &uv->u.value);
|
LUAU_ASSERT(!isblack(o) && upisopen(uv));
|
||||||
|
LUAU_ASSERT(!isdead(g, o));
|
||||||
|
|
||||||
// by removing the upvalue from global/thread open upvalue lists, L->openupval will be pointing to the next upvalue
|
// unlink value *before* closing it since value storage overlaps
|
||||||
luaF_unlinkupval(uv);
|
L->openupval = uv->u.open.threadnext;
|
||||||
|
|
||||||
if (isdead(g, o))
|
luaF_closeupval(L, uv, /* dead= */ false);
|
||||||
{
|
|
||||||
// close the upvalue without copying the dead data so that luaF_freeupval will not unlink again
|
|
||||||
uv->v = &uv->u.value;
|
|
||||||
}
|
}
|
||||||
else
|
}
|
||||||
|
|
||||||
|
void luaF_closeupval(lua_State* L, UpVal* uv, bool dead)
|
||||||
{
|
{
|
||||||
|
// unlink value from all lists *before* closing it since value storage overlaps
|
||||||
|
LUAU_ASSERT(uv->u.open.next->u.open.prev == uv && uv->u.open.prev->u.open.next == uv);
|
||||||
|
uv->u.open.next->u.open.prev = uv->u.open.prev;
|
||||||
|
uv->u.open.prev->u.open.next = uv->u.open.next;
|
||||||
|
|
||||||
|
if (dead)
|
||||||
|
return;
|
||||||
|
|
||||||
setobj(L, &uv->u.value, uv->v);
|
setobj(L, &uv->u.value, uv->v);
|
||||||
uv->v = &uv->u.value;
|
uv->v = &uv->u.value;
|
||||||
// GC state of a new closed upvalue has to be initialized
|
luaC_upvalclosed(L, uv);
|
||||||
luaC_initupval(L, uv);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void luaF_freeproto(lua_State* L, Proto* f, lua_Page* page)
|
void luaF_freeproto(lua_State* L, Proto* f, lua_Page* page)
|
||||||
|
|
|
@ -12,9 +12,9 @@ LUAI_FUNC Closure* luaF_newLclosure(lua_State* L, int nelems, Table* e, Proto* p
|
||||||
LUAI_FUNC Closure* luaF_newCclosure(lua_State* L, int nelems, Table* e);
|
LUAI_FUNC Closure* luaF_newCclosure(lua_State* L, int nelems, Table* e);
|
||||||
LUAI_FUNC UpVal* luaF_findupval(lua_State* L, StkId level);
|
LUAI_FUNC UpVal* luaF_findupval(lua_State* L, StkId level);
|
||||||
LUAI_FUNC void luaF_close(lua_State* L, StkId level);
|
LUAI_FUNC void luaF_close(lua_State* L, StkId level);
|
||||||
|
LUAI_FUNC void luaF_closeupval(lua_State* L, UpVal* uv, bool dead);
|
||||||
LUAI_FUNC void luaF_freeproto(lua_State* L, Proto* f, struct lua_Page* page);
|
LUAI_FUNC void luaF_freeproto(lua_State* L, Proto* f, struct lua_Page* page);
|
||||||
LUAI_FUNC void luaF_freeclosure(lua_State* L, Closure* c, struct lua_Page* page);
|
LUAI_FUNC void luaF_freeclosure(lua_State* L, Closure* c, struct lua_Page* page);
|
||||||
LUAI_FUNC void luaF_unlinkupval(UpVal* uv);
|
|
||||||
LUAI_FUNC void luaF_freeupval(lua_State* L, UpVal* uv, struct lua_Page* page);
|
LUAI_FUNC void luaF_freeupval(lua_State* L, UpVal* uv, struct lua_Page* page);
|
||||||
LUAI_FUNC const LocVar* luaF_getlocal(const Proto* func, int local_number, int pc);
|
LUAI_FUNC const LocVar* luaF_getlocal(const Proto* func, int local_number, int pc);
|
||||||
LUAI_FUNC const LocVar* luaF_findlocal(const Proto* func, int local_reg, int pc);
|
LUAI_FUNC const LocVar* luaF_findlocal(const Proto* func, int local_reg, int pc);
|
||||||
|
|
|
@ -13,6 +13,113 @@
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauBetterThreadMark, false)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Luau uses an incremental non-generational non-moving mark&sweep garbage collector.
|
||||||
|
*
|
||||||
|
* The collector runs in three stages: mark, atomic and sweep. Mark and sweep are incremental and try to do a limited amount
|
||||||
|
* of work every GC step; atomic is ran once per the GC cycle and is indivisible. In either case, the work happens during GC
|
||||||
|
* steps that are "scheduled" by the GC pacing algorithm - the steps happen either from explicit calls to lua_gc, or after
|
||||||
|
* the mutator (aka application) allocates some amount of memory, which is known as "GC assist". In either case, GC steps
|
||||||
|
* can't happen concurrently with other access to VM state.
|
||||||
|
*
|
||||||
|
* Current GC stage is stored in global_State::gcstate, and has two additional stages for pause and second-phase mark, explained below.
|
||||||
|
*
|
||||||
|
* GC pacer is an algorithm that tries to ensure that GC can always catch up to the application allocating garbage, but do this
|
||||||
|
* with minimal amount of effort. To configure the pacer Luau provides control over three variables: GC goal, defined as the
|
||||||
|
* target heap size during atomic phase in relation to live heap size (e.g. 200% goal means the heap's worst case size is double
|
||||||
|
* the total size of alive objects), step size (how many kilobytes should the application allocate for GC step to trigger), and
|
||||||
|
* GC multiplier (how much should the GC try to mark relative to how much the application allocated). It's critical that step
|
||||||
|
* multiplier is significantly above 1, as this is what allows the GC to catch up to the application's allocation rate, and
|
||||||
|
* GC goal and GC multiplier are linked in subtle ways, described in lua.h comments for LUA_GCSETGOAL.
|
||||||
|
*
|
||||||
|
* During mark, GC tries to identify all reachable objects and mark them as reachable, while keeping unreachable objects unmarked.
|
||||||
|
* During sweep, GC tries to sweep all objects that were not reachable at the end of mark. The atomic phase is needed to ensure
|
||||||
|
* that all pending marking has completed and all objects that are still marked as unreachable are, in fact, unreachable.
|
||||||
|
*
|
||||||
|
* Notably, during mark GC doesn't free any objects, and so the heap size constantly grows; during sweep, GC doesn't do any marking
|
||||||
|
* work, so it can't immediately free objects that became unreachable after sweeping started.
|
||||||
|
*
|
||||||
|
* Every collectable object has one of three colors at any given point in time: white, gray or black. This coloring scheme
|
||||||
|
* is necessary to implement incremental marking: white objects have not been marked and may be unreachable, black objects
|
||||||
|
* have been marked and will not be marked again if they stay black, and gray objects have been marked but may contain unmarked
|
||||||
|
* references.
|
||||||
|
*
|
||||||
|
* Objects are allocated as white; however, during sweep, we need to differentiate between objects that remained white in the mark
|
||||||
|
* phase (these are not reachable and can be freed) and objects that were allocated after the mark phase ended. Because of this, the
|
||||||
|
* colors are encoded using three bits inside GCheader::marked: white0, white1 and black (so technically we use a four-color scheme:
|
||||||
|
* any object can be white0, white1, gray or black). All bits are exclusive, and gray objects have all three bits unset. This allows
|
||||||
|
* us to have the "current" white bit, which is flipped during atomic stage - during sweeping, objects that have the white color from
|
||||||
|
* the previous mark may be deleted, and all other objects may or may not be reachable, and will be changed to the current white color,
|
||||||
|
* so that the next mark can start coloring objects from scratch again.
|
||||||
|
*
|
||||||
|
* Crucially, the coloring scheme comes with what's known as a tri-color invariant: a black object may never point to a white object.
|
||||||
|
*
|
||||||
|
* At the end of atomic stage, the expectation is that there are no gray objects anymore, which means all objects are either black
|
||||||
|
* (reachable) or white (unreachable = dead). Tri-color invariant is maintained throughout mark and atomic phase. To uphold this
|
||||||
|
* invariant, every modification of an object needs to check if the object is black and the new referent is white; if so, we
|
||||||
|
* need to either mark the referent, making it non-white (known as a forward barrier), or mark the object as gray and queue it
|
||||||
|
* for additional marking (known as a backward barrier).
|
||||||
|
*
|
||||||
|
* Luau uses both types of barriers. Forward barriers advance GC progress, since they don't create new outstanding work for GC,
|
||||||
|
* but they may be expensive when an object is modified many times in succession. Backward barriers are cheaper, as they defer
|
||||||
|
* most of the work until "later", but they require queueing the object for a rescan which isn't always possible. Table writes usually
|
||||||
|
* use backward barriers (but switch to forward barriers during second-phase mark), whereas upvalue writes and setmetatable use forward
|
||||||
|
* barriers.
|
||||||
|
*
|
||||||
|
* Since marking is incremental, it needs a way to track progress, which is implemented as a gray set: at any point, objects that
|
||||||
|
* are gray need to mark their white references, objects that are black have no pending work, and objects that are white have not yet
|
||||||
|
* been reached. Once the gray set is empty, the work completes; as such, incremental marking is as simple as removing an object from
|
||||||
|
* the gray set, and turning it to black (which requires turning all its white references to gray). The gray set is implemented as
|
||||||
|
* an intrusive singly linked list, using `gclist` field in multiple objects (functions, tables, threads and protos). When an object
|
||||||
|
* doesn't have gclist field, the marking of that object needs to be "immediate", changing the colors of all references in one go.
|
||||||
|
*
|
||||||
|
* When a black object is modified, it needs to become gray again. Objects like this are placed on a separate `grayagain` list by a
|
||||||
|
* barrier - this is important because it allows us to have a mark stage that terminates when the gray set is empty even if the mutator
|
||||||
|
* is constantly changing existing objects to gray. After mark stage finishes traversing `gray` list, we copy `grayagain` list to `gray`
|
||||||
|
* once and incrementally mark it again. During this phase of marking, we may get more objects marked as `grayagain`, so after we finish
|
||||||
|
* emptying out the `gray` list the second time, we finish the mark stage and do final marking of `grayagain` during atomic phase.
|
||||||
|
* GC works correctly without this second-phase mark (called GCSpropagateagain), but it reduces the time spent during atomic phase.
|
||||||
|
*
|
||||||
|
* Sweeping is also incremental, but instead of working at a granularity of an object, it works at a granularity of a page: all GC
|
||||||
|
* objects are allocated in special pages (see lmem.cpp for details), and sweeper traverses all objects in one page in one incremental
|
||||||
|
* step, freeing objects that aren't reachable (old white), and recoloring all other objects with the new white to prepare them for next
|
||||||
|
* mark. During sweeping we don't need to maintain the GC invariant, because our goal is to paint all objects with current white -
|
||||||
|
* however, some barriers will still trigger (because some reachable objects are still black as sweeping didn't get to them yet), and
|
||||||
|
* some barriers will proactively mark black objects as white to avoid extra barriers from triggering excessively.
|
||||||
|
*
|
||||||
|
* Most references that GC deals with are strong, and as such they fit neatly into the incremental marking scheme. Some, however, are
|
||||||
|
* weak - notably, tables can be marked as having weak keys/values (using __mode metafield). During incremental marking, we don't know
|
||||||
|
* for certain if a given object is alive - if it's marked as black, it definitely was reachable during marking, but if it's marked as
|
||||||
|
* white, we don't know if it's actually unreachable. Because of this, we need to defer weak table handling to the atomic phase; after
|
||||||
|
* all objects are marked, we traverse all weak tables (that are linked into special weak table lists using `gclist` during marking),
|
||||||
|
* and remove all entries that have white keys or values. If keys or values are strong, they are marked normally.
|
||||||
|
*
|
||||||
|
* The simplified scheme described above isn't fully accurate because of threads, upvalues and strings.
|
||||||
|
*
|
||||||
|
* Strings are semantically black (they are initially white, and when the mark stage reaches a string, it changes its color and never
|
||||||
|
* touches the object again), but they are technically marked as gray - the black bit is never set on a string object. This behavior
|
||||||
|
* is inherited from Lua 5.1 GC, but doesn't have a clear rationale - effectively, strings are marked as gray but are never part of
|
||||||
|
* a gray list.
|
||||||
|
*
|
||||||
|
* Threads are hard to deal with because for them to fit into the white-gray-black scheme, writes to thread stacks need to have barriers
|
||||||
|
* that turn the thread from black (already scanned) to gray - but this is very expensive because stack writes are very common. To
|
||||||
|
* get around this problem, threads have an "active" state which means that a thread is actively executing code. When GC reaches an active
|
||||||
|
* thread, it keeps it as gray, and rescans it during atomic phase. When a thread is inactive, GC instead paints the thread black. All
|
||||||
|
* API calls that can write to thread stacks outside of execution (which implies active) uses a thread barrier that checks if the thread is
|
||||||
|
* black, and if it is it marks it as gray and puts it on a gray list to be rescanned during atomic phase.
|
||||||
|
*
|
||||||
|
* Upvalues are special objects that can be closed, in which case they contain the value (acting as a reference cell) and can be dealt
|
||||||
|
* with using the regular algorithm, or open, in which case they refer to a stack slot in some other thread. These are difficult to deal
|
||||||
|
* with because the stack writes are not monitored. Because of this open upvalues are treated in a somewhat special way: they are never marked
|
||||||
|
* as black (doing so would violate the GC invariant), and they are kept in a special global list (global_State::uvhead) which is traversed
|
||||||
|
* during atomic phase. This is needed because an open upvalue might point to a stack location in a dead thread that never marked the stack
|
||||||
|
* slot - upvalues like this are identified since they don't have `markedopen` bit set during thread traversal and closed in `clearupvals`.
|
||||||
|
*/
|
||||||
|
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauFasterSweep, false)
|
||||||
|
|
||||||
#define GC_SWEEPPAGESTEPCOST 16
|
#define GC_SWEEPPAGESTEPCOST 16
|
||||||
|
|
||||||
#define GC_INTERRUPT(state) \
|
#define GC_INTERRUPT(state) \
|
||||||
|
@ -150,7 +257,7 @@ static void reallymarkobject(global_State* g, GCObject* o)
|
||||||
{
|
{
|
||||||
UpVal* uv = gco2uv(o);
|
UpVal* uv = gco2uv(o);
|
||||||
markvalue(g, uv->v);
|
markvalue(g, uv->v);
|
||||||
if (uv->v == &uv->u.value) // closed?
|
if (!upisopen(uv)) // closed?
|
||||||
gray2black(o); // open upvalues are never black
|
gray2black(o); // open upvalues are never black
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -289,20 +396,50 @@ static void traverseclosure(global_State* g, Closure* cl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void traversestack(global_State* g, lua_State* l, bool clearstack)
|
static void traversestack(global_State* g, lua_State* l)
|
||||||
{
|
{
|
||||||
markobject(g, l->gt);
|
markobject(g, l->gt);
|
||||||
if (l->namecall)
|
if (l->namecall)
|
||||||
stringmark(l->namecall);
|
stringmark(l->namecall);
|
||||||
for (StkId o = l->stack; o < l->top; o++)
|
for (StkId o = l->stack; o < l->top; o++)
|
||||||
markvalue(g, o);
|
markvalue(g, o);
|
||||||
// final traversal?
|
for (UpVal* uv = l->openupval; uv; uv = uv->u.open.threadnext)
|
||||||
if (g->gcstate == GCSatomic || clearstack)
|
{
|
||||||
|
LUAU_ASSERT(upisopen(uv));
|
||||||
|
uv->markedopen = 1;
|
||||||
|
markobject(g, uv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void clearstack(lua_State* l)
|
||||||
{
|
{
|
||||||
StkId stack_end = l->stack + l->stacksize;
|
StkId stack_end = l->stack + l->stacksize;
|
||||||
for (StkId o = l->top; o < stack_end; o++) // clear not-marked stack slice
|
for (StkId o = l->top; o < stack_end; o++) // clear not-marked stack slice
|
||||||
setnilvalue(o);
|
setnilvalue(o);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void shrinkstack(lua_State* L)
|
||||||
|
{
|
||||||
|
// compute used stack - note that we can't use th->top if we're in the middle of vararg call
|
||||||
|
StkId lim = L->top;
|
||||||
|
for (CallInfo* ci = L->base_ci; ci <= L->ci; ci++)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(ci->top <= L->stack_last);
|
||||||
|
if (lim < ci->top)
|
||||||
|
lim = ci->top;
|
||||||
|
}
|
||||||
|
|
||||||
|
// shrink stack and callinfo arrays if we aren't using most of the space
|
||||||
|
int ci_used = cast_int(L->ci - L->base_ci); // number of `ci' in use
|
||||||
|
int s_used = cast_int(lim - L->stack); // part of stack in use
|
||||||
|
if (L->size_ci > LUAI_MAXCALLS) // handling overflow?
|
||||||
|
return; // do not touch the stacks
|
||||||
|
if (3 * ci_used < L->size_ci && 2 * BASIC_CI_SIZE < L->size_ci)
|
||||||
|
luaD_reallocCI(L, L->size_ci / 2); // still big enough...
|
||||||
|
condhardstacktests(luaD_reallocCI(L, ci_used + 1));
|
||||||
|
if (3 * s_used < L->stacksize && 2 * (BASIC_STACK_SIZE + EXTRA_STACK) < L->stacksize)
|
||||||
|
luaD_reallocstack(L, L->stacksize / 2); // still big enough...
|
||||||
|
condhardstacktests(luaD_reallocstack(L, s_used));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -336,16 +473,37 @@ static size_t propagatemark(global_State* g)
|
||||||
lua_State* th = gco2th(o);
|
lua_State* th = gco2th(o);
|
||||||
g->gray = th->gclist;
|
g->gray = th->gclist;
|
||||||
|
|
||||||
LUAU_ASSERT(!luaC_threadsleeping(th));
|
bool active = th->isactive || th == th->global->mainthread;
|
||||||
|
|
||||||
// threads that are executing and the main thread are not deactivated
|
if (FFlag::LuauBetterThreadMark)
|
||||||
bool active = luaC_threadactive(th) || th == th->global->mainthread;
|
{
|
||||||
|
traversestack(g, th);
|
||||||
|
|
||||||
|
// active threads will need to be rescanned later to mark new stack writes so we mark them gray again
|
||||||
|
if (active)
|
||||||
|
{
|
||||||
|
th->gclist = g->grayagain;
|
||||||
|
g->grayagain = o;
|
||||||
|
|
||||||
|
black2gray(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
// the stack needs to be cleared after the last modification of the thread state before sweep begins
|
||||||
|
// if the thread is inactive, we might not see the thread in this cycle so we must clear it now
|
||||||
|
if (!active || g->gcstate == GCSatomic)
|
||||||
|
clearstack(th);
|
||||||
|
|
||||||
|
// we could shrink stack at any time but we opt to do it during initial mark to do that just once per cycle
|
||||||
|
if (g->gcstate == GCSpropagate)
|
||||||
|
shrinkstack(th);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// TODO: Refactor this logic!
|
||||||
if (!active && g->gcstate == GCSpropagate)
|
if (!active && g->gcstate == GCSpropagate)
|
||||||
{
|
{
|
||||||
traversestack(g, th, /* clearstack= */ true);
|
traversestack(g, th);
|
||||||
|
clearstack(th);
|
||||||
l_setbit(th->stackstate, THREAD_SLEEPINGBIT);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -354,7 +512,16 @@ static size_t propagatemark(global_State* g)
|
||||||
|
|
||||||
black2gray(o);
|
black2gray(o);
|
||||||
|
|
||||||
traversestack(g, th, /* clearstack= */ false);
|
traversestack(g, th);
|
||||||
|
|
||||||
|
// final traversal?
|
||||||
|
if (g->gcstate == GCSatomic)
|
||||||
|
clearstack(th);
|
||||||
|
}
|
||||||
|
|
||||||
|
// we could shrink stack at any time but we opt to skip it during atomic since it's redundant to do that more than once per cycle
|
||||||
|
if (g->gcstate != GCSatomic)
|
||||||
|
shrinkstack(th);
|
||||||
}
|
}
|
||||||
|
|
||||||
return sizeof(lua_State) + sizeof(TValue) * th->stacksize + sizeof(CallInfo) * th->size_ci;
|
return sizeof(lua_State) + sizeof(TValue) * th->stacksize + sizeof(CallInfo) * th->size_ci;
|
||||||
|
@ -458,30 +625,6 @@ static size_t cleartable(lua_State* L, GCObject* l)
|
||||||
return work;
|
return work;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void shrinkstack(lua_State* L)
|
|
||||||
{
|
|
||||||
// compute used stack - note that we can't use th->top if we're in the middle of vararg call
|
|
||||||
StkId lim = L->top;
|
|
||||||
for (CallInfo* ci = L->base_ci; ci <= L->ci; ci++)
|
|
||||||
{
|
|
||||||
LUAU_ASSERT(ci->top <= L->stack_last);
|
|
||||||
if (lim < ci->top)
|
|
||||||
lim = ci->top;
|
|
||||||
}
|
|
||||||
|
|
||||||
// shrink stack and callinfo arrays if we aren't using most of the space
|
|
||||||
int ci_used = cast_int(L->ci - L->base_ci); // number of `ci' in use
|
|
||||||
int s_used = cast_int(lim - L->stack); // part of stack in use
|
|
||||||
if (L->size_ci > LUAI_MAXCALLS) // handling overflow?
|
|
||||||
return; // do not touch the stacks
|
|
||||||
if (3 * ci_used < L->size_ci && 2 * BASIC_CI_SIZE < L->size_ci)
|
|
||||||
luaD_reallocCI(L, L->size_ci / 2); // still big enough...
|
|
||||||
condhardstacktests(luaD_reallocCI(L, ci_used + 1));
|
|
||||||
if (3 * s_used < L->stacksize && 2 * (BASIC_STACK_SIZE + EXTRA_STACK) < L->stacksize)
|
|
||||||
luaD_reallocstack(L, L->stacksize / 2); // still big enough...
|
|
||||||
condhardstacktests(luaD_reallocstack(L, s_used));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void freeobj(lua_State* L, GCObject* o, lua_Page* page)
|
static void freeobj(lua_State* L, GCObject* o, lua_Page* page)
|
||||||
{
|
{
|
||||||
switch (o->gch.tt)
|
switch (o->gch.tt)
|
||||||
|
@ -534,21 +677,6 @@ static void shrinkbuffersfull(lua_State* L)
|
||||||
|
|
||||||
static bool deletegco(void* context, lua_Page* page, GCObject* gco)
|
static bool deletegco(void* context, lua_Page* page, GCObject* gco)
|
||||||
{
|
{
|
||||||
// we are in the process of deleting everything
|
|
||||||
// threads with open upvalues will attempt to close them all on removal
|
|
||||||
// but those upvalues might point to stack values that were already deleted
|
|
||||||
if (gco->gch.tt == LUA_TTHREAD)
|
|
||||||
{
|
|
||||||
lua_State* th = gco2th(gco);
|
|
||||||
|
|
||||||
while (UpVal* uv = th->openupval)
|
|
||||||
{
|
|
||||||
luaF_unlinkupval(uv);
|
|
||||||
// close the upvalue without copying the dead data so that luaF_freeupval will not unlink again
|
|
||||||
uv->v = &uv->u.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lua_State* L = (lua_State*)context;
|
lua_State* L = (lua_State*)context;
|
||||||
freeobj(L, gco, page);
|
freeobj(L, gco, page);
|
||||||
return true;
|
return true;
|
||||||
|
@ -566,7 +694,6 @@ void luaC_freeall(lua_State* L)
|
||||||
LUAU_ASSERT(g->strt.hash[i] == NULL);
|
LUAU_ASSERT(g->strt.hash[i] == NULL);
|
||||||
|
|
||||||
LUAU_ASSERT(L->global->strt.nuse == 0);
|
LUAU_ASSERT(L->global->strt.nuse == 0);
|
||||||
LUAU_ASSERT(g->strbufgc == NULL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void markmt(global_State* g)
|
static void markmt(global_State* g)
|
||||||
|
@ -595,13 +722,53 @@ static void markroot(lua_State* L)
|
||||||
static size_t remarkupvals(global_State* g)
|
static size_t remarkupvals(global_State* g)
|
||||||
{
|
{
|
||||||
size_t work = 0;
|
size_t work = 0;
|
||||||
for (UpVal* uv = g->uvhead.u.l.next; uv != &g->uvhead; uv = uv->u.l.next)
|
|
||||||
|
for (UpVal* uv = g->uvhead.u.open.next; uv != &g->uvhead; uv = uv->u.open.next)
|
||||||
{
|
{
|
||||||
work += sizeof(UpVal);
|
work += sizeof(UpVal);
|
||||||
LUAU_ASSERT(uv->u.l.next->u.l.prev == uv && uv->u.l.prev->u.l.next == uv);
|
|
||||||
|
LUAU_ASSERT(upisopen(uv));
|
||||||
|
LUAU_ASSERT(uv->u.open.next->u.open.prev == uv && uv->u.open.prev->u.open.next == uv);
|
||||||
|
LUAU_ASSERT(!isblack(obj2gco(uv))); // open upvalues are never black
|
||||||
|
|
||||||
if (isgray(obj2gco(uv)))
|
if (isgray(obj2gco(uv)))
|
||||||
markvalue(g, uv->v);
|
markvalue(g, uv->v);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return work;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t clearupvals(lua_State* L)
|
||||||
|
{
|
||||||
|
global_State* g = L->global;
|
||||||
|
|
||||||
|
size_t work = 0;
|
||||||
|
|
||||||
|
for (UpVal* uv = g->uvhead.u.open.next; uv != &g->uvhead;)
|
||||||
|
{
|
||||||
|
work += sizeof(UpVal);
|
||||||
|
|
||||||
|
LUAU_ASSERT(upisopen(uv));
|
||||||
|
LUAU_ASSERT(uv->u.open.next->u.open.prev == uv && uv->u.open.prev->u.open.next == uv);
|
||||||
|
LUAU_ASSERT(!isblack(obj2gco(uv))); // open upvalues are never black
|
||||||
|
LUAU_ASSERT(iswhite(obj2gco(uv)) || !iscollectable(uv->v) || !iswhite(gcvalue(uv->v)));
|
||||||
|
|
||||||
|
if (uv->markedopen)
|
||||||
|
{
|
||||||
|
// upvalue is still open (belongs to alive thread)
|
||||||
|
LUAU_ASSERT(isgray(obj2gco(uv)));
|
||||||
|
uv->markedopen = 0; // for next cycle
|
||||||
|
uv = uv->u.open.next;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// upvalue is either dead, or alive but the thread is dead; unlink and close
|
||||||
|
UpVal* next = uv->u.open.next;
|
||||||
|
luaF_closeupval(L, uv, /* dead= */ iswhite(obj2gco(uv)));
|
||||||
|
uv = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return work;
|
return work;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -654,6 +821,13 @@ static size_t atomic(lua_State* L)
|
||||||
g->gcmetrics.currcycle.atomictimeclear += recordGcDeltaTime(currts);
|
g->gcmetrics.currcycle.atomictimeclear += recordGcDeltaTime(currts);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// close orphaned live upvalues of dead threads and clear dead upvalues
|
||||||
|
work += clearupvals(L);
|
||||||
|
|
||||||
|
#ifdef LUAI_GCMETRICS
|
||||||
|
g->gcmetrics.currcycle.atomictimeupval += recordGcDeltaTime(currts);
|
||||||
|
#endif
|
||||||
|
|
||||||
// flip current white
|
// flip current white
|
||||||
g->currentwhite = cast_byte(otherwhite(g));
|
g->currentwhite = cast_byte(otherwhite(g));
|
||||||
g->sweepgcopage = g->allgcopages;
|
g->sweepgcopage = g->allgcopages;
|
||||||
|
@ -664,6 +838,7 @@ static size_t atomic(lua_State* L)
|
||||||
|
|
||||||
static bool sweepgco(lua_State* L, lua_Page* page, GCObject* gco)
|
static bool sweepgco(lua_State* L, lua_Page* page, GCObject* gco)
|
||||||
{
|
{
|
||||||
|
LUAU_ASSERT(!FFlag::LuauFasterSweep);
|
||||||
global_State* g = L->global;
|
global_State* g = L->global;
|
||||||
|
|
||||||
int deadmask = otherwhite(g);
|
int deadmask = otherwhite(g);
|
||||||
|
@ -671,17 +846,6 @@ static bool sweepgco(lua_State* L, lua_Page* page, GCObject* gco)
|
||||||
|
|
||||||
int alive = (gco->gch.marked ^ WHITEBITS) & deadmask;
|
int alive = (gco->gch.marked ^ WHITEBITS) & deadmask;
|
||||||
|
|
||||||
if (gco->gch.tt == LUA_TTHREAD)
|
|
||||||
{
|
|
||||||
lua_State* th = gco2th(gco);
|
|
||||||
|
|
||||||
if (alive)
|
|
||||||
{
|
|
||||||
resetbit(th->stackstate, THREAD_SLEEPINGBIT);
|
|
||||||
shrinkstack(th);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (alive)
|
if (alive)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(!isdead(g, gco));
|
LUAU_ASSERT(!isdead(g, gco));
|
||||||
|
@ -703,6 +867,45 @@ static int sweepgcopage(lua_State* L, lua_Page* page)
|
||||||
int blockSize;
|
int blockSize;
|
||||||
luaM_getpagewalkinfo(page, &start, &end, &busyBlocks, &blockSize);
|
luaM_getpagewalkinfo(page, &start, &end, &busyBlocks, &blockSize);
|
||||||
|
|
||||||
|
LUAU_ASSERT(busyBlocks > 0);
|
||||||
|
|
||||||
|
if (FFlag::LuauFasterSweep)
|
||||||
|
{
|
||||||
|
global_State* g = L->global;
|
||||||
|
|
||||||
|
int deadmask = otherwhite(g);
|
||||||
|
LUAU_ASSERT(testbit(deadmask, FIXEDBIT)); // make sure we never sweep fixed objects
|
||||||
|
|
||||||
|
int newwhite = luaC_white(g);
|
||||||
|
|
||||||
|
for (char* pos = start; pos != end; pos += blockSize)
|
||||||
|
{
|
||||||
|
GCObject* gco = (GCObject*)pos;
|
||||||
|
|
||||||
|
// skip memory blocks that are already freed
|
||||||
|
if (gco->gch.tt == LUA_TNIL)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// is the object alive?
|
||||||
|
if ((gco->gch.marked ^ WHITEBITS) & deadmask)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(!isdead(g, gco));
|
||||||
|
// make it white (for next cycle)
|
||||||
|
gco->gch.marked = cast_byte((gco->gch.marked & maskmarks) | newwhite);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(isdead(g, gco));
|
||||||
|
freeobj(L, gco, page);
|
||||||
|
|
||||||
|
// if the last block was removed, page would be removed as well
|
||||||
|
if (--busyBlocks == 0)
|
||||||
|
return int(pos - start) / blockSize + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
for (char* pos = start; pos != end; pos += blockSize)
|
for (char* pos = start; pos != end; pos += blockSize)
|
||||||
{
|
{
|
||||||
GCObject* gco = (GCObject*)pos;
|
GCObject* gco = (GCObject*)pos;
|
||||||
|
@ -714,13 +917,12 @@ static int sweepgcopage(lua_State* L, lua_Page* page)
|
||||||
// when true is returned it means that the element was deleted
|
// when true is returned it means that the element was deleted
|
||||||
if (sweepgco(L, page, gco))
|
if (sweepgco(L, page, gco))
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(busyBlocks > 0);
|
|
||||||
|
|
||||||
// if the last block was removed, page would be removed as well
|
// if the last block was removed, page would be removed as well
|
||||||
if (--busyBlocks == 0)
|
if (--busyBlocks == 0)
|
||||||
return int(pos - start) / blockSize + 1;
|
return int(pos - start) / blockSize + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return int(end - start) / blockSize;
|
return int(end - start) / blockSize;
|
||||||
}
|
}
|
||||||
|
@ -806,10 +1008,19 @@ static size_t gcstep(lua_State* L, size_t limit)
|
||||||
// nothing more to sweep?
|
// nothing more to sweep?
|
||||||
if (g->sweepgcopage == NULL)
|
if (g->sweepgcopage == NULL)
|
||||||
{
|
{
|
||||||
// don't forget to visit main thread
|
// don't forget to visit main thread, it's the only object not allocated in GCO pages
|
||||||
|
if (FFlag::LuauFasterSweep)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(!isdead(g, obj2gco(g->mainthread)));
|
||||||
|
makewhite(g, obj2gco(g->mainthread)); // make it white (for next cycle)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
sweepgco(L, NULL, obj2gco(g->mainthread));
|
sweepgco(L, NULL, obj2gco(g->mainthread));
|
||||||
|
}
|
||||||
|
|
||||||
shrinkbuffers(L);
|
shrinkbuffers(L);
|
||||||
|
|
||||||
g->gcstate = GCSpause; // end collection
|
g->gcstate = GCSpause; // end collection
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -945,7 +1156,7 @@ void luaC_fullgc(lua_State* L)
|
||||||
startGcCycleMetrics(g);
|
startGcCycleMetrics(g);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (g->gcstate <= GCSatomic)
|
if (keepinvariant(g))
|
||||||
{
|
{
|
||||||
// reset sweep marks to sweep all elements (returning them to white)
|
// reset sweep marks to sweep all elements (returning them to white)
|
||||||
g->sweepgcopage = g->allgcopages;
|
g->sweepgcopage = g->allgcopages;
|
||||||
|
@ -955,7 +1166,7 @@ void luaC_fullgc(lua_State* L)
|
||||||
g->weak = NULL;
|
g->weak = NULL;
|
||||||
g->gcstate = GCSsweep;
|
g->gcstate = GCSsweep;
|
||||||
}
|
}
|
||||||
LUAU_ASSERT(g->gcstate == GCSsweep);
|
LUAU_ASSERT(g->gcstate == GCSpause || g->gcstate == GCSsweep);
|
||||||
// finish any pending sweep phase
|
// finish any pending sweep phase
|
||||||
while (g->gcstate != GCSpause)
|
while (g->gcstate != GCSpause)
|
||||||
{
|
{
|
||||||
|
@ -963,6 +1174,13 @@ void luaC_fullgc(lua_State* L)
|
||||||
gcstep(L, SIZE_MAX);
|
gcstep(L, SIZE_MAX);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// clear markedopen bits for all open upvalues; these might be stuck from half-finished mark prior to full gc
|
||||||
|
for (UpVal* uv = g->uvhead.u.open.next; uv != &g->uvhead; uv = uv->u.open.next)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(upisopen(uv));
|
||||||
|
uv->markedopen = 0;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef LUAI_GCMETRICS
|
#ifdef LUAI_GCMETRICS
|
||||||
finishGcCycleMetrics(g);
|
finishGcCycleMetrics(g);
|
||||||
startGcCycleMetrics(g);
|
startGcCycleMetrics(g);
|
||||||
|
@ -997,15 +1215,6 @@ void luaC_fullgc(lua_State* L)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void luaC_barrierupval(lua_State* L, GCObject* v)
|
|
||||||
{
|
|
||||||
global_State* g = L->global;
|
|
||||||
LUAU_ASSERT(iswhite(v) && !isdead(g, v));
|
|
||||||
|
|
||||||
if (keepinvariant(g))
|
|
||||||
reallymarkobject(g, v);
|
|
||||||
}
|
|
||||||
|
|
||||||
void luaC_barrierf(lua_State* L, GCObject* o, GCObject* v)
|
void luaC_barrierf(lua_State* L, GCObject* o, GCObject* v)
|
||||||
{
|
{
|
||||||
global_State* g = L->global;
|
global_State* g = L->global;
|
||||||
|
@ -1038,30 +1247,24 @@ void luaC_barriertable(lua_State* L, Table* t, GCObject* v)
|
||||||
g->grayagain = o;
|
g->grayagain = o;
|
||||||
}
|
}
|
||||||
|
|
||||||
void luaC_barrierback(lua_State* L, Table* t)
|
void luaC_barrierback(lua_State* L, GCObject* o, GCObject** gclist)
|
||||||
{
|
{
|
||||||
global_State* g = L->global;
|
global_State* g = L->global;
|
||||||
GCObject* o = obj2gco(t);
|
|
||||||
LUAU_ASSERT(isblack(o) && !isdead(g, o));
|
LUAU_ASSERT(isblack(o) && !isdead(g, o));
|
||||||
LUAU_ASSERT(g->gcstate != GCSpause);
|
LUAU_ASSERT(g->gcstate != GCSpause);
|
||||||
black2gray(o); // make table gray (again)
|
|
||||||
t->gclist = g->grayagain;
|
black2gray(o); // make object gray (again)
|
||||||
|
*gclist = g->grayagain;
|
||||||
g->grayagain = o;
|
g->grayagain = o;
|
||||||
}
|
}
|
||||||
|
|
||||||
void luaC_initobj(lua_State* L, GCObject* o, uint8_t tt)
|
void luaC_upvalclosed(lua_State* L, UpVal* uv)
|
||||||
{
|
|
||||||
global_State* g = L->global;
|
|
||||||
o->gch.marked = luaC_white(g);
|
|
||||||
o->gch.tt = tt;
|
|
||||||
o->gch.memcat = L->activememcat;
|
|
||||||
}
|
|
||||||
|
|
||||||
void luaC_initupval(lua_State* L, UpVal* uv)
|
|
||||||
{
|
{
|
||||||
global_State* g = L->global;
|
global_State* g = L->global;
|
||||||
GCObject* o = obj2gco(uv);
|
GCObject* o = obj2gco(uv);
|
||||||
|
|
||||||
|
LUAU_ASSERT(!upisopen(uv)); // upvalue was closed but needs GC state fixup
|
||||||
|
|
||||||
if (isgray(o))
|
if (isgray(o))
|
||||||
{
|
{
|
||||||
if (keepinvariant(g))
|
if (keepinvariant(g))
|
||||||
|
@ -1103,26 +1306,6 @@ int64_t luaC_allocationrate(lua_State* L)
|
||||||
return int64_t((g->gcstats.atomicstarttotalsizebytes - g->gcstats.endtotalsizebytes) / duration);
|
return int64_t((g->gcstats.atomicstarttotalsizebytes - g->gcstats.endtotalsizebytes) / duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
void luaC_wakethread(lua_State* L)
|
|
||||||
{
|
|
||||||
if (!luaC_threadsleeping(L))
|
|
||||||
return;
|
|
||||||
|
|
||||||
global_State* g = L->global;
|
|
||||||
|
|
||||||
resetbit(L->stackstate, THREAD_SLEEPINGBIT);
|
|
||||||
|
|
||||||
if (keepinvariant(g))
|
|
||||||
{
|
|
||||||
GCObject* o = obj2gco(L);
|
|
||||||
|
|
||||||
L->gclist = g->grayagain;
|
|
||||||
g->grayagain = o;
|
|
||||||
|
|
||||||
black2gray(o);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* luaC_statename(int state)
|
const char* luaC_statename(int state)
|
||||||
{
|
{
|
||||||
switch (state)
|
switch (state)
|
||||||
|
|
|
@ -73,13 +73,6 @@
|
||||||
|
|
||||||
#define luaC_white(g) cast_to(uint8_t, ((g)->currentwhite) & WHITEBITS)
|
#define luaC_white(g) cast_to(uint8_t, ((g)->currentwhite) & WHITEBITS)
|
||||||
|
|
||||||
// Thread stack states
|
|
||||||
#define THREAD_ACTIVEBIT 0 // thread is currently active
|
|
||||||
#define THREAD_SLEEPINGBIT 1 // thread is not executing and stack should not be modified
|
|
||||||
|
|
||||||
#define luaC_threadactive(L) (testbit((L)->stackstate, THREAD_ACTIVEBIT))
|
|
||||||
#define luaC_threadsleeping(L) (testbit((L)->stackstate, THREAD_SLEEPINGBIT))
|
|
||||||
|
|
||||||
#define luaC_checkGC(L) \
|
#define luaC_checkGC(L) \
|
||||||
{ \
|
{ \
|
||||||
condhardstacktests(luaD_reallocstack(L, L->stacksize - EXTRA_STACK)); \
|
condhardstacktests(luaD_reallocstack(L, L->stacksize - EXTRA_STACK)); \
|
||||||
|
@ -109,7 +102,7 @@
|
||||||
#define luaC_barrierfast(L, t) \
|
#define luaC_barrierfast(L, t) \
|
||||||
{ \
|
{ \
|
||||||
if (isblack(obj2gco(t))) \
|
if (isblack(obj2gco(t))) \
|
||||||
luaC_barrierback(L, t); \
|
luaC_barrierback(L, obj2gco(t), &t->gclist); \
|
||||||
}
|
}
|
||||||
|
|
||||||
#define luaC_objbarrier(L, p, o) \
|
#define luaC_objbarrier(L, p, o) \
|
||||||
|
@ -118,31 +111,28 @@
|
||||||
luaC_barrierf(L, obj2gco(p), obj2gco(o)); \
|
luaC_barrierf(L, obj2gco(p), obj2gco(o)); \
|
||||||
}
|
}
|
||||||
|
|
||||||
#define luaC_upvalbarrier(L, uv, tv) \
|
#define luaC_threadbarrier(L) \
|
||||||
{ \
|
{ \
|
||||||
if (iscollectable(tv) && iswhite(gcvalue(tv)) && (!(uv) || (uv)->v != &(uv)->u.value)) \
|
if (isblack(obj2gco(L))) \
|
||||||
luaC_barrierupval(L, gcvalue(tv)); \
|
luaC_barrierback(L, obj2gco(L), &L->gclist); \
|
||||||
}
|
}
|
||||||
|
|
||||||
#define luaC_checkthreadsleep(L) \
|
#define luaC_init(L, o, tt_) \
|
||||||
{ \
|
{ \
|
||||||
if (luaC_threadsleeping(L)) \
|
o->marked = luaC_white(L->global); \
|
||||||
luaC_wakethread(L); \
|
o->tt = tt_; \
|
||||||
|
o->memcat = L->activememcat; \
|
||||||
}
|
}
|
||||||
|
|
||||||
#define luaC_init(L, o, tt) luaC_initobj(L, cast_to(GCObject*, (o)), tt)
|
|
||||||
|
|
||||||
LUAI_FUNC void luaC_freeall(lua_State* L);
|
LUAI_FUNC void luaC_freeall(lua_State* L);
|
||||||
LUAI_FUNC size_t luaC_step(lua_State* L, bool assist);
|
LUAI_FUNC size_t luaC_step(lua_State* L, bool assist);
|
||||||
LUAI_FUNC void luaC_fullgc(lua_State* L);
|
LUAI_FUNC void luaC_fullgc(lua_State* L);
|
||||||
LUAI_FUNC void luaC_initobj(lua_State* L, GCObject* o, uint8_t tt);
|
LUAI_FUNC void luaC_initobj(lua_State* L, GCObject* o, uint8_t tt);
|
||||||
LUAI_FUNC void luaC_initupval(lua_State* L, UpVal* uv);
|
LUAI_FUNC void luaC_upvalclosed(lua_State* L, UpVal* uv);
|
||||||
LUAI_FUNC void luaC_barrierupval(lua_State* L, GCObject* v);
|
|
||||||
LUAI_FUNC void luaC_barrierf(lua_State* L, GCObject* o, GCObject* v);
|
LUAI_FUNC void luaC_barrierf(lua_State* L, GCObject* o, GCObject* v);
|
||||||
LUAI_FUNC void luaC_barriertable(lua_State* L, Table* t, GCObject* v);
|
LUAI_FUNC void luaC_barriertable(lua_State* L, Table* t, GCObject* v);
|
||||||
LUAI_FUNC void luaC_barrierback(lua_State* L, Table* t);
|
LUAI_FUNC void luaC_barrierback(lua_State* L, GCObject* o, GCObject** gclist);
|
||||||
LUAI_FUNC void luaC_validate(lua_State* L);
|
LUAI_FUNC void luaC_validate(lua_State* L);
|
||||||
LUAI_FUNC void luaC_dump(lua_State* L, void* file, const char* (*categoryName)(lua_State* L, uint8_t memcat));
|
LUAI_FUNC void luaC_dump(lua_State* L, void* file, const char* (*categoryName)(lua_State* L, uint8_t memcat));
|
||||||
LUAI_FUNC int64_t luaC_allocationrate(lua_State* L);
|
LUAI_FUNC int64_t luaC_allocationrate(lua_State* L);
|
||||||
LUAI_FUNC void luaC_wakethread(lua_State* L);
|
|
||||||
LUAI_FUNC const char* luaC_statename(int state);
|
LUAI_FUNC const char* luaC_statename(int state);
|
||||||
|
|
|
@ -102,10 +102,12 @@ static void validatestack(global_State* g, lua_State* l)
|
||||||
if (l->namecall)
|
if (l->namecall)
|
||||||
validateobjref(g, obj2gco(l), obj2gco(l->namecall));
|
validateobjref(g, obj2gco(l), obj2gco(l->namecall));
|
||||||
|
|
||||||
for (UpVal* uv = l->openupval; uv; uv = uv->u.l.threadnext)
|
for (UpVal* uv = l->openupval; uv; uv = uv->u.open.threadnext)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(uv->tt == LUA_TUPVAL);
|
LUAU_ASSERT(uv->tt == LUA_TUPVAL);
|
||||||
LUAU_ASSERT(uv->v != &uv->u.value);
|
LUAU_ASSERT(upisopen(uv));
|
||||||
|
LUAU_ASSERT(uv->u.open.next->u.open.prev == uv && uv->u.open.prev->u.open.next == uv);
|
||||||
|
LUAU_ASSERT(!isblack(obj2gco(uv))); // open upvalues are never black
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,11 +237,12 @@ void luaC_validate(lua_State* L)
|
||||||
|
|
||||||
luaM_visitgco(L, L, validategco);
|
luaM_visitgco(L, L, validategco);
|
||||||
|
|
||||||
for (UpVal* uv = g->uvhead.u.l.next; uv != &g->uvhead; uv = uv->u.l.next)
|
for (UpVal* uv = g->uvhead.u.open.next; uv != &g->uvhead; uv = uv->u.open.next)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(uv->tt == LUA_TUPVAL);
|
LUAU_ASSERT(uv->tt == LUA_TUPVAL);
|
||||||
LUAU_ASSERT(uv->v != &uv->u.value);
|
LUAU_ASSERT(upisopen(uv));
|
||||||
LUAU_ASSERT(uv->u.l.next->u.l.prev == uv && uv->u.l.prev->u.l.next == uv);
|
LUAU_ASSERT(uv->u.open.next->u.open.prev == uv && uv->u.open.prev->u.open.next == uv);
|
||||||
|
LUAU_ASSERT(!isblack(obj2gco(uv))); // open upvalues are never black
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -508,13 +511,14 @@ static void dumpproto(FILE* f, Proto* p)
|
||||||
|
|
||||||
static void dumpupval(FILE* f, UpVal* uv)
|
static void dumpupval(FILE* f, UpVal* uv)
|
||||||
{
|
{
|
||||||
fprintf(f, "{\"type\":\"upvalue\",\"cat\":%d,\"size\":%d", uv->memcat, int(sizeof(UpVal)));
|
fprintf(f, "{\"type\":\"upvalue\",\"cat\":%d,\"size\":%d,\"open\":%s", uv->memcat, int(sizeof(UpVal)), upisopen(uv) ? "true" : "false");
|
||||||
|
|
||||||
if (iscollectable(uv->v))
|
if (iscollectable(uv->v))
|
||||||
{
|
{
|
||||||
fprintf(f, ",\"object\":");
|
fprintf(f, ",\"object\":");
|
||||||
dumpref(f, gcvalue(uv->v));
|
dumpref(f, gcvalue(uv->v));
|
||||||
}
|
}
|
||||||
|
|
||||||
fprintf(f, "}");
|
fprintf(f, "}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -232,7 +232,7 @@ typedef struct TString
|
||||||
int16_t atom;
|
int16_t atom;
|
||||||
// 2 byte padding
|
// 2 byte padding
|
||||||
|
|
||||||
TString* next; // next string in the hash table bucket or the string buffer linked list
|
TString* next; // next string in the hash table bucket
|
||||||
|
|
||||||
unsigned int hash;
|
unsigned int hash;
|
||||||
unsigned int len;
|
unsigned int len;
|
||||||
|
@ -316,7 +316,10 @@ typedef struct LocVar
|
||||||
typedef struct UpVal
|
typedef struct UpVal
|
||||||
{
|
{
|
||||||
CommonHeader;
|
CommonHeader;
|
||||||
// 1 (x86) or 5 (x64) byte padding
|
uint8_t markedopen; // set if reachable from an alive thread (only valid during atomic)
|
||||||
|
|
||||||
|
// 4 byte padding (x64)
|
||||||
|
|
||||||
TValue* v; // points to stack or to its own value
|
TValue* v; // points to stack or to its own value
|
||||||
union
|
union
|
||||||
{
|
{
|
||||||
|
@ -327,14 +330,14 @@ typedef struct UpVal
|
||||||
struct UpVal* prev;
|
struct UpVal* prev;
|
||||||
struct UpVal* next;
|
struct UpVal* next;
|
||||||
|
|
||||||
// thread double linked list (when open)
|
// thread linked list (when open)
|
||||||
struct UpVal* threadnext;
|
struct UpVal* threadnext;
|
||||||
// note: this is the location of a pointer to this upvalue in the previous element that can be either an UpVal or a lua_State
|
} open;
|
||||||
struct UpVal** threadprev;
|
|
||||||
} l;
|
|
||||||
} u;
|
} u;
|
||||||
} UpVal;
|
} UpVal;
|
||||||
|
|
||||||
|
#define upisopen(up) ((up)->v != &(up)->u.value)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** Closures
|
** Closures
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -77,7 +77,7 @@ static void preinit_state(lua_State* L, global_State* g)
|
||||||
L->namecall = NULL;
|
L->namecall = NULL;
|
||||||
L->cachedslot = 0;
|
L->cachedslot = 0;
|
||||||
L->singlestep = false;
|
L->singlestep = false;
|
||||||
L->stackstate = 0;
|
L->isactive = false;
|
||||||
L->activememcat = 0;
|
L->activememcat = 0;
|
||||||
L->userdata = NULL;
|
L->userdata = NULL;
|
||||||
}
|
}
|
||||||
|
@ -87,7 +87,6 @@ static void close_state(lua_State* L)
|
||||||
global_State* g = L->global;
|
global_State* g = L->global;
|
||||||
luaF_close(L, L->stack); // close all upvalues for this thread
|
luaF_close(L, L->stack); // close all upvalues for this thread
|
||||||
luaC_freeall(L); // collect all objects
|
luaC_freeall(L); // collect all objects
|
||||||
LUAU_ASSERT(g->strbufgc == NULL);
|
|
||||||
LUAU_ASSERT(g->strt.nuse == 0);
|
LUAU_ASSERT(g->strt.nuse == 0);
|
||||||
luaM_freearray(L, L->global->strt.hash, L->global->strt.size, TString*, 0);
|
luaM_freearray(L, L->global->strt.hash, L->global->strt.size, TString*, 0);
|
||||||
freestack(L, L);
|
freestack(L, L);
|
||||||
|
@ -119,8 +118,6 @@ lua_State* luaE_newthread(lua_State* L)
|
||||||
|
|
||||||
void luaE_freethread(lua_State* L, lua_State* L1, lua_Page* page)
|
void luaE_freethread(lua_State* L, lua_State* L1, lua_Page* page)
|
||||||
{
|
{
|
||||||
luaF_close(L1, L1->stack); // close all upvalues for this thread
|
|
||||||
LUAU_ASSERT(L1->openupval == NULL);
|
|
||||||
global_State* g = L->global;
|
global_State* g = L->global;
|
||||||
if (g->cb.userthread)
|
if (g->cb.userthread)
|
||||||
g->cb.userthread(NULL, L1);
|
g->cb.userthread(NULL, L1);
|
||||||
|
@ -175,8 +172,8 @@ lua_State* lua_newstate(lua_Alloc f, void* ud)
|
||||||
g->frealloc = f;
|
g->frealloc = f;
|
||||||
g->ud = ud;
|
g->ud = ud;
|
||||||
g->mainthread = L;
|
g->mainthread = L;
|
||||||
g->uvhead.u.l.prev = &g->uvhead;
|
g->uvhead.u.open.prev = &g->uvhead;
|
||||||
g->uvhead.u.l.next = &g->uvhead;
|
g->uvhead.u.open.next = &g->uvhead;
|
||||||
g->GCthreshold = 0; // mark it as unfinished state
|
g->GCthreshold = 0; // mark it as unfinished state
|
||||||
g->registryfree = 0;
|
g->registryfree = 0;
|
||||||
g->errorjmp = NULL;
|
g->errorjmp = NULL;
|
||||||
|
@ -194,7 +191,6 @@ lua_State* lua_newstate(lua_Alloc f, void* ud)
|
||||||
g->gray = NULL;
|
g->gray = NULL;
|
||||||
g->grayagain = NULL;
|
g->grayagain = NULL;
|
||||||
g->weak = NULL;
|
g->weak = NULL;
|
||||||
g->strbufgc = NULL;
|
|
||||||
g->totalbytes = sizeof(LG);
|
g->totalbytes = sizeof(LG);
|
||||||
g->gcgoal = LUAI_GCGOAL;
|
g->gcgoal = LUAI_GCGOAL;
|
||||||
g->gcstepmul = LUAI_GCSTEPMUL;
|
g->gcstepmul = LUAI_GCSTEPMUL;
|
||||||
|
|
|
@ -167,8 +167,6 @@ typedef struct global_State
|
||||||
GCObject* grayagain; // list of objects to be traversed atomically
|
GCObject* grayagain; // list of objects to be traversed atomically
|
||||||
GCObject* weak; // list of weak tables (to be cleared)
|
GCObject* weak; // list of weak tables (to be cleared)
|
||||||
|
|
||||||
TString* strbufgc; // list of all string buffer objects
|
|
||||||
|
|
||||||
|
|
||||||
size_t GCthreshold; // when totalbytes > GCthreshold, run GC step
|
size_t GCthreshold; // when totalbytes > GCthreshold, run GC step
|
||||||
size_t totalbytes; // number of bytes currently allocated
|
size_t totalbytes; // number of bytes currently allocated
|
||||||
|
@ -222,8 +220,8 @@ struct lua_State
|
||||||
uint8_t status;
|
uint8_t status;
|
||||||
|
|
||||||
uint8_t activememcat; // memory category that is used for new GC object allocations
|
uint8_t activememcat; // memory category that is used for new GC object allocations
|
||||||
uint8_t stackstate;
|
|
||||||
|
|
||||||
|
bool isactive; // thread is currently executing, stack may be mutated without barriers
|
||||||
bool singlestep; // call debugstep hook after each instruction
|
bool singlestep; // call debugstep hook after each instruction
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -70,73 +70,43 @@ void luaS_resize(lua_State* L, int newsize)
|
||||||
|
|
||||||
static TString* newlstr(lua_State* L, const char* str, size_t l, unsigned int h)
|
static TString* newlstr(lua_State* L, const char* str, size_t l, unsigned int h)
|
||||||
{
|
{
|
||||||
TString* ts;
|
|
||||||
stringtable* tb;
|
|
||||||
if (l > MAXSSIZE)
|
if (l > MAXSSIZE)
|
||||||
luaM_toobig(L);
|
luaM_toobig(L);
|
||||||
ts = luaM_newgco(L, TString, sizestring(l), L->activememcat);
|
|
||||||
ts->len = unsigned(l);
|
TString* ts = luaM_newgco(L, TString, sizestring(l), L->activememcat);
|
||||||
|
luaC_init(L, ts, LUA_TSTRING);
|
||||||
|
ts->atom = ATOM_UNDEF;
|
||||||
ts->hash = h;
|
ts->hash = h;
|
||||||
ts->marked = luaC_white(L->global);
|
ts->len = unsigned(l);
|
||||||
ts->tt = LUA_TSTRING;
|
|
||||||
ts->memcat = L->activememcat;
|
|
||||||
memcpy(ts->data, str, l);
|
memcpy(ts->data, str, l);
|
||||||
ts->data[l] = '\0'; // ending 0
|
ts->data[l] = '\0'; // ending 0
|
||||||
ts->atom = ATOM_UNDEF;
|
|
||||||
tb = &L->global->strt;
|
stringtable* tb = &L->global->strt;
|
||||||
h = lmod(h, tb->size);
|
h = lmod(h, tb->size);
|
||||||
ts->next = tb->hash[h]; // chain new entry
|
ts->next = tb->hash[h]; // chain new entry
|
||||||
tb->hash[h] = ts;
|
tb->hash[h] = ts;
|
||||||
|
|
||||||
tb->nuse++;
|
tb->nuse++;
|
||||||
if (tb->nuse > cast_to(uint32_t, tb->size) && tb->size <= INT_MAX / 2)
|
if (tb->nuse > cast_to(uint32_t, tb->size) && tb->size <= INT_MAX / 2)
|
||||||
luaS_resize(L, tb->size * 2); // too crowded
|
luaS_resize(L, tb->size * 2); // too crowded
|
||||||
|
|
||||||
return ts;
|
return ts;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void linkstrbuf(lua_State* L, TString* ts)
|
|
||||||
{
|
|
||||||
global_State* g = L->global;
|
|
||||||
|
|
||||||
ts->next = g->strbufgc;
|
|
||||||
g->strbufgc = ts;
|
|
||||||
ts->marked = luaC_white(g);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void unlinkstrbuf(lua_State* L, TString* ts)
|
|
||||||
{
|
|
||||||
global_State* g = L->global;
|
|
||||||
|
|
||||||
TString** p = &g->strbufgc;
|
|
||||||
|
|
||||||
while (TString* curr = *p)
|
|
||||||
{
|
|
||||||
if (curr == ts)
|
|
||||||
{
|
|
||||||
*p = curr->next;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
p = &curr->next;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LUAU_ASSERT(!"failed to find string buffer");
|
|
||||||
}
|
|
||||||
|
|
||||||
TString* luaS_bufstart(lua_State* L, size_t size)
|
TString* luaS_bufstart(lua_State* L, size_t size)
|
||||||
{
|
{
|
||||||
if (size > MAXSSIZE)
|
if (size > MAXSSIZE)
|
||||||
luaM_toobig(L);
|
luaM_toobig(L);
|
||||||
|
|
||||||
TString* ts = luaM_newgco(L, TString, sizestring(size), L->activememcat);
|
TString* ts = luaM_newgco(L, TString, sizestring(size), L->activememcat);
|
||||||
|
luaC_init(L, ts, LUA_TSTRING);
|
||||||
ts->tt = LUA_TSTRING;
|
ts->atom = ATOM_UNDEF;
|
||||||
ts->memcat = L->activememcat;
|
ts->hash = 0; // computed in luaS_buffinish
|
||||||
linkstrbuf(L, ts);
|
|
||||||
|
|
||||||
ts->len = unsigned(size);
|
ts->len = unsigned(size);
|
||||||
|
|
||||||
|
ts->next = NULL;
|
||||||
|
|
||||||
return ts;
|
return ts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -159,7 +129,7 @@ TString* luaS_buffinish(lua_State* L, TString* ts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unlinkstrbuf(L, ts);
|
LUAU_ASSERT(ts->next == NULL);
|
||||||
|
|
||||||
ts->hash = h;
|
ts->hash = h;
|
||||||
ts->data[ts->len] = '\0'; // ending 0
|
ts->data[ts->len] = '\0'; // ending 0
|
||||||
|
@ -214,11 +184,10 @@ static bool unlinkstr(lua_State* L, TString* ts)
|
||||||
|
|
||||||
void luaS_free(lua_State* L, TString* ts, lua_Page* page)
|
void luaS_free(lua_State* L, TString* ts, lua_Page* page)
|
||||||
{
|
{
|
||||||
// Unchain from the string table
|
if (unlinkstr(L, ts))
|
||||||
if (!unlinkstr(L, ts))
|
|
||||||
unlinkstrbuf(L, ts); // An unlikely scenario when we have a string buffer on our hands
|
|
||||||
else
|
|
||||||
L->global->strt.nuse--;
|
L->global->strt.nuse--;
|
||||||
|
else
|
||||||
|
LUAU_ASSERT(ts->next == NULL); // orphaned string buffer
|
||||||
|
|
||||||
luaM_freegco(L, ts, sizestring(ts->len), ts->memcat, page);
|
luaM_freegco(L, ts, sizestring(ts->len), ts->memcat, page);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,8 +8,6 @@
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauTostringFormatSpecifier, false);
|
|
||||||
|
|
||||||
// macro to `unsign' a character
|
// macro to `unsign' a character
|
||||||
#define uchar(c) ((unsigned char)(c))
|
#define uchar(c) ((unsigned char)(c))
|
||||||
|
|
||||||
|
@ -1036,9 +1034,6 @@ static int str_format(lua_State* L)
|
||||||
}
|
}
|
||||||
case '*':
|
case '*':
|
||||||
{
|
{
|
||||||
if (!FFlag::LuauTostringFormatSpecifier)
|
|
||||||
luaL_error(L, "invalid option '%%*' to 'format'");
|
|
||||||
|
|
||||||
if (formatItemSize != 1)
|
if (formatItemSize != 1)
|
||||||
luaL_error(L, "'%%*' does not take a form");
|
luaL_error(L, "'%%*' does not take a form");
|
||||||
|
|
||||||
|
|
|
@ -16,8 +16,6 @@
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauNicerMethodErrors, false)
|
|
||||||
|
|
||||||
// Disable c99-designator to avoid the warning in CGOTO dispatch table
|
// Disable c99-designator to avoid the warning in CGOTO dispatch table
|
||||||
#ifdef __clang__
|
#ifdef __clang__
|
||||||
#if __has_warning("-Wc99-designator")
|
#if __has_warning("-Wc99-designator")
|
||||||
|
@ -68,11 +66,6 @@ LUAU_FASTFLAGVARIABLE(LuauNicerMethodErrors, false)
|
||||||
#define VM_PATCH_C(pc, slot) *const_cast<Instruction*>(pc) = ((uint8_t(slot) << 24) | (0x00ffffffu & *(pc)))
|
#define VM_PATCH_C(pc, slot) *const_cast<Instruction*>(pc) = ((uint8_t(slot) << 24) | (0x00ffffffu & *(pc)))
|
||||||
#define VM_PATCH_E(pc, slot) *const_cast<Instruction*>(pc) = ((uint32_t(slot) << 8) | (0x000000ffu & *(pc)))
|
#define VM_PATCH_E(pc, slot) *const_cast<Instruction*>(pc) = ((uint32_t(slot) << 8) | (0x000000ffu & *(pc)))
|
||||||
|
|
||||||
// NOTE: If debugging the Luau code, disable this macro to prevent timeouts from
|
|
||||||
// occurring when tracing code in Visual Studio / XCode
|
|
||||||
#if 0
|
|
||||||
#define VM_INTERRUPT()
|
|
||||||
#else
|
|
||||||
#define VM_INTERRUPT() \
|
#define VM_INTERRUPT() \
|
||||||
{ \
|
{ \
|
||||||
void (*interrupt)(lua_State*, int) = L->global->cb.interrupt; \
|
void (*interrupt)(lua_State*, int) = L->global->cb.interrupt; \
|
||||||
|
@ -86,7 +79,6 @@ LUAU_FASTFLAGVARIABLE(LuauNicerMethodErrors, false)
|
||||||
} \
|
} \
|
||||||
} \
|
} \
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
#define VM_DISPATCH_OP(op) &&CASE_##op
|
#define VM_DISPATCH_OP(op) &&CASE_##op
|
||||||
|
@ -106,12 +98,12 @@ LUAU_FASTFLAGVARIABLE(LuauNicerMethodErrors, false)
|
||||||
VM_DISPATCH_OP(LOP_POWK), VM_DISPATCH_OP(LOP_AND), VM_DISPATCH_OP(LOP_OR), VM_DISPATCH_OP(LOP_ANDK), VM_DISPATCH_OP(LOP_ORK), \
|
VM_DISPATCH_OP(LOP_POWK), VM_DISPATCH_OP(LOP_AND), VM_DISPATCH_OP(LOP_OR), VM_DISPATCH_OP(LOP_ANDK), VM_DISPATCH_OP(LOP_ORK), \
|
||||||
VM_DISPATCH_OP(LOP_CONCAT), VM_DISPATCH_OP(LOP_NOT), VM_DISPATCH_OP(LOP_MINUS), VM_DISPATCH_OP(LOP_LENGTH), VM_DISPATCH_OP(LOP_NEWTABLE), \
|
VM_DISPATCH_OP(LOP_CONCAT), VM_DISPATCH_OP(LOP_NOT), VM_DISPATCH_OP(LOP_MINUS), VM_DISPATCH_OP(LOP_LENGTH), VM_DISPATCH_OP(LOP_NEWTABLE), \
|
||||||
VM_DISPATCH_OP(LOP_DUPTABLE), VM_DISPATCH_OP(LOP_SETLIST), VM_DISPATCH_OP(LOP_FORNPREP), VM_DISPATCH_OP(LOP_FORNLOOP), \
|
VM_DISPATCH_OP(LOP_DUPTABLE), VM_DISPATCH_OP(LOP_SETLIST), VM_DISPATCH_OP(LOP_FORNPREP), VM_DISPATCH_OP(LOP_FORNLOOP), \
|
||||||
VM_DISPATCH_OP(LOP_FORGLOOP), VM_DISPATCH_OP(LOP_FORGPREP_INEXT), VM_DISPATCH_OP(LOP_FORGLOOP_INEXT), VM_DISPATCH_OP(LOP_FORGPREP_NEXT), \
|
VM_DISPATCH_OP(LOP_FORGLOOP), VM_DISPATCH_OP(LOP_FORGPREP_INEXT), VM_DISPATCH_OP(LOP_DEP_FORGLOOP_INEXT), VM_DISPATCH_OP(LOP_FORGPREP_NEXT), \
|
||||||
VM_DISPATCH_OP(LOP_FORGLOOP_NEXT), VM_DISPATCH_OP(LOP_GETVARARGS), VM_DISPATCH_OP(LOP_DUPCLOSURE), VM_DISPATCH_OP(LOP_PREPVARARGS), \
|
VM_DISPATCH_OP(LOP_DEP_FORGLOOP_NEXT), VM_DISPATCH_OP(LOP_GETVARARGS), VM_DISPATCH_OP(LOP_DUPCLOSURE), VM_DISPATCH_OP(LOP_PREPVARARGS), \
|
||||||
VM_DISPATCH_OP(LOP_LOADKX), VM_DISPATCH_OP(LOP_JUMPX), VM_DISPATCH_OP(LOP_FASTCALL), VM_DISPATCH_OP(LOP_COVERAGE), \
|
VM_DISPATCH_OP(LOP_LOADKX), VM_DISPATCH_OP(LOP_JUMPX), VM_DISPATCH_OP(LOP_FASTCALL), VM_DISPATCH_OP(LOP_COVERAGE), \
|
||||||
VM_DISPATCH_OP(LOP_CAPTURE), VM_DISPATCH_OP(LOP_JUMPIFEQK), VM_DISPATCH_OP(LOP_JUMPIFNOTEQK), VM_DISPATCH_OP(LOP_FASTCALL1), \
|
VM_DISPATCH_OP(LOP_CAPTURE), VM_DISPATCH_OP(LOP_DEP_JUMPIFEQK), VM_DISPATCH_OP(LOP_DEP_JUMPIFNOTEQK), VM_DISPATCH_OP(LOP_FASTCALL1), \
|
||||||
VM_DISPATCH_OP(LOP_FASTCALL2), VM_DISPATCH_OP(LOP_FASTCALL2K), VM_DISPATCH_OP(LOP_FORGPREP), VM_DISPATCH_OP(LOP_JUMPXEQKNIL), \
|
VM_DISPATCH_OP(LOP_FASTCALL2), VM_DISPATCH_OP(LOP_FASTCALL2K), VM_DISPATCH_OP(LOP_FORGPREP), VM_DISPATCH_OP(LOP_JUMPXEQKNIL), \
|
||||||
VM_DISPATCH_OP(LOP_JUMPXEQKB), VM_DISPATCH_OP(LOP_JUMPXEQKN), VM_DISPATCH_OP(LOP_JUMPXEQKS), \
|
VM_DISPATCH_OP(LOP_JUMPXEQKB), VM_DISPATCH_OP(LOP_JUMPXEQKN), VM_DISPATCH_OP(LOP_JUMPXEQKS),
|
||||||
|
|
||||||
#if defined(__GNUC__) || defined(__clang__)
|
#if defined(__GNUC__) || defined(__clang__)
|
||||||
#define VM_USE_CGOTO 1
|
#define VM_USE_CGOTO 1
|
||||||
|
@ -149,32 +141,6 @@ LUAU_NOINLINE static void luau_prepareFORN(lua_State* L, StkId plimit, StkId pst
|
||||||
luaG_forerror(L, pstep, "step");
|
luaG_forerror(L, pstep, "step");
|
||||||
}
|
}
|
||||||
|
|
||||||
LUAU_NOINLINE static bool luau_loopFORG(lua_State* L, int a, int c)
|
|
||||||
{
|
|
||||||
// note: it's safe to push arguments past top for complicated reasons (see top of the file)
|
|
||||||
StkId ra = &L->base[a];
|
|
||||||
LUAU_ASSERT(ra + 3 <= L->top);
|
|
||||||
|
|
||||||
setobjs2s(L, ra + 3 + 2, ra + 2);
|
|
||||||
setobjs2s(L, ra + 3 + 1, ra + 1);
|
|
||||||
setobjs2s(L, ra + 3, ra);
|
|
||||||
|
|
||||||
L->top = ra + 3 + 3; // func. + 2 args (state and index)
|
|
||||||
LUAU_ASSERT(L->top <= L->stack_last);
|
|
||||||
|
|
||||||
luaD_call(L, ra + 3, c);
|
|
||||||
L->top = L->ci->top;
|
|
||||||
|
|
||||||
// recompute ra since stack might have been reallocated
|
|
||||||
ra = &L->base[a];
|
|
||||||
LUAU_ASSERT(ra < L->top);
|
|
||||||
|
|
||||||
// copy first variable back into the iteration index
|
|
||||||
setobjs2s(L, ra + 2, ra + 3);
|
|
||||||
|
|
||||||
return ttisnil(ra + 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
// calls a C function f with no yielding support; optionally save one resulting value to the res register
|
// calls a C function f with no yielding support; optionally save one resulting value to the res register
|
||||||
// the function and arguments have to already be pushed to L->top
|
// the function and arguments have to already be pushed to L->top
|
||||||
LUAU_NOINLINE static void luau_callTM(lua_State* L, int nparams, int res)
|
LUAU_NOINLINE static void luau_callTM(lua_State* L, int nparams, int res)
|
||||||
|
@ -315,8 +281,8 @@ static void luau_execute(lua_State* L)
|
||||||
const Instruction* pc;
|
const Instruction* pc;
|
||||||
|
|
||||||
LUAU_ASSERT(isLua(L->ci));
|
LUAU_ASSERT(isLua(L->ci));
|
||||||
LUAU_ASSERT(luaC_threadactive(L));
|
LUAU_ASSERT(L->isactive);
|
||||||
LUAU_ASSERT(!luaC_threadsleeping(L));
|
LUAU_ASSERT(!isblack(obj2gco(L))); // we don't use luaC_threadbarrier because active threads never turn black
|
||||||
|
|
||||||
pc = L->ci->savedpc;
|
pc = L->ci->savedpc;
|
||||||
cl = clvalue(L->ci->func);
|
cl = clvalue(L->ci->func);
|
||||||
|
@ -496,7 +462,6 @@ static void luau_execute(lua_State* L)
|
||||||
|
|
||||||
setobj(L, uv->v, ra);
|
setobj(L, uv->v, ra);
|
||||||
luaC_barrier(L, uv, ra);
|
luaC_barrier(L, uv, ra);
|
||||||
luaC_upvalbarrier(L, uv, uv->v);
|
|
||||||
VM_NEXT();
|
VM_NEXT();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -932,7 +897,7 @@ static void luau_execute(lua_State* L)
|
||||||
VM_PATCH_C(pc - 2, L->cachedslot);
|
VM_PATCH_C(pc - 2, L->cachedslot);
|
||||||
// recompute ra since stack might have been reallocated
|
// recompute ra since stack might have been reallocated
|
||||||
ra = VM_REG(LUAU_INSN_A(insn));
|
ra = VM_REG(LUAU_INSN_A(insn));
|
||||||
if (FFlag::LuauNicerMethodErrors && ttisnil(ra))
|
if (ttisnil(ra))
|
||||||
luaG_methoderror(L, ra + 1, tsvalue(kv));
|
luaG_methoderror(L, ra + 1, tsvalue(kv));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -973,7 +938,7 @@ static void luau_execute(lua_State* L)
|
||||||
VM_PATCH_C(pc - 2, L->cachedslot);
|
VM_PATCH_C(pc - 2, L->cachedslot);
|
||||||
// recompute ra since stack might have been reallocated
|
// recompute ra since stack might have been reallocated
|
||||||
ra = VM_REG(LUAU_INSN_A(insn));
|
ra = VM_REG(LUAU_INSN_A(insn));
|
||||||
if (FFlag::LuauNicerMethodErrors && ttisnil(ra))
|
if (ttisnil(ra))
|
||||||
luaG_methoderror(L, ra + 1, tsvalue(kv));
|
luaG_methoderror(L, ra + 1, tsvalue(kv));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -984,7 +949,7 @@ static void luau_execute(lua_State* L)
|
||||||
VM_PROTECT(luaV_gettable(L, rb, kv, ra));
|
VM_PROTECT(luaV_gettable(L, rb, kv, ra));
|
||||||
// recompute ra since stack might have been reallocated
|
// recompute ra since stack might have been reallocated
|
||||||
ra = VM_REG(LUAU_INSN_A(insn));
|
ra = VM_REG(LUAU_INSN_A(insn));
|
||||||
if (FFlag::LuauNicerMethodErrors && ttisnil(ra))
|
if (ttisnil(ra))
|
||||||
luaG_methoderror(L, ra + 1, tsvalue(kv));
|
luaG_methoderror(L, ra + 1, tsvalue(kv));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2398,54 +2363,10 @@ static void luau_execute(lua_State* L)
|
||||||
VM_NEXT();
|
VM_NEXT();
|
||||||
}
|
}
|
||||||
|
|
||||||
VM_CASE(LOP_FORGLOOP_INEXT)
|
VM_CASE(LOP_DEP_FORGLOOP_INEXT)
|
||||||
{
|
{
|
||||||
VM_INTERRUPT();
|
LUAU_ASSERT(!"Unsupported deprecated opcode");
|
||||||
Instruction insn = *pc++;
|
LUAU_UNREACHABLE();
|
||||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
|
||||||
|
|
||||||
// fast-path: ipairs/inext
|
|
||||||
if (ttisnil(ra) && ttistable(ra + 1) && ttislightuserdata(ra + 2))
|
|
||||||
{
|
|
||||||
Table* h = hvalue(ra + 1);
|
|
||||||
int index = int(reinterpret_cast<uintptr_t>(pvalue(ra + 2)));
|
|
||||||
|
|
||||||
// if 1-based index of the last iteration is in bounds, this means 0-based index of the current iteration is in bounds
|
|
||||||
if (unsigned(index) < unsigned(h->sizearray))
|
|
||||||
{
|
|
||||||
// note that nil elements inside the array terminate the traversal
|
|
||||||
if (!ttisnil(&h->array[index]))
|
|
||||||
{
|
|
||||||
setpvalue(ra + 2, reinterpret_cast<void*>(uintptr_t(index + 1)));
|
|
||||||
setnvalue(ra + 3, double(index + 1));
|
|
||||||
setobj2s(L, ra + 4, &h->array[index]);
|
|
||||||
|
|
||||||
pc += LUAU_INSN_D(insn);
|
|
||||||
LUAU_ASSERT(unsigned(pc - cl->l.p->code) < unsigned(cl->l.p->sizecode));
|
|
||||||
VM_NEXT();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// fallthrough to exit
|
|
||||||
VM_NEXT();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// fallthrough to exit
|
|
||||||
VM_NEXT();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// slow-path; can call Lua/C generators
|
|
||||||
bool stop;
|
|
||||||
VM_PROTECT(stop = luau_loopFORG(L, LUAU_INSN_A(insn), 2));
|
|
||||||
|
|
||||||
pc += stop ? 0 : LUAU_INSN_D(insn);
|
|
||||||
LUAU_ASSERT(unsigned(pc - cl->l.p->code) < unsigned(cl->l.p->sizecode));
|
|
||||||
VM_NEXT();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
VM_CASE(LOP_FORGPREP_NEXT)
|
VM_CASE(LOP_FORGPREP_NEXT)
|
||||||
|
@ -2470,70 +2391,10 @@ static void luau_execute(lua_State* L)
|
||||||
VM_NEXT();
|
VM_NEXT();
|
||||||
}
|
}
|
||||||
|
|
||||||
VM_CASE(LOP_FORGLOOP_NEXT)
|
VM_CASE(LOP_DEP_FORGLOOP_NEXT)
|
||||||
{
|
{
|
||||||
VM_INTERRUPT();
|
LUAU_ASSERT(!"Unsupported deprecated opcode");
|
||||||
Instruction insn = *pc++;
|
LUAU_UNREACHABLE();
|
||||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
|
||||||
|
|
||||||
// fast-path: pairs/next
|
|
||||||
if (ttisnil(ra) && ttistable(ra + 1) && ttislightuserdata(ra + 2))
|
|
||||||
{
|
|
||||||
Table* h = hvalue(ra + 1);
|
|
||||||
int index = int(reinterpret_cast<uintptr_t>(pvalue(ra + 2)));
|
|
||||||
|
|
||||||
int sizearray = h->sizearray;
|
|
||||||
int sizenode = 1 << h->lsizenode;
|
|
||||||
|
|
||||||
// first we advance index through the array portion
|
|
||||||
while (unsigned(index) < unsigned(sizearray))
|
|
||||||
{
|
|
||||||
if (!ttisnil(&h->array[index]))
|
|
||||||
{
|
|
||||||
setpvalue(ra + 2, reinterpret_cast<void*>(uintptr_t(index + 1)));
|
|
||||||
setnvalue(ra + 3, double(index + 1));
|
|
||||||
setobj2s(L, ra + 4, &h->array[index]);
|
|
||||||
|
|
||||||
pc += LUAU_INSN_D(insn);
|
|
||||||
LUAU_ASSERT(unsigned(pc - cl->l.p->code) < unsigned(cl->l.p->sizecode));
|
|
||||||
VM_NEXT();
|
|
||||||
}
|
|
||||||
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// then we advance index through the hash portion
|
|
||||||
while (unsigned(index - sizearray) < unsigned(sizenode))
|
|
||||||
{
|
|
||||||
LuaNode* n = &h->node[index - sizearray];
|
|
||||||
|
|
||||||
if (!ttisnil(gval(n)))
|
|
||||||
{
|
|
||||||
setpvalue(ra + 2, reinterpret_cast<void*>(uintptr_t(index + 1)));
|
|
||||||
getnodekey(L, ra + 3, n);
|
|
||||||
setobj2s(L, ra + 4, gval(n));
|
|
||||||
|
|
||||||
pc += LUAU_INSN_D(insn);
|
|
||||||
LUAU_ASSERT(unsigned(pc - cl->l.p->code) < unsigned(cl->l.p->sizecode));
|
|
||||||
VM_NEXT();
|
|
||||||
}
|
|
||||||
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// fallthrough to exit
|
|
||||||
VM_NEXT();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// slow-path; can call Lua/C generators
|
|
||||||
bool stop;
|
|
||||||
VM_PROTECT(stop = luau_loopFORG(L, LUAU_INSN_A(insn), 2));
|
|
||||||
|
|
||||||
pc += stop ? 0 : LUAU_INSN_D(insn);
|
|
||||||
LUAU_ASSERT(unsigned(pc - cl->l.p->code) < unsigned(cl->l.p->sizecode));
|
|
||||||
VM_NEXT();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
VM_CASE(LOP_GETVARARGS)
|
VM_CASE(LOP_GETVARARGS)
|
||||||
|
@ -2745,94 +2606,16 @@ static void luau_execute(lua_State* L)
|
||||||
LUAU_UNREACHABLE();
|
LUAU_UNREACHABLE();
|
||||||
}
|
}
|
||||||
|
|
||||||
VM_CASE(LOP_JUMPIFEQK)
|
VM_CASE(LOP_DEP_JUMPIFEQK)
|
||||||
{
|
{
|
||||||
Instruction insn = *pc++;
|
LUAU_ASSERT(!"Unsupported deprecated opcode");
|
||||||
uint32_t aux = *pc;
|
LUAU_UNREACHABLE();
|
||||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
|
||||||
TValue* rb = VM_KV(aux);
|
|
||||||
|
|
||||||
// Note that all jumps below jump by 1 in the "false" case to skip over aux
|
|
||||||
if (ttype(ra) == ttype(rb))
|
|
||||||
{
|
|
||||||
switch (ttype(ra))
|
|
||||||
{
|
|
||||||
case LUA_TNIL:
|
|
||||||
pc += LUAU_INSN_D(insn);
|
|
||||||
LUAU_ASSERT(unsigned(pc - cl->l.p->code) < unsigned(cl->l.p->sizecode));
|
|
||||||
VM_NEXT();
|
|
||||||
|
|
||||||
case LUA_TBOOLEAN:
|
|
||||||
pc += bvalue(ra) == bvalue(rb) ? LUAU_INSN_D(insn) : 1;
|
|
||||||
LUAU_ASSERT(unsigned(pc - cl->l.p->code) < unsigned(cl->l.p->sizecode));
|
|
||||||
VM_NEXT();
|
|
||||||
|
|
||||||
case LUA_TNUMBER:
|
|
||||||
pc += nvalue(ra) == nvalue(rb) ? LUAU_INSN_D(insn) : 1;
|
|
||||||
LUAU_ASSERT(unsigned(pc - cl->l.p->code) < unsigned(cl->l.p->sizecode));
|
|
||||||
VM_NEXT();
|
|
||||||
|
|
||||||
case LUA_TSTRING:
|
|
||||||
pc += gcvalue(ra) == gcvalue(rb) ? LUAU_INSN_D(insn) : 1;
|
|
||||||
LUAU_ASSERT(unsigned(pc - cl->l.p->code) < unsigned(cl->l.p->sizecode));
|
|
||||||
VM_NEXT();
|
|
||||||
|
|
||||||
default:;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LUAU_ASSERT(!"Constant is expected to be of primitive type");
|
VM_CASE(LOP_DEP_JUMPIFNOTEQK)
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
pc += 1;
|
LUAU_ASSERT(!"Unsupported deprecated opcode");
|
||||||
LUAU_ASSERT(unsigned(pc - cl->l.p->code) < unsigned(cl->l.p->sizecode));
|
LUAU_UNREACHABLE();
|
||||||
VM_NEXT();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
VM_CASE(LOP_JUMPIFNOTEQK)
|
|
||||||
{
|
|
||||||
Instruction insn = *pc++;
|
|
||||||
uint32_t aux = *pc;
|
|
||||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
|
||||||
TValue* rb = VM_KV(aux);
|
|
||||||
|
|
||||||
// Note that all jumps below jump by 1 in the "true" case to skip over aux
|
|
||||||
if (ttype(ra) == ttype(rb))
|
|
||||||
{
|
|
||||||
switch (ttype(ra))
|
|
||||||
{
|
|
||||||
case LUA_TNIL:
|
|
||||||
pc += 1;
|
|
||||||
LUAU_ASSERT(unsigned(pc - cl->l.p->code) < unsigned(cl->l.p->sizecode));
|
|
||||||
VM_NEXT();
|
|
||||||
|
|
||||||
case LUA_TBOOLEAN:
|
|
||||||
pc += bvalue(ra) != bvalue(rb) ? LUAU_INSN_D(insn) : 1;
|
|
||||||
LUAU_ASSERT(unsigned(pc - cl->l.p->code) < unsigned(cl->l.p->sizecode));
|
|
||||||
VM_NEXT();
|
|
||||||
|
|
||||||
case LUA_TNUMBER:
|
|
||||||
pc += nvalue(ra) != nvalue(rb) ? LUAU_INSN_D(insn) : 1;
|
|
||||||
LUAU_ASSERT(unsigned(pc - cl->l.p->code) < unsigned(cl->l.p->sizecode));
|
|
||||||
VM_NEXT();
|
|
||||||
|
|
||||||
case LUA_TSTRING:
|
|
||||||
pc += gcvalue(ra) != gcvalue(rb) ? LUAU_INSN_D(insn) : 1;
|
|
||||||
LUAU_ASSERT(unsigned(pc - cl->l.p->code) < unsigned(cl->l.p->sizecode));
|
|
||||||
VM_NEXT();
|
|
||||||
|
|
||||||
default:;
|
|
||||||
}
|
|
||||||
|
|
||||||
LUAU_ASSERT(!"Constant is expected to be of primitive type");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
pc += LUAU_INSN_D(insn);
|
|
||||||
LUAU_ASSERT(unsigned(pc - cl->l.p->code) < unsigned(cl->l.p->sizecode));
|
|
||||||
VM_NEXT();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
VM_CASE(LOP_FASTCALL1)
|
VM_CASE(LOP_FASTCALL1)
|
||||||
|
@ -3028,7 +2811,16 @@ static void luau_execute(lua_State* L)
|
||||||
TValue* kv = VM_KV(aux & 0xffffff);
|
TValue* kv = VM_KV(aux & 0xffffff);
|
||||||
LUAU_ASSERT(ttisnumber(kv));
|
LUAU_ASSERT(ttisnumber(kv));
|
||||||
|
|
||||||
|
#if defined(__aarch64__)
|
||||||
|
// On several ARM chips (Apple M1/M2, Neoverse N1), comparing the result of a floating-point comparison is expensive, and a branch
|
||||||
|
// is much cheaper; on some 32-bit ARM chips (Cortex A53) the performance is about the same so we prefer less branchy variant there
|
||||||
|
if (aux >> 31)
|
||||||
|
pc += !(ttisnumber(ra) && nvalue(ra) == nvalue(kv)) ? LUAU_INSN_D(insn) : 1;
|
||||||
|
else
|
||||||
|
pc += (ttisnumber(ra) && nvalue(ra) == nvalue(kv)) ? LUAU_INSN_D(insn) : 1;
|
||||||
|
#else
|
||||||
pc += int(ttisnumber(ra) && nvalue(ra) == nvalue(kv)) != (aux >> 31) ? LUAU_INSN_D(insn) : 1;
|
pc += int(ttisnumber(ra) && nvalue(ra) == nvalue(kv)) != (aux >> 31) ? LUAU_INSN_D(insn) : 1;
|
||||||
|
#endif
|
||||||
LUAU_ASSERT(unsigned(pc - cl->l.p->code) < unsigned(cl->l.p->sizecode));
|
LUAU_ASSERT(unsigned(pc - cl->l.p->code) < unsigned(cl->l.p->sizecode));
|
||||||
VM_NEXT();
|
VM_NEXT();
|
||||||
}
|
}
|
||||||
|
|
|
@ -351,7 +351,7 @@ int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size
|
||||||
uint32_t mainid = readVarInt(data, size, offset);
|
uint32_t mainid = readVarInt(data, size, offset);
|
||||||
Proto* main = protos[mainid];
|
Proto* main = protos[mainid];
|
||||||
|
|
||||||
luaC_checkthreadsleep(L);
|
luaC_threadbarrier(L);
|
||||||
|
|
||||||
Closure* cl = luaF_newLclosure(L, 0, envt, main);
|
Closure* cl = luaF_newLclosure(L, 0, envt, main);
|
||||||
setclvalue(L, L->top, cl);
|
setclvalue(L, L->top, cl);
|
||||||
|
|
|
@ -10,9 +10,6 @@
|
||||||
#include "lnumutils.h"
|
#include "lnumutils.h"
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauBetterNewindex, false)
|
|
||||||
|
|
||||||
// limit for table tag-method chains (to avoid loops)
|
// limit for table tag-method chains (to avoid loops)
|
||||||
#define MAXTAGLOOP 100
|
#define MAXTAGLOOP 100
|
||||||
|
@ -142,8 +139,6 @@ void luaV_settable(lua_State* L, const TValue* t, TValue* key, StkId val)
|
||||||
{ // `t' is a table?
|
{ // `t' is a table?
|
||||||
Table* h = hvalue(t);
|
Table* h = hvalue(t);
|
||||||
|
|
||||||
if (FFlag::LuauBetterNewindex)
|
|
||||||
{
|
|
||||||
const TValue* oldval = luaH_get(h, key);
|
const TValue* oldval = luaH_get(h, key);
|
||||||
|
|
||||||
// should we assign the key? (if key is valid or __newindex is not set)
|
// should we assign the key? (if key is valid or __newindex is not set)
|
||||||
|
@ -164,25 +159,6 @@ void luaV_settable(lua_State* L, const TValue* t, TValue* key, StkId val)
|
||||||
|
|
||||||
// fallthrough to metamethod
|
// fallthrough to metamethod
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
if (h->readonly)
|
|
||||||
luaG_readonlyerror(L);
|
|
||||||
|
|
||||||
TValue* oldval = luaH_set(L, h, key); // do a primitive set
|
|
||||||
|
|
||||||
L->cachedslot = gval2slot(h, oldval); // remember slot to accelerate future lookups
|
|
||||||
|
|
||||||
if (!ttisnil(oldval) || // result is no nil?
|
|
||||||
(tm = fasttm(L, h->metatable, TM_NEWINDEX)) == NULL)
|
|
||||||
{ // or no TM?
|
|
||||||
setobj2t(L, oldval, val);
|
|
||||||
luaC_barriert(L, h, val);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// else will try the tag method
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (ttisnil(tm = luaT_gettmbyobj(L, t, TM_NEWINDEX)))
|
else if (ttisnil(tm = luaT_gettmbyobj(L, t, TM_NEWINDEX)))
|
||||||
luaG_indexerror(L, t, key);
|
luaG_indexerror(L, t, key);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue