v0.3.8+luau545

This commit is contained in:
Alex Orlenko 2022-09-17 22:10:30 +01:00
parent 66218e74c3
commit 9bd5b63c3c
No known key found for this signature in database
GPG Key ID: 4C150C250863B96D
34 changed files with 1237 additions and 1083 deletions

View File

@ -1,6 +1,6 @@
[package]
name = "luau0-src"
version = "0.3.7+luau541"
version = "0.3.8+luau545"
authors = ["Aleksandr Orlenko <zxteam@protonmail.com>"]
edition = "2021"
repository = "https://github.com/khvzak/luau-src-rs"

View File

@ -134,6 +134,10 @@ public:
{
return visit((class AstExpr*)node);
}
virtual bool visit(class AstExprInterpString* node)
{
return visit((class AstExpr*)node);
}
virtual bool visit(class AstExprError* node)
{
return visit((class AstExpr*)node);
@ -594,9 +598,9 @@ public:
LUAU_RTTI(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,
const AstName& debugname, std::optional<AstTypeList> returnAnnotation = {}, AstTypePack* varargAnnotation = nullptr, bool hasEnd = false,
std::optional<Location> argLocation = std::nullopt);
AstLocal* self, const AstArray<AstLocal*>& args, bool vararg, const Location& varargLocation, AstStatBlock* body, size_t functionDepth,
const AstName& debugname, const std::optional<AstTypeList>& returnAnnotation = {}, AstTypePack* varargAnnotation = nullptr,
bool hasEnd = false, const std::optional<Location>& argLocation = std::nullopt);
void visit(AstVisitor* visitor) override;
@ -732,6 +736,22 @@ public:
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
{
public:
@ -1256,6 +1276,16 @@ public:
};
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

View File

@ -61,6 +61,12 @@ struct Lexeme
SkinnyArrow,
DoubleColon,
InterpStringBegin,
InterpStringMid,
InterpStringEnd,
// An interpolated string with no expressions (like `x`)
InterpStringSimple,
AddAssign,
SubAssign,
MulAssign,
@ -80,6 +86,8 @@ struct Lexeme
BrokenString,
BrokenComment,
BrokenUnicode,
BrokenInterpDoubleBrace,
Error,
Reserved_BEGIN,
@ -208,6 +216,11 @@ private:
Lexeme readLongString(const Position& start, int sep, Lexeme::Type ok, Lexeme::Type broken);
Lexeme readQuotedString();
Lexeme readInterpolatedStringBegin();
Lexeme readInterpolatedStringSection(Position start, Lexeme::Type formatType, Lexeme::Type endType);
void readBackslashInString();
std::pair<AstName, Lexeme::Type> readName();
Lexeme readNumber(const Position& start, unsigned int startOffset);
@ -231,6 +244,14 @@ private:
bool skipComments;
bool readNames;
enum class BraceType
{
InterpolatedString,
Normal
};
std::vector<BraceType> braceStack;
};
inline bool isSpace(char ch)

View File

@ -11,6 +11,7 @@
#include <initializer_list>
#include <optional>
#include <tuple>
namespace Luau
{
@ -109,8 +110,10 @@ private:
// for namelist in explist do block end |
AstStat* parseFor();
// function funcname funcbody |
// funcname ::= Name {`.' Name} [`:' Name]
AstExpr* parseFunctionName(Location start, bool& hasself, AstName& debugname);
// function funcname funcbody
AstStat* parseFunctionStat();
// local function Name funcbody |
@ -135,8 +138,10 @@ private:
// var [`+=' | `-=' | `*=' | `/=' | `%=' | `^=' | `..='] exp
AstStat* parseCompoundAssignment(AstExpr* initial, AstExprBinary::Op op);
// funcbody ::= `(' [parlist] `)' block end
// parlist ::= namelist [`,' `...'] | `...'
std::pair<AstLocal*, AstArray<AstLocal*>> prepareFunctionArguments(const Location& start, bool hasself, const TempVector<Binding>& args);
// funcbodyhead ::= `(' [namelist [`,' `...'] | `...'] `)' [`:` TypeAnnotation]
// funcbody ::= funcbodyhead block end
std::pair<AstExprFunction*, AstLocal*> parseFunctionBody(
bool hasself, const Lexeme& matchFunction, const AstName& debugname, const Name* localName);
@ -148,7 +153,7 @@ private:
// bindinglist ::= (binding | `...') {`,' bindinglist}
// 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();
@ -228,6 +233,9 @@ private:
// TODO: Add grammar rules here?
AstExpr* parseIfElseExpr();
// stringinterp ::= <INTERP_BEGIN> exp {<INTERP_MID> exp} <INTERP_END>
AstExpr* parseInterpString();
// Name
std::optional<Name> parseNameOpt(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);
AstTypeError* reportTypeAnnotationError(const Location& location, const AstArray<AstType*>& types, bool isMissing, const char* format, ...)
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);
void reportAmbiguousCallError();
@ -379,6 +393,7 @@ private:
std::vector<unsigned int> matchRecoveryStopOnToken;
std::vector<AstStat*> scratchStat;
std::vector<AstArray<char>> scratchString;
std::vector<AstExpr*> scratchExpr;
std::vector<AstExpr*> scratchExprAux;
std::vector<AstName> scratchName;

View File

@ -35,6 +35,6 @@ bool equalsLower(std::string_view lhs, std::string_view rhs);
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);
} // namespace Luau

View File

@ -160,17 +160,17 @@ void AstExprIndexExpr::visit(AstVisitor* visitor)
}
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,
const AstName& debugname, std::optional<AstTypeList> returnAnnotation, AstTypePack* varargAnnotation, bool hasEnd,
std::optional<Location> argLocation)
AstLocal* self, const AstArray<AstLocal*>& args, bool vararg, const Location& varargLocation, AstStatBlock* body, size_t functionDepth,
const AstName& debugname, const std::optional<AstTypeList>& returnAnnotation, AstTypePack* varargAnnotation, bool hasEnd,
const std::optional<Location>& argLocation)
: AstExpr(ClassIndex(), location)
, generics(generics)
, genericPacks(genericPacks)
, self(self)
, args(args)
, returnAnnotation(returnAnnotation)
, vararg(vararg.has_value())
, varargLocation(vararg.value_or(Location()))
, vararg(vararg)
, varargLocation(varargLocation)
, varargAnnotation(varargAnnotation)
, body(body)
, 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)
{
if (visitor->visit(this))
@ -936,4 +952,17 @@ AstName getIdentifier(AstExpr* node)
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

View File

@ -6,6 +6,8 @@
#include <limits.h>
LUAU_FASTFLAG(LuauInterpolatedStringBaseSupport)
namespace Luau
{
@ -89,7 +91,8 @@ Lexeme::Lexeme(const Location& location, Type type, const char* data, size_t siz
, length(unsigned(size))
, 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)
@ -160,6 +163,18 @@ std::string Lexeme::toString() const
case QuotedString:
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:
return data ? format("'%.*s'", length, data) : "number";
@ -175,6 +190,9 @@ std::string Lexeme::toString() const
case BrokenComment:
return "unfinished comment";
case BrokenInterpDoubleBrace:
return "'{{', which is invalid (did you mean '\\{'?)";
case BrokenUnicode:
if (codepoint)
{
@ -515,6 +533,32 @@ Lexeme Lexer::readLongString(const Position& start, int sep, Lexeme::Type ok, Le
return Lexeme(Location(start, position()), broken);
}
void Lexer::readBackslashInString()
{
LUAU_ASSERT(peekch() == '\\');
consume();
switch (peekch())
{
case '\r':
consume();
if (peekch() == '\n')
consume();
break;
case 0:
break;
case 'z':
consume();
while (isSpace(peekch()))
consume();
break;
default:
consume();
}
}
Lexeme Lexer::readQuotedString()
{
Position start = position();
@ -535,27 +579,7 @@ Lexeme Lexer::readQuotedString()
return Lexeme(Location(start, position()), Lexeme::BrokenString);
case '\\':
consume();
switch (peekch())
{
case '\r':
consume();
if (peekch() == '\n')
consume();
break;
case 0:
break;
case 'z':
consume();
while (isSpace(peekch()))
consume();
break;
default:
consume();
}
readBackslashInString();
break;
default:
@ -568,6 +592,70 @@ Lexeme Lexer::readQuotedString()
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)
{
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 '=':
{
consume();
@ -716,6 +834,15 @@ Lexeme Lexer::readNext()
case '\'':
return readQuotedString();
case '`':
if (FFlag::LuauInterpolatedStringBaseSupport)
return readInterpolatedStringBegin();
else
{
consume();
return Lexeme(Location(start, 1), '`');
}
case '.':
consume();
@ -817,8 +944,6 @@ Lexeme Lexer::readNext()
case '(':
case ')':
case '{':
case '}':
case ']':
case ';':
case ',':

View File

@ -23,10 +23,15 @@ LUAU_FASTFLAGVARIABLE(LuauErrorDoubleHexPrefix, false)
LUAU_FASTFLAGVARIABLE(LuauLintParseIntegerIssues, 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_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
{
@ -601,16 +606,11 @@ AstStat* Parser::parseFor()
}
}
// function funcname funcbody |
// funcname ::= Name {`.' Name} [`:' Name]
AstStat* Parser::parseFunctionStat()
AstExpr* Parser::parseFunctionName(Location start, bool& hasself, AstName& debugname)
{
Location start = lexer.current().location;
Lexeme matchFunction = lexer.current();
nextLexeme();
AstName debugname = (lexer.current().type == Lexeme::Name) ? AstName(lexer.current().name) : AstName();
if (lexer.current().type == Lexeme::Name)
debugname = AstName(lexer.current().name);
// parse funcname into a chain of indexing operators
AstExpr* expr = parseNameExpr("function name");
@ -636,8 +636,6 @@ AstStat* Parser::parseFunctionStat()
recursionCounter = recursionCounterOld;
// finish with :
bool hasself = false;
if (lexer.current().type == ':')
{
Position opPosition = lexer.current().location.begin;
@ -653,6 +651,21 @@ AstStat* Parser::parseFunctionStat()
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]++;
AstExprFunction* body = parseFunctionBody(hasself, matchFunction, debugname, nullptr).first;
@ -781,10 +794,11 @@ AstDeclaredClassProp Parser::parseDeclaredClassMethod()
TempVector<Binding> args(scratchBinding);
std::optional<Location> vararg = std::nullopt;
bool vararg = false;
Location varargLocation;
AstTypePack* varargAnnotation = nullptr;
if (lexer.current().type != ')')
std::tie(vararg, varargAnnotation) = parseBindingList(args, /* allowDot3 */ true);
std::tie(vararg, varargLocation, varargAnnotation) = parseBindingList(args, /* allowDot3 */ true);
expectMatchAndConsume(')', matchParen);
@ -838,11 +852,12 @@ AstStat* Parser::parseDeclaration(const Location& start)
TempVector<Binding> args(scratchBinding);
std::optional<Location> vararg;
bool vararg = false;
Location varargLocation;
AstTypePack* varargAnnotation = nullptr;
if (lexer.current().type != ')')
std::tie(vararg, varargAnnotation) = parseBindingList(args, /* allowDot3= */ true);
std::tie(vararg, varargLocation, varargAnnotation) = parseBindingList(args, /* allowDot3= */ true);
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);
}
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
// parlist ::= bindinglist [`,' `...'] | `...'
std::pair<AstExprFunction*, AstLocal*> Parser::parseFunctionBody(
@ -979,15 +1009,18 @@ std::pair<AstExprFunction*, AstLocal*> Parser::parseFunctionBody(
TempVector<Binding> args(scratchBinding);
std::optional<Location> vararg;
bool vararg = false;
Location varargLocation;
AstTypePack* varargAnnotation = nullptr;
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);
std::optional<AstTypeList> typelist = parseOptionalReturnTypeAnnotation();
@ -1000,19 +1033,11 @@ std::pair<AstExprFunction*, AstLocal*> Parser::parseFunctionBody(
unsigned int localsBegin = saveLocals();
Function fun;
fun.vararg = vararg.has_value();
fun.vararg = vararg;
functionStack.push_back(fun);
functionStack.emplace_back(fun);
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]));
auto [self, vars] = prepareFunctionArguments(start, hasself, args);
AstStatBlock* body = parseBlock();
@ -1024,8 +1049,8 @@ std::pair<AstExprFunction*, AstLocal*> Parser::parseFunctionBody(
bool hasEnd = expectMatchEndAndConsume(Lexeme::ReservedEnd, matchFunction);
return {allocator.alloc<AstExprFunction>(Location(start, end), generics, genericPacks, self, copy(vars), vararg, body, functionStack.size(),
debugname, typelist, varargAnnotation, hasEnd, argLocation),
return {allocator.alloc<AstExprFunction>(Location(start, end), generics, genericPacks, self, vars, vararg, varargLocation, body,
functionStack.size(), debugname, typelist, varargAnnotation, hasEnd, argLocation),
funLocal};
}
@ -1056,7 +1081,7 @@ Parser::Binding Parser::parseBinding()
}
// 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)
{
@ -1072,7 +1097,7 @@ std::pair<std::optional<Location>, AstTypePack*> Parser::parseBindingList(TempVe
tailAnnotation = parseVariadicArgumentAnnotation();
}
return {varargLocation, tailAnnotation};
return {true, varargLocation, tailAnnotation};
}
result.push_back(parseBinding());
@ -1082,7 +1107,7 @@ std::pair<std::optional<Location>, AstTypePack*> Parser::parseBindingList(TempVe
nextLexeme();
}
return {std::nullopt, nullptr};
return {false, Location(), nullptr};
}
AstType* Parser::parseOptionalTypeAnnotation()
@ -1540,38 +1565,43 @@ AstTypeOrPack Parser::parseSimpleTypeAnnotation(bool allowPack)
{
incrementRecursionCounter("type annotation");
Location begin = lexer.current().location;
Location start = lexer.current().location;
if (lexer.current().type == Lexeme::ReservedNil)
{
nextLexeme();
return {allocator.alloc<AstTypeReference>(begin, std::nullopt, nameNil), {}};
return {allocator.alloc<AstTypeReference>(start, std::nullopt, nameNil), {}};
}
else if (lexer.current().type == Lexeme::ReservedTrue)
{
nextLexeme();
return {allocator.alloc<AstTypeSingletonBool>(begin, true)};
return {allocator.alloc<AstTypeSingletonBool>(start, true)};
}
else if (lexer.current().type == Lexeme::ReservedFalse)
{
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)
{
if (std::optional<AstArray<char>> value = parseCharArray())
{
AstArray<char> svalue = *value;
return {allocator.alloc<AstTypeSingletonString>(begin, svalue)};
return {allocator.alloc<AstTypeSingletonString>(start, svalue)};
}
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)
{
Location location = lexer.current().location;
nextLexeme();
return {reportTypeAnnotationError(location, {}, /*isMissing*/ false, "Malformed string")};
return {reportTypeAnnotationError(start, {}, /*isMissing*/ false, "Malformed string")};
}
else if (lexer.current().type == Lexeme::Name)
{
@ -1602,7 +1632,7 @@ AstTypeOrPack Parser::parseSimpleTypeAnnotation(bool allowPack)
expectMatchAndConsume(')', typeofBegin);
return {allocator.alloc<AstTypeTypeof>(Location(begin, end), expr), {}};
return {allocator.alloc<AstTypeTypeof>(Location(start, end), expr), {}};
}
bool hasParameters = false;
@ -1616,7 +1646,7 @@ AstTypeOrPack Parser::parseSimpleTypeAnnotation(bool allowPack)
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 == '{')
{
@ -1628,23 +1658,35 @@ AstTypeOrPack Parser::parseSimpleTypeAnnotation(bool allowPack)
}
else if (lexer.current().type == Lexeme::ReservedFunction)
{
Location location = lexer.current().location;
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) -> "
"...any'"),
{}};
}
else
{
Location location = lexer.current().location;
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;
// For a missing type annotation, capture 'space' between last token and the next one
location = Location(lexer.previousLocation().end, lexer.current().location.begin);
// For a missing type annotation, capture 'space' between last token and the next one
location = Location(lexer.previousLocation().end, lexer.current().location.begin);
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()), {}};
}
}
}
@ -2215,15 +2257,25 @@ AstExpr* Parser::parseSimpleExpr()
{
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();
}
else if (FFlag::LuauInterpolatedStringBaseSupport && lexer.current().type == Lexeme::InterpStringBegin)
{
return parseInterpString();
}
else if (lexer.current().type == Lexeme::BrokenString)
{
nextLexeme();
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)
{
if (functionStack.back().vararg)
@ -2614,11 +2666,12 @@ AstArray<AstTypeOrPack> Parser::parseTypeParams()
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);
if (lexer.current().type == Lexeme::QuotedString)
if (lexer.current().type == Lexeme::QuotedString || lexer.current().type == Lexeme::InterpStringSimple)
{
if (!Lexer::fixupQuotedString(scratchData))
{
@ -2645,6 +2698,67 @@ AstExpr* Parser::parseString()
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()
{
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
// This can be used to pinpoint the problem location for a possible future *actual* mismatch
if (lexer.current().location.begin.line != begin.position.line &&
lexer.current().location.begin.column != begin.position.column &&
if (lexer.current().location.begin.line != begin.position.line && lexer.current().location.begin.column != begin.position.column &&
endMismatchSuspect.position.line < begin.position.line) // Only replace the previous suspect with more recent suspects
{
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, ...)
{
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_start(args, format);
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));
}
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()
{
Lexeme::Type type = lexer.next(/* skipComments= */ false, true).type;

View File

@ -230,19 +230,25 @@ bool isIdentifier(std::string_view s)
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;
r.reserve(s.size() + 50); // arbitrary number to guess how many characters we'll be inserting
for (uint8_t c : s)
{
if (c >= ' ' && c != '\\' && c != '\'' && c != '\"')
if (c >= ' ' && c != '\\' && c != '\'' && c != '\"' && c != '`' && c != '{')
r += c;
else
{
r += '\\';
if (escapeForInterpString && (c == '`' || c == '{'))
{
r += c;
continue;
}
switch (c)
{
case '\a':

View File

@ -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
LOP_FORGLOOP,
// FORGPREP_INEXT/FORGLOOP_INEXT: FORGLOOP with 2 output variables (no AUX encoding), assuming generator is luaB_inext
// FORGPREP_INEXT prepares the index variable and jumps to FORGLOOP_INEXT
// FORGLOOP_INEXT has identical encoding and semantics to FORGLOOP (except for AUX encoding)
// FORGPREP_INEXT: prepare FORGLOOP with 2 output variables (no AUX encoding), assuming generator is luaB_inext, and jump to FORGLOOP
// A: target register (see FORGLOOP for register layout)
LOP_FORGPREP_INEXT,
LOP_FORGLOOP_INEXT,
// FORGPREP_NEXT/FORGLOOP_NEXT: FORGLOOP with 2 output variables (no AUX encoding), assuming generator is luaB_next
// FORGPREP_NEXT prepares the index variable and jumps to FORGLOOP_NEXT
// FORGLOOP_NEXT has identical encoding and semantics to FORGLOOP (except for AUX encoding)
// removed in v3
LOP_DEP_FORGLOOP_INEXT,
// 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_FORGLOOP_NEXT,
// removed in v3
LOP_DEP_FORGLOOP_NEXT,
// GETVARARGS: copy variables into the target register from vararg storage for current function
// A: target register
@ -343,12 +345,9 @@ enum LuauOpcode
// B: source register (for VAL/REF) or upvalue index (for UPVAL/UPREF)
LOP_CAPTURE,
// JUMPIFEQK, JUMPIFNOTEQK: jumps to target offset if the comparison with constant is true (or false, for NOT variants)
// A: source register 1
// D: jump offset (-32768..32767; 0 means "next instruction" aka "don't jump")
// AUX: constant table index
LOP_JUMPIFEQK,
LOP_JUMPIFNOTEQK,
// removed in v3
LOP_DEP_JUMPIFEQK,
LOP_DEP_JUMPIFNOTEQK,
// FASTCALL1: perform a fast call of a built-in function using 1 register argument
// A: builtin function id (see LuauBuiltinFunction)
@ -412,9 +411,9 @@ enum LuauOpcode
enum LuauBytecodeTag
{
// 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_TARGET = 2,
LBC_VERSION_TARGET = 3,
// Types of constant table entries
LBC_CONSTANT_NIL = 0,
LBC_CONSTANT_BOOLEAN,

View File

@ -12,7 +12,9 @@ inline bool isFlagExperimental(const char* flag)
// or critical bugs that are found after the code has been submitted.
static const char* kList[] = {
"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)

View File

@ -6,8 +6,6 @@
#include <algorithm>
#include <string.h>
LUAU_FASTFLAGVARIABLE(LuauCompileBytecodeV3, false)
namespace Luau
{
@ -75,8 +73,6 @@ static int getOpLength(LuauOpcode op)
case LOP_SETLIST:
case LOP_FORGLOOP:
case LOP_LOADKX:
case LOP_JUMPIFEQK:
case LOP_JUMPIFNOTEQK:
case LOP_FASTCALL2:
case LOP_FASTCALL2K:
case LOP_JUMPXEQKNIL:
@ -108,12 +104,8 @@ inline bool isJumpD(LuauOpcode op)
case LOP_FORGPREP:
case LOP_FORGLOOP:
case LOP_FORGPREP_INEXT:
case LOP_FORGLOOP_INEXT:
case LOP_FORGPREP_NEXT:
case LOP_FORGLOOP_NEXT:
case LOP_JUMPBACK:
case LOP_JUMPIFEQK:
case LOP_JUMPIFNOTEQK:
case LOP_JUMPXEQKNIL:
case LOP_JUMPXEQKB:
case LOP_JUMPXEQKN:
@ -1079,9 +1071,6 @@ std::string BytecodeBuilder::getError(const std::string& message)
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
return LBC_VERSION_TARGET;
}
@ -1252,13 +1241,6 @@ void BytecodeBuilder::validate() const
VJUMP(LUAU_INSN_D(insn));
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_JUMPXEQKB:
VREG(LUAU_INSN_A(insn));
@ -1365,9 +1347,7 @@ void BytecodeBuilder::validate() const
break;
case LOP_FORGPREP_INEXT:
case LOP_FORGLOOP_INEXT:
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
VJUMP(LUAU_INSN_D(insn));
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);
break;
case LOP_FORGLOOP_INEXT:
formatAppend(result, "FORGLOOP_INEXT R%d L%d\n", LUAU_INSN_A(insn), targetLabel);
break;
case LOP_FORGPREP_NEXT:
formatAppend(result, "FORGPREP_NEXT R%d L%d\n", LUAU_INSN_A(insn), targetLabel);
break;
case LOP_FORGLOOP_NEXT:
formatAppend(result, "FORGLOOP_NEXT R%d L%d\n", LUAU_INSN_A(insn), targetLabel);
break;
case LOP_GETVARARGS:
formatAppend(result, "GETVARARGS R%d %d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn) - 1);
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));
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:
formatAppend(result, "JUMPXEQKNIL R%d L%d%s\n", LUAU_INSN_A(insn), targetLabel, *code >> 31 ? " NOT" : "");
code++;

View File

@ -14,6 +14,8 @@
#include <algorithm>
#include <bitset>
#include <memory>
#include <math.h>
LUAU_FASTINTVARIABLE(LuauCompileLoopUnrollThreshold, 25)
@ -23,11 +25,7 @@ LUAU_FASTINTVARIABLE(LuauCompileInlineThreshold, 25)
LUAU_FASTINTVARIABLE(LuauCompileInlineThresholdMaxBoost, 300)
LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5)
LUAU_FASTFLAGVARIABLE(LuauCompileXEQ, false)
LUAU_FASTFLAGVARIABLE(LuauCompileOptimalAssignment, false)
LUAU_FASTFLAGVARIABLE(LuauCompileExtractK, false)
LUAU_FASTFLAG(LuauInterpolatedStringBaseSupport)
namespace Luau
{
@ -402,47 +400,29 @@ 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->args.size >= 1);
LUAU_ASSERT(expr->args.size <= 2 || (bfid == LBF_BIT32_EXTRACTK && expr->args.size == 3));
LUAU_ASSERT(bfid == LBF_BIT32_EXTRACTK ? bfK >= 0 : bfK < 0);
LuauOpcode opc = expr->args.size == 1 ? LOP_FASTCALL1 : LOP_FASTCALL2;
if (FFlag::LuauCompileExtractK)
{
opc = expr->args.size == 1 ? LOP_FASTCALL1 : (bfK >= 0 || isConstant(expr->args.data[1])) ? LOP_FASTCALL2K : LOP_FASTCALL2;
}
LuauOpcode opc = expr->args.size == 1 ? LOP_FASTCALL1 : (bfK >= 0 || isConstant(expr->args.data[1])) ? LOP_FASTCALL2K : LOP_FASTCALL2;
uint32_t args[3] = {};
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)
{
int32_t cid = getConstantIndex(expr->args.data[i]);
if (cid < 0)
CompileError::raise(expr->location, "Exceeded constant limit; simplify the code to compile");
int32_t cid = getConstantIndex(expr->args.data[i]);
if (cid < 0)
CompileError::raise(expr->location, "Exceeded constant limit; simplify the code to compile");
args[i] = cid;
continue; // TODO: remove this and change if below to else if
}
args[i] = cid;
}
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)
else if (int reg = getExprLocalReg(expr->args.data[i]); reg >= 0)
{
args[i] = uint8_t(reg);
}
@ -464,24 +444,10 @@ struct Compiler
// these FASTCALL variants.
for (size_t i = 0; i < expr->args.size; ++i)
{
if (FFlag::LuauCompileExtractK)
{
if (i > 0 && opc == LOP_FASTCALL2K)
emitLoadK(uint8_t(regs + 1 + i), args[i]);
else if (args[i] != regs + 1 + i)
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);
}
if (i > 0 && opc == LOP_FASTCALL2K)
emitLoadK(uint8_t(regs + 1 + i), args[i]);
else 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
@ -754,7 +720,7 @@ struct Compiler
}
// 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 wc = getConstant(expr->args.data[2]);
@ -1076,102 +1042,64 @@ struct Compiler
std::swap(left, right);
}
if (FFlag::LuauCompileXEQ)
uint8_t rl = compileExprAuto(left, rs);
if (isEq && operandIsConstant)
{
uint8_t rl = compileExprAuto(left, rs);
const Constant* cv = constants.find(right);
LUAU_ASSERT(cv && cv->type != Constant::Type_Unknown);
if (isEq && operandIsConstant)
LuauOpcode opc = LOP_NOP;
int32_t cid = -1;
uint32_t flip = (expr->op == AstExprBinary::CompareEq) == not_ ? 0x80000000 : 0;
switch (cv->type)
{
const Constant* cv = constants.find(right);
LUAU_ASSERT(cv && cv->type != Constant::Type_Unknown);
case Constant::Type_Nil:
opc = LOP_JUMPXEQKNIL;
cid = 0;
break;
LuauOpcode opc = LOP_NOP;
int32_t cid = -1;
uint32_t flip = (expr->op == AstExprBinary::CompareEq) == not_ ? 0x80000000 : 0;
case Constant::Type_Boolean:
opc = LOP_JUMPXEQKB;
cid = cv->valueBoolean;
break;
switch (cv->type)
{
case Constant::Type_Nil:
opc = LOP_JUMPXEQKNIL;
cid = 0;
break;
case Constant::Type_Number:
opc = LOP_JUMPXEQKN;
cid = getConstantIndex(right);
break;
case Constant::Type_Boolean:
opc = LOP_JUMPXEQKB;
cid = cv->valueBoolean;
break;
case Constant::Type_String:
opc = LOP_JUMPXEQKS;
cid = getConstantIndex(right);
break;
case Constant::Type_Number:
opc = LOP_JUMPXEQKN;
cid = getConstantIndex(right);
break;
case Constant::Type_String:
opc = LOP_JUMPXEQKS;
cid = getConstantIndex(right);
break;
default:
LUAU_ASSERT(!"Unexpected constant type");
}
if (cid < 0)
CompileError::raise(expr->location, "Exceeded constant limit; simplify the code to compile");
size_t jumpLabel = bytecode.emitLabel();
bytecode.emitAD(opc, rl, 0);
bytecode.emitAux(cid | flip);
return jumpLabel;
default:
LUAU_ASSERT(!"Unexpected constant type");
}
else
{
LuauOpcode opc = getJumpOpCompare(expr->op, not_);
uint8_t rr = compileExprAuto(right, rs);
if (cid < 0)
CompileError::raise(expr->location, "Exceeded constant limit; simplify the code to compile");
size_t jumpLabel = bytecode.emitLabel();
size_t jumpLabel = bytecode.emitLabel();
if (expr->op == AstExprBinary::CompareGt || expr->op == AstExprBinary::CompareGe)
{
bytecode.emitAD(opc, rr, 0);
bytecode.emitAux(rl);
}
else
{
bytecode.emitAD(opc, rl, 0);
bytecode.emitAux(rr);
}
bytecode.emitAD(opc, rl, 0);
bytecode.emitAux(cid | flip);
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);
uint8_t 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.emitAD(opc, rr, 0);
bytecode.emitAux(rl);
}
else
@ -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)
{
size_t hashSizeLog2 = 0;
@ -2059,6 +2057,10 @@ struct Compiler
{
compileExprIfElse(expr, target, targetTemp);
}
else if (AstExprInterpString* interpString = node->as<AstExprInterpString>(); FFlag::LuauInterpolatedStringBaseSupport && interpString)
{
compileExprInterpString(interpString, target, targetTemp);
}
else
{
LUAU_ASSERT(!"Unknown expression type");
@ -2901,62 +2903,6 @@ struct Compiler
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
{
LValue lvalue;
@ -2965,6 +2911,18 @@ struct Compiler
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)
{
struct Visitor : AstVisitor
@ -3056,111 +3014,82 @@ struct Compiler
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
// left hand side - for example, in "a[expr] = foo" expr will get evaluated here
std::vector<Assignment> vars(stat->vars.size);
for (size_t i = 0; i < stat->vars.size; ++i)
vars[i].lvalue = compileLValue(stat->vars.data[i], rs);
// perform conflict resolution: if any expression refers to a local that is assigned before evaluating it, we assign to a temporary
// register after this, vars[i].conflictReg is set for locals that need to be assigned in the second pass
resolveAssignConflicts(stat, vars, stat->values);
// compute rhs into (mostly) fresh registers
// note that when the lhs assigment is a local, we evaluate directly into that register
// this is possible because resolveAssignConflicts renamed conflicting locals into temporaries
// after this, vars[i].valueReg is set to a register with the value for *all* vars, but some have already been assigned
for (size_t i = 0; i < stat->vars.size && i < stat->values.size; ++i)
{
// 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<Assignment> vars(stat->vars.size);
AstExpr* value = stat->values.data[i];
for (size_t i = 0; i < stat->vars.size; ++i)
vars[i].lvalue = compileLValue(stat->vars.data[i], rs);
// perform conflict resolution: if any expression refers to a local that is assigned before evaluating it, we assign to a temporary
// register after this, vars[i].conflictReg is set for locals that need to be assigned in the second pass
resolveAssignConflicts(stat, vars, stat->values);
// compute rhs into (mostly) fresh registers
// note that when the lhs assigment is a local, we evaluate directly into that register
// this is possible because resolveAssignConflicts renamed conflicting locals into temporaries
// after this, vars[i].valueReg is set to a register with the value for *all* vars, but some have already been assigned
for (size_t i = 0; i < stat->vars.size && i < stat->values.size; ++i)
if (i + 1 == stat->values.size && stat->vars.size > stat->values.size)
{
AstExpr* value = stat->values.data[i];
// allocate a consecutive range of regs for all remaining vars and compute everything into temps
// note, this also handles trailing nils
uint8_t rest = uint8_t(stat->vars.size - stat->values.size + 1);
uint8_t temp = allocReg(stat, rest);
if (i + 1 == stat->values.size && stat->vars.size > stat->values.size)
compileExprTempN(value, temp, rest, /* targetTop= */ true);
for (size_t j = i; j < stat->vars.size; ++j)
vars[j].valueReg = uint8_t(temp + (j - i));
}
else
{
Assignment& var = vars[i];
// if target is a local, use compileExpr directly to target
if (var.lvalue.kind == LValue::Kind_Local)
{
// allocate a consecutive range of regs for all remaining vars and compute everything into temps
// note, this also handles trailing nils
uint8_t rest = uint8_t(stat->vars.size - stat->values.size + 1);
uint8_t temp = allocReg(stat, rest);
var.valueReg = (var.conflictReg == kInvalidReg) ? var.lvalue.reg : var.conflictReg;
compileExprTempN(value, temp, rest, /* targetTop= */ true);
for (size_t j = i; j < stat->vars.size; ++j)
vars[j].valueReg = uint8_t(temp + (j - i));
compileExpr(stat->values.data[i], var.valueReg);
}
else
{
Assignment& var = vars[i];
// if target is a local, use compileExpr directly to target
if (var.lvalue.kind == LValue::Kind_Local)
{
var.valueReg = (var.conflictReg == kInvalidReg) ? var.lvalue.reg : var.conflictReg;
compileExpr(stat->values.data[i], var.valueReg);
}
else
{
var.valueReg = compileExprAuto(stat->values.data[i], rs);
}
var.valueReg = compileExprAuto(stat->values.data[i], rs);
}
}
// compute expressions with side effects for lulz
for (size_t i = stat->vars.size; i < stat->values.size; ++i)
{
RegScope rsi(this);
compileExprAuto(stat->values.data[i], rsi);
}
// almost done... let's assign everything left to right, noting that locals were either written-to directly, or will be written-to in a
// separate pass to avoid conflicts
for (const Assignment& var : vars)
{
LUAU_ASSERT(var.valueReg != kInvalidReg);
if (var.lvalue.kind != LValue::Kind_Local)
{
setDebugLine(var.lvalue.location);
compileAssign(var.lvalue, var.valueReg);
}
}
// all regular local writes are done by the prior loops by computing result directly into target, so this just handles conflicts OR
// local copies from temporary registers in multret context, since in that case we have to allocate consecutive temporaries
for (const Assignment& var : vars)
{
if (var.lvalue.kind == LValue::Kind_Local && var.valueReg != var.lvalue.reg)
bytecode.emitABC(LOP_MOVE, var.lvalue.reg, var.valueReg, 0);
}
}
else
// compute expressions with side effects for lulz
for (size_t i = stat->vars.size; i < stat->values.size; ++i)
{
// 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);
RegScope rsi(this);
compileExprAuto(stat->values.data[i], rsi);
}
for (size_t i = 0; i < stat->vars.size; ++i)
vars[i] = compileLValue(stat->vars.data[i], rs);
// almost done... let's assign everything left to right, noting that locals were either written-to directly, or will be written-to in a
// separate pass to avoid conflicts
for (const Assignment& var : vars)
{
LUAU_ASSERT(var.valueReg != kInvalidReg);
// 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)
if (var.lvalue.kind != LValue::Kind_Local)
{
setDebugLine(stat->vars.data[i]);
compileAssign(vars[i], uint8_t(regs + i));
setDebugLine(var.lvalue.location);
compileAssign(var.lvalue, var.valueReg);
}
}
// all regular local writes are done by the prior loops by computing result directly into target, so this just handles conflicts OR
// local copies from temporary registers in multret context, since in that case we have to allocate consecutive temporaries
for (const Assignment& var : vars)
{
if (var.lvalue.kind == LValue::Kind_Local && var.valueReg != var.lvalue.reg)
bytecode.emitABC(LOP_MOVE, var.lvalue.reg, var.valueReg, 0);
}
}
void compileStatCompoundAssign(AstStatCompoundAssign* stat)
@ -3528,14 +3457,6 @@ struct Compiler
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)
{
if (options.debugLevel >= 1)
@ -3808,6 +3729,7 @@ struct Compiler
std::vector<Loop> loops;
std::vector<InlineFrame> inlineFrames;
std::vector<Capture> captures;
std::vector<std::unique_ptr<char[]>> interpStrings;
};
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);
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);
const Compiler::Function* mainf = compiler.functions.find(&main);

View File

@ -349,6 +349,11 @@ struct ConstantVisitor : AstVisitor
if (cond.type != Constant::Type_Unknown)
result = cond.isTruthful() ? trueExpr : falseExpr;
}
else if (AstExprInterpString* expr = node->as<AstExprInterpString>())
{
for (AstExpr* expression : expr->expressions)
analyze(expression);
}
else
{
LUAU_ASSERT(!"Unknown expression type");

View File

@ -215,6 +215,16 @@ struct CostVisitor : AstVisitor
{
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
{
LUAU_ASSERT(!"Unknown expression type");

View File

@ -35,6 +35,15 @@ enum lua_Status
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 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_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_rawseti(lua_State* L, int idx, int n);
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 void* lua_getthreaddata(lua_State* L);
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

View File

@ -24,14 +24,14 @@
* 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.
*
* Functions that push any collectable objects to the stack *should* call luaC_checkthreadsleep. Failure to do this can result
* in stack references that point to dead objects since sleeping threads don't get rescanned.
* 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 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.
*
* Note that luaC_checkGC may scan the thread and put it back to sleep; 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.
* 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_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"
@ -152,7 +152,7 @@ void lua_xmove(lua_State* from, lua_State* to, int n)
api_checknelems(from, n);
api_check(from, from->global == to->global);
api_check(from, to->ci->top - to->top >= n);
luaC_checkthreadsleep(to);
luaC_threadbarrier(to);
StkId ttop = to->top;
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)
{
api_check(from, from->global == to->global);
luaC_checkthreadsleep(to);
luaC_threadbarrier(to);
setobj2s(to, to->top, index2addr(from, idx));
api_incr_top(to);
return;
@ -177,7 +177,7 @@ void lua_xpush(lua_State* from, lua_State* to, int idx)
lua_State* lua_newthread(lua_State* L)
{
luaC_checkGC(L);
luaC_checkthreadsleep(L);
luaC_threadbarrier(L);
lua_State* L1 = luaE_newthread(L);
setthvalue(L, L->top, L1);
api_incr_top(L);
@ -236,7 +236,7 @@ void lua_remove(lua_State* L, int idx)
void lua_insert(lua_State* L, int idx)
{
luaC_checkthreadsleep(L);
luaC_threadbarrier(L);
StkId p = index2addr(L, idx);
api_checkvalidindex(L, p);
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)
{
api_checknelems(L, 1);
luaC_checkthreadsleep(L);
luaC_threadbarrier(L);
StkId o = index2addr(L, idx);
api_checkvalidindex(L, o);
if (idx == LUA_ENVIRONINDEX)
@ -276,7 +276,7 @@ void lua_replace(lua_State* L, int idx)
void lua_pushvalue(lua_State* L, int idx)
{
luaC_checkthreadsleep(L);
luaC_threadbarrier(L);
StkId o = index2addr(L, idx);
setobj2s(L, L->top, o);
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);
if (!ttisstring(o))
{
luaC_checkthreadsleep(L);
luaC_threadbarrier(L);
if (!luaV_tostring(L, o))
{ // conversion failed?
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)
{
luaC_checkGC(L);
luaC_checkthreadsleep(L);
luaC_threadbarrier(L);
setsvalue2s(L, L->top, luaS_newlstr(L, s, len));
api_incr_top(L);
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)
{
luaC_checkGC(L);
luaC_checkthreadsleep(L);
luaC_threadbarrier(L);
const char* ret = luaO_pushvfstring(L, fmt, argp);
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, ...)
{
luaC_checkGC(L);
luaC_checkthreadsleep(L);
luaC_threadbarrier(L);
va_list argp;
va_start(argp, fmt);
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)
{
luaC_checkGC(L);
luaC_checkthreadsleep(L);
luaC_threadbarrier(L);
api_checknelems(L, nup);
Closure* cl = luaF_newCclosure(L, nup, getcurrenv(L));
cl->c.f = fn;
@ -674,7 +674,7 @@ void lua_pushlightuserdata(lua_State* L, void* p)
int lua_pushthread(lua_State* L)
{
luaC_checkthreadsleep(L);
luaC_threadbarrier(L);
setthvalue(L, L->top, L);
api_incr_top(L);
return L->global->mainthread == L;
@ -686,7 +686,7 @@ int lua_pushthread(lua_State* L)
int lua_gettable(lua_State* L, int idx)
{
luaC_checkthreadsleep(L);
luaC_threadbarrier(L);
StkId t = index2addr(L, idx);
api_checkvalidindex(L, t);
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)
{
luaC_checkthreadsleep(L);
luaC_threadbarrier(L);
StkId t = index2addr(L, idx);
api_checkvalidindex(L, t);
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)
{
luaC_checkthreadsleep(L);
luaC_threadbarrier(L);
StkId t = index2addr(L, idx);
api_check(L, ttistable(t));
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)
{
luaC_checkthreadsleep(L);
luaC_threadbarrier(L);
StkId t = index2addr(L, idx);
api_check(L, ttistable(t));
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)
{
luaC_checkthreadsleep(L);
luaC_threadbarrier(L);
StkId t = index2addr(L, idx);
api_check(L, ttistable(t));
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)
{
luaC_checkGC(L);
luaC_checkthreadsleep(L);
luaC_threadbarrier(L);
sethvalue(L, L->top, luaH_new(L, narray, nrec));
api_incr_top(L);
return;
@ -775,7 +775,7 @@ void lua_setsafeenv(lua_State* L, int objindex, int enabled)
int lua_getmetatable(lua_State* L, int objindex)
{
luaC_checkthreadsleep(L);
luaC_threadbarrier(L);
Table* mt = NULL;
const TValue* obj = index2addr(L, objindex);
switch (ttype(obj))
@ -800,7 +800,7 @@ int lua_getmetatable(lua_State* L, int objindex)
void lua_getfenv(lua_State* L, int idx)
{
luaC_checkthreadsleep(L);
luaC_threadbarrier(L);
StkId o = index2addr(L, idx);
api_checkvalidindex(L, o);
switch (ttype(o))
@ -845,6 +845,19 @@ void lua_setfield(lua_State* L, int idx, const char* k)
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)
{
api_checknelems(L, 2);
@ -1006,6 +1019,23 @@ int lua_status(lua_State* L)
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)
{
return L->userdata;
@ -1161,7 +1191,7 @@ l_noret lua_error(lua_State* L)
int lua_next(lua_State* L, int idx)
{
luaC_checkthreadsleep(L);
luaC_threadbarrier(L);
StkId t = index2addr(L, idx);
api_check(L, ttistable(t));
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)
{
luaC_checkGC(L);
luaC_checkthreadsleep(L);
luaC_threadbarrier(L);
luaV_concat(L, n, cast_int(L->top - L->base) - 1);
L->top -= (n - 1);
}
else if (n == 0)
{ // push empty string
luaC_checkthreadsleep(L);
luaC_threadbarrier(L);
setsvalue2s(L, L->top, luaS_newlstr(L, "", 0));
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);
luaC_checkGC(L);
luaC_checkthreadsleep(L);
luaC_threadbarrier(L);
Udata* u = luaU_newudata(L, sz, tag);
setuvalue(L, L->top, u);
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*))
{
luaC_checkGC(L);
luaC_checkthreadsleep(L);
luaC_threadbarrier(L);
// 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;
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)
{
luaC_checkthreadsleep(L);
luaC_threadbarrier(L);
TValue* val;
const char* name = aux_upvalue(index2addr(L, funcindex), n, &val);
if (name)
@ -1266,7 +1296,6 @@ const char* lua_setupvalue(lua_State* L, int funcindex, int n)
L->top--;
setobj(L, val, L->top);
luaC_barrier(L, clvalue(fi), L->top);
luaC_upvalbarrier(L, cast_to(UpVal*, NULL), val);
}
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)
{
luaC_checkGC(L);
luaC_checkthreadsleep(L);
luaC_threadbarrier(L);
StkId p = index2addr(L, idx);
api_check(L, isLfunction(p));
Closure* cl = clvalue(p);

View File

@ -15,8 +15,6 @@
#include <intrin.h>
#endif
LUAU_FASTFLAGVARIABLE(LuauFasterBit32NoWidth, false)
// luauF functions implement FASTCALL instruction that performs a direct execution of some builtin functions from the VM
// The rule of thumb is that FASTCALL functions can not call user code, yield, fail, or reallocate stack.
// If types of the arguments mismatch, luauF_* needs to return -1 and the execution will fall back to the usual call path
@ -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)
{
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 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)
{
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 a2 = nvalue(args);

View File

@ -5,38 +5,16 @@
#include "lstate.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_BREAK -2
static const char* const statnames[] = {"running", "suspended", "normal", "dead"};
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 const char* const statnames[] = {"running", "suspended", "normal", "dead", "dead"}; // dead appears twice for LUA_COERR and LUA_COFIN
static int costatus(lua_State* L)
{
lua_State* co = lua_tothread(L, 1);
luaL_argexpected(L, co, 1, "thread");
lua_pushstring(L, statnames[auxstatus(L, co)]);
lua_pushstring(L, statnames[lua_costatus(L, co)]);
return 1;
}
@ -45,8 +23,8 @@ static int auxresume(lua_State* L, lua_State* co, int narg)
// error handling for edge cases
if (co->status != LUA_YIELD)
{
int status = auxstatus(L, co);
if (status != CO_SUS)
int status = lua_costatus(L, co);
if (status != LUA_COSUS)
{
lua_pushfstring(L, "cannot resume %s coroutine", statnames[status]);
return CO_STATUS_ERROR;
@ -236,8 +214,8 @@ static int coclose(lua_State* L)
lua_State* co = lua_tothread(L, 1);
luaL_argexpected(L, co, 1, "thread");
int status = auxstatus(L, co);
if (status != CO_DEAD && status != CO_SUS)
int status = lua_costatus(L, co);
if (status != LUA_COFIN && status != LUA_COERR && status != LUA_COSUS)
luaL_error(L, "cannot close %s coroutine", statnames[status]);
if (co->status == LUA_OK || co->status == LUA_YIELD)

View File

@ -12,8 +12,6 @@
#include <string.h>
#include <stdio.h>
LUAU_FASTFLAGVARIABLE(LuauDebuggerBreakpointHitOnNextBestLine, false);
static const char* getfuncname(Closure* f);
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)
{
luaC_checkthreadsleep(L);
luaC_threadbarrier(L);
luaA_pushobject(L, ci->base + (n - 1));
res = 1;
}
else if (fp->is_vararg && n < ci->base - ci->func)
{
luaC_checkthreadsleep(L);
luaC_threadbarrier(L);
luaA_pushobject(L, ci->func + n);
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;
if (var)
{
luaC_checkthreadsleep(L);
luaC_threadbarrier(L);
luaA_pushobject(L, ci->base + var->reg);
}
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);
if (strchr(what, 'f'))
{
luaC_checkthreadsleep(L);
luaC_threadbarrier(L);
setclvalue(L, L->top, f);
incr_top(L);
}
@ -437,29 +435,17 @@ static int getnextline(Proto* p, int line)
int lua_breakpoint(lua_State* L, int funcindex, int line, int enabled)
{
int target = -1;
const TValue* func = luaA_toobject(L, funcindex);
api_check(L, ttisfunction(func) && !clvalue(func)->isC);
if (FFlag::LuauDebuggerBreakpointHitOnNextBestLine)
Proto* p = clvalue(func)->l.p;
// Find line number to add the breakpoint to.
int target = getnextline(p, line);
if (target != -1)
{
const TValue* func = luaA_toobject(L, funcindex);
api_check(L, ttisfunction(func) && !clvalue(func)->isC);
Proto* p = clvalue(func)->l.p;
// Find line number to add the breakpoint to.
target = getnextline(p, line);
if (target != -1)
{
// Add breakpoint on the exact line
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));
// Add breakpoint on the exact line
luaG_breakpoint(L, p, target, bool(enabled));
}
return target;

View File

@ -158,7 +158,7 @@ l_noret luaD_throw(lua_State* L, int errcode)
static void correctstack(lua_State* L, TValue* oldstack)
{
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;
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?
L->ci->flags |= LUA_CALLINFO_RETURN; // luau_execute will stop after returning from the stack frame
int oldactive = luaC_threadactive(L);
l_setbit(L->stackstate, THREAD_ACTIVEBIT);
luaC_checkthreadsleep(L);
bool oldactive = L->isactive;
L->isactive = true;
luaC_threadbarrier(L);
luau_execute(L); // call it
if (!oldactive)
resetbit(L->stackstate, THREAD_ACTIVEBIT);
L->isactive = false;
}
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)
{
L->nCcalls = L->baseCcalls;
resetbit(L->stackstate, THREAD_ACTIVEBIT);
L->isactive = false;
if (status != 0)
{ // error?
@ -452,9 +452,9 @@ int lua_resume(lua_State* L, lua_State* from, int nargs)
return resume_error(L, "C stack overflow");
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);
@ -481,9 +481,9 @@ int lua_resumeerror(lua_State* L, lua_State* from)
return resume_error(L, "C stack overflow");
L->baseCcalls = ++L->nCcalls;
l_setbit(L->stackstate, THREAD_ACTIVEBIT);
L->isactive = true;
luaC_checkthreadsleep(L);
luaC_threadbarrier(L);
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;
ptrdiff_t old_ci = saveci(L, L->ci);
int oldactive = luaC_threadactive(L);
bool oldactive = L->isactive;
int status = luaD_rawrunprotected(L, func, u);
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
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.
L->nCcalls = oldnCcalls;

View File

@ -71,59 +71,39 @@ UpVal* luaF_findupval(lua_State* L, StkId level)
UpVal* p;
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)
{ // found a corresponding upvalue?
if (isdead(g, obj2gco(p))) // is it dead?
changewhite(obj2gco(p)); // resurrect it
return p;
}
pp = &p->u.l.threadnext;
pp = &p->u.open.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
uv->tt = LUA_TUPVAL;
uv->marked = luaC_white(g);
uv->memcat = L->activememcat;
luaC_init(L, uv, LUA_TUPVAL);
uv->markedopen = 0;
uv->v = level; // current value lives in the stack
// chain the upvalue in the threads open upvalue list at the proper position
UpVal* next = *pp;
uv->u.l.threadnext = next;
uv->u.l.threadprev = pp;
if (next)
next->u.l.threadprev = &uv->u.l.threadnext;
uv->u.open.threadnext = *pp;
*pp = uv;
// double link the upvalue in the global open upvalue list
uv->u.l.prev = &g->uvhead;
uv->u.l.next = g->uvhead.u.l.next;
uv->u.l.next->u.l.prev = uv;
g->uvhead.u.l.next = uv;
LUAU_ASSERT(uv->u.l.next->u.l.prev == uv && uv->u.l.prev->u.l.next == uv);
uv->u.open.prev = &g->uvhead;
uv->u.open.next = g->uvhead.u.open.next;
uv->u.open.next->u.open.prev = uv;
g->uvhead.u.open.next = uv;
LUAU_ASSERT(uv->u.open.next->u.open.prev == uv && uv->u.open.prev->u.open.next == 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)
{
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
}
void luaF_close(lua_State* L, StkId level)
@ -133,26 +113,31 @@ void luaF_close(lua_State* L, StkId level)
while (L->openupval != NULL && (uv = L->openupval)->v >= level)
{
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
luaF_unlinkupval(uv);
// unlink value *before* closing it since value storage overlaps
L->openupval = uv->u.open.threadnext;
if (isdead(g, o))
{
// close the upvalue without copying the dead data so that luaF_freeupval will not unlink again
uv->v = &uv->u.value;
}
else
{
setobj(L, &uv->u.value, uv->v);
uv->v = &uv->u.value;
// GC state of a new closed upvalue has to be initialized
luaC_initupval(L, uv);
}
luaF_closeupval(L, uv, /* dead= */ false);
}
}
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);
uv->v = &uv->u.value;
luaC_upvalclosed(L, uv);
}
void luaF_freeproto(lua_State* L, Proto* f, lua_Page* page)
{
luaM_freearray(L, f->code, f->sizecode, Instruction, f->memcat);

View File

@ -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 UpVal* luaF_findupval(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_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 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);

View File

@ -13,6 +13,113 @@
#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_INTERRUPT(state) \
@ -150,8 +257,8 @@ static void reallymarkobject(global_State* g, GCObject* o)
{
UpVal* uv = gco2uv(o);
markvalue(g, uv->v);
if (uv->v == &uv->u.value) // closed?
gray2black(o); // open upvalues are never black
if (!upisopen(uv)) // closed?
gray2black(o); // open upvalues are never black
return;
}
case LUA_TFUNCTION:
@ -289,22 +396,52 @@ 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);
if (l->namecall)
stringmark(l->namecall);
for (StkId o = l->stack; o < l->top; o++)
markvalue(g, o);
// final traversal?
if (g->gcstate == GCSatomic || clearstack)
for (UpVal* uv = l->openupval; uv; uv = uv->u.open.threadnext)
{
StkId stack_end = l->stack + l->stacksize;
for (StkId o = l->top; o < stack_end; o++) // clear not-marked stack slice
setnilvalue(o);
LUAU_ASSERT(upisopen(uv));
uv->markedopen = 1;
markobject(g, uv);
}
}
static void clearstack(lua_State* l)
{
StkId stack_end = l->stack + l->stacksize;
for (StkId o = l->top; o < stack_end; o++) // clear not-marked stack slice
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));
}
/*
** traverse one gray object, turning it to black.
** Returns `quantity' traversed.
@ -336,25 +473,55 @@ static size_t propagatemark(global_State* g)
lua_State* th = gco2th(o);
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
bool active = luaC_threadactive(th) || th == th->global->mainthread;
if (!active && g->gcstate == GCSpropagate)
if (FFlag::LuauBetterThreadMark)
{
traversestack(g, th, /* clearstack= */ true);
traversestack(g, th);
l_setbit(th->stackstate, THREAD_SLEEPINGBIT);
// 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
{
th->gclist = g->grayagain;
g->grayagain = o;
// TODO: Refactor this logic!
if (!active && g->gcstate == GCSpropagate)
{
traversestack(g, th);
clearstack(th);
}
else
{
th->gclist = g->grayagain;
g->grayagain = o;
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;
@ -458,30 +625,6 @@ static size_t cleartable(lua_State* L, GCObject* l)
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)
{
switch (o->gch.tt)
@ -534,21 +677,6 @@ static void shrinkbuffersfull(lua_State* L)
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;
freeobj(L, gco, page);
return true;
@ -566,7 +694,6 @@ void luaC_freeall(lua_State* L)
LUAU_ASSERT(g->strt.hash[i] == NULL);
LUAU_ASSERT(L->global->strt.nuse == 0);
LUAU_ASSERT(g->strbufgc == NULL);
}
static void markmt(global_State* g)
@ -595,13 +722,53 @@ static void markroot(lua_State* L)
static size_t remarkupvals(global_State* g)
{
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);
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)))
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;
}
@ -654,6 +821,13 @@ static size_t atomic(lua_State* L)
g->gcmetrics.currcycle.atomictimeclear += recordGcDeltaTime(currts);
#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
g->currentwhite = cast_byte(otherwhite(g));
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)
{
LUAU_ASSERT(!FFlag::LuauFasterSweep);
global_State* g = L->global;
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;
if (gco->gch.tt == LUA_TTHREAD)
{
lua_State* th = gco2th(gco);
if (alive)
{
resetbit(th->stackstate, THREAD_SLEEPINGBIT);
shrinkstack(th);
}
}
if (alive)
{
LUAU_ASSERT(!isdead(g, gco));
@ -703,22 +867,60 @@ static int sweepgcopage(lua_State* L, lua_Page* page)
int blockSize;
luaM_getpagewalkinfo(page, &start, &end, &busyBlocks, &blockSize);
for (char* pos = start; pos != end; pos += blockSize)
LUAU_ASSERT(busyBlocks > 0);
if (FFlag::LuauFasterSweep)
{
GCObject* gco = (GCObject*)pos;
global_State* g = L->global;
// skip memory blocks that are already freed
if (gco->gch.tt == LUA_TNIL)
continue;
int deadmask = otherwhite(g);
LUAU_ASSERT(testbit(deadmask, FIXEDBIT)); // make sure we never sweep fixed objects
// when true is returned it means that the element was deleted
if (sweepgco(L, page, gco))
int newwhite = luaC_white(g);
for (char* pos = start; pos != end; pos += blockSize)
{
LUAU_ASSERT(busyBlocks > 0);
GCObject* gco = (GCObject*)pos;
// if the last block was removed, page would be removed as well
if (--busyBlocks == 0)
return int(pos - start) / blockSize + 1;
// 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)
{
GCObject* gco = (GCObject*)pos;
// skip memory blocks that are already freed
if (gco->gch.tt == LUA_TNIL)
continue;
// when true is returned it means that the element was deleted
if (sweepgco(L, page, gco))
{
// if the last block was removed, page would be removed as well
if (--busyBlocks == 0)
return int(pos - start) / blockSize + 1;
}
}
}
@ -806,10 +1008,19 @@ static size_t gcstep(lua_State* L, size_t limit)
// nothing more to sweep?
if (g->sweepgcopage == NULL)
{
// don't forget to visit main thread
sweepgco(L, NULL, obj2gco(g->mainthread));
// 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));
}
shrinkbuffers(L);
g->gcstate = GCSpause; // end collection
}
break;
@ -945,7 +1156,7 @@ void luaC_fullgc(lua_State* L)
startGcCycleMetrics(g);
#endif
if (g->gcstate <= GCSatomic)
if (keepinvariant(g))
{
// reset sweep marks to sweep all elements (returning them to white)
g->sweepgcopage = g->allgcopages;
@ -955,7 +1166,7 @@ void luaC_fullgc(lua_State* L)
g->weak = NULL;
g->gcstate = GCSsweep;
}
LUAU_ASSERT(g->gcstate == GCSsweep);
LUAU_ASSERT(g->gcstate == GCSpause || g->gcstate == GCSsweep);
// finish any pending sweep phase
while (g->gcstate != GCSpause)
{
@ -963,6 +1174,13 @@ void luaC_fullgc(lua_State* L)
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
finishGcCycleMetrics(g);
startGcCycleMetrics(g);
@ -997,15 +1215,6 @@ void luaC_fullgc(lua_State* L)
#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)
{
global_State* g = L->global;
@ -1038,30 +1247,24 @@ void luaC_barriertable(lua_State* L, Table* t, GCObject* v)
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;
GCObject* o = obj2gco(t);
LUAU_ASSERT(isblack(o) && !isdead(g, o));
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;
}
void luaC_initobj(lua_State* L, GCObject* o, uint8_t tt)
{
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)
void luaC_upvalclosed(lua_State* L, UpVal* uv)
{
global_State* g = L->global;
GCObject* o = obj2gco(uv);
LUAU_ASSERT(!upisopen(uv)); // upvalue was closed but needs GC state fixup
if (isgray(o))
{
if (keepinvariant(g))
@ -1103,26 +1306,6 @@ int64_t luaC_allocationrate(lua_State* L)
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)
{
switch (state)

View File

@ -73,13 +73,6 @@
#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) \
{ \
condhardstacktests(luaD_reallocstack(L, L->stacksize - EXTRA_STACK)); \
@ -109,7 +102,7 @@
#define luaC_barrierfast(L, t) \
{ \
if (isblack(obj2gco(t))) \
luaC_barrierback(L, t); \
luaC_barrierback(L, obj2gco(t), &t->gclist); \
}
#define luaC_objbarrier(L, p, o) \
@ -118,31 +111,28 @@
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)) \
luaC_barrierupval(L, gcvalue(tv)); \
if (isblack(obj2gco(L))) \
luaC_barrierback(L, obj2gco(L), &L->gclist); \
}
#define luaC_checkthreadsleep(L) \
#define luaC_init(L, o, tt_) \
{ \
if (luaC_threadsleeping(L)) \
luaC_wakethread(L); \
o->marked = luaC_white(L->global); \
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 size_t luaC_step(lua_State* L, bool assist);
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_initupval(lua_State* L, UpVal* uv);
LUAI_FUNC void luaC_barrierupval(lua_State* L, GCObject* v);
LUAI_FUNC void luaC_upvalclosed(lua_State* L, UpVal* uv);
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_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_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 void luaC_wakethread(lua_State* L);
LUAI_FUNC const char* luaC_statename(int state);

View File

@ -102,10 +102,12 @@ static void validatestack(global_State* g, lua_State* l)
if (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->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);
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->v != &uv->u.value);
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
}
}
@ -508,13 +511,14 @@ static void dumpproto(FILE* f, Proto* p)
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))
{
fprintf(f, ",\"object\":");
dumpref(f, gcvalue(uv->v));
}
fprintf(f, "}");
}

View File

@ -232,7 +232,7 @@ typedef struct TString
int16_t atom;
// 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 len;
@ -316,7 +316,10 @@ typedef struct LocVar
typedef struct UpVal
{
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
union
{
@ -327,14 +330,14 @@ typedef struct UpVal
struct UpVal* prev;
struct UpVal* next;
// thread double linked list (when open)
// thread linked list (when open)
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
struct UpVal** threadprev;
} l;
} open;
} u;
} UpVal;
#define upisopen(up) ((up)->v != &(up)->u.value)
/*
** Closures
*/

View File

@ -77,7 +77,7 @@ static void preinit_state(lua_State* L, global_State* g)
L->namecall = NULL;
L->cachedslot = 0;
L->singlestep = false;
L->stackstate = 0;
L->isactive = false;
L->activememcat = 0;
L->userdata = NULL;
}
@ -87,7 +87,6 @@ static void close_state(lua_State* L)
global_State* g = L->global;
luaF_close(L, L->stack); // close all upvalues for this thread
luaC_freeall(L); // collect all objects
LUAU_ASSERT(g->strbufgc == NULL);
LUAU_ASSERT(g->strt.nuse == 0);
luaM_freearray(L, L->global->strt.hash, L->global->strt.size, TString*, 0);
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)
{
luaF_close(L1, L1->stack); // close all upvalues for this thread
LUAU_ASSERT(L1->openupval == NULL);
global_State* g = L->global;
if (g->cb.userthread)
g->cb.userthread(NULL, L1);
@ -175,8 +172,8 @@ lua_State* lua_newstate(lua_Alloc f, void* ud)
g->frealloc = f;
g->ud = ud;
g->mainthread = L;
g->uvhead.u.l.prev = &g->uvhead;
g->uvhead.u.l.next = &g->uvhead;
g->uvhead.u.open.prev = &g->uvhead;
g->uvhead.u.open.next = &g->uvhead;
g->GCthreshold = 0; // mark it as unfinished state
g->registryfree = 0;
g->errorjmp = NULL;
@ -194,7 +191,6 @@ lua_State* lua_newstate(lua_Alloc f, void* ud)
g->gray = NULL;
g->grayagain = NULL;
g->weak = NULL;
g->strbufgc = NULL;
g->totalbytes = sizeof(LG);
g->gcgoal = LUAI_GCGOAL;
g->gcstepmul = LUAI_GCSTEPMUL;

View File

@ -167,8 +167,6 @@ typedef struct global_State
GCObject* grayagain; // list of objects to be traversed atomically
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 totalbytes; // number of bytes currently allocated
@ -222,8 +220,8 @@ struct lua_State
uint8_t status;
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

View File

@ -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)
{
TString* ts;
stringtable* tb;
if (l > MAXSSIZE)
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->marked = luaC_white(L->global);
ts->tt = LUA_TSTRING;
ts->memcat = L->activememcat;
ts->len = unsigned(l);
memcpy(ts->data, str, l);
ts->data[l] = '\0'; // ending 0
ts->atom = ATOM_UNDEF;
tb = &L->global->strt;
stringtable* tb = &L->global->strt;
h = lmod(h, tb->size);
ts->next = tb->hash[h]; // chain new entry
tb->hash[h] = ts;
tb->nuse++;
if (tb->nuse > cast_to(uint32_t, tb->size) && tb->size <= INT_MAX / 2)
luaS_resize(L, tb->size * 2); // too crowded
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)
{
if (size > MAXSSIZE)
luaM_toobig(L);
TString* ts = luaM_newgco(L, TString, sizestring(size), L->activememcat);
ts->tt = LUA_TSTRING;
ts->memcat = L->activememcat;
linkstrbuf(L, ts);
luaC_init(L, ts, LUA_TSTRING);
ts->atom = ATOM_UNDEF;
ts->hash = 0; // computed in luaS_buffinish
ts->len = unsigned(size);
ts->next = NULL;
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->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)
{
// Unchain from the string table
if (!unlinkstr(L, ts))
unlinkstrbuf(L, ts); // An unlikely scenario when we have a string buffer on our hands
else
if (unlinkstr(L, ts))
L->global->strt.nuse--;
else
LUAU_ASSERT(ts->next == NULL); // orphaned string buffer
luaM_freegco(L, ts, sizestring(ts->len), ts->memcat, page);
}

View File

@ -8,8 +8,6 @@
#include <string.h>
#include <stdio.h>
LUAU_FASTFLAGVARIABLE(LuauTostringFormatSpecifier, false);
// macro to `unsign' a character
#define uchar(c) ((unsigned char)(c))
@ -1036,9 +1034,6 @@ static int str_format(lua_State* L)
}
case '*':
{
if (!FFlag::LuauTostringFormatSpecifier)
luaL_error(L, "invalid option '%%*' to 'format'");
if (formatItemSize != 1)
luaL_error(L, "'%%*' does not take a form");

View File

@ -16,8 +16,6 @@
#include <string.h>
LUAU_FASTFLAGVARIABLE(LuauNicerMethodErrors, false)
// Disable c99-designator to avoid the warning in CGOTO dispatch table
#ifdef __clang__
#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_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() \
{ \
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
@ -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_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_FORGLOOP), VM_DISPATCH_OP(LOP_FORGPREP_INEXT), VM_DISPATCH_OP(LOP_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_FORGLOOP), VM_DISPATCH_OP(LOP_FORGPREP_INEXT), VM_DISPATCH_OP(LOP_DEP_FORGLOOP_INEXT), VM_DISPATCH_OP(LOP_FORGPREP_NEXT), \
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_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_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__)
#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");
}
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
// 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)
@ -315,8 +281,8 @@ static void luau_execute(lua_State* L)
const Instruction* pc;
LUAU_ASSERT(isLua(L->ci));
LUAU_ASSERT(luaC_threadactive(L));
LUAU_ASSERT(!luaC_threadsleeping(L));
LUAU_ASSERT(L->isactive);
LUAU_ASSERT(!isblack(obj2gco(L))); // we don't use luaC_threadbarrier because active threads never turn black
pc = L->ci->savedpc;
cl = clvalue(L->ci->func);
@ -496,7 +462,6 @@ static void luau_execute(lua_State* L)
setobj(L, uv->v, ra);
luaC_barrier(L, uv, ra);
luaC_upvalbarrier(L, uv, uv->v);
VM_NEXT();
}
@ -932,7 +897,7 @@ static void luau_execute(lua_State* L)
VM_PATCH_C(pc - 2, L->cachedslot);
// recompute ra since stack might have been reallocated
ra = VM_REG(LUAU_INSN_A(insn));
if (FFlag::LuauNicerMethodErrors && ttisnil(ra))
if (ttisnil(ra))
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);
// recompute ra since stack might have been reallocated
ra = VM_REG(LUAU_INSN_A(insn));
if (FFlag::LuauNicerMethodErrors && ttisnil(ra))
if (ttisnil(ra))
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));
// recompute ra since stack might have been reallocated
ra = VM_REG(LUAU_INSN_A(insn));
if (FFlag::LuauNicerMethodErrors && ttisnil(ra))
if (ttisnil(ra))
luaG_methoderror(L, ra + 1, tsvalue(kv));
}
}
@ -2398,54 +2363,10 @@ static void luau_execute(lua_State* L)
VM_NEXT();
}
VM_CASE(LOP_FORGLOOP_INEXT)
VM_CASE(LOP_DEP_FORGLOOP_INEXT)
{
VM_INTERRUPT();
Instruction insn = *pc++;
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();
}
LUAU_ASSERT(!"Unsupported deprecated opcode");
LUAU_UNREACHABLE();
}
VM_CASE(LOP_FORGPREP_NEXT)
@ -2470,70 +2391,10 @@ static void luau_execute(lua_State* L)
VM_NEXT();
}
VM_CASE(LOP_FORGLOOP_NEXT)
VM_CASE(LOP_DEP_FORGLOOP_NEXT)
{
VM_INTERRUPT();
Instruction insn = *pc++;
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();
}
LUAU_ASSERT(!"Unsupported deprecated opcode");
LUAU_UNREACHABLE();
}
VM_CASE(LOP_GETVARARGS)
@ -2745,94 +2606,16 @@ static void luau_execute(lua_State* L)
LUAU_UNREACHABLE();
}
VM_CASE(LOP_JUMPIFEQK)
VM_CASE(LOP_DEP_JUMPIFEQK)
{
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 "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");
}
else
{
pc += 1;
LUAU_ASSERT(unsigned(pc - cl->l.p->code) < unsigned(cl->l.p->sizecode));
VM_NEXT();
}
LUAU_ASSERT(!"Unsupported deprecated opcode");
LUAU_UNREACHABLE();
}
VM_CASE(LOP_JUMPIFNOTEQK)
VM_CASE(LOP_DEP_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();
}
LUAU_ASSERT(!"Unsupported deprecated opcode");
LUAU_UNREACHABLE();
}
VM_CASE(LOP_FASTCALL1)
@ -3028,7 +2811,16 @@ static void luau_execute(lua_State* L)
TValue* kv = VM_KV(aux & 0xffffff);
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;
#endif
LUAU_ASSERT(unsigned(pc - cl->l.p->code) < unsigned(cl->l.p->sizecode));
VM_NEXT();
}

View File

@ -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);
Proto* main = protos[mainid];
luaC_checkthreadsleep(L);
luaC_threadbarrier(L);
Closure* cl = luaF_newLclosure(L, 0, envt, main);
setclvalue(L, L->top, cl);

View File

@ -10,9 +10,6 @@
#include "lnumutils.h"
#include <string.h>
#include <stdio.h>
LUAU_FASTFLAGVARIABLE(LuauBetterNewindex, false)
// limit for table tag-method chains (to avoid loops)
#define MAXTAGLOOP 100
@ -142,46 +139,25 @@ void luaV_settable(lua_State* L, const TValue* t, TValue* key, StkId val)
{ // `t' is a table?
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)
if (!ttisnil(oldval) || (tm = fasttm(L, h->metatable, TM_NEWINDEX)) == NULL)
{
if (h->readonly)
luaG_readonlyerror(L);
// luaH_set would work but would repeat the lookup so we use luaH_setslot that can reuse oldval if it's safe
TValue* newval = luaH_setslot(L, h, oldval, key);
L->cachedslot = gval2slot(h, newval); // remember slot to accelerate future lookups
setobj2t(L, newval, val);
luaC_barriert(L, h, val);
return;
}
// fallthrough to metamethod
}
else
// should we assign the key? (if key is valid or __newindex is not set)
if (!ttisnil(oldval) || (tm = fasttm(L, h->metatable, TM_NEWINDEX)) == NULL)
{
if (h->readonly)
luaG_readonlyerror(L);
TValue* oldval = luaH_set(L, h, key); // do a primitive set
// luaH_set would work but would repeat the lookup so we use luaH_setslot that can reuse oldval if it's safe
TValue* newval = luaH_setslot(L, h, oldval, key);
L->cachedslot = gval2slot(h, oldval); // remember slot to accelerate future lookups
L->cachedslot = gval2slot(h, newval); // 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
setobj2t(L, newval, val);
luaC_barriert(L, h, val);
return;
}
// fallthrough to metamethod
}
else if (ttisnil(tm = luaT_gettmbyobj(L, t, TM_NEWINDEX)))
luaG_indexerror(L, t, key);