Compare commits
No commits in common. "46f714691b2f47a541fc986c4d3121a2087381c3" and "95cfe305ce50e0584351d344f49df90f4e87dcab" have entirely different histories.
46f714691b
...
95cfe305ce
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "luau0-src"
|
name = "luau0-src"
|
||||||
version = "0.5.10+luau581"
|
version = "0.5.5+luau571"
|
||||||
authors = ["Aleksandr Orlenko <zxteam@protonmail.com>"]
|
authors = ["Aleksandr Orlenko <zxteam@protonmail.com>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
repository = "https://github.com/khvzak/luau-src-rs"
|
repository = "https://github.com/khvzak/luau-src-rs"
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,9 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
namespace Luau
|
||||||
|
{
|
||||||
|
const char* findConfusable(uint32_t codepoint);
|
||||||
|
}
|
|
@ -0,0 +1,264 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Luau/Ast.h"
|
||||||
|
#include "Luau/Location.h"
|
||||||
|
#include "Luau/DenseHash.h"
|
||||||
|
#include "Luau/Common.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Luau
|
||||||
|
{
|
||||||
|
|
||||||
|
class Allocator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Allocator();
|
||||||
|
Allocator(Allocator&&);
|
||||||
|
|
||||||
|
Allocator& operator=(Allocator&&) = delete;
|
||||||
|
|
||||||
|
~Allocator();
|
||||||
|
|
||||||
|
void* allocate(size_t size);
|
||||||
|
|
||||||
|
template<typename T, typename... Args>
|
||||||
|
T* alloc(Args&&... args)
|
||||||
|
{
|
||||||
|
static_assert(std::is_trivially_destructible<T>::value, "Objects allocated with this allocator will never have their destructors run!");
|
||||||
|
|
||||||
|
T* t = static_cast<T*>(allocate(sizeof(T)));
|
||||||
|
new (t) T(std::forward<Args>(args)...);
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Page
|
||||||
|
{
|
||||||
|
Page* next;
|
||||||
|
|
||||||
|
char data[8192];
|
||||||
|
};
|
||||||
|
|
||||||
|
Page* root;
|
||||||
|
size_t offset;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Lexeme
|
||||||
|
{
|
||||||
|
enum Type
|
||||||
|
{
|
||||||
|
Eof = 0,
|
||||||
|
|
||||||
|
// 1..255 means actual character values
|
||||||
|
Char_END = 256,
|
||||||
|
|
||||||
|
Equal,
|
||||||
|
LessEqual,
|
||||||
|
GreaterEqual,
|
||||||
|
NotEqual,
|
||||||
|
Dot2,
|
||||||
|
Dot3,
|
||||||
|
SkinnyArrow,
|
||||||
|
DoubleColon,
|
||||||
|
|
||||||
|
InterpStringBegin,
|
||||||
|
InterpStringMid,
|
||||||
|
InterpStringEnd,
|
||||||
|
// An interpolated string with no expressions (like `x`)
|
||||||
|
InterpStringSimple,
|
||||||
|
|
||||||
|
AddAssign,
|
||||||
|
SubAssign,
|
||||||
|
MulAssign,
|
||||||
|
DivAssign,
|
||||||
|
ModAssign,
|
||||||
|
PowAssign,
|
||||||
|
ConcatAssign,
|
||||||
|
|
||||||
|
RawString,
|
||||||
|
QuotedString,
|
||||||
|
Number,
|
||||||
|
Name,
|
||||||
|
|
||||||
|
Comment,
|
||||||
|
BlockComment,
|
||||||
|
|
||||||
|
BrokenString,
|
||||||
|
BrokenComment,
|
||||||
|
BrokenUnicode,
|
||||||
|
BrokenInterpDoubleBrace,
|
||||||
|
|
||||||
|
Error,
|
||||||
|
|
||||||
|
Reserved_BEGIN,
|
||||||
|
ReservedAnd = Reserved_BEGIN,
|
||||||
|
ReservedBreak,
|
||||||
|
ReservedDo,
|
||||||
|
ReservedElse,
|
||||||
|
ReservedElseif,
|
||||||
|
ReservedEnd,
|
||||||
|
ReservedFalse,
|
||||||
|
ReservedFor,
|
||||||
|
ReservedFunction,
|
||||||
|
ReservedIf,
|
||||||
|
ReservedIn,
|
||||||
|
ReservedLocal,
|
||||||
|
ReservedNil,
|
||||||
|
ReservedNot,
|
||||||
|
ReservedOr,
|
||||||
|
ReservedRepeat,
|
||||||
|
ReservedReturn,
|
||||||
|
ReservedThen,
|
||||||
|
ReservedTrue,
|
||||||
|
ReservedUntil,
|
||||||
|
ReservedWhile,
|
||||||
|
Reserved_END
|
||||||
|
};
|
||||||
|
|
||||||
|
Type type;
|
||||||
|
Location location;
|
||||||
|
unsigned int length;
|
||||||
|
|
||||||
|
union
|
||||||
|
{
|
||||||
|
const char* data; // String, Number, Comment
|
||||||
|
const char* name; // Name
|
||||||
|
unsigned int codepoint; // BrokenUnicode
|
||||||
|
};
|
||||||
|
|
||||||
|
Lexeme(const Location& location, Type type);
|
||||||
|
Lexeme(const Location& location, char character);
|
||||||
|
Lexeme(const Location& location, Type type, const char* data, size_t size);
|
||||||
|
Lexeme(const Location& location, Type type, const char* name);
|
||||||
|
|
||||||
|
std::string toString() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
class AstNameTable
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
AstNameTable(Allocator& allocator);
|
||||||
|
|
||||||
|
AstName addStatic(const char* name, Lexeme::Type type = Lexeme::Name);
|
||||||
|
|
||||||
|
std::pair<AstName, Lexeme::Type> getOrAddWithType(const char* name, size_t length);
|
||||||
|
std::pair<AstName, Lexeme::Type> getWithType(const char* name, size_t length) const;
|
||||||
|
|
||||||
|
AstName getOrAdd(const char* name);
|
||||||
|
AstName get(const char* name) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Entry
|
||||||
|
{
|
||||||
|
AstName value;
|
||||||
|
uint32_t length;
|
||||||
|
Lexeme::Type type;
|
||||||
|
|
||||||
|
bool operator==(const Entry& other) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct EntryHash
|
||||||
|
{
|
||||||
|
size_t operator()(const Entry& e) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
DenseHashSet<Entry, EntryHash> data;
|
||||||
|
|
||||||
|
Allocator& allocator;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Lexer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Lexer(const char* buffer, std::size_t bufferSize, AstNameTable& names);
|
||||||
|
|
||||||
|
void setSkipComments(bool skip);
|
||||||
|
void setReadNames(bool read);
|
||||||
|
|
||||||
|
const Location& previousLocation() const
|
||||||
|
{
|
||||||
|
return prevLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Lexeme& next();
|
||||||
|
const Lexeme& next(bool skipComments, bool updatePrevLocation);
|
||||||
|
void nextline();
|
||||||
|
|
||||||
|
Lexeme lookahead();
|
||||||
|
|
||||||
|
const Lexeme& current() const
|
||||||
|
{
|
||||||
|
return lexeme;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isReserved(const std::string& word);
|
||||||
|
|
||||||
|
static bool fixupQuotedString(std::string& data);
|
||||||
|
static void fixupMultilineString(std::string& data);
|
||||||
|
|
||||||
|
private:
|
||||||
|
char peekch() const;
|
||||||
|
char peekch(unsigned int lookahead) const;
|
||||||
|
|
||||||
|
Position position() const;
|
||||||
|
|
||||||
|
void consume();
|
||||||
|
|
||||||
|
Lexeme readCommentBody();
|
||||||
|
|
||||||
|
// Given a sequence [===[ or ]===], returns:
|
||||||
|
// 1. number of equal signs (or 0 if none present) between the brackets
|
||||||
|
// 2. -1 if this is not a long comment/string separator
|
||||||
|
// 3. -N if this is a malformed separator
|
||||||
|
// Does *not* consume the closing brace.
|
||||||
|
int skipLongSeparator();
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
Lexeme readUtf8Error();
|
||||||
|
Lexeme readNext();
|
||||||
|
|
||||||
|
const char* buffer;
|
||||||
|
std::size_t bufferSize;
|
||||||
|
|
||||||
|
unsigned int offset;
|
||||||
|
|
||||||
|
unsigned int line;
|
||||||
|
unsigned int lineOffset;
|
||||||
|
|
||||||
|
Lexeme lexeme;
|
||||||
|
|
||||||
|
Location prevLocation;
|
||||||
|
|
||||||
|
AstNameTable& names;
|
||||||
|
|
||||||
|
bool skipComments;
|
||||||
|
bool readNames;
|
||||||
|
|
||||||
|
enum class BraceType
|
||||||
|
{
|
||||||
|
InterpolatedString,
|
||||||
|
Normal
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<BraceType> braceStack;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline bool isSpace(char ch)
|
||||||
|
{
|
||||||
|
return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == '\v' || ch == '\f';
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Luau
|
|
@ -0,0 +1,43 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace Luau
|
||||||
|
{
|
||||||
|
|
||||||
|
struct Position
|
||||||
|
{
|
||||||
|
unsigned int line, column;
|
||||||
|
|
||||||
|
Position(unsigned int line, unsigned int column);
|
||||||
|
|
||||||
|
bool operator==(const Position& rhs) const;
|
||||||
|
bool operator!=(const Position& rhs) const;
|
||||||
|
bool operator<(const Position& rhs) const;
|
||||||
|
bool operator>(const Position& rhs) const;
|
||||||
|
bool operator<=(const Position& rhs) const;
|
||||||
|
bool operator>=(const Position& rhs) const;
|
||||||
|
|
||||||
|
void shift(const Position& start, const Position& oldEnd, const Position& newEnd);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Location
|
||||||
|
{
|
||||||
|
Position begin, end;
|
||||||
|
|
||||||
|
Location();
|
||||||
|
Location(const Position& begin, const Position& end);
|
||||||
|
Location(const Position& begin, unsigned int length);
|
||||||
|
Location(const Location& begin, const Location& end);
|
||||||
|
|
||||||
|
bool operator==(const Location& rhs) const;
|
||||||
|
bool operator!=(const Location& rhs) const;
|
||||||
|
|
||||||
|
bool encloses(const Location& l) const;
|
||||||
|
bool overlaps(const Location& l) const;
|
||||||
|
bool contains(const Position& p) const;
|
||||||
|
bool containsClosed(const Position& p) const;
|
||||||
|
void extend(const Location& other);
|
||||||
|
void shift(const Position& start, const Position& oldEnd, const Position& newEnd);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Luau
|
|
@ -0,0 +1,23 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace Luau
|
||||||
|
{
|
||||||
|
|
||||||
|
enum class Mode
|
||||||
|
{
|
||||||
|
NoCheck, // Do not perform any inference
|
||||||
|
Nonstrict, // Unannotated symbols are any
|
||||||
|
Strict, // Unannotated symbols are inferred
|
||||||
|
Definition, // Type definition module, has special parsing rules
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ParseOptions
|
||||||
|
{
|
||||||
|
bool allowTypeAnnotations = true;
|
||||||
|
bool supportContinueStatement = true;
|
||||||
|
bool allowDeclarationSyntax = false;
|
||||||
|
bool captureComments = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Luau
|
|
@ -0,0 +1,71 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Luau/Common.h"
|
||||||
|
#include "Luau/Location.h"
|
||||||
|
#include "Luau/Lexer.h"
|
||||||
|
#include "Luau/StringUtils.h"
|
||||||
|
|
||||||
|
namespace Luau
|
||||||
|
{
|
||||||
|
|
||||||
|
class AstStatBlock;
|
||||||
|
|
||||||
|
class ParseError : public std::exception
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ParseError(const Location& location, const std::string& message);
|
||||||
|
|
||||||
|
virtual const char* what() const throw();
|
||||||
|
|
||||||
|
const Location& getLocation() const;
|
||||||
|
const std::string& getMessage() const;
|
||||||
|
|
||||||
|
static LUAU_NORETURN void raise(const Location& location, const char* format, ...) LUAU_PRINTF_ATTR(2, 3);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Location location;
|
||||||
|
std::string message;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ParseErrors : public std::exception
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ParseErrors(std::vector<ParseError> errors);
|
||||||
|
|
||||||
|
virtual const char* what() const throw();
|
||||||
|
|
||||||
|
const std::vector<ParseError>& getErrors() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<ParseError> errors;
|
||||||
|
std::string message;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HotComment
|
||||||
|
{
|
||||||
|
bool header;
|
||||||
|
Location location;
|
||||||
|
std::string content;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Comment
|
||||||
|
{
|
||||||
|
Lexeme::Type type; // Comment, BlockComment, or BrokenComment
|
||||||
|
Location location;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ParseResult
|
||||||
|
{
|
||||||
|
AstStatBlock* root;
|
||||||
|
size_t lines = 0;
|
||||||
|
|
||||||
|
std::vector<HotComment> hotcomments;
|
||||||
|
std::vector<ParseError> errors;
|
||||||
|
|
||||||
|
std::vector<Comment> commentLocations;
|
||||||
|
};
|
||||||
|
|
||||||
|
static constexpr const char* kParseNameError = "%error-id%";
|
||||||
|
|
||||||
|
} // namespace Luau
|
|
@ -0,0 +1,415 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Luau/Ast.h"
|
||||||
|
#include "Luau/Lexer.h"
|
||||||
|
#include "Luau/ParseOptions.h"
|
||||||
|
#include "Luau/ParseResult.h"
|
||||||
|
#include "Luau/StringUtils.h"
|
||||||
|
#include "Luau/DenseHash.h"
|
||||||
|
#include "Luau/Common.h"
|
||||||
|
|
||||||
|
#include <initializer_list>
|
||||||
|
#include <optional>
|
||||||
|
#include <tuple>
|
||||||
|
|
||||||
|
namespace Luau
|
||||||
|
{
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
class TempVector
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit TempVector(std::vector<T>& storage);
|
||||||
|
|
||||||
|
~TempVector();
|
||||||
|
|
||||||
|
const T& operator[](std::size_t index) const;
|
||||||
|
|
||||||
|
const T& front() const;
|
||||||
|
|
||||||
|
const T& back() const;
|
||||||
|
|
||||||
|
bool empty() const;
|
||||||
|
|
||||||
|
std::size_t size() const;
|
||||||
|
|
||||||
|
void push_back(const T& item);
|
||||||
|
|
||||||
|
typename std::vector<T>::const_iterator begin() const
|
||||||
|
{
|
||||||
|
return storage.begin() + offset;
|
||||||
|
}
|
||||||
|
typename std::vector<T>::const_iterator end() const
|
||||||
|
{
|
||||||
|
return storage.begin() + offset + size_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<T>& storage;
|
||||||
|
size_t offset;
|
||||||
|
size_t size_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Parser
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static ParseResult parse(
|
||||||
|
const char* buffer, std::size_t bufferSize, AstNameTable& names, Allocator& allocator, ParseOptions options = ParseOptions());
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Name;
|
||||||
|
struct Binding;
|
||||||
|
|
||||||
|
Parser(const char* buffer, std::size_t bufferSize, AstNameTable& names, Allocator& allocator, const ParseOptions& options);
|
||||||
|
|
||||||
|
bool blockFollow(const Lexeme& l);
|
||||||
|
|
||||||
|
AstStatBlock* parseChunk();
|
||||||
|
|
||||||
|
// chunk ::= {stat [`;']} [laststat [`;']]
|
||||||
|
// block ::= chunk
|
||||||
|
AstStatBlock* parseBlock();
|
||||||
|
|
||||||
|
AstStatBlock* parseBlockNoScope();
|
||||||
|
|
||||||
|
// stat ::=
|
||||||
|
// varlist `=' explist |
|
||||||
|
// functioncall |
|
||||||
|
// do block end |
|
||||||
|
// while exp do block end |
|
||||||
|
// repeat block until exp |
|
||||||
|
// if exp then block {elseif exp then block} [else block] end |
|
||||||
|
// for Name `=' exp `,' exp [`,' exp] do block end |
|
||||||
|
// for namelist in explist do block end |
|
||||||
|
// function funcname funcbody |
|
||||||
|
// local function Name funcbody |
|
||||||
|
// local namelist [`=' explist]
|
||||||
|
// laststat ::= return [explist] | break
|
||||||
|
AstStat* parseStat();
|
||||||
|
|
||||||
|
// if exp then block {elseif exp then block} [else block] end
|
||||||
|
AstStat* parseIf();
|
||||||
|
|
||||||
|
// while exp do block end
|
||||||
|
AstStat* parseWhile();
|
||||||
|
|
||||||
|
// repeat block until exp
|
||||||
|
AstStat* parseRepeat();
|
||||||
|
|
||||||
|
// do block end
|
||||||
|
AstStat* parseDo();
|
||||||
|
|
||||||
|
// break
|
||||||
|
AstStat* parseBreak();
|
||||||
|
|
||||||
|
// continue
|
||||||
|
AstStat* parseContinue(const Location& start);
|
||||||
|
|
||||||
|
// for Name `=' exp `,' exp [`,' exp] do block end |
|
||||||
|
// for namelist in explist do block end |
|
||||||
|
AstStat* parseFor();
|
||||||
|
|
||||||
|
// funcname ::= Name {`.' Name} [`:' Name]
|
||||||
|
AstExpr* parseFunctionName(Location start, bool& hasself, AstName& debugname);
|
||||||
|
|
||||||
|
// function funcname funcbody
|
||||||
|
AstStat* parseFunctionStat();
|
||||||
|
|
||||||
|
// local function Name funcbody |
|
||||||
|
// local namelist [`=' explist]
|
||||||
|
AstStat* parseLocal();
|
||||||
|
|
||||||
|
// return [explist]
|
||||||
|
AstStat* parseReturn();
|
||||||
|
|
||||||
|
// type Name `=' Type
|
||||||
|
AstStat* parseTypeAlias(const Location& start, bool exported);
|
||||||
|
|
||||||
|
AstDeclaredClassProp parseDeclaredClassMethod();
|
||||||
|
|
||||||
|
// `declare global' Name: Type |
|
||||||
|
// `declare function' Name`(' [parlist] `)' [`:` Type]
|
||||||
|
AstStat* parseDeclaration(const Location& start);
|
||||||
|
|
||||||
|
// varlist `=' explist
|
||||||
|
AstStat* parseAssignment(AstExpr* initial);
|
||||||
|
|
||||||
|
// var [`+=' | `-=' | `*=' | `/=' | `%=' | `^=' | `..='] exp
|
||||||
|
AstStat* parseCompoundAssignment(AstExpr* initial, AstExprBinary::Op op);
|
||||||
|
|
||||||
|
std::pair<AstLocal*, AstArray<AstLocal*>> prepareFunctionArguments(const Location& start, bool hasself, const TempVector<Binding>& args);
|
||||||
|
|
||||||
|
// funcbodyhead ::= `(' [namelist [`,' `...'] | `...'] `)' [`:` Type]
|
||||||
|
// funcbody ::= funcbodyhead block end
|
||||||
|
std::pair<AstExprFunction*, AstLocal*> parseFunctionBody(
|
||||||
|
bool hasself, const Lexeme& matchFunction, const AstName& debugname, const Name* localName);
|
||||||
|
|
||||||
|
// explist ::= {exp `,'} exp
|
||||||
|
void parseExprList(TempVector<AstExpr*>& result);
|
||||||
|
|
||||||
|
// binding ::= Name [`:` Type]
|
||||||
|
Binding parseBinding();
|
||||||
|
|
||||||
|
// bindinglist ::= (binding | `...') {`,' bindinglist}
|
||||||
|
// Returns the location of the vararg ..., or std::nullopt if the function is not vararg.
|
||||||
|
std::tuple<bool, Location, AstTypePack*> parseBindingList(TempVector<Binding>& result, bool allowDot3 = false);
|
||||||
|
|
||||||
|
AstType* parseOptionalType();
|
||||||
|
|
||||||
|
// TypeList ::= Type [`,' TypeList]
|
||||||
|
// ReturnType ::= Type | `(' TypeList `)'
|
||||||
|
// TableProp ::= Name `:' Type
|
||||||
|
// TableIndexer ::= `[' Type `]' `:' Type
|
||||||
|
// PropList ::= (TableProp | TableIndexer) [`,' PropList]
|
||||||
|
// Type
|
||||||
|
// ::= Name
|
||||||
|
// | `nil`
|
||||||
|
// | `{' [PropList] `}'
|
||||||
|
// | `(' [TypeList] `)' `->` ReturnType
|
||||||
|
|
||||||
|
// Returns the variadic annotation, if it exists.
|
||||||
|
AstTypePack* parseTypeList(TempVector<AstType*>& result, TempVector<std::optional<AstArgumentName>>& resultNames);
|
||||||
|
|
||||||
|
std::optional<AstTypeList> parseOptionalReturnType();
|
||||||
|
std::pair<Location, AstTypeList> parseReturnType();
|
||||||
|
|
||||||
|
AstTableIndexer* parseTableIndexer();
|
||||||
|
|
||||||
|
AstTypeOrPack parseFunctionType(bool allowPack);
|
||||||
|
AstType* parseFunctionTypeTail(const Lexeme& begin, AstArray<AstGenericType> generics, AstArray<AstGenericTypePack> genericPacks,
|
||||||
|
AstArray<AstType*> params, AstArray<std::optional<AstArgumentName>> paramNames, AstTypePack* varargAnnotation);
|
||||||
|
|
||||||
|
AstType* parseTableType();
|
||||||
|
AstTypeOrPack parseSimpleType(bool allowPack);
|
||||||
|
|
||||||
|
AstTypeOrPack parseTypeOrPack();
|
||||||
|
AstType* parseType();
|
||||||
|
|
||||||
|
AstTypePack* parseTypePack();
|
||||||
|
AstTypePack* parseVariadicArgumentTypePack();
|
||||||
|
|
||||||
|
AstType* parseTypeSuffix(AstType* type, const Location& begin);
|
||||||
|
|
||||||
|
static std::optional<AstExprUnary::Op> parseUnaryOp(const Lexeme& l);
|
||||||
|
static std::optional<AstExprBinary::Op> parseBinaryOp(const Lexeme& l);
|
||||||
|
static std::optional<AstExprBinary::Op> parseCompoundOp(const Lexeme& l);
|
||||||
|
|
||||||
|
struct BinaryOpPriority
|
||||||
|
{
|
||||||
|
unsigned char left, right;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::optional<AstExprUnary::Op> checkUnaryConfusables();
|
||||||
|
std::optional<AstExprBinary::Op> checkBinaryConfusables(const BinaryOpPriority binaryPriority[], unsigned int limit);
|
||||||
|
|
||||||
|
// subexpr -> (asexp | unop subexpr) { binop subexpr }
|
||||||
|
// where `binop' is any binary operator with a priority higher than `limit'
|
||||||
|
AstExpr* parseExpr(unsigned int limit = 0);
|
||||||
|
|
||||||
|
// NAME
|
||||||
|
AstExpr* parseNameExpr(const char* context = nullptr);
|
||||||
|
|
||||||
|
// prefixexp -> NAME | '(' expr ')'
|
||||||
|
AstExpr* parsePrefixExpr();
|
||||||
|
|
||||||
|
// primaryexp -> prefixexp { `.' NAME | `[' exp `]' | `:' NAME funcargs | funcargs }
|
||||||
|
AstExpr* parsePrimaryExpr(bool asStatement);
|
||||||
|
|
||||||
|
// asexp -> simpleexp [`::' Type]
|
||||||
|
AstExpr* parseAssertionExpr();
|
||||||
|
|
||||||
|
// simpleexp -> NUMBER | STRING | NIL | true | false | ... | constructor | FUNCTION body | primaryexp
|
||||||
|
AstExpr* parseSimpleExpr();
|
||||||
|
|
||||||
|
// args ::= `(' [explist] `)' | tableconstructor | String
|
||||||
|
AstExpr* parseFunctionArgs(AstExpr* func, bool self);
|
||||||
|
|
||||||
|
// tableconstructor ::= `{' [fieldlist] `}'
|
||||||
|
// fieldlist ::= field {fieldsep field} [fieldsep]
|
||||||
|
// field ::= `[' exp `]' `=' exp | Name `=' exp | exp
|
||||||
|
// fieldsep ::= `,' | `;'
|
||||||
|
AstExpr* parseTableConstructor();
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
Name parseIndexName(const char* context, const Position& previous);
|
||||||
|
|
||||||
|
// `<' namelist `>'
|
||||||
|
std::pair<AstArray<AstGenericType>, AstArray<AstGenericTypePack>> parseGenericTypeList(bool withDefaultValues);
|
||||||
|
|
||||||
|
// `<' Type[, ...] `>'
|
||||||
|
AstArray<AstTypeOrPack> parseTypeParams();
|
||||||
|
|
||||||
|
std::optional<AstArray<char>> parseCharArray();
|
||||||
|
AstExpr* parseString();
|
||||||
|
AstExpr* parseNumber();
|
||||||
|
|
||||||
|
AstLocal* pushLocal(const Binding& binding);
|
||||||
|
|
||||||
|
unsigned int saveLocals();
|
||||||
|
|
||||||
|
void restoreLocals(unsigned int offset);
|
||||||
|
|
||||||
|
// check that parser is at lexeme/symbol, move to next lexeme/symbol on success, report failure and continue on failure
|
||||||
|
bool expectAndConsume(char value, const char* context = nullptr);
|
||||||
|
bool expectAndConsume(Lexeme::Type type, const char* context = nullptr);
|
||||||
|
void expectAndConsumeFail(Lexeme::Type type, const char* context);
|
||||||
|
|
||||||
|
struct MatchLexeme
|
||||||
|
{
|
||||||
|
MatchLexeme(const Lexeme& l)
|
||||||
|
: type(l.type)
|
||||||
|
, position(l.location.begin)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Lexeme::Type type;
|
||||||
|
Position position;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool expectMatchAndConsume(char value, const MatchLexeme& begin, bool searchForMissing = false);
|
||||||
|
void expectMatchAndConsumeFail(Lexeme::Type type, const MatchLexeme& begin, const char* extra = nullptr);
|
||||||
|
bool expectMatchAndConsumeRecover(char value, const MatchLexeme& begin, bool searchForMissing);
|
||||||
|
|
||||||
|
bool expectMatchEndAndConsume(Lexeme::Type type, const MatchLexeme& begin);
|
||||||
|
void expectMatchEndAndConsumeFail(Lexeme::Type type, const MatchLexeme& begin);
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
AstArray<T> copy(const T* data, std::size_t size);
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
AstArray<T> copy(const TempVector<T>& data);
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
AstArray<T> copy(std::initializer_list<T> data);
|
||||||
|
|
||||||
|
AstArray<char> copy(const std::string& data);
|
||||||
|
|
||||||
|
void incrementRecursionCounter(const char* context);
|
||||||
|
|
||||||
|
void report(const Location& location, const char* format, va_list args);
|
||||||
|
void report(const Location& location, const char* format, ...) LUAU_PRINTF_ATTR(3, 4);
|
||||||
|
|
||||||
|
void reportNameError(const char* context);
|
||||||
|
|
||||||
|
AstStatError* reportStatError(const Location& location, const AstArray<AstExpr*>& expressions, const AstArray<AstStat*>& statements,
|
||||||
|
const char* format, ...) LUAU_PRINTF_ATTR(5, 6);
|
||||||
|
AstExprError* reportExprError(const Location& location, const AstArray<AstExpr*>& expressions, const char* format, ...) LUAU_PRINTF_ATTR(4, 5);
|
||||||
|
AstTypeError* reportTypeError(const Location& location, const AstArray<AstType*>& types, const char* format, ...) LUAU_PRINTF_ATTR(4, 5);
|
||||||
|
// `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* reportMissingTypeError(const Location& parseErrorLocation, const Location& astErrorLocation, const char* format, ...)
|
||||||
|
LUAU_PRINTF_ATTR(4, 5);
|
||||||
|
|
||||||
|
AstExpr* reportFunctionArgsError(AstExpr* func, bool self);
|
||||||
|
void reportAmbiguousCallError();
|
||||||
|
|
||||||
|
void nextLexeme();
|
||||||
|
|
||||||
|
struct Function
|
||||||
|
{
|
||||||
|
bool vararg;
|
||||||
|
unsigned int loopDepth;
|
||||||
|
|
||||||
|
Function()
|
||||||
|
: vararg(false)
|
||||||
|
, loopDepth(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Local
|
||||||
|
{
|
||||||
|
AstLocal* local;
|
||||||
|
unsigned int offset;
|
||||||
|
|
||||||
|
Local()
|
||||||
|
: local(nullptr)
|
||||||
|
, offset(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Name
|
||||||
|
{
|
||||||
|
AstName name;
|
||||||
|
Location location;
|
||||||
|
|
||||||
|
Name(const AstName& name, const Location& location)
|
||||||
|
: name(name)
|
||||||
|
, location(location)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Binding
|
||||||
|
{
|
||||||
|
Name name;
|
||||||
|
AstType* annotation;
|
||||||
|
|
||||||
|
explicit Binding(const Name& name, AstType* annotation = nullptr)
|
||||||
|
: name(name)
|
||||||
|
, annotation(annotation)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ParseOptions options;
|
||||||
|
|
||||||
|
Lexer lexer;
|
||||||
|
Allocator& allocator;
|
||||||
|
|
||||||
|
std::vector<Comment> commentLocations;
|
||||||
|
std::vector<HotComment> hotcomments;
|
||||||
|
|
||||||
|
bool hotcommentHeader = true;
|
||||||
|
|
||||||
|
unsigned int recursionCounter;
|
||||||
|
|
||||||
|
AstName nameSelf;
|
||||||
|
AstName nameNumber;
|
||||||
|
AstName nameError;
|
||||||
|
AstName nameNil;
|
||||||
|
|
||||||
|
MatchLexeme endMismatchSuspect;
|
||||||
|
|
||||||
|
std::vector<Function> functionStack;
|
||||||
|
|
||||||
|
DenseHashMap<AstName, AstLocal*> localMap;
|
||||||
|
std::vector<AstLocal*> localStack;
|
||||||
|
|
||||||
|
std::vector<ParseError> parseErrors;
|
||||||
|
|
||||||
|
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;
|
||||||
|
std::vector<AstName> scratchPackName;
|
||||||
|
std::vector<Binding> scratchBinding;
|
||||||
|
std::vector<AstLocal*> scratchLocal;
|
||||||
|
std::vector<AstTableProp> scratchTableTypeProps;
|
||||||
|
std::vector<AstType*> scratchType;
|
||||||
|
std::vector<AstTypeOrPack> scratchTypeOrPack;
|
||||||
|
std::vector<AstDeclaredClassProp> scratchDeclaredClassProps;
|
||||||
|
std::vector<AstExprTable::Item> scratchItem;
|
||||||
|
std::vector<AstArgumentName> scratchArgName;
|
||||||
|
std::vector<AstGenericType> scratchGenericTypes;
|
||||||
|
std::vector<AstGenericTypePack> scratchGenericTypePacks;
|
||||||
|
std::vector<std::optional<AstArgumentName>> scratchOptArgName;
|
||||||
|
std::string scratchData;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Luau
|
|
@ -0,0 +1,36 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Luau/Common.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <stdarg.h>
|
||||||
|
|
||||||
|
namespace Luau
|
||||||
|
{
|
||||||
|
|
||||||
|
std::string format(const char* fmt, ...) LUAU_PRINTF_ATTR(1, 2);
|
||||||
|
std::string vformat(const char* fmt, va_list args);
|
||||||
|
|
||||||
|
void formatAppend(std::string& str, const char* fmt, ...) LUAU_PRINTF_ATTR(2, 3);
|
||||||
|
void vformatAppend(std::string& ret, const char* fmt, va_list args);
|
||||||
|
|
||||||
|
std::string join(const std::vector<std::string_view>& segments, std::string_view delimiter);
|
||||||
|
std::string join(const std::vector<std::string>& segments, std::string_view delimiter);
|
||||||
|
|
||||||
|
std::vector<std::string_view> split(std::string_view s, char delimiter);
|
||||||
|
|
||||||
|
// Computes the Damerau-Levenshtein distance of A and B.
|
||||||
|
// https://en.wikipedia.org/wiki/Damerau-Levenshtein_distance#Distance_with_adjacent_transpositions
|
||||||
|
size_t editDistance(std::string_view a, std::string_view b);
|
||||||
|
|
||||||
|
bool startsWith(std::string_view lhs, std::string_view rhs);
|
||||||
|
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, bool escapeForInterpString = false);
|
||||||
|
bool isIdentifier(std::string_view s);
|
||||||
|
} // namespace Luau
|
|
@ -0,0 +1,230 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Luau/Common.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
LUAU_FASTFLAG(DebugLuauTimeTracing)
|
||||||
|
|
||||||
|
namespace Luau
|
||||||
|
{
|
||||||
|
namespace TimeTrace
|
||||||
|
{
|
||||||
|
double getClock();
|
||||||
|
uint32_t getClockMicroseconds();
|
||||||
|
} // namespace TimeTrace
|
||||||
|
} // namespace Luau
|
||||||
|
|
||||||
|
#if defined(LUAU_ENABLE_TIME_TRACE)
|
||||||
|
|
||||||
|
namespace Luau
|
||||||
|
{
|
||||||
|
namespace TimeTrace
|
||||||
|
{
|
||||||
|
struct Token
|
||||||
|
{
|
||||||
|
const char* name;
|
||||||
|
const char* category;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class EventType : uint8_t
|
||||||
|
{
|
||||||
|
Enter,
|
||||||
|
Leave,
|
||||||
|
|
||||||
|
ArgName,
|
||||||
|
ArgValue,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Event
|
||||||
|
{
|
||||||
|
EventType type;
|
||||||
|
uint16_t token;
|
||||||
|
|
||||||
|
union
|
||||||
|
{
|
||||||
|
uint32_t microsec; // 1 hour trace limit
|
||||||
|
uint32_t dataPos;
|
||||||
|
} data;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GlobalContext;
|
||||||
|
struct ThreadContext;
|
||||||
|
|
||||||
|
GlobalContext& getGlobalContext();
|
||||||
|
|
||||||
|
uint16_t createToken(GlobalContext& context, const char* name, const char* category);
|
||||||
|
uint32_t createThread(GlobalContext& context, ThreadContext* threadContext);
|
||||||
|
void releaseThread(GlobalContext& context, ThreadContext* threadContext);
|
||||||
|
void flushEvents(GlobalContext& context, uint32_t threadId, const std::vector<Event>& events, const std::vector<char>& data);
|
||||||
|
|
||||||
|
struct ThreadContext
|
||||||
|
{
|
||||||
|
ThreadContext()
|
||||||
|
: globalContext(getGlobalContext())
|
||||||
|
{
|
||||||
|
threadId = createThread(globalContext, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
~ThreadContext()
|
||||||
|
{
|
||||||
|
if (!events.empty())
|
||||||
|
flushEvents();
|
||||||
|
|
||||||
|
releaseThread(globalContext, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void flushEvents()
|
||||||
|
{
|
||||||
|
static uint16_t flushToken = createToken(globalContext, "flushEvents", "TimeTrace");
|
||||||
|
|
||||||
|
events.push_back({EventType::Enter, flushToken, {getClockMicroseconds()}});
|
||||||
|
|
||||||
|
TimeTrace::flushEvents(globalContext, threadId, events, data);
|
||||||
|
|
||||||
|
events.clear();
|
||||||
|
data.clear();
|
||||||
|
|
||||||
|
events.push_back({EventType::Leave, 0, {getClockMicroseconds()}});
|
||||||
|
}
|
||||||
|
|
||||||
|
void eventEnter(uint16_t token)
|
||||||
|
{
|
||||||
|
eventEnter(token, getClockMicroseconds());
|
||||||
|
}
|
||||||
|
|
||||||
|
void eventEnter(uint16_t token, uint32_t microsec)
|
||||||
|
{
|
||||||
|
events.push_back({EventType::Enter, token, {microsec}});
|
||||||
|
}
|
||||||
|
|
||||||
|
void eventLeave()
|
||||||
|
{
|
||||||
|
eventLeave(getClockMicroseconds());
|
||||||
|
}
|
||||||
|
|
||||||
|
void eventLeave(uint32_t microsec)
|
||||||
|
{
|
||||||
|
events.push_back({EventType::Leave, 0, {microsec}});
|
||||||
|
|
||||||
|
if (events.size() > kEventFlushLimit)
|
||||||
|
flushEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
void eventArgument(const char* name, const char* value)
|
||||||
|
{
|
||||||
|
uint32_t pos = uint32_t(data.size());
|
||||||
|
data.insert(data.end(), name, name + strlen(name) + 1);
|
||||||
|
events.push_back({EventType::ArgName, 0, {pos}});
|
||||||
|
|
||||||
|
pos = uint32_t(data.size());
|
||||||
|
data.insert(data.end(), value, value + strlen(value) + 1);
|
||||||
|
events.push_back({EventType::ArgValue, 0, {pos}});
|
||||||
|
}
|
||||||
|
|
||||||
|
GlobalContext& globalContext;
|
||||||
|
uint32_t threadId;
|
||||||
|
std::vector<Event> events;
|
||||||
|
std::vector<char> data;
|
||||||
|
|
||||||
|
static constexpr size_t kEventFlushLimit = 8192;
|
||||||
|
};
|
||||||
|
|
||||||
|
ThreadContext& getThreadContext();
|
||||||
|
|
||||||
|
struct Scope
|
||||||
|
{
|
||||||
|
explicit Scope(uint16_t token)
|
||||||
|
: context(getThreadContext())
|
||||||
|
{
|
||||||
|
if (!FFlag::DebugLuauTimeTracing)
|
||||||
|
return;
|
||||||
|
|
||||||
|
context.eventEnter(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
~Scope()
|
||||||
|
{
|
||||||
|
if (!FFlag::DebugLuauTimeTracing)
|
||||||
|
return;
|
||||||
|
|
||||||
|
context.eventLeave();
|
||||||
|
}
|
||||||
|
|
||||||
|
ThreadContext& context;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct OptionalTailScope
|
||||||
|
{
|
||||||
|
explicit OptionalTailScope(uint16_t token, uint32_t threshold)
|
||||||
|
: context(getThreadContext())
|
||||||
|
, token(token)
|
||||||
|
, threshold(threshold)
|
||||||
|
{
|
||||||
|
if (!FFlag::DebugLuauTimeTracing)
|
||||||
|
return;
|
||||||
|
|
||||||
|
pos = uint32_t(context.events.size());
|
||||||
|
microsec = getClockMicroseconds();
|
||||||
|
}
|
||||||
|
|
||||||
|
~OptionalTailScope()
|
||||||
|
{
|
||||||
|
if (!FFlag::DebugLuauTimeTracing)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (pos == context.events.size())
|
||||||
|
{
|
||||||
|
uint32_t curr = getClockMicroseconds();
|
||||||
|
|
||||||
|
if (curr - microsec > threshold)
|
||||||
|
{
|
||||||
|
context.eventEnter(token, microsec);
|
||||||
|
context.eventLeave(curr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ThreadContext& context;
|
||||||
|
uint16_t token;
|
||||||
|
uint32_t threshold;
|
||||||
|
uint32_t microsec;
|
||||||
|
uint32_t pos;
|
||||||
|
};
|
||||||
|
|
||||||
|
LUAU_NOINLINE uint16_t createScopeData(const char* name, const char* category);
|
||||||
|
|
||||||
|
} // namespace TimeTrace
|
||||||
|
} // namespace Luau
|
||||||
|
|
||||||
|
// Regular scope
|
||||||
|
#define LUAU_TIMETRACE_SCOPE(name, category) \
|
||||||
|
static uint16_t lttScopeStatic = Luau::TimeTrace::createScopeData(name, category); \
|
||||||
|
Luau::TimeTrace::Scope lttScope(lttScopeStatic)
|
||||||
|
|
||||||
|
// A scope without nested scopes that may be skipped if the time it took is less than the threshold
|
||||||
|
#define LUAU_TIMETRACE_OPTIONAL_TAIL_SCOPE(name, category, microsec) \
|
||||||
|
static uint16_t lttScopeStaticOptTail = Luau::TimeTrace::createScopeData(name, category); \
|
||||||
|
Luau::TimeTrace::OptionalTailScope lttScope(lttScopeStaticOptTail, microsec)
|
||||||
|
|
||||||
|
// Extra key/value data can be added to regular scopes
|
||||||
|
#define LUAU_TIMETRACE_ARGUMENT(name, value) \
|
||||||
|
do \
|
||||||
|
{ \
|
||||||
|
if (FFlag::DebugLuauTimeTracing) \
|
||||||
|
lttScope.context.eventArgument(name, value); \
|
||||||
|
} while (false)
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
#define LUAU_TIMETRACE_SCOPE(name, category)
|
||||||
|
#define LUAU_TIMETRACE_OPTIONAL_TAIL_SCOPE(name, category, microsec)
|
||||||
|
#define LUAU_TIMETRACE_ARGUMENT(name, value) \
|
||||||
|
do \
|
||||||
|
{ \
|
||||||
|
} while (false)
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,969 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
#include "Luau/Ast.h"
|
||||||
|
|
||||||
|
#include "Luau/Common.h"
|
||||||
|
|
||||||
|
namespace Luau
|
||||||
|
{
|
||||||
|
|
||||||
|
static void visitTypeList(AstVisitor* visitor, const AstTypeList& list)
|
||||||
|
{
|
||||||
|
for (AstType* ty : list.types)
|
||||||
|
ty->visit(visitor);
|
||||||
|
|
||||||
|
if (list.tailType)
|
||||||
|
list.tailType->visit(visitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
int gAstRttiIndex = 0;
|
||||||
|
|
||||||
|
AstExprGroup::AstExprGroup(const Location& location, AstExpr* expr)
|
||||||
|
: AstExpr(ClassIndex(), location)
|
||||||
|
, expr(expr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AstExprGroup::visit(AstVisitor* visitor)
|
||||||
|
{
|
||||||
|
if (visitor->visit(this))
|
||||||
|
expr->visit(visitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
AstExprConstantNil::AstExprConstantNil(const Location& location)
|
||||||
|
: AstExpr(ClassIndex(), location)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AstExprConstantNil::visit(AstVisitor* visitor)
|
||||||
|
{
|
||||||
|
visitor->visit(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
AstExprConstantBool::AstExprConstantBool(const Location& location, bool value)
|
||||||
|
: AstExpr(ClassIndex(), location)
|
||||||
|
, value(value)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AstExprConstantBool::visit(AstVisitor* visitor)
|
||||||
|
{
|
||||||
|
visitor->visit(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
AstExprConstantNumber::AstExprConstantNumber(const Location& location, double value, ConstantNumberParseResult parseResult)
|
||||||
|
: AstExpr(ClassIndex(), location)
|
||||||
|
, value(value)
|
||||||
|
, parseResult(parseResult)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AstExprConstantNumber::visit(AstVisitor* visitor)
|
||||||
|
{
|
||||||
|
visitor->visit(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
AstExprConstantString::AstExprConstantString(const Location& location, const AstArray<char>& value)
|
||||||
|
: AstExpr(ClassIndex(), location)
|
||||||
|
, value(value)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AstExprConstantString::visit(AstVisitor* visitor)
|
||||||
|
{
|
||||||
|
visitor->visit(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
AstExprLocal::AstExprLocal(const Location& location, AstLocal* local, bool upvalue)
|
||||||
|
: AstExpr(ClassIndex(), location)
|
||||||
|
, local(local)
|
||||||
|
, upvalue(upvalue)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AstExprLocal::visit(AstVisitor* visitor)
|
||||||
|
{
|
||||||
|
visitor->visit(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
AstExprGlobal::AstExprGlobal(const Location& location, const AstName& name)
|
||||||
|
: AstExpr(ClassIndex(), location)
|
||||||
|
, name(name)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AstExprGlobal::visit(AstVisitor* visitor)
|
||||||
|
{
|
||||||
|
visitor->visit(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
AstExprVarargs::AstExprVarargs(const Location& location)
|
||||||
|
: AstExpr(ClassIndex(), location)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AstExprVarargs::visit(AstVisitor* visitor)
|
||||||
|
{
|
||||||
|
visitor->visit(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
AstExprCall::AstExprCall(const Location& location, AstExpr* func, const AstArray<AstExpr*>& args, bool self, const Location& argLocation)
|
||||||
|
: AstExpr(ClassIndex(), location)
|
||||||
|
, func(func)
|
||||||
|
, args(args)
|
||||||
|
, self(self)
|
||||||
|
, argLocation(argLocation)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AstExprCall::visit(AstVisitor* visitor)
|
||||||
|
{
|
||||||
|
if (visitor->visit(this))
|
||||||
|
{
|
||||||
|
func->visit(visitor);
|
||||||
|
|
||||||
|
for (AstExpr* arg : args)
|
||||||
|
arg->visit(visitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AstExprIndexName::AstExprIndexName(
|
||||||
|
const Location& location, AstExpr* expr, const AstName& index, const Location& indexLocation, const Position& opPosition, char op)
|
||||||
|
: AstExpr(ClassIndex(), location)
|
||||||
|
, expr(expr)
|
||||||
|
, index(index)
|
||||||
|
, indexLocation(indexLocation)
|
||||||
|
, opPosition(opPosition)
|
||||||
|
, op(op)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AstExprIndexName::visit(AstVisitor* visitor)
|
||||||
|
{
|
||||||
|
if (visitor->visit(this))
|
||||||
|
expr->visit(visitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
AstExprIndexExpr::AstExprIndexExpr(const Location& location, AstExpr* expr, AstExpr* index)
|
||||||
|
: AstExpr(ClassIndex(), location)
|
||||||
|
, expr(expr)
|
||||||
|
, index(index)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AstExprIndexExpr::visit(AstVisitor* visitor)
|
||||||
|
{
|
||||||
|
if (visitor->visit(this))
|
||||||
|
{
|
||||||
|
expr->visit(visitor);
|
||||||
|
index->visit(visitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AstExprFunction::AstExprFunction(const Location& location, const AstArray<AstGenericType>& generics, const AstArray<AstGenericTypePack>& genericPacks,
|
||||||
|
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)
|
||||||
|
, varargLocation(varargLocation)
|
||||||
|
, varargAnnotation(varargAnnotation)
|
||||||
|
, body(body)
|
||||||
|
, functionDepth(functionDepth)
|
||||||
|
, debugname(debugname)
|
||||||
|
, hasEnd(hasEnd)
|
||||||
|
, argLocation(argLocation)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AstExprFunction::visit(AstVisitor* visitor)
|
||||||
|
{
|
||||||
|
if (visitor->visit(this))
|
||||||
|
{
|
||||||
|
for (AstLocal* arg : args)
|
||||||
|
{
|
||||||
|
if (arg->annotation)
|
||||||
|
arg->annotation->visit(visitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (varargAnnotation)
|
||||||
|
varargAnnotation->visit(visitor);
|
||||||
|
|
||||||
|
if (returnAnnotation)
|
||||||
|
visitTypeList(visitor, *returnAnnotation);
|
||||||
|
|
||||||
|
body->visit(visitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AstExprTable::AstExprTable(const Location& location, const AstArray<Item>& items)
|
||||||
|
: AstExpr(ClassIndex(), location)
|
||||||
|
, items(items)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AstExprTable::visit(AstVisitor* visitor)
|
||||||
|
{
|
||||||
|
if (visitor->visit(this))
|
||||||
|
{
|
||||||
|
for (const Item& item : items)
|
||||||
|
{
|
||||||
|
if (item.key)
|
||||||
|
item.key->visit(visitor);
|
||||||
|
|
||||||
|
item.value->visit(visitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AstExprUnary::AstExprUnary(const Location& location, Op op, AstExpr* expr)
|
||||||
|
: AstExpr(ClassIndex(), location)
|
||||||
|
, op(op)
|
||||||
|
, expr(expr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AstExprUnary::visit(AstVisitor* visitor)
|
||||||
|
{
|
||||||
|
if (visitor->visit(this))
|
||||||
|
expr->visit(visitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string toString(AstExprUnary::Op op)
|
||||||
|
{
|
||||||
|
switch (op)
|
||||||
|
{
|
||||||
|
case AstExprUnary::Minus:
|
||||||
|
return "-";
|
||||||
|
case AstExprUnary::Not:
|
||||||
|
return "not";
|
||||||
|
case AstExprUnary::Len:
|
||||||
|
return "#";
|
||||||
|
default:
|
||||||
|
LUAU_ASSERT(false);
|
||||||
|
return ""; // MSVC requires this even though the switch/case is exhaustive
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AstExprBinary::AstExprBinary(const Location& location, Op op, AstExpr* left, AstExpr* right)
|
||||||
|
: AstExpr(ClassIndex(), location)
|
||||||
|
, op(op)
|
||||||
|
, left(left)
|
||||||
|
, right(right)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AstExprBinary::visit(AstVisitor* visitor)
|
||||||
|
{
|
||||||
|
if (visitor->visit(this))
|
||||||
|
{
|
||||||
|
left->visit(visitor);
|
||||||
|
right->visit(visitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string toString(AstExprBinary::Op op)
|
||||||
|
{
|
||||||
|
switch (op)
|
||||||
|
{
|
||||||
|
case AstExprBinary::Add:
|
||||||
|
return "+";
|
||||||
|
case AstExprBinary::Sub:
|
||||||
|
return "-";
|
||||||
|
case AstExprBinary::Mul:
|
||||||
|
return "*";
|
||||||
|
case AstExprBinary::Div:
|
||||||
|
return "/";
|
||||||
|
case AstExprBinary::Mod:
|
||||||
|
return "%";
|
||||||
|
case AstExprBinary::Pow:
|
||||||
|
return "^";
|
||||||
|
case AstExprBinary::Concat:
|
||||||
|
return "..";
|
||||||
|
case AstExprBinary::CompareNe:
|
||||||
|
return "~=";
|
||||||
|
case AstExprBinary::CompareEq:
|
||||||
|
return "==";
|
||||||
|
case AstExprBinary::CompareLt:
|
||||||
|
return "<";
|
||||||
|
case AstExprBinary::CompareLe:
|
||||||
|
return "<=";
|
||||||
|
case AstExprBinary::CompareGt:
|
||||||
|
return ">";
|
||||||
|
case AstExprBinary::CompareGe:
|
||||||
|
return ">=";
|
||||||
|
case AstExprBinary::And:
|
||||||
|
return "and";
|
||||||
|
case AstExprBinary::Or:
|
||||||
|
return "or";
|
||||||
|
default:
|
||||||
|
LUAU_ASSERT(false);
|
||||||
|
return ""; // MSVC requires this even though the switch/case is exhaustive
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AstExprTypeAssertion::AstExprTypeAssertion(const Location& location, AstExpr* expr, AstType* annotation)
|
||||||
|
: AstExpr(ClassIndex(), location)
|
||||||
|
, expr(expr)
|
||||||
|
, annotation(annotation)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AstExprTypeAssertion::visit(AstVisitor* visitor)
|
||||||
|
{
|
||||||
|
if (visitor->visit(this))
|
||||||
|
{
|
||||||
|
expr->visit(visitor);
|
||||||
|
annotation->visit(visitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AstExprIfElse::AstExprIfElse(const Location& location, AstExpr* condition, bool hasThen, AstExpr* trueExpr, bool hasElse, AstExpr* falseExpr)
|
||||||
|
: AstExpr(ClassIndex(), location)
|
||||||
|
, condition(condition)
|
||||||
|
, hasThen(hasThen)
|
||||||
|
, trueExpr(trueExpr)
|
||||||
|
, hasElse(hasElse)
|
||||||
|
, falseExpr(falseExpr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AstExprIfElse::visit(AstVisitor* visitor)
|
||||||
|
{
|
||||||
|
if (visitor->visit(this))
|
||||||
|
{
|
||||||
|
condition->visit(visitor);
|
||||||
|
trueExpr->visit(visitor);
|
||||||
|
falseExpr->visit(visitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AstExprError::AstExprError(const Location& location, const AstArray<AstExpr*>& expressions, unsigned messageIndex)
|
||||||
|
: AstExpr(ClassIndex(), location)
|
||||||
|
, expressions(expressions)
|
||||||
|
, messageIndex(messageIndex)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
{
|
||||||
|
for (AstExpr* expression : expressions)
|
||||||
|
expression->visit(visitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AstStatBlock::AstStatBlock(const Location& location, const AstArray<AstStat*>& body)
|
||||||
|
: AstStat(ClassIndex(), location)
|
||||||
|
, body(body)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AstStatBlock::visit(AstVisitor* visitor)
|
||||||
|
{
|
||||||
|
if (visitor->visit(this))
|
||||||
|
{
|
||||||
|
for (AstStat* stat : body)
|
||||||
|
stat->visit(visitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AstStatIf::AstStatIf(const Location& location, AstExpr* condition, AstStatBlock* thenbody, AstStat* elsebody,
|
||||||
|
const std::optional<Location>& thenLocation, const std::optional<Location>& elseLocation, bool hasEnd)
|
||||||
|
: AstStat(ClassIndex(), location)
|
||||||
|
, condition(condition)
|
||||||
|
, thenbody(thenbody)
|
||||||
|
, elsebody(elsebody)
|
||||||
|
, thenLocation(thenLocation)
|
||||||
|
, elseLocation(elseLocation)
|
||||||
|
, hasEnd(hasEnd)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AstStatIf::visit(AstVisitor* visitor)
|
||||||
|
{
|
||||||
|
if (visitor->visit(this))
|
||||||
|
{
|
||||||
|
condition->visit(visitor);
|
||||||
|
thenbody->visit(visitor);
|
||||||
|
|
||||||
|
if (elsebody)
|
||||||
|
elsebody->visit(visitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AstStatWhile::AstStatWhile(const Location& location, AstExpr* condition, AstStatBlock* body, bool hasDo, const Location& doLocation, bool hasEnd)
|
||||||
|
: AstStat(ClassIndex(), location)
|
||||||
|
, condition(condition)
|
||||||
|
, body(body)
|
||||||
|
, hasDo(hasDo)
|
||||||
|
, doLocation(doLocation)
|
||||||
|
, hasEnd(hasEnd)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AstStatWhile::visit(AstVisitor* visitor)
|
||||||
|
{
|
||||||
|
if (visitor->visit(this))
|
||||||
|
{
|
||||||
|
condition->visit(visitor);
|
||||||
|
body->visit(visitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AstStatRepeat::AstStatRepeat(const Location& location, AstExpr* condition, AstStatBlock* body, bool hasUntil)
|
||||||
|
: AstStat(ClassIndex(), location)
|
||||||
|
, condition(condition)
|
||||||
|
, body(body)
|
||||||
|
, hasUntil(hasUntil)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AstStatRepeat::visit(AstVisitor* visitor)
|
||||||
|
{
|
||||||
|
if (visitor->visit(this))
|
||||||
|
{
|
||||||
|
body->visit(visitor);
|
||||||
|
condition->visit(visitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AstStatBreak::AstStatBreak(const Location& location)
|
||||||
|
: AstStat(ClassIndex(), location)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AstStatBreak::visit(AstVisitor* visitor)
|
||||||
|
{
|
||||||
|
visitor->visit(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
AstStatContinue::AstStatContinue(const Location& location)
|
||||||
|
: AstStat(ClassIndex(), location)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AstStatContinue::visit(AstVisitor* visitor)
|
||||||
|
{
|
||||||
|
visitor->visit(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
AstStatReturn::AstStatReturn(const Location& location, const AstArray<AstExpr*>& list)
|
||||||
|
: AstStat(ClassIndex(), location)
|
||||||
|
, list(list)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AstStatReturn::visit(AstVisitor* visitor)
|
||||||
|
{
|
||||||
|
if (visitor->visit(this))
|
||||||
|
{
|
||||||
|
for (AstExpr* expr : list)
|
||||||
|
expr->visit(visitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AstStatExpr::AstStatExpr(const Location& location, AstExpr* expr)
|
||||||
|
: AstStat(ClassIndex(), location)
|
||||||
|
, expr(expr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AstStatExpr::visit(AstVisitor* visitor)
|
||||||
|
{
|
||||||
|
if (visitor->visit(this))
|
||||||
|
expr->visit(visitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
AstStatLocal::AstStatLocal(
|
||||||
|
const Location& location, const AstArray<AstLocal*>& vars, const AstArray<AstExpr*>& values, const std::optional<Location>& equalsSignLocation)
|
||||||
|
: AstStat(ClassIndex(), location)
|
||||||
|
, vars(vars)
|
||||||
|
, values(values)
|
||||||
|
, equalsSignLocation(equalsSignLocation)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AstStatLocal::visit(AstVisitor* visitor)
|
||||||
|
{
|
||||||
|
if (visitor->visit(this))
|
||||||
|
{
|
||||||
|
for (AstLocal* var : vars)
|
||||||
|
{
|
||||||
|
if (var->annotation)
|
||||||
|
var->annotation->visit(visitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (AstExpr* expr : values)
|
||||||
|
expr->visit(visitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AstStatFor::AstStatFor(const Location& location, AstLocal* var, AstExpr* from, AstExpr* to, AstExpr* step, AstStatBlock* body, bool hasDo,
|
||||||
|
const Location& doLocation, bool hasEnd)
|
||||||
|
: AstStat(ClassIndex(), location)
|
||||||
|
, var(var)
|
||||||
|
, from(from)
|
||||||
|
, to(to)
|
||||||
|
, step(step)
|
||||||
|
, body(body)
|
||||||
|
, hasDo(hasDo)
|
||||||
|
, doLocation(doLocation)
|
||||||
|
, hasEnd(hasEnd)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AstStatFor::visit(AstVisitor* visitor)
|
||||||
|
{
|
||||||
|
if (visitor->visit(this))
|
||||||
|
{
|
||||||
|
if (var->annotation)
|
||||||
|
var->annotation->visit(visitor);
|
||||||
|
|
||||||
|
from->visit(visitor);
|
||||||
|
to->visit(visitor);
|
||||||
|
|
||||||
|
if (step)
|
||||||
|
step->visit(visitor);
|
||||||
|
|
||||||
|
body->visit(visitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AstStatForIn::AstStatForIn(const Location& location, const AstArray<AstLocal*>& vars, const AstArray<AstExpr*>& values, AstStatBlock* body,
|
||||||
|
bool hasIn, const Location& inLocation, bool hasDo, const Location& doLocation, bool hasEnd)
|
||||||
|
: AstStat(ClassIndex(), location)
|
||||||
|
, vars(vars)
|
||||||
|
, values(values)
|
||||||
|
, body(body)
|
||||||
|
, hasIn(hasIn)
|
||||||
|
, inLocation(inLocation)
|
||||||
|
, hasDo(hasDo)
|
||||||
|
, doLocation(doLocation)
|
||||||
|
, hasEnd(hasEnd)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AstStatForIn::visit(AstVisitor* visitor)
|
||||||
|
{
|
||||||
|
if (visitor->visit(this))
|
||||||
|
{
|
||||||
|
for (AstLocal* var : vars)
|
||||||
|
{
|
||||||
|
if (var->annotation)
|
||||||
|
var->annotation->visit(visitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (AstExpr* expr : values)
|
||||||
|
expr->visit(visitor);
|
||||||
|
|
||||||
|
body->visit(visitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AstStatAssign::AstStatAssign(const Location& location, const AstArray<AstExpr*>& vars, const AstArray<AstExpr*>& values)
|
||||||
|
: AstStat(ClassIndex(), location)
|
||||||
|
, vars(vars)
|
||||||
|
, values(values)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AstStatAssign::visit(AstVisitor* visitor)
|
||||||
|
{
|
||||||
|
if (visitor->visit(this))
|
||||||
|
{
|
||||||
|
for (AstExpr* lvalue : vars)
|
||||||
|
lvalue->visit(visitor);
|
||||||
|
|
||||||
|
for (AstExpr* expr : values)
|
||||||
|
expr->visit(visitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AstStatCompoundAssign::AstStatCompoundAssign(const Location& location, AstExprBinary::Op op, AstExpr* var, AstExpr* value)
|
||||||
|
: AstStat(ClassIndex(), location)
|
||||||
|
, op(op)
|
||||||
|
, var(var)
|
||||||
|
, value(value)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AstStatCompoundAssign::visit(AstVisitor* visitor)
|
||||||
|
{
|
||||||
|
if (visitor->visit(this))
|
||||||
|
{
|
||||||
|
var->visit(visitor);
|
||||||
|
value->visit(visitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AstStatFunction::AstStatFunction(const Location& location, AstExpr* name, AstExprFunction* func)
|
||||||
|
: AstStat(ClassIndex(), location)
|
||||||
|
, name(name)
|
||||||
|
, func(func)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AstStatFunction::visit(AstVisitor* visitor)
|
||||||
|
{
|
||||||
|
if (visitor->visit(this))
|
||||||
|
{
|
||||||
|
name->visit(visitor);
|
||||||
|
func->visit(visitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AstStatLocalFunction::AstStatLocalFunction(const Location& location, AstLocal* name, AstExprFunction* func)
|
||||||
|
: AstStat(ClassIndex(), location)
|
||||||
|
, name(name)
|
||||||
|
, func(func)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AstStatLocalFunction::visit(AstVisitor* visitor)
|
||||||
|
{
|
||||||
|
if (visitor->visit(this))
|
||||||
|
func->visit(visitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
AstStatTypeAlias::AstStatTypeAlias(const Location& location, const AstName& name, const Location& nameLocation,
|
||||||
|
const AstArray<AstGenericType>& generics, const AstArray<AstGenericTypePack>& genericPacks, AstType* type, bool exported)
|
||||||
|
: AstStat(ClassIndex(), location)
|
||||||
|
, name(name)
|
||||||
|
, nameLocation(nameLocation)
|
||||||
|
, generics(generics)
|
||||||
|
, genericPacks(genericPacks)
|
||||||
|
, type(type)
|
||||||
|
, exported(exported)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AstStatTypeAlias::visit(AstVisitor* visitor)
|
||||||
|
{
|
||||||
|
if (visitor->visit(this))
|
||||||
|
{
|
||||||
|
for (const AstGenericType& el : generics)
|
||||||
|
{
|
||||||
|
if (el.defaultValue)
|
||||||
|
el.defaultValue->visit(visitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const AstGenericTypePack& el : genericPacks)
|
||||||
|
{
|
||||||
|
if (el.defaultValue)
|
||||||
|
el.defaultValue->visit(visitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
type->visit(visitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AstStatDeclareGlobal::AstStatDeclareGlobal(const Location& location, const AstName& name, AstType* type)
|
||||||
|
: AstStat(ClassIndex(), location)
|
||||||
|
, name(name)
|
||||||
|
, type(type)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AstStatDeclareGlobal::visit(AstVisitor* visitor)
|
||||||
|
{
|
||||||
|
if (visitor->visit(this))
|
||||||
|
type->visit(visitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
AstStatDeclareFunction::AstStatDeclareFunction(const Location& location, const AstName& name, const AstArray<AstGenericType>& generics,
|
||||||
|
const AstArray<AstGenericTypePack>& genericPacks, const AstTypeList& params, const AstArray<AstArgumentName>& paramNames,
|
||||||
|
const AstTypeList& retTypes)
|
||||||
|
: AstStat(ClassIndex(), location)
|
||||||
|
, name(name)
|
||||||
|
, generics(generics)
|
||||||
|
, genericPacks(genericPacks)
|
||||||
|
, params(params)
|
||||||
|
, paramNames(paramNames)
|
||||||
|
, retTypes(retTypes)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AstStatDeclareFunction::visit(AstVisitor* visitor)
|
||||||
|
{
|
||||||
|
if (visitor->visit(this))
|
||||||
|
{
|
||||||
|
visitTypeList(visitor, params);
|
||||||
|
visitTypeList(visitor, retTypes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AstStatDeclareClass::AstStatDeclareClass(
|
||||||
|
const Location& location, const AstName& name, std::optional<AstName> superName, const AstArray<AstDeclaredClassProp>& props)
|
||||||
|
: AstStat(ClassIndex(), location)
|
||||||
|
, name(name)
|
||||||
|
, superName(superName)
|
||||||
|
, props(props)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AstStatDeclareClass::visit(AstVisitor* visitor)
|
||||||
|
{
|
||||||
|
if (visitor->visit(this))
|
||||||
|
{
|
||||||
|
for (const AstDeclaredClassProp& prop : props)
|
||||||
|
prop.ty->visit(visitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AstStatError::AstStatError(
|
||||||
|
const Location& location, const AstArray<AstExpr*>& expressions, const AstArray<AstStat*>& statements, unsigned messageIndex)
|
||||||
|
: AstStat(ClassIndex(), location)
|
||||||
|
, expressions(expressions)
|
||||||
|
, statements(statements)
|
||||||
|
, messageIndex(messageIndex)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AstStatError::visit(AstVisitor* visitor)
|
||||||
|
{
|
||||||
|
if (visitor->visit(this))
|
||||||
|
{
|
||||||
|
for (AstNode* expression : expressions)
|
||||||
|
expression->visit(visitor);
|
||||||
|
|
||||||
|
for (AstNode* statement : statements)
|
||||||
|
statement->visit(visitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AstTypeReference::AstTypeReference(
|
||||||
|
const Location& location, std::optional<AstName> prefix, AstName name, bool hasParameterList, const AstArray<AstTypeOrPack>& parameters)
|
||||||
|
: AstType(ClassIndex(), location)
|
||||||
|
, hasParameterList(hasParameterList)
|
||||||
|
, prefix(prefix)
|
||||||
|
, name(name)
|
||||||
|
, parameters(parameters)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AstTypeReference::visit(AstVisitor* visitor)
|
||||||
|
{
|
||||||
|
if (visitor->visit(this))
|
||||||
|
{
|
||||||
|
for (const AstTypeOrPack& param : parameters)
|
||||||
|
{
|
||||||
|
if (param.type)
|
||||||
|
param.type->visit(visitor);
|
||||||
|
else
|
||||||
|
param.typePack->visit(visitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AstTypeTable::AstTypeTable(const Location& location, const AstArray<AstTableProp>& props, AstTableIndexer* indexer)
|
||||||
|
: AstType(ClassIndex(), location)
|
||||||
|
, props(props)
|
||||||
|
, indexer(indexer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AstTypeTable::visit(AstVisitor* visitor)
|
||||||
|
{
|
||||||
|
if (visitor->visit(this))
|
||||||
|
{
|
||||||
|
for (const AstTableProp& prop : props)
|
||||||
|
prop.type->visit(visitor);
|
||||||
|
|
||||||
|
if (indexer)
|
||||||
|
{
|
||||||
|
indexer->indexType->visit(visitor);
|
||||||
|
indexer->resultType->visit(visitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AstTypeFunction::AstTypeFunction(const Location& location, const AstArray<AstGenericType>& generics, const AstArray<AstGenericTypePack>& genericPacks,
|
||||||
|
const AstTypeList& argTypes, const AstArray<std::optional<AstArgumentName>>& argNames, const AstTypeList& returnTypes)
|
||||||
|
: AstType(ClassIndex(), location)
|
||||||
|
, generics(generics)
|
||||||
|
, genericPacks(genericPacks)
|
||||||
|
, argTypes(argTypes)
|
||||||
|
, argNames(argNames)
|
||||||
|
, returnTypes(returnTypes)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(argNames.size == 0 || argNames.size == argTypes.types.size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AstTypeFunction::visit(AstVisitor* visitor)
|
||||||
|
{
|
||||||
|
if (visitor->visit(this))
|
||||||
|
{
|
||||||
|
visitTypeList(visitor, argTypes);
|
||||||
|
visitTypeList(visitor, returnTypes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AstTypeTypeof::AstTypeTypeof(const Location& location, AstExpr* expr)
|
||||||
|
: AstType(ClassIndex(), location)
|
||||||
|
, expr(expr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AstTypeTypeof::visit(AstVisitor* visitor)
|
||||||
|
{
|
||||||
|
if (visitor->visit(this))
|
||||||
|
expr->visit(visitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
AstTypeUnion::AstTypeUnion(const Location& location, const AstArray<AstType*>& types)
|
||||||
|
: AstType(ClassIndex(), location)
|
||||||
|
, types(types)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AstTypeUnion::visit(AstVisitor* visitor)
|
||||||
|
{
|
||||||
|
if (visitor->visit(this))
|
||||||
|
{
|
||||||
|
for (AstType* type : types)
|
||||||
|
type->visit(visitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AstTypeIntersection::AstTypeIntersection(const Location& location, const AstArray<AstType*>& types)
|
||||||
|
: AstType(ClassIndex(), location)
|
||||||
|
, types(types)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AstTypeIntersection::visit(AstVisitor* visitor)
|
||||||
|
{
|
||||||
|
if (visitor->visit(this))
|
||||||
|
{
|
||||||
|
for (AstType* type : types)
|
||||||
|
type->visit(visitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AstTypeSingletonBool::AstTypeSingletonBool(const Location& location, bool value)
|
||||||
|
: AstType(ClassIndex(), location)
|
||||||
|
, value(value)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AstTypeSingletonBool::visit(AstVisitor* visitor)
|
||||||
|
{
|
||||||
|
visitor->visit(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
AstTypeSingletonString::AstTypeSingletonString(const Location& location, const AstArray<char>& value)
|
||||||
|
: AstType(ClassIndex(), location)
|
||||||
|
, value(value)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AstTypeSingletonString::visit(AstVisitor* visitor)
|
||||||
|
{
|
||||||
|
visitor->visit(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
AstTypeError::AstTypeError(const Location& location, const AstArray<AstType*>& types, bool isMissing, unsigned messageIndex)
|
||||||
|
: AstType(ClassIndex(), location)
|
||||||
|
, types(types)
|
||||||
|
, isMissing(isMissing)
|
||||||
|
, messageIndex(messageIndex)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AstTypeError::visit(AstVisitor* visitor)
|
||||||
|
{
|
||||||
|
if (visitor->visit(this))
|
||||||
|
{
|
||||||
|
for (AstType* type : types)
|
||||||
|
type->visit(visitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AstTypePackExplicit::AstTypePackExplicit(const Location& location, AstTypeList typeList)
|
||||||
|
: AstTypePack(ClassIndex(), location)
|
||||||
|
, typeList(typeList)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AstTypePackExplicit::visit(AstVisitor* visitor)
|
||||||
|
{
|
||||||
|
if (visitor->visit(this))
|
||||||
|
{
|
||||||
|
for (AstType* type : typeList.types)
|
||||||
|
type->visit(visitor);
|
||||||
|
|
||||||
|
if (typeList.tailType)
|
||||||
|
typeList.tailType->visit(visitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AstTypePackVariadic::AstTypePackVariadic(const Location& location, AstType* variadicType)
|
||||||
|
: AstTypePack(ClassIndex(), location)
|
||||||
|
, variadicType(variadicType)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AstTypePackVariadic::visit(AstVisitor* visitor)
|
||||||
|
{
|
||||||
|
if (visitor->visit(this))
|
||||||
|
variadicType->visit(visitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
AstTypePackGeneric::AstTypePackGeneric(const Location& location, AstName name)
|
||||||
|
: AstTypePack(ClassIndex(), location)
|
||||||
|
, genericName(name)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AstTypePackGeneric::visit(AstVisitor* visitor)
|
||||||
|
{
|
||||||
|
visitor->visit(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
AstName getIdentifier(AstExpr* node)
|
||||||
|
{
|
||||||
|
if (AstExprGlobal* expr = node->as<AstExprGlobal>())
|
||||||
|
return expr->name;
|
||||||
|
|
||||||
|
if (AstExprLocal* expr = node->as<AstExprLocal>())
|
||||||
|
return expr->local->name;
|
||||||
|
|
||||||
|
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
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,131 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
#include "Luau/Location.h"
|
||||||
|
|
||||||
|
namespace Luau
|
||||||
|
{
|
||||||
|
|
||||||
|
Position::Position(unsigned int line, unsigned int column)
|
||||||
|
: line(line)
|
||||||
|
, column(column)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Position::operator==(const Position& rhs) const
|
||||||
|
{
|
||||||
|
return this->column == rhs.column && this->line == rhs.line;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Position::operator!=(const Position& rhs) const
|
||||||
|
{
|
||||||
|
return !(*this == rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Position::operator<(const Position& rhs) const
|
||||||
|
{
|
||||||
|
if (line == rhs.line)
|
||||||
|
return column < rhs.column;
|
||||||
|
else
|
||||||
|
return line < rhs.line;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Position::operator>(const Position& rhs) const
|
||||||
|
{
|
||||||
|
if (line == rhs.line)
|
||||||
|
return column > rhs.column;
|
||||||
|
else
|
||||||
|
return line > rhs.line;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Position::operator<=(const Position& rhs) const
|
||||||
|
{
|
||||||
|
return *this == rhs || *this < rhs;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Position::operator>=(const Position& rhs) const
|
||||||
|
{
|
||||||
|
return *this == rhs || *this > rhs;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Position::shift(const Position& start, const Position& oldEnd, const Position& newEnd)
|
||||||
|
{
|
||||||
|
if (*this >= start)
|
||||||
|
{
|
||||||
|
if (this->line > oldEnd.line)
|
||||||
|
this->line += (newEnd.line - oldEnd.line);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this->line = newEnd.line;
|
||||||
|
this->column += (newEnd.column - oldEnd.column);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Location::Location()
|
||||||
|
: begin(0, 0)
|
||||||
|
, end(0, 0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Location::Location(const Position& begin, const Position& end)
|
||||||
|
: begin(begin)
|
||||||
|
, end(end)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Location::Location(const Position& begin, unsigned int length)
|
||||||
|
: begin(begin)
|
||||||
|
, end(begin.line, begin.column + length)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Location::Location(const Location& begin, const Location& end)
|
||||||
|
: begin(begin.begin)
|
||||||
|
, end(end.end)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Location::operator==(const Location& rhs) const
|
||||||
|
{
|
||||||
|
return this->begin == rhs.begin && this->end == rhs.end;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Location::operator!=(const Location& rhs) const
|
||||||
|
{
|
||||||
|
return !(*this == rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Location::encloses(const Location& l) const
|
||||||
|
{
|
||||||
|
return begin <= l.begin && end >= l.end;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Location::overlaps(const Location& l) const
|
||||||
|
{
|
||||||
|
return (begin <= l.begin && end >= l.begin) || (begin <= l.end && end >= l.end) || (begin >= l.begin && end <= l.end);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Location::contains(const Position& p) const
|
||||||
|
{
|
||||||
|
return begin <= p && p < end;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Location::containsClosed(const Position& p) const
|
||||||
|
{
|
||||||
|
return begin <= p && p <= end;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Location::extend(const Location& other)
|
||||||
|
{
|
||||||
|
if (other.begin < begin)
|
||||||
|
begin = other.begin;
|
||||||
|
if (other.end > end)
|
||||||
|
end = other.end;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Location::shift(const Position& start, const Position& oldEnd, const Position& newEnd)
|
||||||
|
{
|
||||||
|
begin.shift(start, oldEnd, newEnd);
|
||||||
|
end.shift(start, oldEnd, newEnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Luau
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,292 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
#include "Luau/StringUtils.h"
|
||||||
|
|
||||||
|
#include "Luau/Common.h"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
namespace Luau
|
||||||
|
{
|
||||||
|
|
||||||
|
void vformatAppend(std::string& ret, const char* fmt, va_list args)
|
||||||
|
{
|
||||||
|
va_list argscopy;
|
||||||
|
va_copy(argscopy, args);
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
int actualSize = _vscprintf(fmt, argscopy);
|
||||||
|
#else
|
||||||
|
int actualSize = vsnprintf(NULL, 0, fmt, argscopy);
|
||||||
|
#endif
|
||||||
|
va_end(argscopy);
|
||||||
|
|
||||||
|
if (actualSize <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
size_t sz = ret.size();
|
||||||
|
ret.resize(sz + actualSize);
|
||||||
|
vsnprintf(&ret[0] + sz, actualSize + 1, fmt, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string format(const char* fmt, ...)
|
||||||
|
{
|
||||||
|
std::string result;
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
vformatAppend(result, fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void formatAppend(std::string& str, const char* fmt, ...)
|
||||||
|
{
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
vformatAppend(str, fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string vformat(const char* fmt, va_list args)
|
||||||
|
{
|
||||||
|
std::string ret;
|
||||||
|
vformatAppend(ret, fmt, args);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename String>
|
||||||
|
static std::string joinImpl(const std::vector<String>& segments, std::string_view delimiter)
|
||||||
|
{
|
||||||
|
if (segments.empty())
|
||||||
|
return "";
|
||||||
|
|
||||||
|
size_t len = (segments.size() - 1) * delimiter.size();
|
||||||
|
for (const auto& sv : segments)
|
||||||
|
len += sv.size();
|
||||||
|
|
||||||
|
std::string result;
|
||||||
|
result.resize(len);
|
||||||
|
char* dest = const_cast<char*>(result.data()); // This const_cast is only necessary until C++17
|
||||||
|
|
||||||
|
auto it = segments.begin();
|
||||||
|
memcpy(dest, it->data(), it->size());
|
||||||
|
dest += it->size();
|
||||||
|
++it;
|
||||||
|
for (; it != segments.end(); ++it)
|
||||||
|
{
|
||||||
|
memcpy(dest, delimiter.data(), delimiter.size());
|
||||||
|
dest += delimiter.size();
|
||||||
|
memcpy(dest, it->data(), it->size());
|
||||||
|
dest += it->size();
|
||||||
|
}
|
||||||
|
|
||||||
|
LUAU_ASSERT(dest == result.data() + len);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string join(const std::vector<std::string_view>& segments, std::string_view delimiter)
|
||||||
|
{
|
||||||
|
return joinImpl(segments, delimiter);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string join(const std::vector<std::string>& segments, std::string_view delimiter)
|
||||||
|
{
|
||||||
|
return joinImpl(segments, delimiter);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string_view> split(std::string_view s, char delimiter)
|
||||||
|
{
|
||||||
|
std::vector<std::string_view> result;
|
||||||
|
|
||||||
|
while (!s.empty())
|
||||||
|
{
|
||||||
|
auto index = s.find(delimiter);
|
||||||
|
if (index == std::string::npos)
|
||||||
|
{
|
||||||
|
result.push_back(s);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
result.push_back(s.substr(0, index));
|
||||||
|
s.remove_prefix(index + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t editDistance(std::string_view a, std::string_view b)
|
||||||
|
{
|
||||||
|
// When there are matching prefix and suffix, they end up computing as zero cost, effectively making it no-op. We drop these characters.
|
||||||
|
while (!a.empty() && !b.empty() && a.front() == b.front())
|
||||||
|
{
|
||||||
|
a.remove_prefix(1);
|
||||||
|
b.remove_prefix(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!a.empty() && !b.empty() && a.back() == b.back())
|
||||||
|
{
|
||||||
|
a.remove_suffix(1);
|
||||||
|
b.remove_suffix(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since we know the edit distance is the difference of the length of A and B discounting the matching prefixes and suffixes,
|
||||||
|
// it is therefore pointless to run the rest of this function to find that out. We immediately infer this size and return it.
|
||||||
|
if (a.empty())
|
||||||
|
return b.size();
|
||||||
|
if (b.empty())
|
||||||
|
return a.size();
|
||||||
|
|
||||||
|
size_t maxDistance = a.size() + b.size();
|
||||||
|
|
||||||
|
std::vector<size_t> distances((a.size() + 2) * (b.size() + 2), 0);
|
||||||
|
auto getPos = [b](size_t x, size_t y) -> size_t {
|
||||||
|
return (x * (b.size() + 2)) + y;
|
||||||
|
};
|
||||||
|
|
||||||
|
distances[0] = maxDistance;
|
||||||
|
|
||||||
|
for (size_t x = 0; x <= a.size(); ++x)
|
||||||
|
{
|
||||||
|
distances[getPos(x + 1, 0)] = maxDistance;
|
||||||
|
distances[getPos(x + 1, 1)] = x;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t y = 0; y <= b.size(); ++y)
|
||||||
|
{
|
||||||
|
distances[getPos(0, y + 1)] = maxDistance;
|
||||||
|
distances[getPos(1, y + 1)] = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<size_t, 256> seenCharToRow;
|
||||||
|
seenCharToRow.fill(0);
|
||||||
|
|
||||||
|
for (size_t x = 1; x <= a.size(); ++x)
|
||||||
|
{
|
||||||
|
size_t lastMatchedY = 0;
|
||||||
|
|
||||||
|
for (size_t y = 1; y <= b.size(); ++y)
|
||||||
|
{
|
||||||
|
size_t x1 = seenCharToRow[b[y - 1]];
|
||||||
|
size_t y1 = lastMatchedY;
|
||||||
|
|
||||||
|
size_t cost = 1;
|
||||||
|
if (a[x - 1] == b[y - 1])
|
||||||
|
{
|
||||||
|
cost = 0;
|
||||||
|
lastMatchedY = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t transposition = distances[getPos(x1, y1)] + (x - x1 - 1) + 1 + (y - y1 - 1);
|
||||||
|
size_t substitution = distances[getPos(x, y)] + cost;
|
||||||
|
size_t insertion = distances[getPos(x, y + 1)] + 1;
|
||||||
|
size_t deletion = distances[getPos(x + 1, y)] + 1;
|
||||||
|
|
||||||
|
// It's more performant to use std::min(size_t, size_t) rather than the initializer_list overload.
|
||||||
|
// Until proven otherwise, please do not change this.
|
||||||
|
distances[getPos(x + 1, y + 1)] = std::min(std::min(insertion, deletion), std::min(substitution, transposition));
|
||||||
|
}
|
||||||
|
|
||||||
|
seenCharToRow[a[x - 1]] = x;
|
||||||
|
}
|
||||||
|
|
||||||
|
return distances[getPos(a.size() + 1, b.size() + 1)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bool startsWith(std::string_view haystack, std::string_view needle)
|
||||||
|
{
|
||||||
|
// ::starts_with is C++20
|
||||||
|
return haystack.size() >= needle.size() && haystack.substr(0, needle.size()) == needle;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool equalsLower(std::string_view lhs, std::string_view rhs)
|
||||||
|
{
|
||||||
|
if (lhs.size() != rhs.size())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < lhs.size(); ++i)
|
||||||
|
if (tolower(uint8_t(lhs[i])) != tolower(uint8_t(rhs[i])))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t hashRange(const char* data, size_t size)
|
||||||
|
{
|
||||||
|
// FNV-1a
|
||||||
|
uint32_t hash = 2166136261;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < size; ++i)
|
||||||
|
{
|
||||||
|
hash ^= uint8_t(data[i]);
|
||||||
|
hash *= 16777619;
|
||||||
|
}
|
||||||
|
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isIdentifier(std::string_view s)
|
||||||
|
{
|
||||||
|
return (s.find_first_not_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890_") == std::string::npos);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 != '\"' && c != '`' && c != '{')
|
||||||
|
r += c;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
r += '\\';
|
||||||
|
|
||||||
|
if (escapeForInterpString && (c == '`' || c == '{'))
|
||||||
|
{
|
||||||
|
r += c;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (c)
|
||||||
|
{
|
||||||
|
case '\a':
|
||||||
|
r += 'a';
|
||||||
|
break;
|
||||||
|
case '\b':
|
||||||
|
r += 'b';
|
||||||
|
break;
|
||||||
|
case '\f':
|
||||||
|
r += 'f';
|
||||||
|
break;
|
||||||
|
case '\n':
|
||||||
|
r += 'n';
|
||||||
|
break;
|
||||||
|
case '\r':
|
||||||
|
r += 'r';
|
||||||
|
break;
|
||||||
|
case '\t':
|
||||||
|
r += 't';
|
||||||
|
break;
|
||||||
|
case '\v':
|
||||||
|
r += 'v';
|
||||||
|
break;
|
||||||
|
case '\'':
|
||||||
|
r += '\'';
|
||||||
|
break;
|
||||||
|
case '\"':
|
||||||
|
r += '\"';
|
||||||
|
break;
|
||||||
|
case '\\':
|
||||||
|
r += '\\';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Luau::formatAppend(r, "%03u", c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
} // namespace Luau
|
|
@ -0,0 +1,269 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
#include "Luau/TimeTrace.h"
|
||||||
|
|
||||||
|
#include "Luau/StringUtils.h"
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#ifndef WIN32_LEAN_AND_MEAN
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#endif
|
||||||
|
#ifndef NOMINMAX
|
||||||
|
#define NOMINMAX
|
||||||
|
#endif
|
||||||
|
#include <Windows.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __APPLE__
|
||||||
|
#include <mach/mach.h>
|
||||||
|
#include <mach/mach_time.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
LUAU_FASTFLAGVARIABLE(DebugLuauTimeTracing, false)
|
||||||
|
namespace Luau
|
||||||
|
{
|
||||||
|
namespace TimeTrace
|
||||||
|
{
|
||||||
|
static double getClockPeriod()
|
||||||
|
{
|
||||||
|
#if defined(_WIN32)
|
||||||
|
LARGE_INTEGER result = {};
|
||||||
|
QueryPerformanceFrequency(&result);
|
||||||
|
return 1.0 / double(result.QuadPart);
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
mach_timebase_info_data_t result = {};
|
||||||
|
mach_timebase_info(&result);
|
||||||
|
return double(result.numer) / double(result.denom) * 1e-9;
|
||||||
|
#elif defined(__linux__)
|
||||||
|
return 1e-9;
|
||||||
|
#else
|
||||||
|
return 1.0 / double(CLOCKS_PER_SEC);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static double getClockTimestamp()
|
||||||
|
{
|
||||||
|
#if defined(_WIN32)
|
||||||
|
LARGE_INTEGER result = {};
|
||||||
|
QueryPerformanceCounter(&result);
|
||||||
|
return double(result.QuadPart);
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
return double(mach_absolute_time());
|
||||||
|
#elif defined(__linux__)
|
||||||
|
timespec now;
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||||
|
return now.tv_sec * 1e9 + now.tv_nsec;
|
||||||
|
#else
|
||||||
|
return double(clock());
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
double getClock()
|
||||||
|
{
|
||||||
|
static double period = getClockPeriod();
|
||||||
|
static double start = getClockTimestamp();
|
||||||
|
|
||||||
|
return (getClockTimestamp() - start) * period;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t getClockMicroseconds()
|
||||||
|
{
|
||||||
|
static double period = getClockPeriod() * 1e6;
|
||||||
|
static double start = getClockTimestamp();
|
||||||
|
|
||||||
|
return uint32_t((getClockTimestamp() - start) * period);
|
||||||
|
}
|
||||||
|
} // namespace TimeTrace
|
||||||
|
} // namespace Luau
|
||||||
|
|
||||||
|
#if defined(LUAU_ENABLE_TIME_TRACE)
|
||||||
|
|
||||||
|
namespace Luau
|
||||||
|
{
|
||||||
|
namespace TimeTrace
|
||||||
|
{
|
||||||
|
struct GlobalContext
|
||||||
|
{
|
||||||
|
GlobalContext() = default;
|
||||||
|
~GlobalContext()
|
||||||
|
{
|
||||||
|
// Ideally we would want all ThreadContext destructors to run
|
||||||
|
// But in VS, not all thread_local object instances are destroyed
|
||||||
|
for (ThreadContext* context : threads)
|
||||||
|
{
|
||||||
|
if (!context->events.empty())
|
||||||
|
context->flushEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (traceFile)
|
||||||
|
fclose(traceFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::mutex mutex;
|
||||||
|
std::vector<ThreadContext*> threads;
|
||||||
|
uint32_t nextThreadId = 0;
|
||||||
|
std::vector<Token> tokens;
|
||||||
|
FILE* traceFile = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
GlobalContext& getGlobalContext()
|
||||||
|
{
|
||||||
|
static GlobalContext context;
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t createToken(GlobalContext& context, const char* name, const char* category)
|
||||||
|
{
|
||||||
|
std::scoped_lock lock(context.mutex);
|
||||||
|
|
||||||
|
LUAU_ASSERT(context.tokens.size() < 64 * 1024);
|
||||||
|
|
||||||
|
context.tokens.push_back({name, category});
|
||||||
|
return uint16_t(context.tokens.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t createThread(GlobalContext& context, ThreadContext* threadContext)
|
||||||
|
{
|
||||||
|
std::scoped_lock lock(context.mutex);
|
||||||
|
|
||||||
|
context.threads.push_back(threadContext);
|
||||||
|
|
||||||
|
return ++context.nextThreadId;
|
||||||
|
}
|
||||||
|
|
||||||
|
void releaseThread(GlobalContext& context, ThreadContext* threadContext)
|
||||||
|
{
|
||||||
|
std::scoped_lock lock(context.mutex);
|
||||||
|
|
||||||
|
if (auto it = std::find(context.threads.begin(), context.threads.end(), threadContext); it != context.threads.end())
|
||||||
|
context.threads.erase(it);
|
||||||
|
}
|
||||||
|
|
||||||
|
void flushEvents(GlobalContext& context, uint32_t threadId, const std::vector<Event>& events, const std::vector<char>& data)
|
||||||
|
{
|
||||||
|
std::scoped_lock lock(context.mutex);
|
||||||
|
|
||||||
|
if (!context.traceFile)
|
||||||
|
{
|
||||||
|
context.traceFile = fopen("trace.json", "w");
|
||||||
|
|
||||||
|
if (!context.traceFile)
|
||||||
|
return;
|
||||||
|
|
||||||
|
fprintf(context.traceFile, "[\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string temp;
|
||||||
|
const unsigned tempReserve = 64 * 1024;
|
||||||
|
temp.reserve(tempReserve);
|
||||||
|
|
||||||
|
const char* rawData = data.data();
|
||||||
|
|
||||||
|
// Formatting state
|
||||||
|
bool unfinishedEnter = false;
|
||||||
|
bool unfinishedArgs = false;
|
||||||
|
|
||||||
|
for (const Event& ev : events)
|
||||||
|
{
|
||||||
|
switch (ev.type)
|
||||||
|
{
|
||||||
|
case EventType::Enter:
|
||||||
|
{
|
||||||
|
if (unfinishedArgs)
|
||||||
|
{
|
||||||
|
formatAppend(temp, "}");
|
||||||
|
unfinishedArgs = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unfinishedEnter)
|
||||||
|
{
|
||||||
|
formatAppend(temp, "},\n");
|
||||||
|
unfinishedEnter = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Token& token = context.tokens[ev.token];
|
||||||
|
|
||||||
|
formatAppend(temp, R"({"name": "%s", "cat": "%s", "ph": "B", "ts": %u, "pid": 0, "tid": %u)", token.name, token.category,
|
||||||
|
ev.data.microsec, threadId);
|
||||||
|
unfinishedEnter = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EventType::Leave:
|
||||||
|
if (unfinishedArgs)
|
||||||
|
{
|
||||||
|
formatAppend(temp, "}");
|
||||||
|
unfinishedArgs = false;
|
||||||
|
}
|
||||||
|
if (unfinishedEnter)
|
||||||
|
{
|
||||||
|
formatAppend(temp, "},\n");
|
||||||
|
unfinishedEnter = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
formatAppend(temp,
|
||||||
|
R"({"ph": "E", "ts": %u, "pid": 0, "tid": %u},)"
|
||||||
|
"\n",
|
||||||
|
ev.data.microsec, threadId);
|
||||||
|
break;
|
||||||
|
case EventType::ArgName:
|
||||||
|
LUAU_ASSERT(unfinishedEnter);
|
||||||
|
|
||||||
|
if (!unfinishedArgs)
|
||||||
|
{
|
||||||
|
formatAppend(temp, R"(, "args": { "%s": )", rawData + ev.data.dataPos);
|
||||||
|
unfinishedArgs = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
formatAppend(temp, R"(, "%s": )", rawData + ev.data.dataPos);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EventType::ArgValue:
|
||||||
|
LUAU_ASSERT(unfinishedArgs);
|
||||||
|
formatAppend(temp, R"("%s")", rawData + ev.data.dataPos);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't want to hit the string capacity and reallocate
|
||||||
|
if (temp.size() > tempReserve - 1024)
|
||||||
|
{
|
||||||
|
fwrite(temp.data(), 1, temp.size(), context.traceFile);
|
||||||
|
temp.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unfinishedArgs)
|
||||||
|
{
|
||||||
|
formatAppend(temp, "}");
|
||||||
|
unfinishedArgs = false;
|
||||||
|
}
|
||||||
|
if (unfinishedEnter)
|
||||||
|
{
|
||||||
|
formatAppend(temp, "},\n");
|
||||||
|
unfinishedEnter = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fwrite(temp.data(), 1, temp.size(), context.traceFile);
|
||||||
|
fflush(context.traceFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
ThreadContext& getThreadContext()
|
||||||
|
{
|
||||||
|
thread_local ThreadContext context;
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t createScopeData(const char* name, const char* category)
|
||||||
|
{
|
||||||
|
return createToken(Luau::TimeTrace::getGlobalContext(), name, category);
|
||||||
|
}
|
||||||
|
} // namespace TimeTrace
|
||||||
|
} // namespace Luau
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,532 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
|
||||||
|
// This header contains the bytecode definition for Luau interpreter
|
||||||
|
// Creating the bytecode is outside the scope of this file and is handled by bytecode builder (BytecodeBuilder.h) and bytecode compiler (Compiler.h)
|
||||||
|
// Note that ALL enums declared in this file are order-sensitive since the values are baked into bytecode that needs to be processed by legacy clients.
|
||||||
|
|
||||||
|
// # Bytecode definitions
|
||||||
|
// Bytecode instructions are using "word code" - each instruction is one or many 32-bit words.
|
||||||
|
// The first word in the instruction is always the instruction header, and *must* contain the opcode (enum below) in the least significant byte.
|
||||||
|
//
|
||||||
|
// Instruction word can be encoded using one of the following encodings:
|
||||||
|
// ABC - least-significant byte for the opcode, followed by three bytes, A, B and C; each byte declares a register index, small index into some other table or an unsigned integral value
|
||||||
|
// AD - least-significant byte for the opcode, followed by A byte, followed by D half-word (16-bit integer). D is a signed integer that commonly specifies constant table index or jump offset
|
||||||
|
// E - least-significant byte for the opcode, followed by E (24-bit integer). E is a signed integer that commonly specifies a jump offset
|
||||||
|
//
|
||||||
|
// Instruction word is sometimes followed by one extra word, indicated as AUX - this is just a 32-bit word and is decoded according to the specification for each opcode.
|
||||||
|
// For each opcode the encoding is *static* - that is, based on the opcode you know a-priory how large the instruction is, with the exception of NEWCLOSURE
|
||||||
|
|
||||||
|
// # Bytecode indices
|
||||||
|
// Bytecode instructions commonly refer to integer values that define offsets or indices for various entities. For each type, there's a maximum encodable value.
|
||||||
|
// Note that in some cases, the compiler will set a lower limit than the maximum encodable value is to prevent fragile code into bumping against the limits whenever we change the compilation details.
|
||||||
|
// Additionally, in some specific instructions such as ANDK, the limit on the encoded value is smaller; this means that if a value is larger, a different instruction must be selected.
|
||||||
|
//
|
||||||
|
// Registers: 0-254. Registers refer to the values on the function's stack frame, including arguments.
|
||||||
|
// Upvalues: 0-199. Upvalues refer to the values stored in the closure object.
|
||||||
|
// Constants: 0-2^23-1. Constants are stored in a table allocated with each proto; to allow for future bytecode tweaks the encodable value is limited to 23 bits.
|
||||||
|
// Closures: 0-2^15-1. Closures are created from child protos via a child index; the limit is for the number of closures immediately referenced in each function.
|
||||||
|
// Jumps: -2^23..2^23. Jump offsets are specified in word increments, so jumping over an instruction may sometimes require an offset of 2 or more. Note that for jump instructions with AUX, the AUX word is included as part of the jump offset.
|
||||||
|
|
||||||
|
// # Bytecode versions
|
||||||
|
// Bytecode serialized format embeds a version number, that dictates both the serialized form as well as the allowed instructions. As long as the bytecode version falls into supported
|
||||||
|
// range (indicated by LBC_BYTECODE_MIN / LBC_BYTECODE_MAX) and was produced by Luau compiler, it should load and execute correctly.
|
||||||
|
//
|
||||||
|
// Note that Luau runtime doesn't provide indefinite bytecode compatibility: support for older versions gets removed over time. As such, bytecode isn't a durable storage format and it's expected
|
||||||
|
// that Luau users can recompile bytecode from source on Luau version upgrades if necessary.
|
||||||
|
|
||||||
|
// # Bytecode version history
|
||||||
|
//
|
||||||
|
// Note: due to limitations of the versioning scheme, some bytecode blobs that carry version 2 are using features from version 3. Starting from version 3, version should be sufficient to indicate bytecode compatibility.
|
||||||
|
//
|
||||||
|
// Version 1: Baseline version for the open-source release. Supported until 0.521.
|
||||||
|
// Version 2: Adds Proto::linedefined. Supported until 0.544.
|
||||||
|
// Version 3: Adds FORGPREP/JUMPXEQK* and enhances AUX encoding for FORGLOOP. Removes FORGLOOP_NEXT/INEXT and JUMPIFEQK/JUMPIFNOTEQK. Currently supported.
|
||||||
|
|
||||||
|
// Bytecode opcode, part of the instruction header
|
||||||
|
enum LuauOpcode
|
||||||
|
{
|
||||||
|
// NOP: noop
|
||||||
|
LOP_NOP,
|
||||||
|
|
||||||
|
// BREAK: debugger break
|
||||||
|
LOP_BREAK,
|
||||||
|
|
||||||
|
// LOADNIL: sets register to nil
|
||||||
|
// A: target register
|
||||||
|
LOP_LOADNIL,
|
||||||
|
|
||||||
|
// LOADB: sets register to boolean and jumps to a given short offset (used to compile comparison results into a boolean)
|
||||||
|
// A: target register
|
||||||
|
// B: value (0/1)
|
||||||
|
// C: jump offset
|
||||||
|
LOP_LOADB,
|
||||||
|
|
||||||
|
// LOADN: sets register to a number literal
|
||||||
|
// A: target register
|
||||||
|
// D: value (-32768..32767)
|
||||||
|
LOP_LOADN,
|
||||||
|
|
||||||
|
// LOADK: sets register to an entry from the constant table from the proto (number/string)
|
||||||
|
// A: target register
|
||||||
|
// D: constant table index (0..32767)
|
||||||
|
LOP_LOADK,
|
||||||
|
|
||||||
|
// MOVE: move (copy) value from one register to another
|
||||||
|
// A: target register
|
||||||
|
// B: source register
|
||||||
|
LOP_MOVE,
|
||||||
|
|
||||||
|
// GETGLOBAL: load value from global table using constant string as a key
|
||||||
|
// A: target register
|
||||||
|
// C: predicted slot index (based on hash)
|
||||||
|
// AUX: constant table index
|
||||||
|
LOP_GETGLOBAL,
|
||||||
|
|
||||||
|
// SETGLOBAL: set value in global table using constant string as a key
|
||||||
|
// A: source register
|
||||||
|
// C: predicted slot index (based on hash)
|
||||||
|
// AUX: constant table index
|
||||||
|
LOP_SETGLOBAL,
|
||||||
|
|
||||||
|
// GETUPVAL: load upvalue from the upvalue table for the current function
|
||||||
|
// A: target register
|
||||||
|
// B: upvalue index
|
||||||
|
LOP_GETUPVAL,
|
||||||
|
|
||||||
|
// SETUPVAL: store value into the upvalue table for the current function
|
||||||
|
// A: target register
|
||||||
|
// B: upvalue index
|
||||||
|
LOP_SETUPVAL,
|
||||||
|
|
||||||
|
// CLOSEUPVALS: close (migrate to heap) all upvalues that were captured for registers >= target
|
||||||
|
// A: target register
|
||||||
|
LOP_CLOSEUPVALS,
|
||||||
|
|
||||||
|
// GETIMPORT: load imported global table global from the constant table
|
||||||
|
// A: target register
|
||||||
|
// D: constant table index (0..32767); we assume that imports are loaded into the constant table
|
||||||
|
// AUX: 3 10-bit indices of constant strings that, combined, constitute an import path; length of the path is set by the top 2 bits (1,2,3)
|
||||||
|
LOP_GETIMPORT,
|
||||||
|
|
||||||
|
// GETTABLE: load value from table into target register using key from register
|
||||||
|
// A: target register
|
||||||
|
// B: table register
|
||||||
|
// C: index register
|
||||||
|
LOP_GETTABLE,
|
||||||
|
|
||||||
|
// SETTABLE: store source register into table using key from register
|
||||||
|
// A: source register
|
||||||
|
// B: table register
|
||||||
|
// C: index register
|
||||||
|
LOP_SETTABLE,
|
||||||
|
|
||||||
|
// GETTABLEKS: load value from table into target register using constant string as a key
|
||||||
|
// A: target register
|
||||||
|
// B: table register
|
||||||
|
// C: predicted slot index (based on hash)
|
||||||
|
// AUX: constant table index
|
||||||
|
LOP_GETTABLEKS,
|
||||||
|
|
||||||
|
// SETTABLEKS: store source register into table using constant string as a key
|
||||||
|
// A: source register
|
||||||
|
// B: table register
|
||||||
|
// C: predicted slot index (based on hash)
|
||||||
|
// AUX: constant table index
|
||||||
|
LOP_SETTABLEKS,
|
||||||
|
|
||||||
|
// GETTABLEN: load value from table into target register using small integer index as a key
|
||||||
|
// A: target register
|
||||||
|
// B: table register
|
||||||
|
// C: index-1 (index is 1..256)
|
||||||
|
LOP_GETTABLEN,
|
||||||
|
|
||||||
|
// SETTABLEN: store source register into table using small integer index as a key
|
||||||
|
// A: source register
|
||||||
|
// B: table register
|
||||||
|
// C: index-1 (index is 1..256)
|
||||||
|
LOP_SETTABLEN,
|
||||||
|
|
||||||
|
// NEWCLOSURE: create closure from a child proto; followed by a CAPTURE instruction for each upvalue
|
||||||
|
// A: target register
|
||||||
|
// D: child proto index (0..32767)
|
||||||
|
LOP_NEWCLOSURE,
|
||||||
|
|
||||||
|
// NAMECALL: prepare to call specified method by name by loading function from source register using constant index into target register and copying source register into target register + 1
|
||||||
|
// A: target register
|
||||||
|
// B: source register
|
||||||
|
// C: predicted slot index (based on hash)
|
||||||
|
// AUX: constant table index
|
||||||
|
// Note that this instruction must be followed directly by CALL; it prepares the arguments
|
||||||
|
// This instruction is roughly equivalent to GETTABLEKS + MOVE pair, but we need a special instruction to support custom __namecall metamethod
|
||||||
|
LOP_NAMECALL,
|
||||||
|
|
||||||
|
// CALL: call specified function
|
||||||
|
// A: register where the function object lives, followed by arguments; results are placed starting from the same register
|
||||||
|
// B: argument count + 1, or 0 to preserve all arguments up to top (MULTRET)
|
||||||
|
// C: result count + 1, or 0 to preserve all values and adjust top (MULTRET)
|
||||||
|
LOP_CALL,
|
||||||
|
|
||||||
|
// RETURN: returns specified values from the function
|
||||||
|
// A: register where the returned values start
|
||||||
|
// B: number of returned values + 1, or 0 to return all values up to top (MULTRET)
|
||||||
|
LOP_RETURN,
|
||||||
|
|
||||||
|
// JUMP: jumps to target offset
|
||||||
|
// D: jump offset (-32768..32767; 0 means "next instruction" aka "don't jump")
|
||||||
|
LOP_JUMP,
|
||||||
|
|
||||||
|
// JUMPBACK: jumps to target offset; this is equivalent to JUMP but is used as a safepoint to be able to interrupt while/repeat loops
|
||||||
|
// D: jump offset (-32768..32767; 0 means "next instruction" aka "don't jump")
|
||||||
|
LOP_JUMPBACK,
|
||||||
|
|
||||||
|
// JUMPIF: jumps to target offset if register is not nil/false
|
||||||
|
// A: source register
|
||||||
|
// D: jump offset (-32768..32767; 0 means "next instruction" aka "don't jump")
|
||||||
|
LOP_JUMPIF,
|
||||||
|
|
||||||
|
// JUMPIFNOT: jumps to target offset if register is nil/false
|
||||||
|
// A: source register
|
||||||
|
// D: jump offset (-32768..32767; 0 means "next instruction" aka "don't jump")
|
||||||
|
LOP_JUMPIFNOT,
|
||||||
|
|
||||||
|
// JUMPIFEQ, JUMPIFLE, JUMPIFLT, JUMPIFNOTEQ, JUMPIFNOTLE, JUMPIFNOTLT: jumps to target offset if the comparison is true (or false, for NOT variants)
|
||||||
|
// A: source register 1
|
||||||
|
// D: jump offset (-32768..32767; 1 means "next instruction" aka "don't jump")
|
||||||
|
// AUX: source register 2
|
||||||
|
LOP_JUMPIFEQ,
|
||||||
|
LOP_JUMPIFLE,
|
||||||
|
LOP_JUMPIFLT,
|
||||||
|
LOP_JUMPIFNOTEQ,
|
||||||
|
LOP_JUMPIFNOTLE,
|
||||||
|
LOP_JUMPIFNOTLT,
|
||||||
|
|
||||||
|
// ADD, SUB, MUL, DIV, MOD, POW: compute arithmetic operation between two source registers and put the result into target register
|
||||||
|
// A: target register
|
||||||
|
// B: source register 1
|
||||||
|
// C: source register 2
|
||||||
|
LOP_ADD,
|
||||||
|
LOP_SUB,
|
||||||
|
LOP_MUL,
|
||||||
|
LOP_DIV,
|
||||||
|
LOP_MOD,
|
||||||
|
LOP_POW,
|
||||||
|
|
||||||
|
// ADDK, SUBK, MULK, DIVK, MODK, POWK: compute arithmetic operation between the source register and a constant and put the result into target register
|
||||||
|
// A: target register
|
||||||
|
// B: source register
|
||||||
|
// C: constant table index (0..255)
|
||||||
|
LOP_ADDK,
|
||||||
|
LOP_SUBK,
|
||||||
|
LOP_MULK,
|
||||||
|
LOP_DIVK,
|
||||||
|
LOP_MODK,
|
||||||
|
LOP_POWK,
|
||||||
|
|
||||||
|
// AND, OR: perform `and` or `or` operation (selecting first or second register based on whether the first one is truthy) and put the result into target register
|
||||||
|
// A: target register
|
||||||
|
// B: source register 1
|
||||||
|
// C: source register 2
|
||||||
|
LOP_AND,
|
||||||
|
LOP_OR,
|
||||||
|
|
||||||
|
// ANDK, ORK: perform `and` or `or` operation (selecting source register or constant based on whether the source register is truthy) and put the result into target register
|
||||||
|
// A: target register
|
||||||
|
// B: source register
|
||||||
|
// C: constant table index (0..255)
|
||||||
|
LOP_ANDK,
|
||||||
|
LOP_ORK,
|
||||||
|
|
||||||
|
// CONCAT: concatenate all strings between B and C (inclusive) and put the result into A
|
||||||
|
// A: target register
|
||||||
|
// B: source register start
|
||||||
|
// C: source register end
|
||||||
|
LOP_CONCAT,
|
||||||
|
|
||||||
|
// NOT, MINUS, LENGTH: compute unary operation for source register and put the result into target register
|
||||||
|
// A: target register
|
||||||
|
// B: source register
|
||||||
|
LOP_NOT,
|
||||||
|
LOP_MINUS,
|
||||||
|
LOP_LENGTH,
|
||||||
|
|
||||||
|
// NEWTABLE: create table in target register
|
||||||
|
// A: target register
|
||||||
|
// B: table size, stored as 0 for v=0 and ceil(log2(v))+1 for v!=0
|
||||||
|
// AUX: array size
|
||||||
|
LOP_NEWTABLE,
|
||||||
|
|
||||||
|
// DUPTABLE: duplicate table using the constant table template to target register
|
||||||
|
// A: target register
|
||||||
|
// D: constant table index (0..32767)
|
||||||
|
LOP_DUPTABLE,
|
||||||
|
|
||||||
|
// SETLIST: set a list of values to table in target register
|
||||||
|
// A: target register
|
||||||
|
// B: source register start
|
||||||
|
// C: value count + 1, or 0 to use all values up to top (MULTRET)
|
||||||
|
// AUX: table index to start from
|
||||||
|
LOP_SETLIST,
|
||||||
|
|
||||||
|
// FORNPREP: prepare a numeric for loop, jump over the loop if first iteration doesn't need to run
|
||||||
|
// A: target register; numeric for loops assume a register layout [limit, step, index, variable]
|
||||||
|
// D: jump offset (-32768..32767)
|
||||||
|
// limit/step are immutable, index isn't visible to user code since it's copied into variable
|
||||||
|
LOP_FORNPREP,
|
||||||
|
|
||||||
|
// FORNLOOP: adjust loop variables for one iteration, jump back to the loop header if loop needs to continue
|
||||||
|
// A: target register; see FORNPREP for register layout
|
||||||
|
// D: jump offset (-32768..32767)
|
||||||
|
LOP_FORNLOOP,
|
||||||
|
|
||||||
|
// FORGLOOP: adjust loop variables for one iteration of a generic for loop, jump back to the loop header if loop needs to continue
|
||||||
|
// A: target register; generic for loops assume a register layout [generator, state, index, variables...]
|
||||||
|
// D: jump offset (-32768..32767)
|
||||||
|
// AUX: variable count (1..255) in the low 8 bits, high bit indicates whether to use ipairs-style traversal in the fast path
|
||||||
|
// loop variables are adjusted by calling generator(state, index) and expecting it to return a tuple that's copied to the user variables
|
||||||
|
// the first variable is then copied into index; generator/state are immutable, index isn't visible to user code
|
||||||
|
LOP_FORGLOOP,
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
|
||||||
|
// removed in v3
|
||||||
|
LOP_DEP_FORGLOOP_NEXT,
|
||||||
|
|
||||||
|
// GETVARARGS: copy variables into the target register from vararg storage for current function
|
||||||
|
// A: target register
|
||||||
|
// B: variable count + 1, or 0 to copy all variables and adjust top (MULTRET)
|
||||||
|
LOP_GETVARARGS,
|
||||||
|
|
||||||
|
// DUPCLOSURE: create closure from a pre-created function object (reusing it unless environments diverge)
|
||||||
|
// A: target register
|
||||||
|
// D: constant table index (0..32767)
|
||||||
|
LOP_DUPCLOSURE,
|
||||||
|
|
||||||
|
// PREPVARARGS: prepare stack for variadic functions so that GETVARARGS works correctly
|
||||||
|
// A: number of fixed arguments
|
||||||
|
LOP_PREPVARARGS,
|
||||||
|
|
||||||
|
// LOADKX: sets register to an entry from the constant table from the proto (number/string)
|
||||||
|
// A: target register
|
||||||
|
// AUX: constant table index
|
||||||
|
LOP_LOADKX,
|
||||||
|
|
||||||
|
// JUMPX: jumps to the target offset; like JUMPBACK, supports interruption
|
||||||
|
// E: jump offset (-2^23..2^23; 0 means "next instruction" aka "don't jump")
|
||||||
|
LOP_JUMPX,
|
||||||
|
|
||||||
|
// FASTCALL: perform a fast call of a built-in function
|
||||||
|
// A: builtin function id (see LuauBuiltinFunction)
|
||||||
|
// C: jump offset to get to following CALL
|
||||||
|
// FASTCALL is followed by one of (GETIMPORT, MOVE, GETUPVAL) instructions and by CALL instruction
|
||||||
|
// This is necessary so that if FASTCALL can't perform the call inline, it can continue normal execution
|
||||||
|
// If FASTCALL *can* perform the call, it jumps over the instructions *and* over the next CALL
|
||||||
|
// Note that FASTCALL will read the actual call arguments, such as argument/result registers and counts, from the CALL instruction
|
||||||
|
LOP_FASTCALL,
|
||||||
|
|
||||||
|
// COVERAGE: update coverage information stored in the instruction
|
||||||
|
// E: hit count for the instruction (0..2^23-1)
|
||||||
|
// The hit count is incremented by VM every time the instruction is executed, and saturates at 2^23-1
|
||||||
|
LOP_COVERAGE,
|
||||||
|
|
||||||
|
// CAPTURE: capture a local or an upvalue as an upvalue into a newly created closure; only valid after NEWCLOSURE
|
||||||
|
// A: capture type, see LuauCaptureType
|
||||||
|
// B: source register (for VAL/REF) or upvalue index (for UPVAL/UPREF)
|
||||||
|
LOP_CAPTURE,
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
// B: source argument register
|
||||||
|
// C: jump offset to get to following CALL
|
||||||
|
LOP_FASTCALL1,
|
||||||
|
|
||||||
|
// FASTCALL2: perform a fast call of a built-in function using 2 register arguments
|
||||||
|
// A: builtin function id (see LuauBuiltinFunction)
|
||||||
|
// B: source argument register
|
||||||
|
// C: jump offset to get to following CALL
|
||||||
|
// AUX: source register 2 in least-significant byte
|
||||||
|
LOP_FASTCALL2,
|
||||||
|
|
||||||
|
// FASTCALL2K: perform a fast call of a built-in function using 1 register argument and 1 constant argument
|
||||||
|
// A: builtin function id (see LuauBuiltinFunction)
|
||||||
|
// B: source argument register
|
||||||
|
// C: jump offset to get to following CALL
|
||||||
|
// AUX: constant index
|
||||||
|
LOP_FASTCALL2K,
|
||||||
|
|
||||||
|
// FORGPREP: prepare loop variables for a generic for loop, jump to the loop backedge unconditionally
|
||||||
|
// A: target register; generic for loops assume a register layout [generator, state, index, variables...]
|
||||||
|
// D: jump offset (-32768..32767)
|
||||||
|
LOP_FORGPREP,
|
||||||
|
|
||||||
|
// JUMPXEQKNIL, JUMPXEQKB: jumps to target offset if the comparison with constant is true (or false, see AUX)
|
||||||
|
// A: source register 1
|
||||||
|
// D: jump offset (-32768..32767; 1 means "next instruction" aka "don't jump")
|
||||||
|
// AUX: constant value (for boolean) in low bit, NOT flag (that flips comparison result) in high bit
|
||||||
|
LOP_JUMPXEQKNIL,
|
||||||
|
LOP_JUMPXEQKB,
|
||||||
|
|
||||||
|
// JUMPXEQKN, JUMPXEQKS: jumps to target offset if the comparison with constant is true (or false, see AUX)
|
||||||
|
// A: source register 1
|
||||||
|
// D: jump offset (-32768..32767; 1 means "next instruction" aka "don't jump")
|
||||||
|
// AUX: constant table index in low 24 bits, NOT flag (that flips comparison result) in high bit
|
||||||
|
LOP_JUMPXEQKN,
|
||||||
|
LOP_JUMPXEQKS,
|
||||||
|
|
||||||
|
// Enum entry for number of opcodes, not a valid opcode by itself!
|
||||||
|
LOP__COUNT
|
||||||
|
};
|
||||||
|
|
||||||
|
// Bytecode instruction header: it's always a 32-bit integer, with low byte (first byte in little endian) containing the opcode
|
||||||
|
// Some instruction types require more data and have more 32-bit integers following the header
|
||||||
|
#define LUAU_INSN_OP(insn) ((insn) & 0xff)
|
||||||
|
|
||||||
|
// ABC encoding: three 8-bit values, containing registers or small numbers
|
||||||
|
#define LUAU_INSN_A(insn) (((insn) >> 8) & 0xff)
|
||||||
|
#define LUAU_INSN_B(insn) (((insn) >> 16) & 0xff)
|
||||||
|
#define LUAU_INSN_C(insn) (((insn) >> 24) & 0xff)
|
||||||
|
|
||||||
|
// AD encoding: one 8-bit value, one signed 16-bit value
|
||||||
|
#define LUAU_INSN_D(insn) (int32_t(insn) >> 16)
|
||||||
|
|
||||||
|
// E encoding: one signed 24-bit value
|
||||||
|
#define LUAU_INSN_E(insn) (int32_t(insn) >> 8)
|
||||||
|
|
||||||
|
// Bytecode tags, used internally for bytecode encoded as a string
|
||||||
|
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 = 3,
|
||||||
|
LBC_VERSION_MAX = 3,
|
||||||
|
LBC_VERSION_TARGET = 3,
|
||||||
|
// Types of constant table entries
|
||||||
|
LBC_CONSTANT_NIL = 0,
|
||||||
|
LBC_CONSTANT_BOOLEAN,
|
||||||
|
LBC_CONSTANT_NUMBER,
|
||||||
|
LBC_CONSTANT_STRING,
|
||||||
|
LBC_CONSTANT_IMPORT,
|
||||||
|
LBC_CONSTANT_TABLE,
|
||||||
|
LBC_CONSTANT_CLOSURE,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Builtin function ids, used in LOP_FASTCALL
|
||||||
|
enum LuauBuiltinFunction
|
||||||
|
{
|
||||||
|
LBF_NONE = 0,
|
||||||
|
|
||||||
|
// assert()
|
||||||
|
LBF_ASSERT,
|
||||||
|
|
||||||
|
// math.
|
||||||
|
LBF_MATH_ABS,
|
||||||
|
LBF_MATH_ACOS,
|
||||||
|
LBF_MATH_ASIN,
|
||||||
|
LBF_MATH_ATAN2,
|
||||||
|
LBF_MATH_ATAN,
|
||||||
|
LBF_MATH_CEIL,
|
||||||
|
LBF_MATH_COSH,
|
||||||
|
LBF_MATH_COS,
|
||||||
|
LBF_MATH_DEG,
|
||||||
|
LBF_MATH_EXP,
|
||||||
|
LBF_MATH_FLOOR,
|
||||||
|
LBF_MATH_FMOD,
|
||||||
|
LBF_MATH_FREXP,
|
||||||
|
LBF_MATH_LDEXP,
|
||||||
|
LBF_MATH_LOG10,
|
||||||
|
LBF_MATH_LOG,
|
||||||
|
LBF_MATH_MAX,
|
||||||
|
LBF_MATH_MIN,
|
||||||
|
LBF_MATH_MODF,
|
||||||
|
LBF_MATH_POW,
|
||||||
|
LBF_MATH_RAD,
|
||||||
|
LBF_MATH_SINH,
|
||||||
|
LBF_MATH_SIN,
|
||||||
|
LBF_MATH_SQRT,
|
||||||
|
LBF_MATH_TANH,
|
||||||
|
LBF_MATH_TAN,
|
||||||
|
|
||||||
|
// bit32.
|
||||||
|
LBF_BIT32_ARSHIFT,
|
||||||
|
LBF_BIT32_BAND,
|
||||||
|
LBF_BIT32_BNOT,
|
||||||
|
LBF_BIT32_BOR,
|
||||||
|
LBF_BIT32_BXOR,
|
||||||
|
LBF_BIT32_BTEST,
|
||||||
|
LBF_BIT32_EXTRACT,
|
||||||
|
LBF_BIT32_LROTATE,
|
||||||
|
LBF_BIT32_LSHIFT,
|
||||||
|
LBF_BIT32_REPLACE,
|
||||||
|
LBF_BIT32_RROTATE,
|
||||||
|
LBF_BIT32_RSHIFT,
|
||||||
|
|
||||||
|
// type()
|
||||||
|
LBF_TYPE,
|
||||||
|
|
||||||
|
// string.
|
||||||
|
LBF_STRING_BYTE,
|
||||||
|
LBF_STRING_CHAR,
|
||||||
|
LBF_STRING_LEN,
|
||||||
|
|
||||||
|
// typeof()
|
||||||
|
LBF_TYPEOF,
|
||||||
|
|
||||||
|
// string.
|
||||||
|
LBF_STRING_SUB,
|
||||||
|
|
||||||
|
// math.
|
||||||
|
LBF_MATH_CLAMP,
|
||||||
|
LBF_MATH_SIGN,
|
||||||
|
LBF_MATH_ROUND,
|
||||||
|
|
||||||
|
// raw*
|
||||||
|
LBF_RAWSET,
|
||||||
|
LBF_RAWGET,
|
||||||
|
LBF_RAWEQUAL,
|
||||||
|
|
||||||
|
// table.
|
||||||
|
LBF_TABLE_INSERT,
|
||||||
|
LBF_TABLE_UNPACK,
|
||||||
|
|
||||||
|
// vector ctor
|
||||||
|
LBF_VECTOR,
|
||||||
|
|
||||||
|
// bit32.count
|
||||||
|
LBF_BIT32_COUNTLZ,
|
||||||
|
LBF_BIT32_COUNTRZ,
|
||||||
|
|
||||||
|
// select(_, ...)
|
||||||
|
LBF_SELECT_VARARG,
|
||||||
|
|
||||||
|
// rawlen
|
||||||
|
LBF_RAWLEN,
|
||||||
|
|
||||||
|
// bit32.extract(_, k, k)
|
||||||
|
LBF_BIT32_EXTRACTK,
|
||||||
|
|
||||||
|
// get/setmetatable
|
||||||
|
LBF_GETMETATABLE,
|
||||||
|
LBF_SETMETATABLE,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Capture type, used in LOP_CAPTURE
|
||||||
|
enum LuauCaptureType
|
||||||
|
{
|
||||||
|
LCT_VAL = 0,
|
||||||
|
LCT_REF,
|
||||||
|
LCT_UPVAL,
|
||||||
|
};
|
|
@ -0,0 +1,137 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// Compiler codegen control macros
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#define LUAU_NORETURN __declspec(noreturn)
|
||||||
|
#define LUAU_NOINLINE __declspec(noinline)
|
||||||
|
#define LUAU_FORCEINLINE __forceinline
|
||||||
|
#define LUAU_LIKELY(x) x
|
||||||
|
#define LUAU_UNLIKELY(x) x
|
||||||
|
#define LUAU_UNREACHABLE() __assume(false)
|
||||||
|
#define LUAU_DEBUGBREAK() __debugbreak()
|
||||||
|
#else
|
||||||
|
#define LUAU_NORETURN __attribute__((__noreturn__))
|
||||||
|
#define LUAU_NOINLINE __attribute__((noinline))
|
||||||
|
#define LUAU_FORCEINLINE inline __attribute__((always_inline))
|
||||||
|
#define LUAU_LIKELY(x) __builtin_expect(x, 1)
|
||||||
|
#define LUAU_UNLIKELY(x) __builtin_expect(x, 0)
|
||||||
|
#define LUAU_UNREACHABLE() __builtin_unreachable()
|
||||||
|
#define LUAU_DEBUGBREAK() __builtin_trap()
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
||||||
|
#define LUAU_BIG_ENDIAN
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace Luau
|
||||||
|
{
|
||||||
|
|
||||||
|
using AssertHandler = int (*)(const char* expression, const char* file, int line, const char* function);
|
||||||
|
|
||||||
|
inline AssertHandler& assertHandler()
|
||||||
|
{
|
||||||
|
static AssertHandler handler = nullptr;
|
||||||
|
return handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We want 'inline' to correctly link this function declared in the header
|
||||||
|
// But we also want to prevent compiler from inlining this function when optimization and assertions are enabled together
|
||||||
|
// Reason for that is that compilation times can increase significantly in such a configuration
|
||||||
|
LUAU_NOINLINE inline int assertCallHandler(const char* expression, const char* file, int line, const char* function)
|
||||||
|
{
|
||||||
|
if (AssertHandler handler = assertHandler())
|
||||||
|
return handler(expression, file, line, function);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Luau
|
||||||
|
|
||||||
|
#if !defined(NDEBUG) || defined(LUAU_ENABLE_ASSERT)
|
||||||
|
#define LUAU_ASSERT(expr) ((void)(!!(expr) || (Luau::assertCallHandler(#expr, __FILE__, __LINE__, __FUNCTION__) && (LUAU_DEBUGBREAK(), 0))))
|
||||||
|
#define LUAU_ASSERTENABLED
|
||||||
|
#else
|
||||||
|
#define LUAU_ASSERT(expr) (void)sizeof(!!(expr))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace Luau
|
||||||
|
{
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
struct FValue
|
||||||
|
{
|
||||||
|
static FValue* list;
|
||||||
|
|
||||||
|
T value;
|
||||||
|
bool dynamic;
|
||||||
|
const char* name;
|
||||||
|
FValue* next;
|
||||||
|
|
||||||
|
FValue(const char* name, T def, bool dynamic)
|
||||||
|
: value(def)
|
||||||
|
, dynamic(dynamic)
|
||||||
|
, name(name)
|
||||||
|
, next(list)
|
||||||
|
{
|
||||||
|
list = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
operator T() const
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
FValue<T>* FValue<T>::list = nullptr;
|
||||||
|
|
||||||
|
} // namespace Luau
|
||||||
|
|
||||||
|
#define LUAU_FASTFLAG(flag) \
|
||||||
|
namespace FFlag \
|
||||||
|
{ \
|
||||||
|
extern Luau::FValue<bool> flag; \
|
||||||
|
}
|
||||||
|
#define LUAU_FASTFLAGVARIABLE(flag, def) \
|
||||||
|
namespace FFlag \
|
||||||
|
{ \
|
||||||
|
Luau::FValue<bool> flag(#flag, def, false); \
|
||||||
|
}
|
||||||
|
#define LUAU_FASTINT(flag) \
|
||||||
|
namespace FInt \
|
||||||
|
{ \
|
||||||
|
extern Luau::FValue<int> flag; \
|
||||||
|
}
|
||||||
|
#define LUAU_FASTINTVARIABLE(flag, def) \
|
||||||
|
namespace FInt \
|
||||||
|
{ \
|
||||||
|
Luau::FValue<int> flag(#flag, def, false); \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define LUAU_DYNAMIC_FASTFLAG(flag) \
|
||||||
|
namespace DFFlag \
|
||||||
|
{ \
|
||||||
|
extern Luau::FValue<bool> flag; \
|
||||||
|
}
|
||||||
|
#define LUAU_DYNAMIC_FASTFLAGVARIABLE(flag, def) \
|
||||||
|
namespace DFFlag \
|
||||||
|
{ \
|
||||||
|
Luau::FValue<bool> flag(#flag, def, true); \
|
||||||
|
}
|
||||||
|
#define LUAU_DYNAMIC_FASTINT(flag) \
|
||||||
|
namespace DFInt \
|
||||||
|
{ \
|
||||||
|
extern Luau::FValue<int> flag; \
|
||||||
|
}
|
||||||
|
#define LUAU_DYNAMIC_FASTINTVARIABLE(flag, def) \
|
||||||
|
namespace DFInt \
|
||||||
|
{ \
|
||||||
|
Luau::FValue<int> flag(#flag, def, true); \
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(__GNUC__)
|
||||||
|
#define LUAU_PRINTF_ATTR(fmt, arg) __attribute__((format(printf, fmt, arg)))
|
||||||
|
#else
|
||||||
|
#define LUAU_PRINTF_ATTR(fmt, arg)
|
||||||
|
#endif
|
|
@ -0,0 +1,618 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Luau/Common.h"
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <utility>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
namespace Luau
|
||||||
|
{
|
||||||
|
|
||||||
|
struct DenseHashPointer
|
||||||
|
{
|
||||||
|
size_t operator()(const void* key) const
|
||||||
|
{
|
||||||
|
return (uintptr_t(key) >> 4) ^ (uintptr_t(key) >> 9);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Internal implementation of DenseHashSet and DenseHashMap
|
||||||
|
namespace detail
|
||||||
|
{
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
using DenseHashDefault = std::conditional_t<std::is_pointer_v<T>, DenseHashPointer, std::hash<T>>;
|
||||||
|
|
||||||
|
template<typename Key, typename Item, typename MutableItem, typename ItemInterface, typename Hash, typename Eq>
|
||||||
|
class DenseHashTable
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
class const_iterator;
|
||||||
|
class iterator;
|
||||||
|
|
||||||
|
DenseHashTable(const Key& empty_key, size_t buckets = 0)
|
||||||
|
: data(nullptr)
|
||||||
|
, capacity(0)
|
||||||
|
, count(0)
|
||||||
|
, empty_key(empty_key)
|
||||||
|
{
|
||||||
|
// validate that equality operator is at least somewhat functional
|
||||||
|
LUAU_ASSERT(eq(empty_key, empty_key));
|
||||||
|
// buckets has to be power-of-two or zero
|
||||||
|
LUAU_ASSERT((buckets & (buckets - 1)) == 0);
|
||||||
|
|
||||||
|
if (buckets)
|
||||||
|
{
|
||||||
|
data = static_cast<Item*>(::operator new(sizeof(Item) * buckets));
|
||||||
|
capacity = buckets;
|
||||||
|
|
||||||
|
ItemInterface::fill(data, buckets, empty_key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~DenseHashTable()
|
||||||
|
{
|
||||||
|
if (data)
|
||||||
|
destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
DenseHashTable(const DenseHashTable& other)
|
||||||
|
: data(nullptr)
|
||||||
|
, capacity(0)
|
||||||
|
, count(other.count)
|
||||||
|
, empty_key(other.empty_key)
|
||||||
|
{
|
||||||
|
if (other.capacity)
|
||||||
|
{
|
||||||
|
data = static_cast<Item*>(::operator new(sizeof(Item) * other.capacity));
|
||||||
|
|
||||||
|
for (size_t i = 0; i < other.capacity; ++i)
|
||||||
|
{
|
||||||
|
new (&data[i]) Item(other.data[i]);
|
||||||
|
capacity = i + 1; // if Item copy throws, capacity will note the number of initialized objects for destroy() to clean up
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DenseHashTable(DenseHashTable&& other)
|
||||||
|
: data(other.data)
|
||||||
|
, capacity(other.capacity)
|
||||||
|
, count(other.count)
|
||||||
|
, empty_key(other.empty_key)
|
||||||
|
{
|
||||||
|
other.data = nullptr;
|
||||||
|
other.capacity = 0;
|
||||||
|
other.count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
DenseHashTable& operator=(DenseHashTable&& other)
|
||||||
|
{
|
||||||
|
if (this != &other)
|
||||||
|
{
|
||||||
|
if (data)
|
||||||
|
destroy();
|
||||||
|
|
||||||
|
data = other.data;
|
||||||
|
capacity = other.capacity;
|
||||||
|
count = other.count;
|
||||||
|
empty_key = other.empty_key;
|
||||||
|
|
||||||
|
other.data = nullptr;
|
||||||
|
other.capacity = 0;
|
||||||
|
other.count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
DenseHashTable& operator=(const DenseHashTable& other)
|
||||||
|
{
|
||||||
|
if (this != &other)
|
||||||
|
{
|
||||||
|
DenseHashTable copy(other);
|
||||||
|
*this = std::move(copy);
|
||||||
|
}
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear()
|
||||||
|
{
|
||||||
|
if (count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (capacity > 32)
|
||||||
|
{
|
||||||
|
destroy();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ItemInterface::destroy(data, capacity);
|
||||||
|
ItemInterface::fill(data, capacity, empty_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void destroy()
|
||||||
|
{
|
||||||
|
ItemInterface::destroy(data, capacity);
|
||||||
|
|
||||||
|
::operator delete(data);
|
||||||
|
data = nullptr;
|
||||||
|
|
||||||
|
capacity = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Item* insert_unsafe(const Key& key)
|
||||||
|
{
|
||||||
|
// It is invalid to insert empty_key into the table since it acts as a "entry does not exist" marker
|
||||||
|
LUAU_ASSERT(!eq(key, empty_key));
|
||||||
|
|
||||||
|
size_t hashmod = capacity - 1;
|
||||||
|
size_t bucket = hasher(key) & hashmod;
|
||||||
|
|
||||||
|
for (size_t probe = 0; probe <= hashmod; ++probe)
|
||||||
|
{
|
||||||
|
Item& probe_item = data[bucket];
|
||||||
|
|
||||||
|
// Element does not exist, insert here
|
||||||
|
if (eq(ItemInterface::getKey(probe_item), empty_key))
|
||||||
|
{
|
||||||
|
ItemInterface::setKey(probe_item, key);
|
||||||
|
count++;
|
||||||
|
return &probe_item;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Element already exists
|
||||||
|
if (eq(ItemInterface::getKey(probe_item), key))
|
||||||
|
{
|
||||||
|
return &probe_item;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash collision, quadratic probing
|
||||||
|
bucket = (bucket + probe + 1) & hashmod;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash table is full - this should not happen
|
||||||
|
LUAU_ASSERT(false);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Item* find(const Key& key) const
|
||||||
|
{
|
||||||
|
if (count == 0)
|
||||||
|
return 0;
|
||||||
|
if (eq(key, empty_key))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
size_t hashmod = capacity - 1;
|
||||||
|
size_t bucket = hasher(key) & hashmod;
|
||||||
|
|
||||||
|
for (size_t probe = 0; probe <= hashmod; ++probe)
|
||||||
|
{
|
||||||
|
const Item& probe_item = data[bucket];
|
||||||
|
|
||||||
|
// Element exists
|
||||||
|
if (eq(ItemInterface::getKey(probe_item), key))
|
||||||
|
return &probe_item;
|
||||||
|
|
||||||
|
// Element does not exist
|
||||||
|
if (eq(ItemInterface::getKey(probe_item), empty_key))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
// Hash collision, quadratic probing
|
||||||
|
bucket = (bucket + probe + 1) & hashmod;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash table is full - this should not happen
|
||||||
|
LUAU_ASSERT(false);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rehash()
|
||||||
|
{
|
||||||
|
size_t newsize = capacity == 0 ? 16 : capacity * 2;
|
||||||
|
|
||||||
|
DenseHashTable newtable(empty_key, newsize);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < capacity; ++i)
|
||||||
|
{
|
||||||
|
const Key& key = ItemInterface::getKey(data[i]);
|
||||||
|
|
||||||
|
if (!eq(key, empty_key))
|
||||||
|
{
|
||||||
|
Item* item = newtable.insert_unsafe(key);
|
||||||
|
*item = std::move(data[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LUAU_ASSERT(count == newtable.count);
|
||||||
|
|
||||||
|
std::swap(data, newtable.data);
|
||||||
|
std::swap(capacity, newtable.capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
void rehash_if_full(const Key& key)
|
||||||
|
{
|
||||||
|
if (count >= capacity * 3 / 4 && !find(key))
|
||||||
|
{
|
||||||
|
rehash();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const_iterator begin() const
|
||||||
|
{
|
||||||
|
size_t start = 0;
|
||||||
|
|
||||||
|
while (start < capacity && eq(ItemInterface::getKey(data[start]), empty_key))
|
||||||
|
start++;
|
||||||
|
|
||||||
|
return const_iterator(this, start);
|
||||||
|
}
|
||||||
|
|
||||||
|
const_iterator end() const
|
||||||
|
{
|
||||||
|
return const_iterator(this, capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator begin()
|
||||||
|
{
|
||||||
|
size_t start = 0;
|
||||||
|
|
||||||
|
while (start < capacity && eq(ItemInterface::getKey(data[start]), empty_key))
|
||||||
|
start++;
|
||||||
|
|
||||||
|
return iterator(this, start);
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator end()
|
||||||
|
{
|
||||||
|
return iterator(this, capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t size() const
|
||||||
|
{
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
class const_iterator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
const_iterator()
|
||||||
|
: set(0)
|
||||||
|
, index(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
const_iterator(const DenseHashTable<Key, Item, MutableItem, ItemInterface, Hash, Eq>* set, size_t index)
|
||||||
|
: set(set)
|
||||||
|
, index(index)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
const Item& operator*() const
|
||||||
|
{
|
||||||
|
return set->data[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
const Item* operator->() const
|
||||||
|
{
|
||||||
|
return &set->data[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const const_iterator& other) const
|
||||||
|
{
|
||||||
|
return set == other.set && index == other.index;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator!=(const const_iterator& other) const
|
||||||
|
{
|
||||||
|
return set != other.set || index != other.index;
|
||||||
|
}
|
||||||
|
|
||||||
|
const_iterator& operator++()
|
||||||
|
{
|
||||||
|
size_t size = set->capacity;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
index++;
|
||||||
|
} while (index < size && set->eq(ItemInterface::getKey(set->data[index]), set->empty_key));
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
const_iterator operator++(int)
|
||||||
|
{
|
||||||
|
const_iterator res = *this;
|
||||||
|
++*this;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const DenseHashTable<Key, Item, MutableItem, ItemInterface, Hash, Eq>* set;
|
||||||
|
size_t index;
|
||||||
|
};
|
||||||
|
|
||||||
|
class iterator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
iterator()
|
||||||
|
: set(0)
|
||||||
|
, index(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator(DenseHashTable<Key, Item, MutableItem, ItemInterface, Hash, Eq>* set, size_t index)
|
||||||
|
: set(set)
|
||||||
|
, index(index)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
MutableItem& operator*() const
|
||||||
|
{
|
||||||
|
return *reinterpret_cast<MutableItem*>(&set->data[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
MutableItem* operator->() const
|
||||||
|
{
|
||||||
|
return reinterpret_cast<MutableItem*>(&set->data[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const iterator& other) const
|
||||||
|
{
|
||||||
|
return set == other.set && index == other.index;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator!=(const iterator& other) const
|
||||||
|
{
|
||||||
|
return set != other.set || index != other.index;
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator& operator++()
|
||||||
|
{
|
||||||
|
size_t size = set->capacity;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
index++;
|
||||||
|
} while (index < size && set->eq(ItemInterface::getKey(set->data[index]), set->empty_key));
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator operator++(int)
|
||||||
|
{
|
||||||
|
iterator res = *this;
|
||||||
|
++*this;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
DenseHashTable<Key, Item, MutableItem, ItemInterface, Hash, Eq>* set;
|
||||||
|
size_t index;
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
Item* data;
|
||||||
|
size_t capacity;
|
||||||
|
size_t count;
|
||||||
|
Key empty_key;
|
||||||
|
Hash hasher;
|
||||||
|
Eq eq;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename Key>
|
||||||
|
struct ItemInterfaceSet
|
||||||
|
{
|
||||||
|
static const Key& getKey(const Key& item)
|
||||||
|
{
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void setKey(Key& item, const Key& key)
|
||||||
|
{
|
||||||
|
item = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fill(Key* data, size_t count, const Key& key)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < count; ++i)
|
||||||
|
new (&data[i]) Key(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void destroy(Key* data, size_t count)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < count; ++i)
|
||||||
|
data[i].~Key();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename Key, typename Value>
|
||||||
|
struct ItemInterfaceMap
|
||||||
|
{
|
||||||
|
static const Key& getKey(const std::pair<Key, Value>& item)
|
||||||
|
{
|
||||||
|
return item.first;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void setKey(std::pair<Key, Value>& item, const Key& key)
|
||||||
|
{
|
||||||
|
item.first = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fill(std::pair<Key, Value>* data, size_t count, const Key& key)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < count; ++i)
|
||||||
|
{
|
||||||
|
new (&data[i].first) Key(key);
|
||||||
|
new (&data[i].second) Value();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void destroy(std::pair<Key, Value>* data, size_t count)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < count; ++i)
|
||||||
|
{
|
||||||
|
data[i].first.~Key();
|
||||||
|
data[i].second.~Value();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
// This is a faster alternative of unordered_set, but it does not implement the same interface (i.e. it does not support erasing)
|
||||||
|
template<typename Key, typename Hash = detail::DenseHashDefault<Key>, typename Eq = std::equal_to<Key>>
|
||||||
|
class DenseHashSet
|
||||||
|
{
|
||||||
|
typedef detail::DenseHashTable<Key, Key, Key, detail::ItemInterfaceSet<Key>, Hash, Eq> Impl;
|
||||||
|
Impl impl;
|
||||||
|
|
||||||
|
public:
|
||||||
|
typedef typename Impl::const_iterator const_iterator;
|
||||||
|
typedef typename Impl::iterator iterator;
|
||||||
|
|
||||||
|
DenseHashSet(const Key& empty_key, size_t buckets = 0)
|
||||||
|
: impl(empty_key, buckets)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear()
|
||||||
|
{
|
||||||
|
impl.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
const Key& insert(const Key& key)
|
||||||
|
{
|
||||||
|
impl.rehash_if_full(key);
|
||||||
|
return *impl.insert_unsafe(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Key* find(const Key& key) const
|
||||||
|
{
|
||||||
|
return impl.find(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool contains(const Key& key) const
|
||||||
|
{
|
||||||
|
return impl.find(key) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t size() const
|
||||||
|
{
|
||||||
|
return impl.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool empty() const
|
||||||
|
{
|
||||||
|
return impl.size() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const_iterator begin() const
|
||||||
|
{
|
||||||
|
return impl.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
const_iterator end() const
|
||||||
|
{
|
||||||
|
return impl.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator begin()
|
||||||
|
{
|
||||||
|
return impl.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator end()
|
||||||
|
{
|
||||||
|
return impl.end();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// This is a faster alternative of unordered_map, but it does not implement the same interface (i.e. it does not support erasing and has
|
||||||
|
// contains() instead of find())
|
||||||
|
template<typename Key, typename Value, typename Hash = detail::DenseHashDefault<Key>, typename Eq = std::equal_to<Key>>
|
||||||
|
class DenseHashMap
|
||||||
|
{
|
||||||
|
typedef detail::DenseHashTable<Key, std::pair<Key, Value>, std::pair<const Key, Value>, detail::ItemInterfaceMap<Key, Value>, Hash, Eq> Impl;
|
||||||
|
Impl impl;
|
||||||
|
|
||||||
|
public:
|
||||||
|
typedef typename Impl::const_iterator const_iterator;
|
||||||
|
typedef typename Impl::iterator iterator;
|
||||||
|
|
||||||
|
DenseHashMap(const Key& empty_key, size_t buckets = 0)
|
||||||
|
: impl(empty_key, buckets)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear()
|
||||||
|
{
|
||||||
|
impl.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: this reference is invalidated by any insert operation (i.e. operator[])
|
||||||
|
Value& operator[](const Key& key)
|
||||||
|
{
|
||||||
|
impl.rehash_if_full(key);
|
||||||
|
return impl.insert_unsafe(key)->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: this pointer is invalidated by any insert operation (i.e. operator[])
|
||||||
|
const Value* find(const Key& key) const
|
||||||
|
{
|
||||||
|
const std::pair<Key, Value>* result = impl.find(key);
|
||||||
|
|
||||||
|
return result ? &result->second : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: this pointer is invalidated by any insert operation (i.e. operator[])
|
||||||
|
Value* find(const Key& key)
|
||||||
|
{
|
||||||
|
const std::pair<Key, Value>* result = impl.find(key);
|
||||||
|
|
||||||
|
return result ? const_cast<Value*>(&result->second) : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool contains(const Key& key) const
|
||||||
|
{
|
||||||
|
return impl.find(key) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t size() const
|
||||||
|
{
|
||||||
|
return impl.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool empty() const
|
||||||
|
{
|
||||||
|
return impl.size() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const_iterator begin() const
|
||||||
|
{
|
||||||
|
return impl.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
const_iterator end() const
|
||||||
|
{
|
||||||
|
return impl.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator begin()
|
||||||
|
{
|
||||||
|
return impl.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator end()
|
||||||
|
{
|
||||||
|
return impl.end();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Luau
|
|
@ -0,0 +1,28 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
namespace Luau
|
||||||
|
{
|
||||||
|
|
||||||
|
inline bool isFlagExperimental(const char* flag)
|
||||||
|
{
|
||||||
|
// Flags in this list are disabled by default in various command-line tools. They may have behavior that is not fully final,
|
||||||
|
// or critical bugs that are found after the code has been submitted.
|
||||||
|
static const char* const kList[] = {
|
||||||
|
"LuauInstantiateInSubtyping", // requires some fixes to lua-apps code
|
||||||
|
"LuauTypecheckTypeguards", // requires some fixes to lua-apps code (CLI-67030)
|
||||||
|
"LuauTinyControlFlowAnalysis", // waiting for updates to packages depended by internal builtin plugins
|
||||||
|
// makes sure we always have at least one entry
|
||||||
|
nullptr,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const char* item : kList)
|
||||||
|
if (item && strcmp(item, flag) == 0)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Luau
|
|
@ -0,0 +1,278 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Luau/Bytecode.h"
|
||||||
|
#include "Luau/DenseHash.h"
|
||||||
|
#include "Luau/StringUtils.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace Luau
|
||||||
|
{
|
||||||
|
|
||||||
|
class BytecodeEncoder
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~BytecodeEncoder() {}
|
||||||
|
|
||||||
|
virtual uint8_t encodeOp(uint8_t op) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class BytecodeBuilder
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// BytecodeBuilder does *not* copy the data passed via StringRef; instead, it keeps the ref around until finalize()
|
||||||
|
// Please be careful with the lifetime of the data that's being passed because of this.
|
||||||
|
// The safe and correct pattern is to only build StringRefs out of pieces of AST (AstName or AstArray<>) that are backed by AstAllocator.
|
||||||
|
// Note that you must finalize() the builder before the Allocator backing the Ast is destroyed.
|
||||||
|
struct StringRef
|
||||||
|
{
|
||||||
|
// To construct a StringRef, use sref() from Compiler.cpp.
|
||||||
|
const char* data = nullptr;
|
||||||
|
size_t length = 0;
|
||||||
|
|
||||||
|
bool operator==(const StringRef& other) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TableShape
|
||||||
|
{
|
||||||
|
static const unsigned int kMaxLength = 32;
|
||||||
|
|
||||||
|
int32_t keys[kMaxLength];
|
||||||
|
unsigned int length = 0;
|
||||||
|
|
||||||
|
bool operator==(const TableShape& other) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
BytecodeBuilder(BytecodeEncoder* encoder = 0);
|
||||||
|
|
||||||
|
uint32_t beginFunction(uint8_t numparams, bool isvararg = false);
|
||||||
|
void endFunction(uint8_t maxstacksize, uint8_t numupvalues);
|
||||||
|
|
||||||
|
void setMainFunction(uint32_t fid);
|
||||||
|
|
||||||
|
int32_t addConstantNil();
|
||||||
|
int32_t addConstantBoolean(bool value);
|
||||||
|
int32_t addConstantNumber(double value);
|
||||||
|
int32_t addConstantString(StringRef value);
|
||||||
|
int32_t addImport(uint32_t iid);
|
||||||
|
int32_t addConstantTable(const TableShape& shape);
|
||||||
|
int32_t addConstantClosure(uint32_t fid);
|
||||||
|
|
||||||
|
int16_t addChildFunction(uint32_t fid);
|
||||||
|
|
||||||
|
void emitABC(LuauOpcode op, uint8_t a, uint8_t b, uint8_t c);
|
||||||
|
void emitAD(LuauOpcode op, uint8_t a, int16_t d);
|
||||||
|
void emitE(LuauOpcode op, int32_t e);
|
||||||
|
void emitAux(uint32_t aux);
|
||||||
|
|
||||||
|
size_t emitLabel();
|
||||||
|
|
||||||
|
[[nodiscard]] bool patchJumpD(size_t jumpLabel, size_t targetLabel);
|
||||||
|
[[nodiscard]] bool patchSkipC(size_t jumpLabel, size_t targetLabel);
|
||||||
|
|
||||||
|
void foldJumps();
|
||||||
|
void expandJumps();
|
||||||
|
|
||||||
|
void setDebugFunctionName(StringRef name);
|
||||||
|
void setDebugFunctionLineDefined(int line);
|
||||||
|
void setDebugLine(int line);
|
||||||
|
void pushDebugLocal(StringRef name, uint8_t reg, uint32_t startpc, uint32_t endpc);
|
||||||
|
void pushDebugUpval(StringRef name);
|
||||||
|
uint32_t getDebugPC() const;
|
||||||
|
|
||||||
|
void addDebugRemark(const char* format, ...) LUAU_PRINTF_ATTR(2, 3);
|
||||||
|
|
||||||
|
void finalize();
|
||||||
|
|
||||||
|
enum DumpFlags
|
||||||
|
{
|
||||||
|
Dump_Code = 1 << 0,
|
||||||
|
Dump_Lines = 1 << 1,
|
||||||
|
Dump_Source = 1 << 2,
|
||||||
|
Dump_Locals = 1 << 3,
|
||||||
|
Dump_Remarks = 1 << 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
void setDumpFlags(uint32_t flags)
|
||||||
|
{
|
||||||
|
dumpFlags = flags;
|
||||||
|
dumpFunctionPtr = &BytecodeBuilder::dumpCurrentFunction;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setDumpSource(const std::string& source);
|
||||||
|
|
||||||
|
bool needsDebugRemarks() const
|
||||||
|
{
|
||||||
|
return (dumpFlags & Dump_Remarks) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& getBytecode() const
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(!bytecode.empty()); // did you forget to call finalize?
|
||||||
|
return bytecode;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string dumpFunction(uint32_t id) const;
|
||||||
|
std::string dumpEverything() const;
|
||||||
|
std::string dumpSourceRemarks() const;
|
||||||
|
|
||||||
|
void annotateInstruction(std::string& result, uint32_t fid, uint32_t instpos) const;
|
||||||
|
|
||||||
|
static uint32_t getImportId(int32_t id0);
|
||||||
|
static uint32_t getImportId(int32_t id0, int32_t id1);
|
||||||
|
static uint32_t getImportId(int32_t id0, int32_t id1, int32_t id2);
|
||||||
|
|
||||||
|
static int decomposeImportId(uint32_t ids, int32_t& id0, int32_t& id1, int32_t& id2);
|
||||||
|
|
||||||
|
static uint32_t getStringHash(StringRef key);
|
||||||
|
|
||||||
|
static std::string getError(const std::string& message);
|
||||||
|
|
||||||
|
static uint8_t getVersion();
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Constant
|
||||||
|
{
|
||||||
|
enum Type
|
||||||
|
{
|
||||||
|
Type_Nil,
|
||||||
|
Type_Boolean,
|
||||||
|
Type_Number,
|
||||||
|
Type_String,
|
||||||
|
Type_Import,
|
||||||
|
Type_Table,
|
||||||
|
Type_Closure,
|
||||||
|
};
|
||||||
|
|
||||||
|
Type type;
|
||||||
|
union
|
||||||
|
{
|
||||||
|
bool valueBoolean;
|
||||||
|
double valueNumber;
|
||||||
|
unsigned int valueString; // index into string table
|
||||||
|
uint32_t valueImport; // 10-10-10-2 encoded import id
|
||||||
|
uint32_t valueTable; // index into tableShapes[]
|
||||||
|
uint32_t valueClosure; // index of function in global list
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ConstantKey
|
||||||
|
{
|
||||||
|
Constant::Type type;
|
||||||
|
// Note: this stores value* from Constant; when type is Number_Double, this stores the same bits as double does but in uint64_t.
|
||||||
|
uint64_t value;
|
||||||
|
|
||||||
|
bool operator==(const ConstantKey& key) const
|
||||||
|
{
|
||||||
|
return type == key.type && value == key.value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Function
|
||||||
|
{
|
||||||
|
std::string data;
|
||||||
|
|
||||||
|
uint8_t maxstacksize = 0;
|
||||||
|
uint8_t numparams = 0;
|
||||||
|
uint8_t numupvalues = 0;
|
||||||
|
bool isvararg = false;
|
||||||
|
|
||||||
|
unsigned int debugname = 0;
|
||||||
|
int debuglinedefined = 0;
|
||||||
|
|
||||||
|
std::string dump;
|
||||||
|
std::string dumpname;
|
||||||
|
std::vector<int> dumpinstoffs;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DebugLocal
|
||||||
|
{
|
||||||
|
unsigned int name;
|
||||||
|
|
||||||
|
uint8_t reg;
|
||||||
|
uint32_t startpc;
|
||||||
|
uint32_t endpc;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DebugUpval
|
||||||
|
{
|
||||||
|
unsigned int name;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Jump
|
||||||
|
{
|
||||||
|
uint32_t source;
|
||||||
|
uint32_t target;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct StringRefHash
|
||||||
|
{
|
||||||
|
size_t operator()(const StringRef& v) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ConstantKeyHash
|
||||||
|
{
|
||||||
|
size_t operator()(const ConstantKey& key) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TableShapeHash
|
||||||
|
{
|
||||||
|
size_t operator()(const TableShape& v) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<Function> functions;
|
||||||
|
uint32_t currentFunction = ~0u;
|
||||||
|
uint32_t mainFunction = ~0u;
|
||||||
|
|
||||||
|
std::vector<uint32_t> insns;
|
||||||
|
std::vector<int> lines;
|
||||||
|
std::vector<Constant> constants;
|
||||||
|
std::vector<uint32_t> protos;
|
||||||
|
std::vector<Jump> jumps;
|
||||||
|
|
||||||
|
std::vector<TableShape> tableShapes;
|
||||||
|
|
||||||
|
bool hasLongJumps = false;
|
||||||
|
|
||||||
|
DenseHashMap<ConstantKey, int32_t, ConstantKeyHash> constantMap;
|
||||||
|
DenseHashMap<TableShape, int32_t, TableShapeHash> tableShapeMap;
|
||||||
|
DenseHashMap<uint32_t, int16_t> protoMap;
|
||||||
|
|
||||||
|
int debugLine = 0;
|
||||||
|
|
||||||
|
std::vector<DebugLocal> debugLocals;
|
||||||
|
std::vector<DebugUpval> debugUpvals;
|
||||||
|
|
||||||
|
DenseHashMap<StringRef, unsigned int, StringRefHash> stringTable;
|
||||||
|
std::vector<StringRef> debugStrings;
|
||||||
|
|
||||||
|
std::vector<std::pair<uint32_t, uint32_t>> debugRemarks;
|
||||||
|
std::string debugRemarkBuffer;
|
||||||
|
|
||||||
|
BytecodeEncoder* encoder = nullptr;
|
||||||
|
std::string bytecode;
|
||||||
|
|
||||||
|
uint32_t dumpFlags = 0;
|
||||||
|
std::vector<std::string> dumpSource;
|
||||||
|
std::vector<std::pair<int, std::string>> dumpRemarks;
|
||||||
|
|
||||||
|
std::string (BytecodeBuilder::*dumpFunctionPtr)(std::vector<int>&) const = nullptr;
|
||||||
|
|
||||||
|
void validate() const;
|
||||||
|
void validateInstructions() const;
|
||||||
|
void validateVariadic() const;
|
||||||
|
|
||||||
|
std::string dumpCurrentFunction(std::vector<int>& dumpinstoffs) const;
|
||||||
|
void dumpConstant(std::string& result, int k) const;
|
||||||
|
void dumpInstruction(const uint32_t* opcode, std::string& output, int targetLabel) const;
|
||||||
|
|
||||||
|
void writeFunction(std::string& ss, uint32_t id) const;
|
||||||
|
void writeLineInfo(std::string& ss) const;
|
||||||
|
void writeStringTable(std::string& ss) const;
|
||||||
|
|
||||||
|
int32_t addConstant(const ConstantKey& key, const Constant& value);
|
||||||
|
unsigned int addStringTableEntry(StringRef value);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Luau
|
|
@ -0,0 +1,68 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Luau/ParseOptions.h"
|
||||||
|
#include "Luau/Location.h"
|
||||||
|
#include "Luau/StringUtils.h"
|
||||||
|
#include "Luau/Common.h"
|
||||||
|
|
||||||
|
namespace Luau
|
||||||
|
{
|
||||||
|
class AstNameTable;
|
||||||
|
struct ParseResult;
|
||||||
|
class BytecodeBuilder;
|
||||||
|
class BytecodeEncoder;
|
||||||
|
|
||||||
|
// Note: this structure is duplicated in luacode.h, don't forget to change these in sync!
|
||||||
|
struct CompileOptions
|
||||||
|
{
|
||||||
|
// 0 - no optimization
|
||||||
|
// 1 - baseline optimization level that doesn't prevent debuggability
|
||||||
|
// 2 - includes optimizations that harm debuggability such as inlining
|
||||||
|
int optimizationLevel = 1;
|
||||||
|
|
||||||
|
// 0 - no debugging support
|
||||||
|
// 1 - line info & function names only; sufficient for backtraces
|
||||||
|
// 2 - full debug info with local & upvalue names; necessary for debugger
|
||||||
|
int debugLevel = 1;
|
||||||
|
|
||||||
|
// 0 - no code coverage support
|
||||||
|
// 1 - statement coverage
|
||||||
|
// 2 - statement and expression coverage (verbose)
|
||||||
|
int coverageLevel = 0;
|
||||||
|
|
||||||
|
// global builtin to construct vectors; disabled by default
|
||||||
|
const char* vectorLib = nullptr;
|
||||||
|
const char* vectorCtor = nullptr;
|
||||||
|
|
||||||
|
// null-terminated array of globals that are mutable; disables the import optimization for fields accessed through these
|
||||||
|
const char** mutableGlobals = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CompileError : public std::exception
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CompileError(const Location& location, const std::string& message);
|
||||||
|
|
||||||
|
virtual ~CompileError() throw();
|
||||||
|
|
||||||
|
virtual const char* what() const throw();
|
||||||
|
|
||||||
|
const Location& getLocation() const;
|
||||||
|
|
||||||
|
static LUAU_NORETURN void raise(const Location& location, const char* format, ...) LUAU_PRINTF_ATTR(2, 3);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Location location;
|
||||||
|
std::string message;
|
||||||
|
};
|
||||||
|
|
||||||
|
// compiles bytecode into bytecode builder using either a pre-parsed AST or parsing it from source; throws on errors
|
||||||
|
void compileOrThrow(BytecodeBuilder& bytecode, const ParseResult& parseResult, const AstNameTable& names, const CompileOptions& options = {});
|
||||||
|
void compileOrThrow(BytecodeBuilder& bytecode, const std::string& source, const CompileOptions& options = {}, const ParseOptions& parseOptions = {});
|
||||||
|
|
||||||
|
// compiles bytecode into a bytecode blob, that either contains the valid bytecode or an encoded error that luau_load can decode
|
||||||
|
std::string compile(
|
||||||
|
const std::string& source, const CompileOptions& options = {}, const ParseOptions& parseOptions = {}, BytecodeEncoder* encoder = nullptr);
|
||||||
|
|
||||||
|
} // namespace Luau
|
|
@ -0,0 +1,39 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
// Can be used to reconfigure visibility/exports for public APIs
|
||||||
|
#ifndef LUACODE_API
|
||||||
|
#define LUACODE_API extern
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct lua_CompileOptions lua_CompileOptions;
|
||||||
|
|
||||||
|
struct lua_CompileOptions
|
||||||
|
{
|
||||||
|
// 0 - no optimization
|
||||||
|
// 1 - baseline optimization level that doesn't prevent debuggability
|
||||||
|
// 2 - includes optimizations that harm debuggability such as inlining
|
||||||
|
int optimizationLevel; // default=1
|
||||||
|
|
||||||
|
// 0 - no debugging support
|
||||||
|
// 1 - line info & function names only; sufficient for backtraces
|
||||||
|
// 2 - full debug info with local & upvalue names; necessary for debugger
|
||||||
|
int debugLevel; // default=1
|
||||||
|
|
||||||
|
// 0 - no code coverage support
|
||||||
|
// 1 - statement coverage
|
||||||
|
// 2 - statement and expression coverage (verbose)
|
||||||
|
int coverageLevel; // default=0
|
||||||
|
|
||||||
|
// global builtin to construct vectors; disabled by default
|
||||||
|
const char* vectorLib;
|
||||||
|
const char* vectorCtor;
|
||||||
|
|
||||||
|
// null-terminated array of globals that are mutable; disables the import optimization for fields accessed through these
|
||||||
|
const char** mutableGlobals;
|
||||||
|
};
|
||||||
|
|
||||||
|
// compile source to bytecode; when source compilation fails, the resulting bytecode contains the encoded error. use free() to destroy
|
||||||
|
LUACODE_API char* luau_compile(const char* source, size_t size, lua_CompileOptions* options, size_t* outsize);
|
|
@ -0,0 +1,464 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
#include "BuiltinFolding.h"
|
||||||
|
|
||||||
|
#include "Luau/Bytecode.h"
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
namespace Luau
|
||||||
|
{
|
||||||
|
namespace Compile
|
||||||
|
{
|
||||||
|
|
||||||
|
const double kRadDeg = 3.14159265358979323846 / 180.0;
|
||||||
|
|
||||||
|
static Constant cvar()
|
||||||
|
{
|
||||||
|
return Constant();
|
||||||
|
}
|
||||||
|
|
||||||
|
static Constant cbool(bool v)
|
||||||
|
{
|
||||||
|
Constant res = {Constant::Type_Boolean};
|
||||||
|
res.valueBoolean = v;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Constant cnum(double v)
|
||||||
|
{
|
||||||
|
Constant res = {Constant::Type_Number};
|
||||||
|
res.valueNumber = v;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Constant cstring(const char* v)
|
||||||
|
{
|
||||||
|
Constant res = {Constant::Type_String};
|
||||||
|
res.stringLength = unsigned(strlen(v));
|
||||||
|
res.valueString = v;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Constant ctype(const Constant& c)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(c.type != Constant::Type_Unknown);
|
||||||
|
|
||||||
|
switch (c.type)
|
||||||
|
{
|
||||||
|
case Constant::Type_Nil:
|
||||||
|
return cstring("nil");
|
||||||
|
|
||||||
|
case Constant::Type_Boolean:
|
||||||
|
return cstring("boolean");
|
||||||
|
|
||||||
|
case Constant::Type_Number:
|
||||||
|
return cstring("number");
|
||||||
|
|
||||||
|
case Constant::Type_String:
|
||||||
|
return cstring("string");
|
||||||
|
|
||||||
|
default:
|
||||||
|
LUAU_ASSERT(!"Unsupported constant type");
|
||||||
|
return cvar();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t bit32(double v)
|
||||||
|
{
|
||||||
|
// convert through signed 64-bit integer to match runtime behavior and gracefully truncate negative integers
|
||||||
|
return uint32_t(int64_t(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
Constant foldBuiltin(int bfid, const Constant* args, size_t count)
|
||||||
|
{
|
||||||
|
switch (bfid)
|
||||||
|
{
|
||||||
|
case LBF_MATH_ABS:
|
||||||
|
if (count == 1 && args[0].type == Constant::Type_Number)
|
||||||
|
return cnum(fabs(args[0].valueNumber));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LBF_MATH_ACOS:
|
||||||
|
if (count == 1 && args[0].type == Constant::Type_Number)
|
||||||
|
return cnum(acos(args[0].valueNumber));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LBF_MATH_ASIN:
|
||||||
|
if (count == 1 && args[0].type == Constant::Type_Number)
|
||||||
|
return cnum(asin(args[0].valueNumber));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LBF_MATH_ATAN2:
|
||||||
|
if (count == 2 && args[0].type == Constant::Type_Number && args[1].type == Constant::Type_Number)
|
||||||
|
return cnum(atan2(args[0].valueNumber, args[1].valueNumber));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LBF_MATH_ATAN:
|
||||||
|
if (count == 1 && args[0].type == Constant::Type_Number)
|
||||||
|
return cnum(atan(args[0].valueNumber));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LBF_MATH_CEIL:
|
||||||
|
if (count == 1 && args[0].type == Constant::Type_Number)
|
||||||
|
return cnum(ceil(args[0].valueNumber));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LBF_MATH_COSH:
|
||||||
|
if (count == 1 && args[0].type == Constant::Type_Number)
|
||||||
|
return cnum(cosh(args[0].valueNumber));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LBF_MATH_COS:
|
||||||
|
if (count == 1 && args[0].type == Constant::Type_Number)
|
||||||
|
return cnum(cos(args[0].valueNumber));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LBF_MATH_DEG:
|
||||||
|
if (count == 1 && args[0].type == Constant::Type_Number)
|
||||||
|
return cnum(args[0].valueNumber / kRadDeg);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LBF_MATH_EXP:
|
||||||
|
if (count == 1 && args[0].type == Constant::Type_Number)
|
||||||
|
return cnum(exp(args[0].valueNumber));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LBF_MATH_FLOOR:
|
||||||
|
if (count == 1 && args[0].type == Constant::Type_Number)
|
||||||
|
return cnum(floor(args[0].valueNumber));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LBF_MATH_FMOD:
|
||||||
|
if (count == 2 && args[0].type == Constant::Type_Number && args[1].type == Constant::Type_Number)
|
||||||
|
return cnum(fmod(args[0].valueNumber, args[1].valueNumber));
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Note: FREXP isn't folded since it returns multiple values
|
||||||
|
|
||||||
|
case LBF_MATH_LDEXP:
|
||||||
|
if (count == 2 && args[0].type == Constant::Type_Number && args[1].type == Constant::Type_Number)
|
||||||
|
return cnum(ldexp(args[0].valueNumber, int(args[1].valueNumber)));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LBF_MATH_LOG10:
|
||||||
|
if (count == 1 && args[0].type == Constant::Type_Number)
|
||||||
|
return cnum(log10(args[0].valueNumber));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LBF_MATH_LOG:
|
||||||
|
if (count == 1 && args[0].type == Constant::Type_Number)
|
||||||
|
return cnum(log(args[0].valueNumber));
|
||||||
|
else if (count == 2 && args[0].type == Constant::Type_Number && args[1].type == Constant::Type_Number)
|
||||||
|
{
|
||||||
|
if (args[1].valueNumber == 2.0)
|
||||||
|
return cnum(log2(args[0].valueNumber));
|
||||||
|
else if (args[1].valueNumber == 10.0)
|
||||||
|
return cnum(log10(args[0].valueNumber));
|
||||||
|
else
|
||||||
|
return cnum(log(args[0].valueNumber) / log(args[1].valueNumber));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LBF_MATH_MAX:
|
||||||
|
if (count >= 1 && args[0].type == Constant::Type_Number)
|
||||||
|
{
|
||||||
|
double r = args[0].valueNumber;
|
||||||
|
|
||||||
|
for (size_t i = 1; i < count; ++i)
|
||||||
|
{
|
||||||
|
if (args[i].type != Constant::Type_Number)
|
||||||
|
return cvar();
|
||||||
|
|
||||||
|
double a = args[i].valueNumber;
|
||||||
|
|
||||||
|
r = (a > r) ? a : r;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cnum(r);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LBF_MATH_MIN:
|
||||||
|
if (count >= 1 && args[0].type == Constant::Type_Number)
|
||||||
|
{
|
||||||
|
double r = args[0].valueNumber;
|
||||||
|
|
||||||
|
for (size_t i = 1; i < count; ++i)
|
||||||
|
{
|
||||||
|
if (args[i].type != Constant::Type_Number)
|
||||||
|
return cvar();
|
||||||
|
|
||||||
|
double a = args[i].valueNumber;
|
||||||
|
|
||||||
|
r = (a < r) ? a : r;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cnum(r);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Note: MODF isn't folded since it returns multiple values
|
||||||
|
|
||||||
|
case LBF_MATH_POW:
|
||||||
|
if (count == 2 && args[0].type == Constant::Type_Number && args[1].type == Constant::Type_Number)
|
||||||
|
return cnum(pow(args[0].valueNumber, args[1].valueNumber));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LBF_MATH_RAD:
|
||||||
|
if (count == 1 && args[0].type == Constant::Type_Number)
|
||||||
|
return cnum(args[0].valueNumber * kRadDeg);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LBF_MATH_SINH:
|
||||||
|
if (count == 1 && args[0].type == Constant::Type_Number)
|
||||||
|
return cnum(sinh(args[0].valueNumber));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LBF_MATH_SIN:
|
||||||
|
if (count == 1 && args[0].type == Constant::Type_Number)
|
||||||
|
return cnum(sin(args[0].valueNumber));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LBF_MATH_SQRT:
|
||||||
|
if (count == 1 && args[0].type == Constant::Type_Number)
|
||||||
|
return cnum(sqrt(args[0].valueNumber));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LBF_MATH_TANH:
|
||||||
|
if (count == 1 && args[0].type == Constant::Type_Number)
|
||||||
|
return cnum(tanh(args[0].valueNumber));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LBF_MATH_TAN:
|
||||||
|
if (count == 1 && args[0].type == Constant::Type_Number)
|
||||||
|
return cnum(tan(args[0].valueNumber));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LBF_BIT32_ARSHIFT:
|
||||||
|
if (count == 2 && args[0].type == Constant::Type_Number && args[1].type == Constant::Type_Number)
|
||||||
|
{
|
||||||
|
uint32_t u = bit32(args[0].valueNumber);
|
||||||
|
int s = int(args[1].valueNumber);
|
||||||
|
|
||||||
|
if (unsigned(s) < 32)
|
||||||
|
return cnum(double(uint32_t(int32_t(u) >> s)));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LBF_BIT32_BAND:
|
||||||
|
if (count >= 1 && args[0].type == Constant::Type_Number)
|
||||||
|
{
|
||||||
|
uint32_t r = bit32(args[0].valueNumber);
|
||||||
|
|
||||||
|
for (size_t i = 1; i < count; ++i)
|
||||||
|
{
|
||||||
|
if (args[i].type != Constant::Type_Number)
|
||||||
|
return cvar();
|
||||||
|
|
||||||
|
r &= bit32(args[i].valueNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
return cnum(double(r));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LBF_BIT32_BNOT:
|
||||||
|
if (count == 1 && args[0].type == Constant::Type_Number)
|
||||||
|
return cnum(double(uint32_t(~bit32(args[0].valueNumber))));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LBF_BIT32_BOR:
|
||||||
|
if (count >= 1 && args[0].type == Constant::Type_Number)
|
||||||
|
{
|
||||||
|
uint32_t r = bit32(args[0].valueNumber);
|
||||||
|
|
||||||
|
for (size_t i = 1; i < count; ++i)
|
||||||
|
{
|
||||||
|
if (args[i].type != Constant::Type_Number)
|
||||||
|
return cvar();
|
||||||
|
|
||||||
|
r |= bit32(args[i].valueNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
return cnum(double(r));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LBF_BIT32_BXOR:
|
||||||
|
if (count >= 1 && args[0].type == Constant::Type_Number)
|
||||||
|
{
|
||||||
|
uint32_t r = bit32(args[0].valueNumber);
|
||||||
|
|
||||||
|
for (size_t i = 1; i < count; ++i)
|
||||||
|
{
|
||||||
|
if (args[i].type != Constant::Type_Number)
|
||||||
|
return cvar();
|
||||||
|
|
||||||
|
r ^= bit32(args[i].valueNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
return cnum(double(r));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LBF_BIT32_BTEST:
|
||||||
|
if (count >= 1 && args[0].type == Constant::Type_Number)
|
||||||
|
{
|
||||||
|
uint32_t r = bit32(args[0].valueNumber);
|
||||||
|
|
||||||
|
for (size_t i = 1; i < count; ++i)
|
||||||
|
{
|
||||||
|
if (args[i].type != Constant::Type_Number)
|
||||||
|
return cvar();
|
||||||
|
|
||||||
|
r &= bit32(args[i].valueNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
return cbool(r != 0);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LBF_BIT32_EXTRACT:
|
||||||
|
if (count >= 2 && args[0].type == Constant::Type_Number && args[1].type == Constant::Type_Number &&
|
||||||
|
(count == 2 || args[2].type == Constant::Type_Number))
|
||||||
|
{
|
||||||
|
uint32_t u = bit32(args[0].valueNumber);
|
||||||
|
int f = int(args[1].valueNumber);
|
||||||
|
int w = count == 2 ? 1 : int(args[2].valueNumber);
|
||||||
|
|
||||||
|
if (f >= 0 && w > 0 && f + w <= 32)
|
||||||
|
{
|
||||||
|
uint32_t m = ~(0xfffffffeu << (w - 1));
|
||||||
|
|
||||||
|
return cnum(double((u >> f) & m));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LBF_BIT32_LROTATE:
|
||||||
|
if (count == 2 && args[0].type == Constant::Type_Number && args[1].type == Constant::Type_Number)
|
||||||
|
{
|
||||||
|
uint32_t u = bit32(args[0].valueNumber);
|
||||||
|
int s = int(args[1].valueNumber);
|
||||||
|
|
||||||
|
return cnum(double((u << (s & 31)) | (u >> ((32 - s) & 31))));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LBF_BIT32_LSHIFT:
|
||||||
|
if (count == 2 && args[0].type == Constant::Type_Number && args[1].type == Constant::Type_Number)
|
||||||
|
{
|
||||||
|
uint32_t u = bit32(args[0].valueNumber);
|
||||||
|
int s = int(args[1].valueNumber);
|
||||||
|
|
||||||
|
if (unsigned(s) < 32)
|
||||||
|
return cnum(double(u << s));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LBF_BIT32_REPLACE:
|
||||||
|
if (count >= 3 && args[0].type == Constant::Type_Number && args[1].type == Constant::Type_Number && args[2].type == Constant::Type_Number &&
|
||||||
|
(count == 3 || args[3].type == Constant::Type_Number))
|
||||||
|
{
|
||||||
|
uint32_t n = bit32(args[0].valueNumber);
|
||||||
|
uint32_t v = bit32(args[1].valueNumber);
|
||||||
|
int f = int(args[2].valueNumber);
|
||||||
|
int w = count == 3 ? 1 : int(args[3].valueNumber);
|
||||||
|
|
||||||
|
if (f >= 0 && w > 0 && f + w <= 32)
|
||||||
|
{
|
||||||
|
uint32_t m = ~(0xfffffffeu << (w - 1));
|
||||||
|
|
||||||
|
return cnum(double((n & ~(m << f)) | ((v & m) << f)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LBF_BIT32_RROTATE:
|
||||||
|
if (count == 2 && args[0].type == Constant::Type_Number && args[1].type == Constant::Type_Number)
|
||||||
|
{
|
||||||
|
uint32_t u = bit32(args[0].valueNumber);
|
||||||
|
int s = int(args[1].valueNumber);
|
||||||
|
|
||||||
|
return cnum(double((u >> (s & 31)) | (u << ((32 - s) & 31))));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LBF_BIT32_RSHIFT:
|
||||||
|
if (count == 2 && args[0].type == Constant::Type_Number && args[1].type == Constant::Type_Number)
|
||||||
|
{
|
||||||
|
uint32_t u = bit32(args[0].valueNumber);
|
||||||
|
int s = int(args[1].valueNumber);
|
||||||
|
|
||||||
|
if (unsigned(s) < 32)
|
||||||
|
return cnum(double(u >> s));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LBF_TYPE:
|
||||||
|
if (count == 1 && args[0].type != Constant::Type_Unknown)
|
||||||
|
return ctype(args[0]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LBF_STRING_BYTE:
|
||||||
|
if (count == 1 && args[0].type == Constant::Type_String)
|
||||||
|
{
|
||||||
|
if (args[0].stringLength > 0)
|
||||||
|
return cnum(double(uint8_t(args[0].valueString[0])));
|
||||||
|
}
|
||||||
|
else if (count == 2 && args[0].type == Constant::Type_String && args[1].type == Constant::Type_Number)
|
||||||
|
{
|
||||||
|
int i = int(args[1].valueNumber);
|
||||||
|
|
||||||
|
if (i > 0 && unsigned(i) <= args[0].stringLength)
|
||||||
|
return cnum(double(uint8_t(args[0].valueString[i - 1])));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LBF_STRING_LEN:
|
||||||
|
if (count == 1 && args[0].type == Constant::Type_String)
|
||||||
|
return cnum(double(args[0].stringLength));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LBF_TYPEOF:
|
||||||
|
if (count == 1 && args[0].type != Constant::Type_Unknown)
|
||||||
|
return ctype(args[0]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LBF_MATH_CLAMP:
|
||||||
|
if (count == 3 && args[0].type == Constant::Type_Number && args[1].type == Constant::Type_Number && args[2].type == Constant::Type_Number)
|
||||||
|
{
|
||||||
|
double min = args[1].valueNumber;
|
||||||
|
double max = args[2].valueNumber;
|
||||||
|
|
||||||
|
if (min <= max)
|
||||||
|
{
|
||||||
|
double v = args[0].valueNumber;
|
||||||
|
v = v < min ? min : v;
|
||||||
|
v = v > max ? max : v;
|
||||||
|
|
||||||
|
return cnum(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LBF_MATH_SIGN:
|
||||||
|
if (count == 1 && args[0].type == Constant::Type_Number)
|
||||||
|
{
|
||||||
|
double v = args[0].valueNumber;
|
||||||
|
|
||||||
|
return cnum(v > 0.0 ? 1.0 : v < 0.0 ? -1.0 : 0.0);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LBF_MATH_ROUND:
|
||||||
|
if (count == 1 && args[0].type == Constant::Type_Number)
|
||||||
|
return cnum(round(args[0].valueNumber));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cvar();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Compile
|
||||||
|
} // namespace Luau
|
|
@ -0,0 +1,14 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ConstantFolding.h"
|
||||||
|
|
||||||
|
namespace Luau
|
||||||
|
{
|
||||||
|
namespace Compile
|
||||||
|
{
|
||||||
|
|
||||||
|
Constant foldBuiltin(int bfid, const Constant* args, size_t count);
|
||||||
|
|
||||||
|
} // namespace Compile
|
||||||
|
} // namespace Luau
|
|
@ -0,0 +1,401 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
#include "Builtins.h"
|
||||||
|
|
||||||
|
#include "Luau/Bytecode.h"
|
||||||
|
#include "Luau/Compiler.h"
|
||||||
|
|
||||||
|
namespace Luau
|
||||||
|
{
|
||||||
|
namespace Compile
|
||||||
|
{
|
||||||
|
|
||||||
|
Builtin getBuiltin(AstExpr* node, const DenseHashMap<AstName, Global>& globals, const DenseHashMap<AstLocal*, Variable>& variables)
|
||||||
|
{
|
||||||
|
if (AstExprLocal* expr = node->as<AstExprLocal>())
|
||||||
|
{
|
||||||
|
const Variable* v = variables.find(expr->local);
|
||||||
|
|
||||||
|
return v && !v->written && v->init ? getBuiltin(v->init, globals, variables) : Builtin();
|
||||||
|
}
|
||||||
|
else if (AstExprIndexName* expr = node->as<AstExprIndexName>())
|
||||||
|
{
|
||||||
|
if (AstExprGlobal* object = expr->expr->as<AstExprGlobal>())
|
||||||
|
{
|
||||||
|
return getGlobalState(globals, object->name) == Global::Default ? Builtin{object->name, expr->index} : Builtin();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return Builtin();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (AstExprGlobal* expr = node->as<AstExprGlobal>())
|
||||||
|
{
|
||||||
|
return getGlobalState(globals, expr->name) == Global::Default ? Builtin{AstName(), expr->name} : Builtin();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return Builtin();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int getBuiltinFunctionId(const Builtin& builtin, const CompileOptions& options)
|
||||||
|
{
|
||||||
|
if (builtin.isGlobal("assert"))
|
||||||
|
return LBF_ASSERT;
|
||||||
|
|
||||||
|
if (builtin.isGlobal("type"))
|
||||||
|
return LBF_TYPE;
|
||||||
|
|
||||||
|
if (builtin.isGlobal("typeof"))
|
||||||
|
return LBF_TYPEOF;
|
||||||
|
|
||||||
|
if (builtin.isGlobal("rawset"))
|
||||||
|
return LBF_RAWSET;
|
||||||
|
if (builtin.isGlobal("rawget"))
|
||||||
|
return LBF_RAWGET;
|
||||||
|
if (builtin.isGlobal("rawequal"))
|
||||||
|
return LBF_RAWEQUAL;
|
||||||
|
if (builtin.isGlobal("rawlen"))
|
||||||
|
return LBF_RAWLEN;
|
||||||
|
|
||||||
|
if (builtin.isGlobal("unpack"))
|
||||||
|
return LBF_TABLE_UNPACK;
|
||||||
|
|
||||||
|
if (builtin.isGlobal("select"))
|
||||||
|
return LBF_SELECT_VARARG;
|
||||||
|
|
||||||
|
if (builtin.isGlobal("getmetatable"))
|
||||||
|
return LBF_GETMETATABLE;
|
||||||
|
if (builtin.isGlobal("setmetatable"))
|
||||||
|
return LBF_SETMETATABLE;
|
||||||
|
|
||||||
|
if (builtin.object == "math")
|
||||||
|
{
|
||||||
|
if (builtin.method == "abs")
|
||||||
|
return LBF_MATH_ABS;
|
||||||
|
if (builtin.method == "acos")
|
||||||
|
return LBF_MATH_ACOS;
|
||||||
|
if (builtin.method == "asin")
|
||||||
|
return LBF_MATH_ASIN;
|
||||||
|
if (builtin.method == "atan2")
|
||||||
|
return LBF_MATH_ATAN2;
|
||||||
|
if (builtin.method == "atan")
|
||||||
|
return LBF_MATH_ATAN;
|
||||||
|
if (builtin.method == "ceil")
|
||||||
|
return LBF_MATH_CEIL;
|
||||||
|
if (builtin.method == "cosh")
|
||||||
|
return LBF_MATH_COSH;
|
||||||
|
if (builtin.method == "cos")
|
||||||
|
return LBF_MATH_COS;
|
||||||
|
if (builtin.method == "deg")
|
||||||
|
return LBF_MATH_DEG;
|
||||||
|
if (builtin.method == "exp")
|
||||||
|
return LBF_MATH_EXP;
|
||||||
|
if (builtin.method == "floor")
|
||||||
|
return LBF_MATH_FLOOR;
|
||||||
|
if (builtin.method == "fmod")
|
||||||
|
return LBF_MATH_FMOD;
|
||||||
|
if (builtin.method == "frexp")
|
||||||
|
return LBF_MATH_FREXP;
|
||||||
|
if (builtin.method == "ldexp")
|
||||||
|
return LBF_MATH_LDEXP;
|
||||||
|
if (builtin.method == "log10")
|
||||||
|
return LBF_MATH_LOG10;
|
||||||
|
if (builtin.method == "log")
|
||||||
|
return LBF_MATH_LOG;
|
||||||
|
if (builtin.method == "max")
|
||||||
|
return LBF_MATH_MAX;
|
||||||
|
if (builtin.method == "min")
|
||||||
|
return LBF_MATH_MIN;
|
||||||
|
if (builtin.method == "modf")
|
||||||
|
return LBF_MATH_MODF;
|
||||||
|
if (builtin.method == "pow")
|
||||||
|
return LBF_MATH_POW;
|
||||||
|
if (builtin.method == "rad")
|
||||||
|
return LBF_MATH_RAD;
|
||||||
|
if (builtin.method == "sinh")
|
||||||
|
return LBF_MATH_SINH;
|
||||||
|
if (builtin.method == "sin")
|
||||||
|
return LBF_MATH_SIN;
|
||||||
|
if (builtin.method == "sqrt")
|
||||||
|
return LBF_MATH_SQRT;
|
||||||
|
if (builtin.method == "tanh")
|
||||||
|
return LBF_MATH_TANH;
|
||||||
|
if (builtin.method == "tan")
|
||||||
|
return LBF_MATH_TAN;
|
||||||
|
if (builtin.method == "clamp")
|
||||||
|
return LBF_MATH_CLAMP;
|
||||||
|
if (builtin.method == "sign")
|
||||||
|
return LBF_MATH_SIGN;
|
||||||
|
if (builtin.method == "round")
|
||||||
|
return LBF_MATH_ROUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (builtin.object == "bit32")
|
||||||
|
{
|
||||||
|
if (builtin.method == "arshift")
|
||||||
|
return LBF_BIT32_ARSHIFT;
|
||||||
|
if (builtin.method == "band")
|
||||||
|
return LBF_BIT32_BAND;
|
||||||
|
if (builtin.method == "bnot")
|
||||||
|
return LBF_BIT32_BNOT;
|
||||||
|
if (builtin.method == "bor")
|
||||||
|
return LBF_BIT32_BOR;
|
||||||
|
if (builtin.method == "bxor")
|
||||||
|
return LBF_BIT32_BXOR;
|
||||||
|
if (builtin.method == "btest")
|
||||||
|
return LBF_BIT32_BTEST;
|
||||||
|
if (builtin.method == "extract")
|
||||||
|
return LBF_BIT32_EXTRACT;
|
||||||
|
if (builtin.method == "lrotate")
|
||||||
|
return LBF_BIT32_LROTATE;
|
||||||
|
if (builtin.method == "lshift")
|
||||||
|
return LBF_BIT32_LSHIFT;
|
||||||
|
if (builtin.method == "replace")
|
||||||
|
return LBF_BIT32_REPLACE;
|
||||||
|
if (builtin.method == "rrotate")
|
||||||
|
return LBF_BIT32_RROTATE;
|
||||||
|
if (builtin.method == "rshift")
|
||||||
|
return LBF_BIT32_RSHIFT;
|
||||||
|
if (builtin.method == "countlz")
|
||||||
|
return LBF_BIT32_COUNTLZ;
|
||||||
|
if (builtin.method == "countrz")
|
||||||
|
return LBF_BIT32_COUNTRZ;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (builtin.object == "string")
|
||||||
|
{
|
||||||
|
if (builtin.method == "byte")
|
||||||
|
return LBF_STRING_BYTE;
|
||||||
|
if (builtin.method == "char")
|
||||||
|
return LBF_STRING_CHAR;
|
||||||
|
if (builtin.method == "len")
|
||||||
|
return LBF_STRING_LEN;
|
||||||
|
if (builtin.method == "sub")
|
||||||
|
return LBF_STRING_SUB;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (builtin.object == "table")
|
||||||
|
{
|
||||||
|
if (builtin.method == "insert")
|
||||||
|
return LBF_TABLE_INSERT;
|
||||||
|
if (builtin.method == "unpack")
|
||||||
|
return LBF_TABLE_UNPACK;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.vectorCtor)
|
||||||
|
{
|
||||||
|
if (options.vectorLib)
|
||||||
|
{
|
||||||
|
if (builtin.isMethod(options.vectorLib, options.vectorCtor))
|
||||||
|
return LBF_VECTOR;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (builtin.isGlobal(options.vectorCtor))
|
||||||
|
return LBF_VECTOR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct BuiltinVisitor : AstVisitor
|
||||||
|
{
|
||||||
|
DenseHashMap<AstExprCall*, int>& result;
|
||||||
|
|
||||||
|
const DenseHashMap<AstName, Global>& globals;
|
||||||
|
const DenseHashMap<AstLocal*, Variable>& variables;
|
||||||
|
|
||||||
|
const CompileOptions& options;
|
||||||
|
|
||||||
|
BuiltinVisitor(DenseHashMap<AstExprCall*, int>& result, const DenseHashMap<AstName, Global>& globals,
|
||||||
|
const DenseHashMap<AstLocal*, Variable>& variables, const CompileOptions& options)
|
||||||
|
: result(result)
|
||||||
|
, globals(globals)
|
||||||
|
, variables(variables)
|
||||||
|
, options(options)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool visit(AstExprCall* node) override
|
||||||
|
{
|
||||||
|
Builtin builtin = node->self ? Builtin() : getBuiltin(node->func, globals, variables);
|
||||||
|
if (builtin.empty())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
int bfid = getBuiltinFunctionId(builtin, options);
|
||||||
|
|
||||||
|
// getBuiltinFunctionId optimistically assumes all select() calls are builtin but actually the second argument must be a vararg
|
||||||
|
if (bfid == LBF_SELECT_VARARG && !(node->args.size == 2 && node->args.data[1]->is<AstExprVarargs>()))
|
||||||
|
bfid = -1;
|
||||||
|
|
||||||
|
if (bfid >= 0)
|
||||||
|
result[node] = bfid;
|
||||||
|
|
||||||
|
return true; // propagate to nested calls
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void analyzeBuiltins(DenseHashMap<AstExprCall*, int>& result, const DenseHashMap<AstName, Global>& globals,
|
||||||
|
const DenseHashMap<AstLocal*, Variable>& variables, const CompileOptions& options, AstNode* root)
|
||||||
|
{
|
||||||
|
BuiltinVisitor visitor{result, globals, variables, options};
|
||||||
|
root->visit(&visitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
BuiltinInfo getBuiltinInfo(int bfid)
|
||||||
|
{
|
||||||
|
switch (LuauBuiltinFunction(bfid))
|
||||||
|
{
|
||||||
|
case LBF_NONE:
|
||||||
|
return {-1, -1};
|
||||||
|
|
||||||
|
case LBF_ASSERT:
|
||||||
|
return {-1, -1};
|
||||||
|
; // assert() returns all values when first value is truthy
|
||||||
|
|
||||||
|
case LBF_MATH_ABS:
|
||||||
|
case LBF_MATH_ACOS:
|
||||||
|
case LBF_MATH_ASIN:
|
||||||
|
return {1, 1};
|
||||||
|
|
||||||
|
case LBF_MATH_ATAN2:
|
||||||
|
return {2, 1};
|
||||||
|
|
||||||
|
case LBF_MATH_ATAN:
|
||||||
|
case LBF_MATH_CEIL:
|
||||||
|
case LBF_MATH_COSH:
|
||||||
|
case LBF_MATH_COS:
|
||||||
|
case LBF_MATH_DEG:
|
||||||
|
case LBF_MATH_EXP:
|
||||||
|
case LBF_MATH_FLOOR:
|
||||||
|
return {1, 1};
|
||||||
|
|
||||||
|
case LBF_MATH_FMOD:
|
||||||
|
return {2, 1};
|
||||||
|
|
||||||
|
case LBF_MATH_FREXP:
|
||||||
|
return {1, 2};
|
||||||
|
|
||||||
|
case LBF_MATH_LDEXP:
|
||||||
|
return {2, 1};
|
||||||
|
|
||||||
|
case LBF_MATH_LOG10:
|
||||||
|
return {1, 1};
|
||||||
|
|
||||||
|
case LBF_MATH_LOG:
|
||||||
|
return {-1, 1}; // 1 or 2 parameters
|
||||||
|
|
||||||
|
case LBF_MATH_MAX:
|
||||||
|
case LBF_MATH_MIN:
|
||||||
|
return {-1, 1}; // variadic
|
||||||
|
|
||||||
|
case LBF_MATH_MODF:
|
||||||
|
return {1, 2};
|
||||||
|
|
||||||
|
case LBF_MATH_POW:
|
||||||
|
return {2, 1};
|
||||||
|
|
||||||
|
case LBF_MATH_RAD:
|
||||||
|
case LBF_MATH_SINH:
|
||||||
|
case LBF_MATH_SIN:
|
||||||
|
case LBF_MATH_SQRT:
|
||||||
|
case LBF_MATH_TANH:
|
||||||
|
case LBF_MATH_TAN:
|
||||||
|
return {1, 1};
|
||||||
|
|
||||||
|
case LBF_BIT32_ARSHIFT:
|
||||||
|
return {2, 1};
|
||||||
|
|
||||||
|
case LBF_BIT32_BAND:
|
||||||
|
return {-1, 1}; // variadic
|
||||||
|
|
||||||
|
case LBF_BIT32_BNOT:
|
||||||
|
return {1, 1};
|
||||||
|
|
||||||
|
case LBF_BIT32_BOR:
|
||||||
|
case LBF_BIT32_BXOR:
|
||||||
|
case LBF_BIT32_BTEST:
|
||||||
|
return {-1, 1}; // variadic
|
||||||
|
|
||||||
|
case LBF_BIT32_EXTRACT:
|
||||||
|
return {-1, 1}; // 2 or 3 parameters
|
||||||
|
|
||||||
|
case LBF_BIT32_LROTATE:
|
||||||
|
case LBF_BIT32_LSHIFT:
|
||||||
|
return {2, 1};
|
||||||
|
|
||||||
|
case LBF_BIT32_REPLACE:
|
||||||
|
return {-1, 1}; // 3 or 4 parameters
|
||||||
|
|
||||||
|
case LBF_BIT32_RROTATE:
|
||||||
|
case LBF_BIT32_RSHIFT:
|
||||||
|
return {2, 1};
|
||||||
|
|
||||||
|
case LBF_TYPE:
|
||||||
|
return {1, 1};
|
||||||
|
|
||||||
|
case LBF_STRING_BYTE:
|
||||||
|
return {-1, -1}; // 1, 2 or 3 parameters
|
||||||
|
|
||||||
|
case LBF_STRING_CHAR:
|
||||||
|
return {-1, 1}; // variadic
|
||||||
|
|
||||||
|
case LBF_STRING_LEN:
|
||||||
|
return {1, 1};
|
||||||
|
|
||||||
|
case LBF_TYPEOF:
|
||||||
|
return {1, 1};
|
||||||
|
|
||||||
|
case LBF_STRING_SUB:
|
||||||
|
return {-1, 1}; // 2 or 3 parameters
|
||||||
|
|
||||||
|
case LBF_MATH_CLAMP:
|
||||||
|
return {3, 1};
|
||||||
|
|
||||||
|
case LBF_MATH_SIGN:
|
||||||
|
case LBF_MATH_ROUND:
|
||||||
|
return {1, 1};
|
||||||
|
|
||||||
|
case LBF_RAWSET:
|
||||||
|
return {3, 1};
|
||||||
|
|
||||||
|
case LBF_RAWGET:
|
||||||
|
case LBF_RAWEQUAL:
|
||||||
|
return {2, 1};
|
||||||
|
|
||||||
|
case LBF_TABLE_INSERT:
|
||||||
|
return {-1, 0}; // 2 or 3 parameters
|
||||||
|
|
||||||
|
case LBF_TABLE_UNPACK:
|
||||||
|
return {-1, -1}; // 1, 2 or 3 parameters
|
||||||
|
|
||||||
|
case LBF_VECTOR:
|
||||||
|
return {-1, 1}; // 3 or 4 parameters in some configurations
|
||||||
|
|
||||||
|
case LBF_BIT32_COUNTLZ:
|
||||||
|
case LBF_BIT32_COUNTRZ:
|
||||||
|
return {1, 1};
|
||||||
|
|
||||||
|
case LBF_SELECT_VARARG:
|
||||||
|
return {-1, -1}; // variadic
|
||||||
|
|
||||||
|
case LBF_RAWLEN:
|
||||||
|
return {1, 1};
|
||||||
|
|
||||||
|
case LBF_BIT32_EXTRACTK:
|
||||||
|
return {3, 1};
|
||||||
|
|
||||||
|
case LBF_GETMETATABLE:
|
||||||
|
return {1, 1};
|
||||||
|
|
||||||
|
case LBF_SETMETATABLE:
|
||||||
|
return {2, 1};
|
||||||
|
};
|
||||||
|
|
||||||
|
LUAU_UNREACHABLE();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Compile
|
||||||
|
} // namespace Luau
|
|
@ -0,0 +1,51 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ValueTracking.h"
|
||||||
|
|
||||||
|
namespace Luau
|
||||||
|
{
|
||||||
|
struct CompileOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Luau
|
||||||
|
{
|
||||||
|
namespace Compile
|
||||||
|
{
|
||||||
|
|
||||||
|
struct Builtin
|
||||||
|
{
|
||||||
|
AstName object;
|
||||||
|
AstName method;
|
||||||
|
|
||||||
|
bool empty() const
|
||||||
|
{
|
||||||
|
return object == AstName() && method == AstName();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isGlobal(const char* name) const
|
||||||
|
{
|
||||||
|
return object == AstName() && method == name;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isMethod(const char* table, const char* name) const
|
||||||
|
{
|
||||||
|
return object == table && method == name;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Builtin getBuiltin(AstExpr* node, const DenseHashMap<AstName, Global>& globals, const DenseHashMap<AstLocal*, Variable>& variables);
|
||||||
|
|
||||||
|
void analyzeBuiltins(DenseHashMap<AstExprCall*, int>& result, const DenseHashMap<AstName, Global>& globals,
|
||||||
|
const DenseHashMap<AstLocal*, Variable>& variables, const CompileOptions& options, AstNode* root);
|
||||||
|
|
||||||
|
struct BuiltinInfo
|
||||||
|
{
|
||||||
|
int params;
|
||||||
|
int results;
|
||||||
|
};
|
||||||
|
|
||||||
|
BuiltinInfo getBuiltinInfo(int bfid);
|
||||||
|
|
||||||
|
} // namespace Compile
|
||||||
|
} // namespace Luau
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,447 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
#include "ConstantFolding.h"
|
||||||
|
|
||||||
|
#include "BuiltinFolding.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
namespace Luau
|
||||||
|
{
|
||||||
|
namespace Compile
|
||||||
|
{
|
||||||
|
|
||||||
|
static bool constantsEqual(const Constant& la, const Constant& ra)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(la.type != Constant::Type_Unknown && ra.type != Constant::Type_Unknown);
|
||||||
|
|
||||||
|
switch (la.type)
|
||||||
|
{
|
||||||
|
case Constant::Type_Nil:
|
||||||
|
return ra.type == Constant::Type_Nil;
|
||||||
|
|
||||||
|
case Constant::Type_Boolean:
|
||||||
|
return ra.type == Constant::Type_Boolean && la.valueBoolean == ra.valueBoolean;
|
||||||
|
|
||||||
|
case Constant::Type_Number:
|
||||||
|
return ra.type == Constant::Type_Number && la.valueNumber == ra.valueNumber;
|
||||||
|
|
||||||
|
case Constant::Type_String:
|
||||||
|
return ra.type == Constant::Type_String && la.stringLength == ra.stringLength && memcmp(la.valueString, ra.valueString, la.stringLength) == 0;
|
||||||
|
|
||||||
|
default:
|
||||||
|
LUAU_ASSERT(!"Unexpected constant type in comparison");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void foldUnary(Constant& result, AstExprUnary::Op op, const Constant& arg)
|
||||||
|
{
|
||||||
|
switch (op)
|
||||||
|
{
|
||||||
|
case AstExprUnary::Not:
|
||||||
|
if (arg.type != Constant::Type_Unknown)
|
||||||
|
{
|
||||||
|
result.type = Constant::Type_Boolean;
|
||||||
|
result.valueBoolean = !arg.isTruthful();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AstExprUnary::Minus:
|
||||||
|
if (arg.type == Constant::Type_Number)
|
||||||
|
{
|
||||||
|
result.type = Constant::Type_Number;
|
||||||
|
result.valueNumber = -arg.valueNumber;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AstExprUnary::Len:
|
||||||
|
if (arg.type == Constant::Type_String)
|
||||||
|
{
|
||||||
|
result.type = Constant::Type_Number;
|
||||||
|
result.valueNumber = double(arg.stringLength);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
LUAU_ASSERT(!"Unexpected unary operation");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void foldBinary(Constant& result, AstExprBinary::Op op, const Constant& la, const Constant& ra)
|
||||||
|
{
|
||||||
|
switch (op)
|
||||||
|
{
|
||||||
|
case AstExprBinary::Add:
|
||||||
|
if (la.type == Constant::Type_Number && ra.type == Constant::Type_Number)
|
||||||
|
{
|
||||||
|
result.type = Constant::Type_Number;
|
||||||
|
result.valueNumber = la.valueNumber + ra.valueNumber;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AstExprBinary::Sub:
|
||||||
|
if (la.type == Constant::Type_Number && ra.type == Constant::Type_Number)
|
||||||
|
{
|
||||||
|
result.type = Constant::Type_Number;
|
||||||
|
result.valueNumber = la.valueNumber - ra.valueNumber;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AstExprBinary::Mul:
|
||||||
|
if (la.type == Constant::Type_Number && ra.type == Constant::Type_Number)
|
||||||
|
{
|
||||||
|
result.type = Constant::Type_Number;
|
||||||
|
result.valueNumber = la.valueNumber * ra.valueNumber;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AstExprBinary::Div:
|
||||||
|
if (la.type == Constant::Type_Number && ra.type == Constant::Type_Number)
|
||||||
|
{
|
||||||
|
result.type = Constant::Type_Number;
|
||||||
|
result.valueNumber = la.valueNumber / ra.valueNumber;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AstExprBinary::Mod:
|
||||||
|
if (la.type == Constant::Type_Number && ra.type == Constant::Type_Number)
|
||||||
|
{
|
||||||
|
result.type = Constant::Type_Number;
|
||||||
|
result.valueNumber = la.valueNumber - floor(la.valueNumber / ra.valueNumber) * ra.valueNumber;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AstExprBinary::Pow:
|
||||||
|
if (la.type == Constant::Type_Number && ra.type == Constant::Type_Number)
|
||||||
|
{
|
||||||
|
result.type = Constant::Type_Number;
|
||||||
|
result.valueNumber = pow(la.valueNumber, ra.valueNumber);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AstExprBinary::Concat:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AstExprBinary::CompareNe:
|
||||||
|
if (la.type != Constant::Type_Unknown && ra.type != Constant::Type_Unknown)
|
||||||
|
{
|
||||||
|
result.type = Constant::Type_Boolean;
|
||||||
|
result.valueBoolean = !constantsEqual(la, ra);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AstExprBinary::CompareEq:
|
||||||
|
if (la.type != Constant::Type_Unknown && ra.type != Constant::Type_Unknown)
|
||||||
|
{
|
||||||
|
result.type = Constant::Type_Boolean;
|
||||||
|
result.valueBoolean = constantsEqual(la, ra);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AstExprBinary::CompareLt:
|
||||||
|
if (la.type == Constant::Type_Number && ra.type == Constant::Type_Number)
|
||||||
|
{
|
||||||
|
result.type = Constant::Type_Boolean;
|
||||||
|
result.valueBoolean = la.valueNumber < ra.valueNumber;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AstExprBinary::CompareLe:
|
||||||
|
if (la.type == Constant::Type_Number && ra.type == Constant::Type_Number)
|
||||||
|
{
|
||||||
|
result.type = Constant::Type_Boolean;
|
||||||
|
result.valueBoolean = la.valueNumber <= ra.valueNumber;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AstExprBinary::CompareGt:
|
||||||
|
if (la.type == Constant::Type_Number && ra.type == Constant::Type_Number)
|
||||||
|
{
|
||||||
|
result.type = Constant::Type_Boolean;
|
||||||
|
result.valueBoolean = la.valueNumber > ra.valueNumber;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AstExprBinary::CompareGe:
|
||||||
|
if (la.type == Constant::Type_Number && ra.type == Constant::Type_Number)
|
||||||
|
{
|
||||||
|
result.type = Constant::Type_Boolean;
|
||||||
|
result.valueBoolean = la.valueNumber >= ra.valueNumber;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AstExprBinary::And:
|
||||||
|
if (la.type != Constant::Type_Unknown)
|
||||||
|
{
|
||||||
|
result = la.isTruthful() ? ra : la;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AstExprBinary::Or:
|
||||||
|
if (la.type != Constant::Type_Unknown)
|
||||||
|
{
|
||||||
|
result = la.isTruthful() ? la : ra;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
LUAU_ASSERT(!"Unexpected binary operation");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ConstantVisitor : AstVisitor
|
||||||
|
{
|
||||||
|
DenseHashMap<AstExpr*, Constant>& constants;
|
||||||
|
DenseHashMap<AstLocal*, Variable>& variables;
|
||||||
|
DenseHashMap<AstLocal*, Constant>& locals;
|
||||||
|
|
||||||
|
const DenseHashMap<AstExprCall*, int>* builtins;
|
||||||
|
|
||||||
|
bool wasEmpty = false;
|
||||||
|
|
||||||
|
std::vector<Constant> builtinArgs;
|
||||||
|
|
||||||
|
ConstantVisitor(DenseHashMap<AstExpr*, Constant>& constants, DenseHashMap<AstLocal*, Variable>& variables,
|
||||||
|
DenseHashMap<AstLocal*, Constant>& locals, const DenseHashMap<AstExprCall*, int>* builtins)
|
||||||
|
: constants(constants)
|
||||||
|
, variables(variables)
|
||||||
|
, locals(locals)
|
||||||
|
, builtins(builtins)
|
||||||
|
{
|
||||||
|
// since we do a single pass over the tree, if the initial state was empty we don't need to clear out old entries
|
||||||
|
wasEmpty = constants.empty() && locals.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
Constant analyze(AstExpr* node)
|
||||||
|
{
|
||||||
|
Constant result;
|
||||||
|
result.type = Constant::Type_Unknown;
|
||||||
|
|
||||||
|
if (AstExprGroup* expr = node->as<AstExprGroup>())
|
||||||
|
{
|
||||||
|
result = analyze(expr->expr);
|
||||||
|
}
|
||||||
|
else if (node->is<AstExprConstantNil>())
|
||||||
|
{
|
||||||
|
result.type = Constant::Type_Nil;
|
||||||
|
}
|
||||||
|
else if (AstExprConstantBool* expr = node->as<AstExprConstantBool>())
|
||||||
|
{
|
||||||
|
result.type = Constant::Type_Boolean;
|
||||||
|
result.valueBoolean = expr->value;
|
||||||
|
}
|
||||||
|
else if (AstExprConstantNumber* expr = node->as<AstExprConstantNumber>())
|
||||||
|
{
|
||||||
|
result.type = Constant::Type_Number;
|
||||||
|
result.valueNumber = expr->value;
|
||||||
|
}
|
||||||
|
else if (AstExprConstantString* expr = node->as<AstExprConstantString>())
|
||||||
|
{
|
||||||
|
result.type = Constant::Type_String;
|
||||||
|
result.valueString = expr->value.data;
|
||||||
|
result.stringLength = unsigned(expr->value.size);
|
||||||
|
}
|
||||||
|
else if (AstExprLocal* expr = node->as<AstExprLocal>())
|
||||||
|
{
|
||||||
|
const Constant* l = locals.find(expr->local);
|
||||||
|
|
||||||
|
if (l)
|
||||||
|
result = *l;
|
||||||
|
}
|
||||||
|
else if (node->is<AstExprGlobal>())
|
||||||
|
{
|
||||||
|
// nope
|
||||||
|
}
|
||||||
|
else if (node->is<AstExprVarargs>())
|
||||||
|
{
|
||||||
|
// nope
|
||||||
|
}
|
||||||
|
else if (AstExprCall* expr = node->as<AstExprCall>())
|
||||||
|
{
|
||||||
|
analyze(expr->func);
|
||||||
|
|
||||||
|
if (const int* bfid = builtins ? builtins->find(expr) : nullptr)
|
||||||
|
{
|
||||||
|
// since recursive calls to analyze() may reuse the vector we need to be careful and preserve existing contents
|
||||||
|
size_t offset = builtinArgs.size();
|
||||||
|
bool canFold = true;
|
||||||
|
|
||||||
|
builtinArgs.reserve(offset + expr->args.size);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < expr->args.size; ++i)
|
||||||
|
{
|
||||||
|
Constant ac = analyze(expr->args.data[i]);
|
||||||
|
|
||||||
|
if (ac.type == Constant::Type_Unknown)
|
||||||
|
canFold = false;
|
||||||
|
else
|
||||||
|
builtinArgs.push_back(ac);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canFold)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(builtinArgs.size() == offset + expr->args.size);
|
||||||
|
result = foldBuiltin(*bfid, builtinArgs.data() + offset, expr->args.size);
|
||||||
|
}
|
||||||
|
|
||||||
|
builtinArgs.resize(offset);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < expr->args.size; ++i)
|
||||||
|
analyze(expr->args.data[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (AstExprIndexName* expr = node->as<AstExprIndexName>())
|
||||||
|
{
|
||||||
|
analyze(expr->expr);
|
||||||
|
}
|
||||||
|
else if (AstExprIndexExpr* expr = node->as<AstExprIndexExpr>())
|
||||||
|
{
|
||||||
|
analyze(expr->expr);
|
||||||
|
analyze(expr->index);
|
||||||
|
}
|
||||||
|
else if (AstExprFunction* expr = node->as<AstExprFunction>())
|
||||||
|
{
|
||||||
|
// this is necessary to propagate constant information in all child functions
|
||||||
|
expr->body->visit(this);
|
||||||
|
}
|
||||||
|
else if (AstExprTable* expr = node->as<AstExprTable>())
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < expr->items.size; ++i)
|
||||||
|
{
|
||||||
|
const AstExprTable::Item& item = expr->items.data[i];
|
||||||
|
|
||||||
|
if (item.key)
|
||||||
|
analyze(item.key);
|
||||||
|
|
||||||
|
analyze(item.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (AstExprUnary* expr = node->as<AstExprUnary>())
|
||||||
|
{
|
||||||
|
Constant arg = analyze(expr->expr);
|
||||||
|
|
||||||
|
if (arg.type != Constant::Type_Unknown)
|
||||||
|
foldUnary(result, expr->op, arg);
|
||||||
|
}
|
||||||
|
else if (AstExprBinary* expr = node->as<AstExprBinary>())
|
||||||
|
{
|
||||||
|
Constant la = analyze(expr->left);
|
||||||
|
Constant ra = analyze(expr->right);
|
||||||
|
|
||||||
|
// note: ra doesn't need to be constant to fold and/or
|
||||||
|
if (la.type != Constant::Type_Unknown)
|
||||||
|
foldBinary(result, expr->op, la, ra);
|
||||||
|
}
|
||||||
|
else if (AstExprTypeAssertion* expr = node->as<AstExprTypeAssertion>())
|
||||||
|
{
|
||||||
|
Constant arg = analyze(expr->expr);
|
||||||
|
|
||||||
|
result = arg;
|
||||||
|
}
|
||||||
|
else if (AstExprIfElse* expr = node->as<AstExprIfElse>())
|
||||||
|
{
|
||||||
|
Constant cond = analyze(expr->condition);
|
||||||
|
Constant trueExpr = analyze(expr->trueExpr);
|
||||||
|
Constant falseExpr = analyze(expr->falseExpr);
|
||||||
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
|
recordConstant(constants, node, result);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
void recordConstant(DenseHashMap<T, Constant>& map, T key, const Constant& value)
|
||||||
|
{
|
||||||
|
if (value.type != Constant::Type_Unknown)
|
||||||
|
map[key] = value;
|
||||||
|
else if (wasEmpty)
|
||||||
|
;
|
||||||
|
else if (Constant* old = map.find(key))
|
||||||
|
old->type = Constant::Type_Unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
void recordValue(AstLocal* local, const Constant& value)
|
||||||
|
{
|
||||||
|
// note: we rely on trackValues to have been run before us
|
||||||
|
Variable* v = variables.find(local);
|
||||||
|
LUAU_ASSERT(v);
|
||||||
|
|
||||||
|
if (!v->written)
|
||||||
|
{
|
||||||
|
v->constant = (value.type != Constant::Type_Unknown);
|
||||||
|
recordConstant(locals, local, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool visit(AstExpr* node) override
|
||||||
|
{
|
||||||
|
// note: we short-circuit the visitor traversal through any expression trees by returning false
|
||||||
|
// recursive traversal is happening inside analyze() which makes it easier to get the resulting value of the subexpression
|
||||||
|
analyze(node);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool visit(AstStatLocal* node) override
|
||||||
|
{
|
||||||
|
// all values that align wrt indexing are simple - we just match them 1-1
|
||||||
|
for (size_t i = 0; i < node->vars.size && i < node->values.size; ++i)
|
||||||
|
{
|
||||||
|
Constant arg = analyze(node->values.data[i]);
|
||||||
|
|
||||||
|
recordValue(node->vars.data[i], arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node->vars.size > node->values.size)
|
||||||
|
{
|
||||||
|
// if we have trailing variables, then depending on whether the last value is capable of returning multiple values
|
||||||
|
// (aka call or varargs), we either don't know anything about these vars, or we know they're nil
|
||||||
|
AstExpr* last = node->values.size ? node->values.data[node->values.size - 1] : nullptr;
|
||||||
|
bool multRet = last && (last->is<AstExprCall>() || last->is<AstExprVarargs>());
|
||||||
|
|
||||||
|
if (!multRet)
|
||||||
|
{
|
||||||
|
for (size_t i = node->values.size; i < node->vars.size; ++i)
|
||||||
|
{
|
||||||
|
Constant nil = {Constant::Type_Nil};
|
||||||
|
recordValue(node->vars.data[i], nil);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// we can have more values than variables; in this case we still need to analyze them to make sure we do constant propagation inside
|
||||||
|
// them
|
||||||
|
for (size_t i = node->vars.size; i < node->values.size; ++i)
|
||||||
|
analyze(node->values.data[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void foldConstants(DenseHashMap<AstExpr*, Constant>& constants, DenseHashMap<AstLocal*, Variable>& variables,
|
||||||
|
DenseHashMap<AstLocal*, Constant>& locals, const DenseHashMap<AstExprCall*, int>* builtins, AstNode* root)
|
||||||
|
{
|
||||||
|
ConstantVisitor visitor{constants, variables, locals, builtins};
|
||||||
|
root->visit(&visitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Compile
|
||||||
|
} // namespace Luau
|
|
@ -0,0 +1,49 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ValueTracking.h"
|
||||||
|
|
||||||
|
namespace Luau
|
||||||
|
{
|
||||||
|
namespace Compile
|
||||||
|
{
|
||||||
|
|
||||||
|
struct Constant
|
||||||
|
{
|
||||||
|
enum Type
|
||||||
|
{
|
||||||
|
Type_Unknown,
|
||||||
|
Type_Nil,
|
||||||
|
Type_Boolean,
|
||||||
|
Type_Number,
|
||||||
|
Type_String,
|
||||||
|
};
|
||||||
|
|
||||||
|
Type type = Type_Unknown;
|
||||||
|
unsigned int stringLength = 0;
|
||||||
|
|
||||||
|
union
|
||||||
|
{
|
||||||
|
bool valueBoolean;
|
||||||
|
double valueNumber;
|
||||||
|
const char* valueString = nullptr; // length stored in stringLength
|
||||||
|
};
|
||||||
|
|
||||||
|
bool isTruthful() const
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(type != Type_Unknown);
|
||||||
|
return type != Type_Nil && !(type == Type_Boolean && valueBoolean == false);
|
||||||
|
}
|
||||||
|
|
||||||
|
AstArray<const char> getString() const
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(type == Type_String);
|
||||||
|
return {valueString, stringLength};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void foldConstants(DenseHashMap<AstExpr*, Constant>& constants, DenseHashMap<AstLocal*, Variable>& variables,
|
||||||
|
DenseHashMap<AstLocal*, Constant>& locals, const DenseHashMap<AstExprCall*, int>* builtins, AstNode* root);
|
||||||
|
|
||||||
|
} // namespace Compile
|
||||||
|
} // namespace Luau
|
|
@ -0,0 +1,392 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
#include "CostModel.h"
|
||||||
|
|
||||||
|
#include "Luau/Common.h"
|
||||||
|
#include "Luau/DenseHash.h"
|
||||||
|
|
||||||
|
#include <limits.h>
|
||||||
|
|
||||||
|
namespace Luau
|
||||||
|
{
|
||||||
|
namespace Compile
|
||||||
|
{
|
||||||
|
|
||||||
|
inline uint64_t parallelAddSat(uint64_t x, uint64_t y)
|
||||||
|
{
|
||||||
|
uint64_t r = x + y;
|
||||||
|
uint64_t s = r & 0x8080808080808080ull; // saturation mask
|
||||||
|
|
||||||
|
return (r ^ s) | (s - (s >> 7));
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t parallelMulSat(uint64_t a, int b)
|
||||||
|
{
|
||||||
|
int bs = (b < 127) ? b : 127;
|
||||||
|
|
||||||
|
// multiply every other value by b, yielding 14-bit products
|
||||||
|
uint64_t l = bs * ((a >> 0) & 0x007f007f007f007full);
|
||||||
|
uint64_t h = bs * ((a >> 8) & 0x007f007f007f007full);
|
||||||
|
|
||||||
|
// each product is 14-bit, so adding 32768-128 sets high bit iff the sum is 128 or larger without an overflow
|
||||||
|
uint64_t ls = l + 0x7f807f807f807f80ull;
|
||||||
|
uint64_t hs = h + 0x7f807f807f807f80ull;
|
||||||
|
|
||||||
|
// we now merge saturation bits as well as low 7-bits of each product into one
|
||||||
|
uint64_t s = (hs & 0x8000800080008000ull) | ((ls & 0x8000800080008000ull) >> 8);
|
||||||
|
uint64_t r = ((h & 0x007f007f007f007full) << 8) | (l & 0x007f007f007f007full);
|
||||||
|
|
||||||
|
// the low bits are now correct for values that didn't saturate, and we simply need to mask them if high bit is 1
|
||||||
|
return r | (s - (s >> 7));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool getNumber(AstExpr* node, double& result)
|
||||||
|
{
|
||||||
|
// since constant model doesn't use constant folding atm, we perform the basic extraction that's sufficient to handle positive/negative literals
|
||||||
|
if (AstExprConstantNumber* ne = node->as<AstExprConstantNumber>())
|
||||||
|
{
|
||||||
|
result = ne->value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (AstExprUnary* ue = node->as<AstExprUnary>(); ue && ue->op == AstExprUnary::Minus)
|
||||||
|
if (AstExprConstantNumber* ne = ue->expr->as<AstExprConstantNumber>())
|
||||||
|
{
|
||||||
|
result = -ne->value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Cost
|
||||||
|
{
|
||||||
|
static const uint64_t kLiteral = ~0ull;
|
||||||
|
|
||||||
|
// cost model: 8 bytes, where first byte is the baseline cost, and the next 7 bytes are discounts for when variable #i is constant
|
||||||
|
uint64_t model;
|
||||||
|
// constant mask: 8-byte 0xff mask; equal to all ff's for literals, for variables only byte #i (1+) is set to align with model
|
||||||
|
uint64_t constant;
|
||||||
|
|
||||||
|
Cost(int cost = 0, uint64_t constant = 0)
|
||||||
|
: model(cost < 0x7f ? cost : 0x7f)
|
||||||
|
, constant(constant)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Cost operator+(const Cost& other) const
|
||||||
|
{
|
||||||
|
Cost result;
|
||||||
|
result.model = parallelAddSat(model, other.model);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Cost& operator+=(const Cost& other)
|
||||||
|
{
|
||||||
|
model = parallelAddSat(model, other.model);
|
||||||
|
constant = 0;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Cost operator*(int other) const
|
||||||
|
{
|
||||||
|
Cost result;
|
||||||
|
result.model = parallelMulSat(model, other);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Cost fold(const Cost& x, const Cost& y)
|
||||||
|
{
|
||||||
|
uint64_t newmodel = parallelAddSat(x.model, y.model);
|
||||||
|
uint64_t newconstant = x.constant & y.constant;
|
||||||
|
|
||||||
|
// the extra cost for folding is 1; the discount is 1 for the variable that is shared by x&y (or whichever one is used in x/y if the other is
|
||||||
|
// literal)
|
||||||
|
uint64_t extra = (newconstant == kLiteral) ? 0 : (1 | (0x0101010101010101ull & newconstant));
|
||||||
|
|
||||||
|
Cost result;
|
||||||
|
result.model = parallelAddSat(newmodel, extra);
|
||||||
|
result.constant = newconstant;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CostVisitor : AstVisitor
|
||||||
|
{
|
||||||
|
const DenseHashMap<AstExprCall*, int>& builtins;
|
||||||
|
|
||||||
|
DenseHashMap<AstLocal*, uint64_t> vars;
|
||||||
|
Cost result;
|
||||||
|
|
||||||
|
CostVisitor(const DenseHashMap<AstExprCall*, int>& builtins)
|
||||||
|
: builtins(builtins)
|
||||||
|
, vars(nullptr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Cost model(AstExpr* node)
|
||||||
|
{
|
||||||
|
if (AstExprGroup* expr = node->as<AstExprGroup>())
|
||||||
|
{
|
||||||
|
return model(expr->expr);
|
||||||
|
}
|
||||||
|
else if (node->is<AstExprConstantNil>() || node->is<AstExprConstantBool>() || node->is<AstExprConstantNumber>() ||
|
||||||
|
node->is<AstExprConstantString>())
|
||||||
|
{
|
||||||
|
return Cost(0, Cost::kLiteral);
|
||||||
|
}
|
||||||
|
else if (AstExprLocal* expr = node->as<AstExprLocal>())
|
||||||
|
{
|
||||||
|
const uint64_t* i = vars.find(expr->local);
|
||||||
|
|
||||||
|
return Cost(0, i ? *i : 0); // locals typically don't require extra instructions to compute
|
||||||
|
}
|
||||||
|
else if (node->is<AstExprGlobal>())
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else if (node->is<AstExprVarargs>())
|
||||||
|
{
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
else if (AstExprCall* expr = node->as<AstExprCall>())
|
||||||
|
{
|
||||||
|
// builtin cost modeling is different from regular calls because we use FASTCALL to compile these
|
||||||
|
// thus we use a cheaper baseline, don't account for function, and assume constant/local copy is free
|
||||||
|
bool builtin = builtins.find(expr) != nullptr;
|
||||||
|
bool builtinShort = builtin && expr->args.size <= 2; // FASTCALL1/2
|
||||||
|
|
||||||
|
Cost cost = builtin ? 2 : 3;
|
||||||
|
|
||||||
|
if (!builtin)
|
||||||
|
cost += model(expr->func);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < expr->args.size; ++i)
|
||||||
|
{
|
||||||
|
Cost ac = model(expr->args.data[i]);
|
||||||
|
// for constants/locals we still need to copy them to the argument list
|
||||||
|
cost += ac.model == 0 && !builtinShort ? Cost(1) : ac;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cost;
|
||||||
|
}
|
||||||
|
else if (AstExprIndexName* expr = node->as<AstExprIndexName>())
|
||||||
|
{
|
||||||
|
return model(expr->expr) + 1;
|
||||||
|
}
|
||||||
|
else if (AstExprIndexExpr* expr = node->as<AstExprIndexExpr>())
|
||||||
|
{
|
||||||
|
return model(expr->expr) + model(expr->index) + 1;
|
||||||
|
}
|
||||||
|
else if (AstExprFunction* expr = node->as<AstExprFunction>())
|
||||||
|
{
|
||||||
|
return 10; // high baseline cost due to allocation
|
||||||
|
}
|
||||||
|
else if (AstExprTable* expr = node->as<AstExprTable>())
|
||||||
|
{
|
||||||
|
Cost cost = 10; // high baseline cost due to allocation
|
||||||
|
|
||||||
|
for (size_t i = 0; i < expr->items.size; ++i)
|
||||||
|
{
|
||||||
|
const AstExprTable::Item& item = expr->items.data[i];
|
||||||
|
|
||||||
|
if (item.key)
|
||||||
|
cost += model(item.key);
|
||||||
|
|
||||||
|
cost += model(item.value);
|
||||||
|
cost += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cost;
|
||||||
|
}
|
||||||
|
else if (AstExprUnary* expr = node->as<AstExprUnary>())
|
||||||
|
{
|
||||||
|
return Cost::fold(model(expr->expr), Cost(0, Cost::kLiteral));
|
||||||
|
}
|
||||||
|
else if (AstExprBinary* expr = node->as<AstExprBinary>())
|
||||||
|
{
|
||||||
|
return Cost::fold(model(expr->left), model(expr->right));
|
||||||
|
}
|
||||||
|
else if (AstExprTypeAssertion* expr = node->as<AstExprTypeAssertion>())
|
||||||
|
{
|
||||||
|
return model(expr->expr);
|
||||||
|
}
|
||||||
|
else if (AstExprIfElse* expr = node->as<AstExprIfElse>())
|
||||||
|
{
|
||||||
|
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");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void assign(AstExpr* expr)
|
||||||
|
{
|
||||||
|
// variable assignments reset variable mask, so that further uses of this variable aren't discounted
|
||||||
|
// this doesn't work perfectly with backwards control flow like loops, but is good enough for a single pass
|
||||||
|
if (AstExprLocal* lv = expr->as<AstExprLocal>())
|
||||||
|
if (uint64_t* i = vars.find(lv->local))
|
||||||
|
*i = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop(AstStatBlock* body, Cost iterCost, int factor = 3)
|
||||||
|
{
|
||||||
|
Cost before = result;
|
||||||
|
|
||||||
|
result = Cost();
|
||||||
|
body->visit(this);
|
||||||
|
|
||||||
|
result = before + (result + iterCost) * factor;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool visit(AstExpr* node) override
|
||||||
|
{
|
||||||
|
// note: we short-circuit the visitor traversal through any expression trees by returning false
|
||||||
|
// recursive traversal is happening inside model() which makes it easier to get the resulting value of the subexpression
|
||||||
|
result += model(node);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool visit(AstStatFor* node) override
|
||||||
|
{
|
||||||
|
result += model(node->from);
|
||||||
|
result += model(node->to);
|
||||||
|
|
||||||
|
if (node->step)
|
||||||
|
result += model(node->step);
|
||||||
|
|
||||||
|
int tripCount = -1;
|
||||||
|
double from, to, step = 1;
|
||||||
|
if (getNumber(node->from, from) && getNumber(node->to, to) && (!node->step || getNumber(node->step, step)))
|
||||||
|
tripCount = getTripCount(from, to, step);
|
||||||
|
|
||||||
|
loop(node->body, 1, tripCount < 0 ? 3 : tripCount);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool visit(AstStatForIn* node) override
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < node->values.size; ++i)
|
||||||
|
result += model(node->values.data[i]);
|
||||||
|
|
||||||
|
loop(node->body, 1);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool visit(AstStatWhile* node) override
|
||||||
|
{
|
||||||
|
Cost condition = model(node->condition);
|
||||||
|
|
||||||
|
loop(node->body, condition);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool visit(AstStatRepeat* node) override
|
||||||
|
{
|
||||||
|
Cost condition = model(node->condition);
|
||||||
|
|
||||||
|
loop(node->body, condition);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool visit(AstStat* node) override
|
||||||
|
{
|
||||||
|
if (node->is<AstStatIf>())
|
||||||
|
result += 2;
|
||||||
|
else if (node->is<AstStatBreak>() || node->is<AstStatContinue>())
|
||||||
|
result += 1;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool visit(AstStatLocal* node) override
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < node->values.size; ++i)
|
||||||
|
{
|
||||||
|
Cost arg = model(node->values.data[i]);
|
||||||
|
|
||||||
|
// propagate constant mask from expression through variables
|
||||||
|
if (arg.constant && i < node->vars.size)
|
||||||
|
vars[node->vars.data[i]] = arg.constant;
|
||||||
|
|
||||||
|
result += arg;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool visit(AstStatAssign* node) override
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < node->vars.size; ++i)
|
||||||
|
assign(node->vars.data[i]);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool visit(AstStatCompoundAssign* node) override
|
||||||
|
{
|
||||||
|
assign(node->var);
|
||||||
|
|
||||||
|
// if lhs is not a local, setting it requires an extra table operation
|
||||||
|
result += node->var->is<AstExprLocal>() ? 1 : 2;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
uint64_t modelCost(AstNode* root, AstLocal* const* vars, size_t varCount, const DenseHashMap<AstExprCall*, int>& builtins)
|
||||||
|
{
|
||||||
|
CostVisitor visitor{builtins};
|
||||||
|
for (size_t i = 0; i < varCount && i < 7; ++i)
|
||||||
|
visitor.vars[vars[i]] = 0xffull << (i * 8 + 8);
|
||||||
|
|
||||||
|
root->visit(&visitor);
|
||||||
|
|
||||||
|
return visitor.result.model;
|
||||||
|
}
|
||||||
|
|
||||||
|
int computeCost(uint64_t model, const bool* varsConst, size_t varCount)
|
||||||
|
{
|
||||||
|
int cost = int(model & 0x7f);
|
||||||
|
|
||||||
|
// don't apply discounts to what is likely a saturated sum
|
||||||
|
if (cost == 0x7f)
|
||||||
|
return cost;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < varCount && i < 7; ++i)
|
||||||
|
cost -= int((model >> (i * 8 + 8)) & 0x7f) * varsConst[i];
|
||||||
|
|
||||||
|
return cost;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getTripCount(double from, double to, double step)
|
||||||
|
{
|
||||||
|
// we compute trip count in integers because that way we know that the loop math (repeated addition) is precise
|
||||||
|
int fromi = (from >= -32767 && from <= 32767 && double(int(from)) == from) ? int(from) : INT_MIN;
|
||||||
|
int toi = (to >= -32767 && to <= 32767 && double(int(to)) == to) ? int(to) : INT_MIN;
|
||||||
|
int stepi = (step >= -32767 && step <= 32767 && double(int(step)) == step) ? int(step) : INT_MIN;
|
||||||
|
|
||||||
|
if (fromi == INT_MIN || toi == INT_MIN || stepi == INT_MIN || stepi == 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if ((stepi < 0 && toi > fromi) || (stepi > 0 && toi < fromi))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return (toi - fromi) / stepi + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Compile
|
||||||
|
} // namespace Luau
|
|
@ -0,0 +1,22 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Luau/Ast.h"
|
||||||
|
#include "Luau/DenseHash.h"
|
||||||
|
|
||||||
|
namespace Luau
|
||||||
|
{
|
||||||
|
namespace Compile
|
||||||
|
{
|
||||||
|
|
||||||
|
// cost model: 8 bytes, where first byte is the baseline cost, and the next 7 bytes are discounts for when variable #i is constant
|
||||||
|
uint64_t modelCost(AstNode* root, AstLocal* const* vars, size_t varCount, const DenseHashMap<AstExprCall*, int>& builtins);
|
||||||
|
|
||||||
|
// cost is computed as B - sum(Di * Ci), where B is baseline cost, Di is the discount for each variable and Ci is 1 when variable #i is constant
|
||||||
|
int computeCost(uint64_t model, const bool* varsConst, size_t varCount);
|
||||||
|
|
||||||
|
// get loop trip count or -1 if we can't compute it precisely
|
||||||
|
int getTripCount(double from, double to, double step);
|
||||||
|
|
||||||
|
} // namespace Compile
|
||||||
|
} // namespace Luau
|
|
@ -0,0 +1,158 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
#include "TableShape.h"
|
||||||
|
|
||||||
|
namespace Luau
|
||||||
|
{
|
||||||
|
namespace Compile
|
||||||
|
{
|
||||||
|
|
||||||
|
// conservative limit for the loop bound that establishes table array size
|
||||||
|
static const int kMaxLoopBound = 16;
|
||||||
|
|
||||||
|
static AstExprTable* getTableHint(AstExpr* expr)
|
||||||
|
{
|
||||||
|
// unadorned table literal
|
||||||
|
if (AstExprTable* table = expr->as<AstExprTable>())
|
||||||
|
return table;
|
||||||
|
|
||||||
|
// setmetatable(table literal, ...)
|
||||||
|
if (AstExprCall* call = expr->as<AstExprCall>(); call && !call->self && call->args.size == 2)
|
||||||
|
if (AstExprGlobal* func = call->func->as<AstExprGlobal>(); func && func->name == "setmetatable")
|
||||||
|
if (AstExprTable* table = call->args.data[0]->as<AstExprTable>())
|
||||||
|
return table;
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ShapeVisitor : AstVisitor
|
||||||
|
{
|
||||||
|
struct Hasher
|
||||||
|
{
|
||||||
|
size_t operator()(const std::pair<AstExprTable*, AstName>& p) const
|
||||||
|
{
|
||||||
|
return DenseHashPointer()(p.first) ^ std::hash<AstName>()(p.second);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
DenseHashMap<AstExprTable*, TableShape>& shapes;
|
||||||
|
|
||||||
|
DenseHashMap<AstLocal*, AstExprTable*> tables;
|
||||||
|
DenseHashSet<std::pair<AstExprTable*, AstName>, Hasher> fields;
|
||||||
|
|
||||||
|
DenseHashMap<AstLocal*, unsigned int> loops; // iterator => upper bound for 1..k
|
||||||
|
|
||||||
|
ShapeVisitor(DenseHashMap<AstExprTable*, TableShape>& shapes)
|
||||||
|
: shapes(shapes)
|
||||||
|
, tables(nullptr)
|
||||||
|
, fields(std::pair<AstExprTable*, AstName>())
|
||||||
|
, loops(nullptr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void assignField(AstExpr* expr, AstName index)
|
||||||
|
{
|
||||||
|
if (AstExprLocal* lv = expr->as<AstExprLocal>())
|
||||||
|
{
|
||||||
|
if (AstExprTable** table = tables.find(lv->local))
|
||||||
|
{
|
||||||
|
std::pair<AstExprTable*, AstName> field = {*table, index};
|
||||||
|
|
||||||
|
if (!fields.contains(field))
|
||||||
|
{
|
||||||
|
fields.insert(field);
|
||||||
|
shapes[*table].hashSize += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void assignField(AstExpr* expr, AstExpr* index)
|
||||||
|
{
|
||||||
|
AstExprLocal* lv = expr->as<AstExprLocal>();
|
||||||
|
if (!lv)
|
||||||
|
return;
|
||||||
|
|
||||||
|
AstExprTable** table = tables.find(lv->local);
|
||||||
|
if (!table)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (AstExprConstantNumber* number = index->as<AstExprConstantNumber>())
|
||||||
|
{
|
||||||
|
TableShape& shape = shapes[*table];
|
||||||
|
|
||||||
|
if (number->value == double(shape.arraySize + 1))
|
||||||
|
shape.arraySize += 1;
|
||||||
|
}
|
||||||
|
else if (AstExprLocal* iter = index->as<AstExprLocal>())
|
||||||
|
{
|
||||||
|
if (const unsigned int* bound = loops.find(iter->local))
|
||||||
|
{
|
||||||
|
TableShape& shape = shapes[*table];
|
||||||
|
|
||||||
|
if (shape.arraySize == 0)
|
||||||
|
shape.arraySize = *bound;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void assign(AstExpr* var)
|
||||||
|
{
|
||||||
|
if (AstExprIndexName* index = var->as<AstExprIndexName>())
|
||||||
|
{
|
||||||
|
assignField(index->expr, index->index);
|
||||||
|
}
|
||||||
|
else if (AstExprIndexExpr* index = var->as<AstExprIndexExpr>())
|
||||||
|
{
|
||||||
|
assignField(index->expr, index->index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool visit(AstStatLocal* node) override
|
||||||
|
{
|
||||||
|
// track local -> table association so that we can update table size prediction in assignField
|
||||||
|
if (node->vars.size == 1 && node->values.size == 1)
|
||||||
|
if (AstExprTable* table = getTableHint(node->values.data[0]); table && table->items.size == 0)
|
||||||
|
tables[node->vars.data[0]] = table;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool visit(AstStatAssign* node) override
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < node->vars.size; ++i)
|
||||||
|
assign(node->vars.data[i]);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < node->values.size; ++i)
|
||||||
|
node->values.data[i]->visit(this);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool visit(AstStatFunction* node) override
|
||||||
|
{
|
||||||
|
assign(node->name);
|
||||||
|
node->func->visit(this);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool visit(AstStatFor* node) override
|
||||||
|
{
|
||||||
|
AstExprConstantNumber* from = node->from->as<AstExprConstantNumber>();
|
||||||
|
AstExprConstantNumber* to = node->to->as<AstExprConstantNumber>();
|
||||||
|
|
||||||
|
if (from && to && from->value == 1.0 && to->value >= 1.0 && to->value <= double(kMaxLoopBound) && !node->step)
|
||||||
|
loops[node->var] = unsigned(to->value);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void predictTableShapes(DenseHashMap<AstExprTable*, TableShape>& shapes, AstNode* root)
|
||||||
|
{
|
||||||
|
ShapeVisitor visitor{shapes};
|
||||||
|
root->visit(&visitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Compile
|
||||||
|
} // namespace Luau
|
|
@ -0,0 +1,21 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Luau/Ast.h"
|
||||||
|
#include "Luau/DenseHash.h"
|
||||||
|
|
||||||
|
namespace Luau
|
||||||
|
{
|
||||||
|
namespace Compile
|
||||||
|
{
|
||||||
|
|
||||||
|
struct TableShape
|
||||||
|
{
|
||||||
|
unsigned int arraySize = 0;
|
||||||
|
unsigned int hashSize = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
void predictTableShapes(DenseHashMap<AstExprTable*, TableShape>& shapes, AstNode* root);
|
||||||
|
|
||||||
|
} // namespace Compile
|
||||||
|
} // namespace Luau
|
|
@ -0,0 +1,103 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
#include "ValueTracking.h"
|
||||||
|
|
||||||
|
#include "Luau/Lexer.h"
|
||||||
|
|
||||||
|
namespace Luau
|
||||||
|
{
|
||||||
|
namespace Compile
|
||||||
|
{
|
||||||
|
|
||||||
|
struct ValueVisitor : AstVisitor
|
||||||
|
{
|
||||||
|
DenseHashMap<AstName, Global>& globals;
|
||||||
|
DenseHashMap<AstLocal*, Variable>& variables;
|
||||||
|
|
||||||
|
ValueVisitor(DenseHashMap<AstName, Global>& globals, DenseHashMap<AstLocal*, Variable>& variables)
|
||||||
|
: globals(globals)
|
||||||
|
, variables(variables)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void assign(AstExpr* var)
|
||||||
|
{
|
||||||
|
if (AstExprLocal* lv = var->as<AstExprLocal>())
|
||||||
|
{
|
||||||
|
variables[lv->local].written = true;
|
||||||
|
}
|
||||||
|
else if (AstExprGlobal* gv = var->as<AstExprGlobal>())
|
||||||
|
{
|
||||||
|
globals[gv->name] = Global::Written;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// we need to be able to track assignments in all expressions, including crazy ones like t[function() t = nil end] = 5
|
||||||
|
var->visit(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool visit(AstStatLocal* node) override
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < node->vars.size && i < node->values.size; ++i)
|
||||||
|
variables[node->vars.data[i]].init = node->values.data[i];
|
||||||
|
|
||||||
|
for (size_t i = node->values.size; i < node->vars.size; ++i)
|
||||||
|
variables[node->vars.data[i]].init = nullptr;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool visit(AstStatAssign* node) override
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < node->vars.size; ++i)
|
||||||
|
assign(node->vars.data[i]);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < node->values.size; ++i)
|
||||||
|
node->values.data[i]->visit(this);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool visit(AstStatCompoundAssign* node) override
|
||||||
|
{
|
||||||
|
assign(node->var);
|
||||||
|
node->value->visit(this);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool visit(AstStatLocalFunction* node) override
|
||||||
|
{
|
||||||
|
variables[node->name].init = node->func;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool visit(AstStatFunction* node) override
|
||||||
|
{
|
||||||
|
assign(node->name);
|
||||||
|
node->func->visit(this);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void assignMutable(DenseHashMap<AstName, Global>& globals, const AstNameTable& names, const char** mutableGlobals)
|
||||||
|
{
|
||||||
|
if (AstName name = names.get("_G"); name.value)
|
||||||
|
globals[name] = Global::Mutable;
|
||||||
|
|
||||||
|
if (mutableGlobals)
|
||||||
|
for (const char** ptr = mutableGlobals; *ptr; ++ptr)
|
||||||
|
if (AstName name = names.get(*ptr); name.value)
|
||||||
|
globals[name] = Global::Mutable;
|
||||||
|
}
|
||||||
|
|
||||||
|
void trackValues(DenseHashMap<AstName, Global>& globals, DenseHashMap<AstLocal*, Variable>& variables, AstNode* root)
|
||||||
|
{
|
||||||
|
ValueVisitor visitor{globals, variables};
|
||||||
|
root->visit(&visitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Compile
|
||||||
|
} // namespace Luau
|
|
@ -0,0 +1,42 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Luau/Ast.h"
|
||||||
|
#include "Luau/DenseHash.h"
|
||||||
|
|
||||||
|
namespace Luau
|
||||||
|
{
|
||||||
|
class AstNameTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Luau
|
||||||
|
{
|
||||||
|
namespace Compile
|
||||||
|
{
|
||||||
|
|
||||||
|
enum class Global
|
||||||
|
{
|
||||||
|
Default = 0,
|
||||||
|
Mutable, // builtin that has contents unknown at compile time, blocks GETIMPORT for chains
|
||||||
|
Written, // written in the code which means we can't reason about the value
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Variable
|
||||||
|
{
|
||||||
|
AstExpr* init = nullptr; // initial value of the variable; filled by trackValues
|
||||||
|
bool written = false; // is the variable ever assigned to? filled by trackValues
|
||||||
|
bool constant = false; // is the variable's value a compile-time constant? filled by constantFold
|
||||||
|
};
|
||||||
|
|
||||||
|
void assignMutable(DenseHashMap<AstName, Global>& globals, const AstNameTable& names, const char** mutableGlobals);
|
||||||
|
void trackValues(DenseHashMap<AstName, Global>& globals, DenseHashMap<AstLocal*, Variable>& variables, AstNode* root);
|
||||||
|
|
||||||
|
inline Global getGlobalState(const DenseHashMap<AstName, Global>& globals, AstName name)
|
||||||
|
{
|
||||||
|
const Global* it = globals.find(name);
|
||||||
|
|
||||||
|
return it ? *it : Global::Default;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Compile
|
||||||
|
} // namespace Luau
|
|
@ -0,0 +1,29 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
#include "luacode.h"
|
||||||
|
|
||||||
|
#include "Luau/Compiler.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
char* luau_compile(const char* source, size_t size, lua_CompileOptions* options, size_t* outsize)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(outsize);
|
||||||
|
|
||||||
|
Luau::CompileOptions opts;
|
||||||
|
|
||||||
|
if (options)
|
||||||
|
{
|
||||||
|
static_assert(sizeof(lua_CompileOptions) == sizeof(Luau::CompileOptions), "C and C++ interface must match");
|
||||||
|
memcpy(static_cast<void*>(&opts), options, sizeof(opts));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string result = compile(std::string(source, size), opts);
|
||||||
|
|
||||||
|
char* copy = static_cast<char*>(malloc(result.size()));
|
||||||
|
if (!copy)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
memcpy(copy, result.data(), result.size());
|
||||||
|
*outsize = result.size();
|
||||||
|
return copy;
|
||||||
|
}
|
|
@ -0,0 +1,463 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "luaconf.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// option for multiple returns in `lua_pcall' and `lua_call'
|
||||||
|
#define LUA_MULTRET (-1)
|
||||||
|
|
||||||
|
/*
|
||||||
|
** pseudo-indices
|
||||||
|
*/
|
||||||
|
#define LUA_REGISTRYINDEX (-LUAI_MAXCSTACK - 2000)
|
||||||
|
#define LUA_ENVIRONINDEX (-LUAI_MAXCSTACK - 2001)
|
||||||
|
#define LUA_GLOBALSINDEX (-LUAI_MAXCSTACK - 2002)
|
||||||
|
#define lua_upvalueindex(i) (LUA_GLOBALSINDEX - (i))
|
||||||
|
#define lua_ispseudo(i) ((i) <= LUA_REGISTRYINDEX)
|
||||||
|
|
||||||
|
// thread status; 0 is OK
|
||||||
|
enum lua_Status
|
||||||
|
{
|
||||||
|
LUA_OK = 0,
|
||||||
|
LUA_YIELD,
|
||||||
|
LUA_ERRRUN,
|
||||||
|
LUA_ERRSYNTAX, // legacy error code, preserved for compatibility
|
||||||
|
LUA_ERRMEM,
|
||||||
|
LUA_ERRERR,
|
||||||
|
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);
|
||||||
|
typedef int (*lua_Continuation)(lua_State* L, int status);
|
||||||
|
|
||||||
|
/*
|
||||||
|
** prototype for memory-allocation functions
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef void* (*lua_Alloc)(void* ud, void* ptr, size_t osize, size_t nsize);
|
||||||
|
|
||||||
|
// non-return type
|
||||||
|
#define l_noret void LUA_NORETURN
|
||||||
|
|
||||||
|
/*
|
||||||
|
** basic types
|
||||||
|
*/
|
||||||
|
#define LUA_TNONE (-1)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* WARNING: if you change the order of this enumeration,
|
||||||
|
* grep "ORDER TYPE"
|
||||||
|
*/
|
||||||
|
// clang-format off
|
||||||
|
enum lua_Type
|
||||||
|
{
|
||||||
|
LUA_TNIL = 0, // must be 0 due to lua_isnoneornil
|
||||||
|
LUA_TBOOLEAN = 1, // must be 1 due to l_isfalse
|
||||||
|
|
||||||
|
|
||||||
|
LUA_TLIGHTUSERDATA,
|
||||||
|
LUA_TNUMBER,
|
||||||
|
LUA_TVECTOR,
|
||||||
|
|
||||||
|
LUA_TSTRING, // all types above this must be value types, all types below this must be GC types - see iscollectable
|
||||||
|
|
||||||
|
|
||||||
|
LUA_TTABLE,
|
||||||
|
LUA_TFUNCTION,
|
||||||
|
LUA_TUSERDATA,
|
||||||
|
LUA_TTHREAD,
|
||||||
|
|
||||||
|
// values below this line are used in GCObject tags but may never show up in TValue type tags
|
||||||
|
LUA_TPROTO,
|
||||||
|
LUA_TUPVAL,
|
||||||
|
LUA_TDEADKEY,
|
||||||
|
|
||||||
|
// the count of TValue type tags
|
||||||
|
LUA_T_COUNT = LUA_TPROTO
|
||||||
|
};
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
// type of numbers in Luau
|
||||||
|
typedef double lua_Number;
|
||||||
|
|
||||||
|
// type for integer functions
|
||||||
|
typedef int lua_Integer;
|
||||||
|
|
||||||
|
// unsigned integer type
|
||||||
|
typedef unsigned lua_Unsigned;
|
||||||
|
|
||||||
|
/*
|
||||||
|
** state manipulation
|
||||||
|
*/
|
||||||
|
LUA_API lua_State* lua_newstate(lua_Alloc f, void* ud);
|
||||||
|
LUA_API void lua_close(lua_State* L);
|
||||||
|
LUA_API lua_State* lua_newthread(lua_State* L);
|
||||||
|
LUA_API lua_State* lua_mainthread(lua_State* L);
|
||||||
|
LUA_API void lua_resetthread(lua_State* L);
|
||||||
|
LUA_API int lua_isthreadreset(lua_State* L);
|
||||||
|
|
||||||
|
/*
|
||||||
|
** basic stack manipulation
|
||||||
|
*/
|
||||||
|
LUA_API int lua_absindex(lua_State* L, int idx);
|
||||||
|
LUA_API int lua_gettop(lua_State* L);
|
||||||
|
LUA_API void lua_settop(lua_State* L, int idx);
|
||||||
|
LUA_API void lua_pushvalue(lua_State* L, int idx);
|
||||||
|
LUA_API void lua_remove(lua_State* L, int idx);
|
||||||
|
LUA_API void lua_insert(lua_State* L, int idx);
|
||||||
|
LUA_API void lua_replace(lua_State* L, int idx);
|
||||||
|
LUA_API int lua_checkstack(lua_State* L, int sz);
|
||||||
|
LUA_API void lua_rawcheckstack(lua_State* L, int sz); // allows for unlimited stack frames
|
||||||
|
|
||||||
|
LUA_API void lua_xmove(lua_State* from, lua_State* to, int n);
|
||||||
|
LUA_API void lua_xpush(lua_State* from, lua_State* to, int idx);
|
||||||
|
|
||||||
|
/*
|
||||||
|
** access functions (stack -> C)
|
||||||
|
*/
|
||||||
|
|
||||||
|
LUA_API int lua_isnumber(lua_State* L, int idx);
|
||||||
|
LUA_API int lua_isstring(lua_State* L, int idx);
|
||||||
|
LUA_API int lua_iscfunction(lua_State* L, int idx);
|
||||||
|
LUA_API int lua_isLfunction(lua_State* L, int idx);
|
||||||
|
LUA_API int lua_isuserdata(lua_State* L, int idx);
|
||||||
|
LUA_API int lua_type(lua_State* L, int idx);
|
||||||
|
LUA_API const char* lua_typename(lua_State* L, int tp);
|
||||||
|
|
||||||
|
LUA_API int lua_equal(lua_State* L, int idx1, int idx2);
|
||||||
|
LUA_API int lua_rawequal(lua_State* L, int idx1, int idx2);
|
||||||
|
LUA_API int lua_lessthan(lua_State* L, int idx1, int idx2);
|
||||||
|
|
||||||
|
LUA_API double lua_tonumberx(lua_State* L, int idx, int* isnum);
|
||||||
|
LUA_API int lua_tointegerx(lua_State* L, int idx, int* isnum);
|
||||||
|
LUA_API unsigned lua_tounsignedx(lua_State* L, int idx, int* isnum);
|
||||||
|
LUA_API const float* lua_tovector(lua_State* L, int idx);
|
||||||
|
LUA_API int lua_toboolean(lua_State* L, int idx);
|
||||||
|
LUA_API const char* lua_tolstring(lua_State* L, int idx, size_t* len);
|
||||||
|
LUA_API const char* lua_tostringatom(lua_State* L, int idx, int* atom);
|
||||||
|
LUA_API const char* lua_namecallatom(lua_State* L, int* atom);
|
||||||
|
LUA_API int lua_objlen(lua_State* L, int idx);
|
||||||
|
LUA_API lua_CFunction lua_tocfunction(lua_State* L, int idx);
|
||||||
|
LUA_API void* lua_tolightuserdata(lua_State* L, int idx);
|
||||||
|
LUA_API void* lua_touserdata(lua_State* L, int idx);
|
||||||
|
LUA_API void* lua_touserdatatagged(lua_State* L, int idx, int tag);
|
||||||
|
LUA_API int lua_userdatatag(lua_State* L, int idx);
|
||||||
|
LUA_API lua_State* lua_tothread(lua_State* L, int idx);
|
||||||
|
LUA_API const void* lua_topointer(lua_State* L, int idx);
|
||||||
|
|
||||||
|
/*
|
||||||
|
** push functions (C -> stack)
|
||||||
|
*/
|
||||||
|
LUA_API void lua_pushnil(lua_State* L);
|
||||||
|
LUA_API void lua_pushnumber(lua_State* L, double n);
|
||||||
|
LUA_API void lua_pushinteger(lua_State* L, int n);
|
||||||
|
LUA_API void lua_pushunsigned(lua_State* L, unsigned n);
|
||||||
|
#if LUA_VECTOR_SIZE == 4
|
||||||
|
LUA_API void lua_pushvector(lua_State* L, float x, float y, float z, float w);
|
||||||
|
#else
|
||||||
|
LUA_API void lua_pushvector(lua_State* L, float x, float y, float z);
|
||||||
|
#endif
|
||||||
|
LUA_API void lua_pushlstring(lua_State* L, const char* s, size_t l);
|
||||||
|
LUA_API void lua_pushstring(lua_State* L, const char* s);
|
||||||
|
LUA_API const char* lua_pushvfstring(lua_State* L, const char* fmt, va_list argp);
|
||||||
|
LUA_API LUA_PRINTF_ATTR(2, 3) const char* lua_pushfstringL(lua_State* L, const char* fmt, ...);
|
||||||
|
LUA_API void lua_pushcclosurek(lua_State* L, lua_CFunction fn, const char* debugname, int nup, lua_Continuation cont);
|
||||||
|
LUA_API void lua_pushboolean(lua_State* L, int b);
|
||||||
|
LUA_API int lua_pushthread(lua_State* L);
|
||||||
|
|
||||||
|
LUA_API void lua_pushlightuserdata(lua_State* L, void* p);
|
||||||
|
LUA_API void* lua_newuserdatatagged(lua_State* L, size_t sz, int tag);
|
||||||
|
LUA_API void* lua_newuserdatadtor(lua_State* L, size_t sz, void (*dtor)(void*));
|
||||||
|
|
||||||
|
/*
|
||||||
|
** get functions (Lua -> stack)
|
||||||
|
*/
|
||||||
|
LUA_API int lua_gettable(lua_State* L, int idx);
|
||||||
|
LUA_API int lua_getfield(lua_State* L, int idx, const char* k);
|
||||||
|
LUA_API int lua_rawgetfield(lua_State* L, int idx, const char* k);
|
||||||
|
LUA_API int lua_rawget(lua_State* L, int idx);
|
||||||
|
LUA_API int lua_rawgeti(lua_State* L, int idx, int n);
|
||||||
|
LUA_API void lua_createtable(lua_State* L, int narr, int nrec);
|
||||||
|
|
||||||
|
LUA_API void lua_setreadonly(lua_State* L, int idx, int enabled);
|
||||||
|
LUA_API int lua_getreadonly(lua_State* L, int idx);
|
||||||
|
LUA_API void lua_setsafeenv(lua_State* L, int idx, int enabled);
|
||||||
|
|
||||||
|
LUA_API int lua_getmetatable(lua_State* L, int objindex);
|
||||||
|
LUA_API void lua_getfenv(lua_State* L, int idx);
|
||||||
|
|
||||||
|
/*
|
||||||
|
** set functions (stack -> Lua)
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
LUA_API int lua_setfenv(lua_State* L, int idx);
|
||||||
|
|
||||||
|
/*
|
||||||
|
** `load' and `call' functions (load and run Luau bytecode)
|
||||||
|
*/
|
||||||
|
LUA_API int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size, int env);
|
||||||
|
LUA_API void lua_call(lua_State* L, int nargs, int nresults);
|
||||||
|
LUA_API int lua_pcall(lua_State* L, int nargs, int nresults, int errfunc);
|
||||||
|
|
||||||
|
/*
|
||||||
|
** coroutine functions
|
||||||
|
*/
|
||||||
|
LUA_API int lua_yield(lua_State* L, int nresults);
|
||||||
|
LUA_API int lua_break(lua_State* L);
|
||||||
|
LUA_API int lua_resume(lua_State* L, lua_State* from, int narg);
|
||||||
|
LUA_API int lua_resumeerror(lua_State* L, lua_State* from);
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
|
||||||
|
enum lua_GCOp
|
||||||
|
{
|
||||||
|
// stop and resume incremental garbage collection
|
||||||
|
LUA_GCSTOP,
|
||||||
|
LUA_GCRESTART,
|
||||||
|
|
||||||
|
// run a full GC cycle; not recommended for latency sensitive applications
|
||||||
|
LUA_GCCOLLECT,
|
||||||
|
|
||||||
|
// return the heap size in KB and the remainder in bytes
|
||||||
|
LUA_GCCOUNT,
|
||||||
|
LUA_GCCOUNTB,
|
||||||
|
|
||||||
|
// return 1 if GC is active (not stopped); note that GC may not be actively collecting even if it's running
|
||||||
|
LUA_GCISRUNNING,
|
||||||
|
|
||||||
|
/*
|
||||||
|
** perform an explicit GC step, with the step size specified in KB
|
||||||
|
**
|
||||||
|
** garbage collection is handled by 'assists' that perform some amount of GC work matching pace of allocation
|
||||||
|
** explicit GC steps allow to perform some amount of work at custom points to offset the need for GC assists
|
||||||
|
** note that GC might also be paused for some duration (until bytes allocated meet the threshold)
|
||||||
|
** if an explicit step is performed during this pause, it will trigger the start of the next collection cycle
|
||||||
|
*/
|
||||||
|
LUA_GCSTEP,
|
||||||
|
|
||||||
|
/*
|
||||||
|
** tune GC parameters G (goal), S (step multiplier) and step size (usually best left ignored)
|
||||||
|
**
|
||||||
|
** garbage collection is incremental and tries to maintain the heap size to balance memory and performance overhead
|
||||||
|
** this overhead is determined by G (goal) which is the ratio between total heap size and the amount of live data in it
|
||||||
|
** G is specified in percentages; by default G=200% which means that the heap is allowed to grow to ~2x the size of live data.
|
||||||
|
**
|
||||||
|
** collector tries to collect S% of allocated bytes by interrupting the application after step size bytes were allocated.
|
||||||
|
** when S is too small, collector may not be able to catch up and the effective goal that can be reached will be larger.
|
||||||
|
** S is specified in percentages; by default S=200% which means that collector will run at ~2x the pace of allocations.
|
||||||
|
**
|
||||||
|
** it is recommended to set S in the interval [100 / (G - 100), 100 + 100 / (G - 100))] with a minimum value of 150%; for example:
|
||||||
|
** - for G=200%, S should be in the interval [150%, 200%]
|
||||||
|
** - for G=150%, S should be in the interval [200%, 300%]
|
||||||
|
** - for G=125%, S should be in the interval [400%, 500%]
|
||||||
|
*/
|
||||||
|
LUA_GCSETGOAL,
|
||||||
|
LUA_GCSETSTEPMUL,
|
||||||
|
LUA_GCSETSTEPSIZE,
|
||||||
|
};
|
||||||
|
|
||||||
|
LUA_API int lua_gc(lua_State* L, int what, int data);
|
||||||
|
|
||||||
|
/*
|
||||||
|
** memory statistics
|
||||||
|
** all allocated bytes are attributed to the memory category of the running thread (0..LUA_MEMORY_CATEGORIES-1)
|
||||||
|
*/
|
||||||
|
|
||||||
|
LUA_API void lua_setmemcat(lua_State* L, int category);
|
||||||
|
LUA_API size_t lua_totalbytes(lua_State* L, int category);
|
||||||
|
|
||||||
|
/*
|
||||||
|
** miscellaneous functions
|
||||||
|
*/
|
||||||
|
|
||||||
|
LUA_API l_noret lua_error(lua_State* L);
|
||||||
|
|
||||||
|
LUA_API int lua_next(lua_State* L, int idx);
|
||||||
|
LUA_API int lua_rawiter(lua_State* L, int idx, int iter);
|
||||||
|
|
||||||
|
LUA_API void lua_concat(lua_State* L, int n);
|
||||||
|
|
||||||
|
LUA_API uintptr_t lua_encodepointer(lua_State* L, uintptr_t p);
|
||||||
|
|
||||||
|
LUA_API double lua_clock();
|
||||||
|
|
||||||
|
LUA_API void lua_setuserdatatag(lua_State* L, int idx, int tag);
|
||||||
|
LUA_API void lua_setuserdatadtor(lua_State* L, int tag, void (*dtor)(lua_State*, void*));
|
||||||
|
|
||||||
|
LUA_API void lua_clonefunction(lua_State* L, int idx);
|
||||||
|
|
||||||
|
LUA_API void lua_cleartable(lua_State* L, int idx);
|
||||||
|
|
||||||
|
/*
|
||||||
|
** reference system, can be used to pin objects
|
||||||
|
*/
|
||||||
|
#define LUA_NOREF -1
|
||||||
|
#define LUA_REFNIL 0
|
||||||
|
|
||||||
|
LUA_API int lua_ref(lua_State* L, int idx);
|
||||||
|
LUA_API void lua_unref(lua_State* L, int ref);
|
||||||
|
|
||||||
|
#define lua_getref(L, ref) lua_rawgeti(L, LUA_REGISTRYINDEX, (ref))
|
||||||
|
|
||||||
|
/*
|
||||||
|
** ===============================================================
|
||||||
|
** some useful macros
|
||||||
|
** ===============================================================
|
||||||
|
*/
|
||||||
|
#define lua_tonumber(L, i) lua_tonumberx(L, i, NULL)
|
||||||
|
#define lua_tointeger(L, i) lua_tointegerx(L, i, NULL)
|
||||||
|
#define lua_tounsigned(L, i) lua_tounsignedx(L, i, NULL)
|
||||||
|
|
||||||
|
#define lua_pop(L, n) lua_settop(L, -(n)-1)
|
||||||
|
|
||||||
|
#define lua_newtable(L) lua_createtable(L, 0, 0)
|
||||||
|
#define lua_newuserdata(L, s) lua_newuserdatatagged(L, s, 0)
|
||||||
|
|
||||||
|
#define lua_strlen(L, i) lua_objlen(L, (i))
|
||||||
|
|
||||||
|
#define lua_isfunction(L, n) (lua_type(L, (n)) == LUA_TFUNCTION)
|
||||||
|
#define lua_istable(L, n) (lua_type(L, (n)) == LUA_TTABLE)
|
||||||
|
#define lua_islightuserdata(L, n) (lua_type(L, (n)) == LUA_TLIGHTUSERDATA)
|
||||||
|
#define lua_isnil(L, n) (lua_type(L, (n)) == LUA_TNIL)
|
||||||
|
#define lua_isboolean(L, n) (lua_type(L, (n)) == LUA_TBOOLEAN)
|
||||||
|
#define lua_isvector(L, n) (lua_type(L, (n)) == LUA_TVECTOR)
|
||||||
|
#define lua_isthread(L, n) (lua_type(L, (n)) == LUA_TTHREAD)
|
||||||
|
#define lua_isnone(L, n) (lua_type(L, (n)) == LUA_TNONE)
|
||||||
|
#define lua_isnoneornil(L, n) (lua_type(L, (n)) <= LUA_TNIL)
|
||||||
|
|
||||||
|
#define lua_pushliteral(L, s) lua_pushlstring(L, "" s, (sizeof(s) / sizeof(char)) - 1)
|
||||||
|
#define lua_pushcfunction(L, fn, debugname) lua_pushcclosurek(L, fn, debugname, 0, NULL)
|
||||||
|
#define lua_pushcclosure(L, fn, debugname, nup) lua_pushcclosurek(L, fn, debugname, nup, NULL)
|
||||||
|
|
||||||
|
#define lua_setglobal(L, s) lua_setfield(L, LUA_GLOBALSINDEX, (s))
|
||||||
|
#define lua_getglobal(L, s) lua_getfield(L, LUA_GLOBALSINDEX, (s))
|
||||||
|
|
||||||
|
#define lua_tostring(L, i) lua_tolstring(L, (i), NULL)
|
||||||
|
|
||||||
|
#define lua_pushfstring(L, fmt, ...) lua_pushfstringL(L, fmt, ##__VA_ARGS__)
|
||||||
|
|
||||||
|
/*
|
||||||
|
** {======================================================================
|
||||||
|
** Debug API
|
||||||
|
** =======================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef struct lua_Debug lua_Debug; // activation record
|
||||||
|
|
||||||
|
// Functions to be called by the debugger in specific events
|
||||||
|
typedef void (*lua_Hook)(lua_State* L, lua_Debug* ar);
|
||||||
|
|
||||||
|
LUA_API int lua_stackdepth(lua_State* L);
|
||||||
|
LUA_API int lua_getinfo(lua_State* L, int level, const char* what, lua_Debug* ar);
|
||||||
|
LUA_API int lua_getargument(lua_State* L, int level, int n);
|
||||||
|
LUA_API const char* lua_getlocal(lua_State* L, int level, int n);
|
||||||
|
LUA_API const char* lua_setlocal(lua_State* L, int level, int n);
|
||||||
|
LUA_API const char* lua_getupvalue(lua_State* L, int funcindex, int n);
|
||||||
|
LUA_API const char* lua_setupvalue(lua_State* L, int funcindex, int n);
|
||||||
|
|
||||||
|
LUA_API void lua_singlestep(lua_State* L, int enabled);
|
||||||
|
LUA_API int lua_breakpoint(lua_State* L, int funcindex, int line, int enabled);
|
||||||
|
|
||||||
|
typedef void (*lua_Coverage)(void* context, const char* function, int linedefined, int depth, const int* hits, size_t size);
|
||||||
|
|
||||||
|
LUA_API void lua_getcoverage(lua_State* L, int funcindex, void* context, lua_Coverage callback);
|
||||||
|
|
||||||
|
// Warning: this function is not thread-safe since it stores the result in a shared global array! Only use for debugging.
|
||||||
|
LUA_API const char* lua_debugtrace(lua_State* L);
|
||||||
|
|
||||||
|
struct lua_Debug
|
||||||
|
{
|
||||||
|
const char* name; // (n)
|
||||||
|
const char* what; // (s) `Lua', `C', `main', `tail'
|
||||||
|
const char* source; // (s)
|
||||||
|
const char* short_src; // (s)
|
||||||
|
int linedefined; // (s)
|
||||||
|
int currentline; // (l)
|
||||||
|
unsigned char nupvals; // (u) number of upvalues
|
||||||
|
unsigned char nparams; // (a) number of parameters
|
||||||
|
char isvararg; // (a)
|
||||||
|
void* userdata; // only valid in luau_callhook
|
||||||
|
|
||||||
|
char ssbuf[LUA_IDSIZE];
|
||||||
|
};
|
||||||
|
|
||||||
|
// }======================================================================
|
||||||
|
|
||||||
|
/* Callbacks that can be used to reconfigure behavior of the VM dynamically.
|
||||||
|
* These are shared between all coroutines.
|
||||||
|
*
|
||||||
|
* Note: interrupt is safe to set from an arbitrary thread but all other callbacks
|
||||||
|
* can only be changed when the VM is not running any code */
|
||||||
|
struct lua_Callbacks
|
||||||
|
{
|
||||||
|
void* userdata; // arbitrary userdata pointer that is never overwritten by Luau
|
||||||
|
|
||||||
|
void (*interrupt)(lua_State* L, int gc); // gets called at safepoints (loop back edges, call/ret, gc) if set
|
||||||
|
void (*panic)(lua_State* L, int errcode); // gets called when an unprotected error is raised (if longjmp is used)
|
||||||
|
|
||||||
|
void (*userthread)(lua_State* LP, lua_State* L); // gets called when L is created (LP == parent) or destroyed (LP == NULL)
|
||||||
|
int16_t (*useratom)(const char* s, size_t l); // gets called when a string is created; returned atom can be retrieved via tostringatom
|
||||||
|
|
||||||
|
void (*debugbreak)(lua_State* L, lua_Debug* ar); // gets called when BREAK instruction is encountered
|
||||||
|
void (*debugstep)(lua_State* L, lua_Debug* ar); // gets called after each instruction in single step mode
|
||||||
|
void (*debuginterrupt)(lua_State* L, lua_Debug* ar); // gets called when thread execution is interrupted by break in another thread
|
||||||
|
void (*debugprotectederror)(lua_State* L); // gets called when protected call results in an error
|
||||||
|
};
|
||||||
|
typedef struct lua_Callbacks lua_Callbacks;
|
||||||
|
|
||||||
|
LUA_API lua_Callbacks* lua_callbacks(lua_State* L);
|
||||||
|
|
||||||
|
/******************************************************************************
|
||||||
|
* Copyright (c) 2019-2022 Roblox Corporation
|
||||||
|
* Copyright (C) 1994-2008 Lua.org, PUC-Rio. All rights reserved.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
* a copy of this software and associated documentation files (the
|
||||||
|
* "Software"), to deal in the Software without restriction, including
|
||||||
|
* without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
* permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
* the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||||
|
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||||
|
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
******************************************************************************/
|
|
@ -0,0 +1,148 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// When debugging complex issues, consider enabling one of these:
|
||||||
|
// This will reallocate the stack very aggressively at every opportunity; use this with asan to catch stale stack pointers
|
||||||
|
// #define HARDSTACKTESTS 1
|
||||||
|
// This will call GC validation very aggressively at every incremental GC step; use this with caution as it's SLOW
|
||||||
|
// #define HARDMEMTESTS 1
|
||||||
|
// This will call GC validation very aggressively at every GC opportunity; use this with caution as it's VERY SLOW
|
||||||
|
// #define HARDMEMTESTS 2
|
||||||
|
|
||||||
|
// To force MSVC2017+ to generate SSE2 code for some stdlib functions we need to locally enable /fp:fast
|
||||||
|
// Note that /fp:fast changes the semantics of floating point comparisons so this is only safe to do for functions without ones
|
||||||
|
#if defined(_MSC_VER) && !defined(__clang__)
|
||||||
|
#define LUAU_FASTMATH_BEGIN __pragma(float_control(precise, off, push))
|
||||||
|
#define LUAU_FASTMATH_END __pragma(float_control(pop))
|
||||||
|
#else
|
||||||
|
#define LUAU_FASTMATH_BEGIN
|
||||||
|
#define LUAU_FASTMATH_END
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Some functions like floor/ceil have SSE4.1 equivalents but we currently support systems without SSE4.1
|
||||||
|
// Note that we only need to do this when SSE4.1 support is not guaranteed by compiler settings, as otherwise compiler will optimize these for us.
|
||||||
|
#if (defined(__x86_64__) || defined(_M_X64)) && !defined(__SSE4_1__) && !defined(__AVX__)
|
||||||
|
#if defined(_MSC_VER) && !defined(__clang__)
|
||||||
|
#define LUAU_TARGET_SSE41
|
||||||
|
#elif defined(__GNUC__) && defined(__has_attribute)
|
||||||
|
#if __has_attribute(target)
|
||||||
|
#define LUAU_TARGET_SSE41 __attribute__((target("sse4.1")))
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Used on functions that have a printf-like interface to validate them statically
|
||||||
|
#if defined(__GNUC__)
|
||||||
|
#define LUA_PRINTF_ATTR(fmt, arg) __attribute__((format(printf, fmt, arg)))
|
||||||
|
#else
|
||||||
|
#define LUA_PRINTF_ATTR(fmt, arg)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#define LUA_NORETURN __declspec(noreturn)
|
||||||
|
#else
|
||||||
|
#define LUA_NORETURN __attribute__((__noreturn__))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Can be used to reconfigure visibility/exports for public APIs
|
||||||
|
#ifndef LUA_API
|
||||||
|
#define LUA_API extern
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define LUALIB_API LUA_API
|
||||||
|
|
||||||
|
// Can be used to reconfigure visibility for internal APIs
|
||||||
|
#if defined(__GNUC__)
|
||||||
|
#define LUAI_FUNC __attribute__((visibility("hidden"))) extern
|
||||||
|
#define LUAI_DATA LUAI_FUNC
|
||||||
|
#else
|
||||||
|
#define LUAI_FUNC extern
|
||||||
|
#define LUAI_DATA extern
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Can be used to reconfigure internal error handling to use longjmp instead of C++ EH
|
||||||
|
#ifndef LUA_USE_LONGJMP
|
||||||
|
#define LUA_USE_LONGJMP 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// LUA_IDSIZE gives the maximum size for the description of the source
|
||||||
|
#ifndef LUA_IDSIZE
|
||||||
|
#define LUA_IDSIZE 256
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// LUA_MINSTACK is the guaranteed number of Lua stack slots available to a C function
|
||||||
|
#ifndef LUA_MINSTACK
|
||||||
|
#define LUA_MINSTACK 20
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// LUAI_MAXCSTACK limits the number of Lua stack slots that a C function can use
|
||||||
|
#ifndef LUAI_MAXCSTACK
|
||||||
|
#define LUAI_MAXCSTACK 8000
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// LUAI_MAXCALLS limits the number of nested calls
|
||||||
|
#ifndef LUAI_MAXCALLS
|
||||||
|
#define LUAI_MAXCALLS 20000
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// LUAI_MAXCCALLS is the maximum depth for nested C calls; this limit depends on native stack size
|
||||||
|
#ifndef LUAI_MAXCCALLS
|
||||||
|
#define LUAI_MAXCCALLS 200
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// buffer size used for on-stack string operations; this limit depends on native stack size
|
||||||
|
#ifndef LUA_BUFFERSIZE
|
||||||
|
#define LUA_BUFFERSIZE 512
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// number of valid Lua userdata tags
|
||||||
|
#ifndef LUA_UTAG_LIMIT
|
||||||
|
#define LUA_UTAG_LIMIT 128
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// upper bound for number of size classes used by page allocator
|
||||||
|
#ifndef LUA_SIZECLASSES
|
||||||
|
#define LUA_SIZECLASSES 32
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// available number of separate memory categories
|
||||||
|
#ifndef LUA_MEMORY_CATEGORIES
|
||||||
|
#define LUA_MEMORY_CATEGORIES 256
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// minimum size for the string table (must be power of 2)
|
||||||
|
#ifndef LUA_MINSTRTABSIZE
|
||||||
|
#define LUA_MINSTRTABSIZE 32
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// maximum number of captures supported by pattern matching
|
||||||
|
#ifndef LUA_MAXCAPTURES
|
||||||
|
#define LUA_MAXCAPTURES 32
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// enables callbacks to redirect code execution from Luau VM to a custom implementation
|
||||||
|
#ifndef LUA_CUSTOM_EXECUTION
|
||||||
|
#define LUA_CUSTOM_EXECUTION 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// }==================================================================
|
||||||
|
|
||||||
|
/*
|
||||||
|
@@ LUAI_USER_ALIGNMENT_T is a type that requires maximum alignment.
|
||||||
|
** CHANGE it if your system requires alignments larger than double. (For
|
||||||
|
** instance, if your system supports long doubles and they must be
|
||||||
|
** aligned in 16-byte boundaries, then you should add long double in the
|
||||||
|
** union.) Probably you do not need to change this.
|
||||||
|
*/
|
||||||
|
#define LUAI_USER_ALIGNMENT_T \
|
||||||
|
union \
|
||||||
|
{ \
|
||||||
|
double u; \
|
||||||
|
void* s; \
|
||||||
|
long l; \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define LUA_VECTOR_SIZE 3 // must be 3 or 4
|
||||||
|
|
||||||
|
#define LUA_EXTRA_SIZE (LUA_VECTOR_SIZE - 2)
|
|
@ -0,0 +1,137 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "lua.h"
|
||||||
|
|
||||||
|
#define luaL_error(L, fmt, ...) luaL_errorL(L, fmt, ##__VA_ARGS__)
|
||||||
|
#define luaL_typeerror(L, narg, tname) luaL_typeerrorL(L, narg, tname)
|
||||||
|
#define luaL_argerror(L, narg, extramsg) luaL_argerrorL(L, narg, extramsg)
|
||||||
|
|
||||||
|
struct luaL_Reg
|
||||||
|
{
|
||||||
|
const char* name;
|
||||||
|
lua_CFunction func;
|
||||||
|
};
|
||||||
|
typedef struct luaL_Reg luaL_Reg;
|
||||||
|
|
||||||
|
LUALIB_API void luaL_register(lua_State* L, const char* libname, const luaL_Reg* l);
|
||||||
|
LUALIB_API int luaL_getmetafield(lua_State* L, int obj, const char* e);
|
||||||
|
LUALIB_API int luaL_callmeta(lua_State* L, int obj, const char* e);
|
||||||
|
LUALIB_API l_noret luaL_typeerrorL(lua_State* L, int narg, const char* tname);
|
||||||
|
LUALIB_API l_noret luaL_argerrorL(lua_State* L, int narg, const char* extramsg);
|
||||||
|
LUALIB_API const char* luaL_checklstring(lua_State* L, int numArg, size_t* l);
|
||||||
|
LUALIB_API const char* luaL_optlstring(lua_State* L, int numArg, const char* def, size_t* l);
|
||||||
|
LUALIB_API double luaL_checknumber(lua_State* L, int numArg);
|
||||||
|
LUALIB_API double luaL_optnumber(lua_State* L, int nArg, double def);
|
||||||
|
|
||||||
|
LUALIB_API int luaL_checkboolean(lua_State* L, int narg);
|
||||||
|
LUALIB_API int luaL_optboolean(lua_State* L, int narg, int def);
|
||||||
|
|
||||||
|
LUALIB_API int luaL_checkinteger(lua_State* L, int numArg);
|
||||||
|
LUALIB_API int luaL_optinteger(lua_State* L, int nArg, int def);
|
||||||
|
LUALIB_API unsigned luaL_checkunsigned(lua_State* L, int numArg);
|
||||||
|
LUALIB_API unsigned luaL_optunsigned(lua_State* L, int numArg, unsigned def);
|
||||||
|
|
||||||
|
LUALIB_API const float* luaL_checkvector(lua_State* L, int narg);
|
||||||
|
LUALIB_API const float* luaL_optvector(lua_State* L, int narg, const float* def);
|
||||||
|
|
||||||
|
LUALIB_API void luaL_checkstack(lua_State* L, int sz, const char* msg);
|
||||||
|
LUALIB_API void luaL_checktype(lua_State* L, int narg, int t);
|
||||||
|
LUALIB_API void luaL_checkany(lua_State* L, int narg);
|
||||||
|
|
||||||
|
LUALIB_API int luaL_newmetatable(lua_State* L, const char* tname);
|
||||||
|
LUALIB_API void* luaL_checkudata(lua_State* L, int ud, const char* tname);
|
||||||
|
|
||||||
|
LUALIB_API void luaL_where(lua_State* L, int lvl);
|
||||||
|
LUALIB_API LUA_PRINTF_ATTR(2, 3) l_noret luaL_errorL(lua_State* L, const char* fmt, ...);
|
||||||
|
|
||||||
|
LUALIB_API int luaL_checkoption(lua_State* L, int narg, const char* def, const char* const lst[]);
|
||||||
|
|
||||||
|
LUALIB_API const char* luaL_tolstring(lua_State* L, int idx, size_t* len);
|
||||||
|
|
||||||
|
LUALIB_API lua_State* luaL_newstate(void);
|
||||||
|
|
||||||
|
LUALIB_API const char* luaL_findtable(lua_State* L, int idx, const char* fname, int szhint);
|
||||||
|
|
||||||
|
LUALIB_API const char* luaL_typename(lua_State* L, int idx);
|
||||||
|
|
||||||
|
/*
|
||||||
|
** ===============================================================
|
||||||
|
** some useful macros
|
||||||
|
** ===============================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define luaL_argcheck(L, cond, arg, extramsg) ((void)((cond) ? (void)0 : luaL_argerror(L, arg, extramsg)))
|
||||||
|
#define luaL_argexpected(L, cond, arg, tname) ((void)((cond) ? (void)0 : luaL_typeerror(L, arg, tname)))
|
||||||
|
|
||||||
|
#define luaL_checkstring(L, n) (luaL_checklstring(L, (n), NULL))
|
||||||
|
#define luaL_optstring(L, n, d) (luaL_optlstring(L, (n), (d), NULL))
|
||||||
|
|
||||||
|
#define luaL_getmetatable(L, n) (lua_getfield(L, LUA_REGISTRYINDEX, (n)))
|
||||||
|
|
||||||
|
#define luaL_opt(L, f, n, d) (lua_isnoneornil(L, (n)) ? (d) : f(L, (n)))
|
||||||
|
|
||||||
|
// generic buffer manipulation
|
||||||
|
|
||||||
|
struct luaL_Buffer
|
||||||
|
{
|
||||||
|
char* p; // current position in buffer
|
||||||
|
char* end; // end of the current buffer
|
||||||
|
lua_State* L;
|
||||||
|
struct TString* storage;
|
||||||
|
char buffer[LUA_BUFFERSIZE];
|
||||||
|
};
|
||||||
|
typedef struct luaL_Buffer luaL_Buffer;
|
||||||
|
|
||||||
|
// when internal buffer storage is exhausted, a mutable string value 'storage' will be placed on the stack
|
||||||
|
// in general, functions expect the mutable string buffer to be placed on top of the stack (top-1)
|
||||||
|
// with the exception of luaL_addvalue that expects the value at the top and string buffer further away (top-2)
|
||||||
|
// functions that accept a 'boxloc' support string buffer placement at any location in the stack
|
||||||
|
// all the buffer users we have in Luau match this pattern, but it's something to keep in mind for new uses of buffers
|
||||||
|
|
||||||
|
#define luaL_addchar(B, c) ((void)((B)->p < (B)->end || luaL_extendbuffer(B, 1, -1)), (*(B)->p++ = (char)(c)))
|
||||||
|
#define luaL_addstring(B, s) luaL_addlstring(B, s, strlen(s), -1)
|
||||||
|
|
||||||
|
LUALIB_API void luaL_buffinit(lua_State* L, luaL_Buffer* B);
|
||||||
|
LUALIB_API char* luaL_buffinitsize(lua_State* L, luaL_Buffer* B, size_t size);
|
||||||
|
LUALIB_API char* luaL_extendbuffer(luaL_Buffer* B, size_t additionalsize, int boxloc);
|
||||||
|
LUALIB_API void luaL_reservebuffer(luaL_Buffer* B, size_t size, int boxloc);
|
||||||
|
LUALIB_API void luaL_addlstring(luaL_Buffer* B, const char* s, size_t l, int boxloc);
|
||||||
|
LUALIB_API void luaL_addvalue(luaL_Buffer* B);
|
||||||
|
LUALIB_API void luaL_pushresult(luaL_Buffer* B);
|
||||||
|
LUALIB_API void luaL_pushresultsize(luaL_Buffer* B, size_t size);
|
||||||
|
|
||||||
|
// builtin libraries
|
||||||
|
LUALIB_API int luaopen_base(lua_State* L);
|
||||||
|
|
||||||
|
#define LUA_COLIBNAME "coroutine"
|
||||||
|
LUALIB_API int luaopen_coroutine(lua_State* L);
|
||||||
|
|
||||||
|
#define LUA_TABLIBNAME "table"
|
||||||
|
LUALIB_API int luaopen_table(lua_State* L);
|
||||||
|
|
||||||
|
#define LUA_OSLIBNAME "os"
|
||||||
|
LUALIB_API int luaopen_os(lua_State* L);
|
||||||
|
|
||||||
|
#define LUA_STRLIBNAME "string"
|
||||||
|
LUALIB_API int luaopen_string(lua_State* L);
|
||||||
|
|
||||||
|
#define LUA_BITLIBNAME "bit32"
|
||||||
|
LUALIB_API int luaopen_bit32(lua_State* L);
|
||||||
|
|
||||||
|
#define LUA_UTF8LIBNAME "utf8"
|
||||||
|
LUALIB_API int luaopen_utf8(lua_State* L);
|
||||||
|
|
||||||
|
#define LUA_MATHLIBNAME "math"
|
||||||
|
LUALIB_API int luaopen_math(lua_State* L);
|
||||||
|
|
||||||
|
#define LUA_DBLIBNAME "debug"
|
||||||
|
LUALIB_API int luaopen_debug(lua_State* L);
|
||||||
|
|
||||||
|
// open all builtin libraries
|
||||||
|
LUALIB_API void luaL_openlibs(lua_State* L);
|
||||||
|
|
||||||
|
// sandbox libraries and globals
|
||||||
|
LUALIB_API void luaL_sandbox(lua_State* L);
|
||||||
|
LUALIB_API void luaL_sandboxthread(lua_State* L);
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,8 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "lobject.h"
|
||||||
|
|
||||||
|
LUAI_FUNC const TValue* luaA_toobject(lua_State* L, int idx);
|
||||||
|
LUAI_FUNC void luaA_pushobject(lua_State* L, const TValue* o);
|
|
@ -0,0 +1,530 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
||||||
|
#include "lualib.h"
|
||||||
|
|
||||||
|
#include "lobject.h"
|
||||||
|
#include "lstate.h"
|
||||||
|
#include "lstring.h"
|
||||||
|
#include "lapi.h"
|
||||||
|
#include "lgc.h"
|
||||||
|
#include "lnumutils.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
// convert a stack index to positive
|
||||||
|
#define abs_index(L, i) ((i) > 0 || (i) <= LUA_REGISTRYINDEX ? (i) : lua_gettop(L) + (i) + 1)
|
||||||
|
|
||||||
|
/*
|
||||||
|
** {======================================================
|
||||||
|
** Error-report functions
|
||||||
|
** =======================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
static const char* currfuncname(lua_State* L)
|
||||||
|
{
|
||||||
|
Closure* cl = L->ci > L->base_ci ? curr_func(L) : NULL;
|
||||||
|
const char* debugname = cl && cl->isC ? cl->c.debugname + 0 : NULL;
|
||||||
|
|
||||||
|
if (debugname && strcmp(debugname, "__namecall") == 0)
|
||||||
|
return L->namecall ? getstr(L->namecall) : NULL;
|
||||||
|
else
|
||||||
|
return debugname;
|
||||||
|
}
|
||||||
|
|
||||||
|
l_noret luaL_argerrorL(lua_State* L, int narg, const char* extramsg)
|
||||||
|
{
|
||||||
|
const char* fname = currfuncname(L);
|
||||||
|
|
||||||
|
if (fname)
|
||||||
|
luaL_error(L, "invalid argument #%d to '%s' (%s)", narg, fname, extramsg);
|
||||||
|
else
|
||||||
|
luaL_error(L, "invalid argument #%d (%s)", narg, extramsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
l_noret luaL_typeerrorL(lua_State* L, int narg, const char* tname)
|
||||||
|
{
|
||||||
|
const char* fname = currfuncname(L);
|
||||||
|
const TValue* obj = luaA_toobject(L, narg);
|
||||||
|
|
||||||
|
if (obj)
|
||||||
|
{
|
||||||
|
if (fname)
|
||||||
|
luaL_error(L, "invalid argument #%d to '%s' (%s expected, got %s)", narg, fname, tname, luaT_objtypename(L, obj));
|
||||||
|
else
|
||||||
|
luaL_error(L, "invalid argument #%d (%s expected, got %s)", narg, tname, luaT_objtypename(L, obj));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (fname)
|
||||||
|
luaL_error(L, "missing argument #%d to '%s' (%s expected)", narg, fname, tname);
|
||||||
|
else
|
||||||
|
luaL_error(L, "missing argument #%d (%s expected)", narg, tname);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static l_noret tag_error(lua_State* L, int narg, int tag)
|
||||||
|
{
|
||||||
|
luaL_typeerrorL(L, narg, lua_typename(L, tag));
|
||||||
|
}
|
||||||
|
|
||||||
|
void luaL_where(lua_State* L, int level)
|
||||||
|
{
|
||||||
|
lua_Debug ar;
|
||||||
|
if (lua_getinfo(L, level, "sl", &ar) && ar.currentline > 0)
|
||||||
|
{
|
||||||
|
lua_pushfstring(L, "%s:%d: ", ar.short_src, ar.currentline);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lua_pushliteral(L, ""); // else, no information available...
|
||||||
|
}
|
||||||
|
|
||||||
|
l_noret luaL_errorL(lua_State* L, const char* fmt, ...)
|
||||||
|
{
|
||||||
|
va_list argp;
|
||||||
|
va_start(argp, fmt);
|
||||||
|
luaL_where(L, 1);
|
||||||
|
lua_pushvfstring(L, fmt, argp);
|
||||||
|
va_end(argp);
|
||||||
|
lua_concat(L, 2);
|
||||||
|
lua_error(L);
|
||||||
|
}
|
||||||
|
|
||||||
|
// }======================================================
|
||||||
|
|
||||||
|
int luaL_checkoption(lua_State* L, int narg, const char* def, const char* const lst[])
|
||||||
|
{
|
||||||
|
const char* name = (def) ? luaL_optstring(L, narg, def) : luaL_checkstring(L, narg);
|
||||||
|
int i;
|
||||||
|
for (i = 0; lst[i]; i++)
|
||||||
|
if (strcmp(lst[i], name) == 0)
|
||||||
|
return i;
|
||||||
|
const char* msg = lua_pushfstring(L, "invalid option '%s'", name);
|
||||||
|
luaL_argerrorL(L, narg, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
int luaL_newmetatable(lua_State* L, const char* tname)
|
||||||
|
{
|
||||||
|
lua_getfield(L, LUA_REGISTRYINDEX, tname); // get registry.name
|
||||||
|
if (!lua_isnil(L, -1)) // name already in use?
|
||||||
|
return 0; // leave previous value on top, but return 0
|
||||||
|
lua_pop(L, 1);
|
||||||
|
lua_newtable(L); // create metatable
|
||||||
|
lua_pushvalue(L, -1);
|
||||||
|
lua_setfield(L, LUA_REGISTRYINDEX, tname); // registry.name = metatable
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* luaL_checkudata(lua_State* L, int ud, const char* tname)
|
||||||
|
{
|
||||||
|
void* p = lua_touserdata(L, ud);
|
||||||
|
if (p != NULL)
|
||||||
|
{ // value is a userdata?
|
||||||
|
if (lua_getmetatable(L, ud))
|
||||||
|
{ // does it have a metatable?
|
||||||
|
lua_getfield(L, LUA_REGISTRYINDEX, tname); // get correct metatable
|
||||||
|
if (lua_rawequal(L, -1, -2))
|
||||||
|
{ // does it have the correct mt?
|
||||||
|
lua_pop(L, 2); // remove both metatables
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
luaL_typeerrorL(L, ud, tname); // else error
|
||||||
|
}
|
||||||
|
|
||||||
|
void luaL_checkstack(lua_State* L, int space, const char* mes)
|
||||||
|
{
|
||||||
|
if (!lua_checkstack(L, space))
|
||||||
|
luaL_error(L, "stack overflow (%s)", mes);
|
||||||
|
}
|
||||||
|
|
||||||
|
void luaL_checktype(lua_State* L, int narg, int t)
|
||||||
|
{
|
||||||
|
if (lua_type(L, narg) != t)
|
||||||
|
tag_error(L, narg, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
void luaL_checkany(lua_State* L, int narg)
|
||||||
|
{
|
||||||
|
if (lua_type(L, narg) == LUA_TNONE)
|
||||||
|
luaL_error(L, "missing argument #%d", narg);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* luaL_checklstring(lua_State* L, int narg, size_t* len)
|
||||||
|
{
|
||||||
|
const char* s = lua_tolstring(L, narg, len);
|
||||||
|
if (!s)
|
||||||
|
tag_error(L, narg, LUA_TSTRING);
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* luaL_optlstring(lua_State* L, int narg, const char* def, size_t* len)
|
||||||
|
{
|
||||||
|
if (lua_isnoneornil(L, narg))
|
||||||
|
{
|
||||||
|
if (len)
|
||||||
|
*len = (def ? strlen(def) : 0);
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return luaL_checklstring(L, narg, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
double luaL_checknumber(lua_State* L, int narg)
|
||||||
|
{
|
||||||
|
int isnum;
|
||||||
|
double d = lua_tonumberx(L, narg, &isnum);
|
||||||
|
if (!isnum)
|
||||||
|
tag_error(L, narg, LUA_TNUMBER);
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
double luaL_optnumber(lua_State* L, int narg, double def)
|
||||||
|
{
|
||||||
|
return luaL_opt(L, luaL_checknumber, narg, def);
|
||||||
|
}
|
||||||
|
|
||||||
|
int luaL_checkboolean(lua_State* L, int narg)
|
||||||
|
{
|
||||||
|
// This checks specifically for boolean values, ignoring
|
||||||
|
// all other truthy/falsy values. If the desired result
|
||||||
|
// is true if value is present then lua_toboolean should
|
||||||
|
// directly be used instead.
|
||||||
|
if (!lua_isboolean(L, narg))
|
||||||
|
tag_error(L, narg, LUA_TBOOLEAN);
|
||||||
|
return lua_toboolean(L, narg);
|
||||||
|
}
|
||||||
|
|
||||||
|
int luaL_optboolean(lua_State* L, int narg, int def)
|
||||||
|
{
|
||||||
|
return luaL_opt(L, luaL_checkboolean, narg, def);
|
||||||
|
}
|
||||||
|
|
||||||
|
int luaL_checkinteger(lua_State* L, int narg)
|
||||||
|
{
|
||||||
|
int isnum;
|
||||||
|
int d = lua_tointegerx(L, narg, &isnum);
|
||||||
|
if (!isnum)
|
||||||
|
tag_error(L, narg, LUA_TNUMBER);
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
int luaL_optinteger(lua_State* L, int narg, int def)
|
||||||
|
{
|
||||||
|
return luaL_opt(L, luaL_checkinteger, narg, def);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned luaL_checkunsigned(lua_State* L, int narg)
|
||||||
|
{
|
||||||
|
int isnum;
|
||||||
|
unsigned d = lua_tounsignedx(L, narg, &isnum);
|
||||||
|
if (!isnum)
|
||||||
|
tag_error(L, narg, LUA_TNUMBER);
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned luaL_optunsigned(lua_State* L, int narg, unsigned def)
|
||||||
|
{
|
||||||
|
return luaL_opt(L, luaL_checkunsigned, narg, def);
|
||||||
|
}
|
||||||
|
|
||||||
|
const float* luaL_checkvector(lua_State* L, int narg)
|
||||||
|
{
|
||||||
|
const float* v = lua_tovector(L, narg);
|
||||||
|
if (!v)
|
||||||
|
tag_error(L, narg, LUA_TVECTOR);
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float* luaL_optvector(lua_State* L, int narg, const float* def)
|
||||||
|
{
|
||||||
|
return luaL_opt(L, luaL_checkvector, narg, def);
|
||||||
|
}
|
||||||
|
|
||||||
|
int luaL_getmetafield(lua_State* L, int obj, const char* event)
|
||||||
|
{
|
||||||
|
if (!lua_getmetatable(L, obj)) // no metatable?
|
||||||
|
return 0;
|
||||||
|
lua_pushstring(L, event);
|
||||||
|
lua_rawget(L, -2);
|
||||||
|
if (lua_isnil(L, -1))
|
||||||
|
{
|
||||||
|
lua_pop(L, 2); // remove metatable and metafield
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lua_remove(L, -2); // remove only metatable
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int luaL_callmeta(lua_State* L, int obj, const char* event)
|
||||||
|
{
|
||||||
|
obj = abs_index(L, obj);
|
||||||
|
if (!luaL_getmetafield(L, obj, event)) // no metafield?
|
||||||
|
return 0;
|
||||||
|
lua_pushvalue(L, obj);
|
||||||
|
lua_call(L, 1, 1);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int libsize(const luaL_Reg* l)
|
||||||
|
{
|
||||||
|
int size = 0;
|
||||||
|
for (; l->name; l++)
|
||||||
|
size++;
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
void luaL_register(lua_State* L, const char* libname, const luaL_Reg* l)
|
||||||
|
{
|
||||||
|
if (libname)
|
||||||
|
{
|
||||||
|
int size = libsize(l);
|
||||||
|
// check whether lib already exists
|
||||||
|
luaL_findtable(L, LUA_REGISTRYINDEX, "_LOADED", 1);
|
||||||
|
lua_getfield(L, -1, libname); // get _LOADED[libname]
|
||||||
|
if (!lua_istable(L, -1))
|
||||||
|
{ // not found?
|
||||||
|
lua_pop(L, 1); // remove previous result
|
||||||
|
// try global variable (and create one if it does not exist)
|
||||||
|
if (luaL_findtable(L, LUA_GLOBALSINDEX, libname, size) != NULL)
|
||||||
|
luaL_error(L, "name conflict for module '%s'", libname);
|
||||||
|
lua_pushvalue(L, -1);
|
||||||
|
lua_setfield(L, -3, libname); // _LOADED[libname] = new table
|
||||||
|
}
|
||||||
|
lua_remove(L, -2); // remove _LOADED table
|
||||||
|
}
|
||||||
|
for (; l->name; l++)
|
||||||
|
{
|
||||||
|
lua_pushcfunction(L, l->func, l->name);
|
||||||
|
lua_setfield(L, -2, l->name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* luaL_findtable(lua_State* L, int idx, const char* fname, int szhint)
|
||||||
|
{
|
||||||
|
const char* e;
|
||||||
|
lua_pushvalue(L, idx);
|
||||||
|
do
|
||||||
|
{
|
||||||
|
e = strchr(fname, '.');
|
||||||
|
if (e == NULL)
|
||||||
|
e = fname + strlen(fname);
|
||||||
|
lua_pushlstring(L, fname, e - fname);
|
||||||
|
lua_rawget(L, -2);
|
||||||
|
if (lua_isnil(L, -1))
|
||||||
|
{ // no such field?
|
||||||
|
lua_pop(L, 1); // remove this nil
|
||||||
|
lua_createtable(L, 0, (*e == '.' ? 1 : szhint)); // new table for field
|
||||||
|
lua_pushlstring(L, fname, e - fname);
|
||||||
|
lua_pushvalue(L, -2);
|
||||||
|
lua_settable(L, -4); // set new table into field
|
||||||
|
}
|
||||||
|
else if (!lua_istable(L, -1))
|
||||||
|
{ // field has a non-table value?
|
||||||
|
lua_pop(L, 2); // remove table and value
|
||||||
|
return fname; // return problematic part of the name
|
||||||
|
}
|
||||||
|
lua_remove(L, -2); // remove previous table
|
||||||
|
fname = e + 1;
|
||||||
|
} while (*e == '.');
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* luaL_typename(lua_State* L, int idx)
|
||||||
|
{
|
||||||
|
const TValue* obj = luaA_toobject(L, idx);
|
||||||
|
return luaT_objtypename(L, obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** {======================================================
|
||||||
|
** Generic Buffer manipulation
|
||||||
|
** =======================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
static size_t getnextbuffersize(lua_State* L, size_t currentsize, size_t desiredsize)
|
||||||
|
{
|
||||||
|
size_t newsize = currentsize + currentsize / 2;
|
||||||
|
|
||||||
|
// check for size overflow
|
||||||
|
if (SIZE_MAX - desiredsize < currentsize)
|
||||||
|
luaL_error(L, "buffer too large");
|
||||||
|
|
||||||
|
// growth factor might not be enough to satisfy the desired size
|
||||||
|
if (newsize < desiredsize)
|
||||||
|
newsize = desiredsize;
|
||||||
|
|
||||||
|
return newsize;
|
||||||
|
}
|
||||||
|
|
||||||
|
void luaL_buffinit(lua_State* L, luaL_Buffer* B)
|
||||||
|
{
|
||||||
|
// start with an internal buffer
|
||||||
|
B->p = B->buffer;
|
||||||
|
B->end = B->p + LUA_BUFFERSIZE;
|
||||||
|
|
||||||
|
B->L = L;
|
||||||
|
B->storage = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* luaL_buffinitsize(lua_State* L, luaL_Buffer* B, size_t size)
|
||||||
|
{
|
||||||
|
luaL_buffinit(L, B);
|
||||||
|
luaL_reservebuffer(B, size, -1);
|
||||||
|
return B->p;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* luaL_extendbuffer(luaL_Buffer* B, size_t additionalsize, int boxloc)
|
||||||
|
{
|
||||||
|
lua_State* L = B->L;
|
||||||
|
|
||||||
|
if (B->storage)
|
||||||
|
LUAU_ASSERT(B->storage == tsvalue(L->top + boxloc));
|
||||||
|
|
||||||
|
char* base = B->storage ? B->storage->data : B->buffer;
|
||||||
|
|
||||||
|
size_t capacity = B->end - base;
|
||||||
|
size_t nextsize = getnextbuffersize(B->L, capacity, capacity + additionalsize);
|
||||||
|
|
||||||
|
TString* newStorage = luaS_bufstart(L, nextsize);
|
||||||
|
|
||||||
|
memcpy(newStorage->data, base, B->p - base);
|
||||||
|
|
||||||
|
// place the string storage at the expected position in the stack
|
||||||
|
if (base == B->buffer)
|
||||||
|
{
|
||||||
|
lua_pushnil(L);
|
||||||
|
lua_insert(L, boxloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
setsvalue(L, L->top + boxloc, newStorage);
|
||||||
|
B->p = newStorage->data + (B->p - base);
|
||||||
|
B->end = newStorage->data + nextsize;
|
||||||
|
B->storage = newStorage;
|
||||||
|
|
||||||
|
return B->p;
|
||||||
|
}
|
||||||
|
|
||||||
|
void luaL_reservebuffer(luaL_Buffer* B, size_t size, int boxloc)
|
||||||
|
{
|
||||||
|
if (size_t(B->end - B->p) < size)
|
||||||
|
luaL_extendbuffer(B, size - (B->end - B->p), boxloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
void luaL_addlstring(luaL_Buffer* B, const char* s, size_t len, int boxloc)
|
||||||
|
{
|
||||||
|
if (size_t(B->end - B->p) < len)
|
||||||
|
luaL_extendbuffer(B, len - (B->end - B->p), boxloc);
|
||||||
|
|
||||||
|
memcpy(B->p, s, len);
|
||||||
|
B->p += len;
|
||||||
|
}
|
||||||
|
|
||||||
|
void luaL_addvalue(luaL_Buffer* B)
|
||||||
|
{
|
||||||
|
lua_State* L = B->L;
|
||||||
|
|
||||||
|
size_t vl;
|
||||||
|
if (const char* s = lua_tolstring(L, -1, &vl))
|
||||||
|
{
|
||||||
|
if (size_t(B->end - B->p) < vl)
|
||||||
|
luaL_extendbuffer(B, vl - (B->end - B->p), -2);
|
||||||
|
|
||||||
|
memcpy(B->p, s, vl);
|
||||||
|
B->p += vl;
|
||||||
|
|
||||||
|
lua_pop(L, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void luaL_pushresult(luaL_Buffer* B)
|
||||||
|
{
|
||||||
|
lua_State* L = B->L;
|
||||||
|
|
||||||
|
if (TString* storage = B->storage)
|
||||||
|
{
|
||||||
|
luaC_checkGC(L);
|
||||||
|
|
||||||
|
// if we finished just at the end of the string buffer, we can convert it to a mutable stirng without a copy
|
||||||
|
if (B->p == B->end)
|
||||||
|
{
|
||||||
|
setsvalue(L, L->top - 1, luaS_buffinish(L, storage));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setsvalue(L, L->top - 1, luaS_newlstr(L, storage->data, B->p - storage->data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lua_pushlstring(L, B->buffer, B->p - B->buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void luaL_pushresultsize(luaL_Buffer* B, size_t size)
|
||||||
|
{
|
||||||
|
B->p += size;
|
||||||
|
luaL_pushresult(B);
|
||||||
|
}
|
||||||
|
|
||||||
|
// }======================================================
|
||||||
|
|
||||||
|
const char* luaL_tolstring(lua_State* L, int idx, size_t* len)
|
||||||
|
{
|
||||||
|
if (luaL_callmeta(L, idx, "__tostring")) // is there a metafield?
|
||||||
|
{
|
||||||
|
if (!lua_isstring(L, -1))
|
||||||
|
luaL_error(L, "'__tostring' must return a string");
|
||||||
|
return lua_tolstring(L, -1, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (lua_type(L, idx))
|
||||||
|
{
|
||||||
|
case LUA_TNUMBER:
|
||||||
|
{
|
||||||
|
double n = lua_tonumber(L, idx);
|
||||||
|
char s[LUAI_MAXNUM2STR];
|
||||||
|
char* e = luai_num2str(s, n);
|
||||||
|
lua_pushlstring(L, s, e - s);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case LUA_TSTRING:
|
||||||
|
lua_pushvalue(L, idx);
|
||||||
|
break;
|
||||||
|
case LUA_TBOOLEAN:
|
||||||
|
lua_pushstring(L, (lua_toboolean(L, idx) ? "true" : "false"));
|
||||||
|
break;
|
||||||
|
case LUA_TNIL:
|
||||||
|
lua_pushliteral(L, "nil");
|
||||||
|
break;
|
||||||
|
case LUA_TVECTOR:
|
||||||
|
{
|
||||||
|
const float* v = lua_tovector(L, idx);
|
||||||
|
|
||||||
|
char s[LUAI_MAXNUM2STR * LUA_VECTOR_SIZE];
|
||||||
|
char* e = s;
|
||||||
|
for (int i = 0; i < LUA_VECTOR_SIZE; ++i)
|
||||||
|
{
|
||||||
|
if (i != 0)
|
||||||
|
{
|
||||||
|
*e++ = ',';
|
||||||
|
*e++ = ' ';
|
||||||
|
}
|
||||||
|
e = luai_num2str(e, v[i]);
|
||||||
|
}
|
||||||
|
lua_pushlstring(L, s, e - s);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
const void* ptr = lua_topointer(L, idx);
|
||||||
|
unsigned long long enc = lua_encodepointer(L, uintptr_t(ptr));
|
||||||
|
lua_pushfstring(L, "%s: 0x%016llx", luaL_typename(L, idx), enc);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lua_tolstring(L, -1, len);
|
||||||
|
}
|
|
@ -0,0 +1,480 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
||||||
|
#include "lualib.h"
|
||||||
|
|
||||||
|
#include "lstate.h"
|
||||||
|
#include "lapi.h"
|
||||||
|
#include "ldo.h"
|
||||||
|
#include "ludata.h"
|
||||||
|
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
static void writestring(const char* s, size_t l)
|
||||||
|
{
|
||||||
|
fwrite(s, 1, l, stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int luaB_print(lua_State* L)
|
||||||
|
{
|
||||||
|
int n = lua_gettop(L); // number of arguments
|
||||||
|
for (int i = 1; i <= n; i++)
|
||||||
|
{
|
||||||
|
size_t l;
|
||||||
|
const char* s = luaL_tolstring(L, i, &l); // convert to string using __tostring et al
|
||||||
|
if (i > 1)
|
||||||
|
writestring("\t", 1);
|
||||||
|
writestring(s, l);
|
||||||
|
lua_pop(L, 1); // pop result
|
||||||
|
}
|
||||||
|
writestring("\n", 1);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int luaB_tonumber(lua_State* L)
|
||||||
|
{
|
||||||
|
int base = luaL_optinteger(L, 2, 10);
|
||||||
|
if (base == 10)
|
||||||
|
{ // standard conversion
|
||||||
|
int isnum = 0;
|
||||||
|
double n = lua_tonumberx(L, 1, &isnum);
|
||||||
|
if (isnum)
|
||||||
|
{
|
||||||
|
lua_pushnumber(L, n);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
luaL_checkany(L, 1); // error if we don't have any argument
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const char* s1 = luaL_checkstring(L, 1);
|
||||||
|
luaL_argcheck(L, 2 <= base && base <= 36, 2, "base out of range");
|
||||||
|
char* s2;
|
||||||
|
unsigned long long n;
|
||||||
|
n = strtoull(s1, &s2, base);
|
||||||
|
if (s1 != s2)
|
||||||
|
{ // at least one valid digit?
|
||||||
|
while (isspace((unsigned char)(*s2)))
|
||||||
|
s2++; // skip trailing spaces
|
||||||
|
if (*s2 == '\0')
|
||||||
|
{ // no invalid trailing characters?
|
||||||
|
lua_pushnumber(L, (double)n);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lua_pushnil(L); // else not a number
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int luaB_error(lua_State* L)
|
||||||
|
{
|
||||||
|
int level = luaL_optinteger(L, 2, 1);
|
||||||
|
lua_settop(L, 1);
|
||||||
|
if (lua_isstring(L, 1) && level > 0)
|
||||||
|
{ // add extra information?
|
||||||
|
luaL_where(L, level);
|
||||||
|
lua_pushvalue(L, 1);
|
||||||
|
lua_concat(L, 2);
|
||||||
|
}
|
||||||
|
lua_error(L);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int luaB_getmetatable(lua_State* L)
|
||||||
|
{
|
||||||
|
luaL_checkany(L, 1);
|
||||||
|
if (!lua_getmetatable(L, 1))
|
||||||
|
{
|
||||||
|
lua_pushnil(L);
|
||||||
|
return 1; // no metatable
|
||||||
|
}
|
||||||
|
luaL_getmetafield(L, 1, "__metatable");
|
||||||
|
return 1; // returns either __metatable field (if present) or metatable
|
||||||
|
}
|
||||||
|
|
||||||
|
static int luaB_setmetatable(lua_State* L)
|
||||||
|
{
|
||||||
|
int t = lua_type(L, 2);
|
||||||
|
luaL_checktype(L, 1, LUA_TTABLE);
|
||||||
|
luaL_argexpected(L, t == LUA_TNIL || t == LUA_TTABLE, 2, "nil or table");
|
||||||
|
if (luaL_getmetafield(L, 1, "__metatable"))
|
||||||
|
luaL_error(L, "cannot change a protected metatable");
|
||||||
|
lua_settop(L, 2);
|
||||||
|
lua_setmetatable(L, 1);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void getfunc(lua_State* L, int opt)
|
||||||
|
{
|
||||||
|
if (lua_isfunction(L, 1))
|
||||||
|
lua_pushvalue(L, 1);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lua_Debug ar;
|
||||||
|
int level = opt ? luaL_optinteger(L, 1, 1) : luaL_checkinteger(L, 1);
|
||||||
|
luaL_argcheck(L, level >= 0, 1, "level must be non-negative");
|
||||||
|
if (lua_getinfo(L, level, "f", &ar) == 0)
|
||||||
|
luaL_argerror(L, 1, "invalid level");
|
||||||
|
if (lua_isnil(L, -1))
|
||||||
|
luaL_error(L, "no function environment for tail call at level %d", level);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int luaB_getfenv(lua_State* L)
|
||||||
|
{
|
||||||
|
getfunc(L, 1);
|
||||||
|
if (lua_iscfunction(L, -1)) // is a C function?
|
||||||
|
lua_pushvalue(L, LUA_GLOBALSINDEX); // return the thread's global env.
|
||||||
|
else
|
||||||
|
lua_getfenv(L, -1);
|
||||||
|
lua_setsafeenv(L, -1, false);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int luaB_setfenv(lua_State* L)
|
||||||
|
{
|
||||||
|
luaL_checktype(L, 2, LUA_TTABLE);
|
||||||
|
getfunc(L, 0);
|
||||||
|
lua_pushvalue(L, 2);
|
||||||
|
lua_setsafeenv(L, -1, false);
|
||||||
|
if (lua_isnumber(L, 1) && lua_tonumber(L, 1) == 0)
|
||||||
|
{
|
||||||
|
// change environment of current thread
|
||||||
|
lua_pushthread(L);
|
||||||
|
lua_insert(L, -2);
|
||||||
|
lua_setfenv(L, -2);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else if (lua_iscfunction(L, -2) || lua_setfenv(L, -2) == 0)
|
||||||
|
luaL_error(L, "'setfenv' cannot change environment of given object");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int luaB_rawequal(lua_State* L)
|
||||||
|
{
|
||||||
|
luaL_checkany(L, 1);
|
||||||
|
luaL_checkany(L, 2);
|
||||||
|
lua_pushboolean(L, lua_rawequal(L, 1, 2));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int luaB_rawget(lua_State* L)
|
||||||
|
{
|
||||||
|
luaL_checktype(L, 1, LUA_TTABLE);
|
||||||
|
luaL_checkany(L, 2);
|
||||||
|
lua_settop(L, 2);
|
||||||
|
lua_rawget(L, 1);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int luaB_rawset(lua_State* L)
|
||||||
|
{
|
||||||
|
luaL_checktype(L, 1, LUA_TTABLE);
|
||||||
|
luaL_checkany(L, 2);
|
||||||
|
luaL_checkany(L, 3);
|
||||||
|
lua_settop(L, 3);
|
||||||
|
lua_rawset(L, 1);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int luaB_rawlen(lua_State* L)
|
||||||
|
{
|
||||||
|
int tt = lua_type(L, 1);
|
||||||
|
luaL_argcheck(L, tt == LUA_TTABLE || tt == LUA_TSTRING, 1, "table or string expected");
|
||||||
|
int len = lua_objlen(L, 1);
|
||||||
|
lua_pushinteger(L, len);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int luaB_gcinfo(lua_State* L)
|
||||||
|
{
|
||||||
|
lua_pushinteger(L, lua_gc(L, LUA_GCCOUNT, 0));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int luaB_type(lua_State* L)
|
||||||
|
{
|
||||||
|
luaL_checkany(L, 1);
|
||||||
|
// resulting name doesn't differentiate between userdata types
|
||||||
|
lua_pushstring(L, lua_typename(L, lua_type(L, 1)));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int luaB_typeof(lua_State* L)
|
||||||
|
{
|
||||||
|
luaL_checkany(L, 1);
|
||||||
|
// resulting name returns __type if specified unless the input is a newproxy-created userdata
|
||||||
|
lua_pushstring(L, luaL_typename(L, 1));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int luaB_next(lua_State* L)
|
||||||
|
{
|
||||||
|
luaL_checktype(L, 1, LUA_TTABLE);
|
||||||
|
lua_settop(L, 2); // create a 2nd argument if there isn't one
|
||||||
|
if (lua_next(L, 1))
|
||||||
|
return 2;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lua_pushnil(L);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int luaB_pairs(lua_State* L)
|
||||||
|
{
|
||||||
|
luaL_checktype(L, 1, LUA_TTABLE);
|
||||||
|
lua_pushvalue(L, lua_upvalueindex(1)); // return generator,
|
||||||
|
lua_pushvalue(L, 1); // state,
|
||||||
|
lua_pushnil(L); // and initial value
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
int luaB_inext(lua_State* L)
|
||||||
|
{
|
||||||
|
int i = luaL_checkinteger(L, 2);
|
||||||
|
luaL_checktype(L, 1, LUA_TTABLE);
|
||||||
|
i++; // next value
|
||||||
|
lua_pushinteger(L, i);
|
||||||
|
lua_rawgeti(L, 1, i);
|
||||||
|
return (lua_isnil(L, -1)) ? 0 : 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int luaB_ipairs(lua_State* L)
|
||||||
|
{
|
||||||
|
luaL_checktype(L, 1, LUA_TTABLE);
|
||||||
|
lua_pushvalue(L, lua_upvalueindex(1)); // return generator,
|
||||||
|
lua_pushvalue(L, 1); // state,
|
||||||
|
lua_pushinteger(L, 0); // and initial value
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int luaB_assert(lua_State* L)
|
||||||
|
{
|
||||||
|
luaL_checkany(L, 1);
|
||||||
|
if (!lua_toboolean(L, 1))
|
||||||
|
luaL_error(L, "%s", luaL_optstring(L, 2, "assertion failed!"));
|
||||||
|
return lua_gettop(L);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int luaB_select(lua_State* L)
|
||||||
|
{
|
||||||
|
int n = lua_gettop(L);
|
||||||
|
if (lua_type(L, 1) == LUA_TSTRING && *lua_tostring(L, 1) == '#')
|
||||||
|
{
|
||||||
|
lua_pushinteger(L, n - 1);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int i = luaL_checkinteger(L, 1);
|
||||||
|
if (i < 0)
|
||||||
|
i = n + i;
|
||||||
|
else if (i > n)
|
||||||
|
i = n;
|
||||||
|
luaL_argcheck(L, 1 <= i, 1, "index out of range");
|
||||||
|
return n - i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void luaB_pcallrun(lua_State* L, void* ud)
|
||||||
|
{
|
||||||
|
StkId func = (StkId)ud;
|
||||||
|
|
||||||
|
luaD_call(L, func, LUA_MULTRET);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int luaB_pcally(lua_State* L)
|
||||||
|
{
|
||||||
|
luaL_checkany(L, 1);
|
||||||
|
|
||||||
|
StkId func = L->base;
|
||||||
|
|
||||||
|
// any errors from this point on are handled by continuation
|
||||||
|
L->ci->flags |= LUA_CALLINFO_HANDLE;
|
||||||
|
|
||||||
|
// maintain yieldable invariant (baseCcalls <= nCcalls)
|
||||||
|
L->baseCcalls++;
|
||||||
|
int status = luaD_pcall(L, luaB_pcallrun, func, savestack(L, func), 0);
|
||||||
|
L->baseCcalls--;
|
||||||
|
|
||||||
|
// necessary to accomodate functions that return lots of values
|
||||||
|
expandstacklimit(L, L->top);
|
||||||
|
|
||||||
|
// yielding means we need to propagate yield; resume will call continuation function later
|
||||||
|
if (status == 0 && (L->status == LUA_YIELD || L->status == LUA_BREAK))
|
||||||
|
return -1; // -1 is a marker for yielding from C
|
||||||
|
|
||||||
|
// immediate return (error or success)
|
||||||
|
lua_rawcheckstack(L, 1);
|
||||||
|
lua_pushboolean(L, status == 0);
|
||||||
|
lua_insert(L, 1);
|
||||||
|
return lua_gettop(L); // return status + all results
|
||||||
|
}
|
||||||
|
|
||||||
|
static int luaB_pcallcont(lua_State* L, int status)
|
||||||
|
{
|
||||||
|
if (status == 0)
|
||||||
|
{
|
||||||
|
lua_rawcheckstack(L, 1);
|
||||||
|
lua_pushboolean(L, true);
|
||||||
|
lua_insert(L, 1); // insert status before all results
|
||||||
|
return lua_gettop(L);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lua_rawcheckstack(L, 1);
|
||||||
|
lua_pushboolean(L, false);
|
||||||
|
lua_insert(L, -2); // insert status before error object
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int luaB_xpcally(lua_State* L)
|
||||||
|
{
|
||||||
|
luaL_checktype(L, 2, LUA_TFUNCTION);
|
||||||
|
|
||||||
|
// swap function & error function
|
||||||
|
lua_pushvalue(L, 1);
|
||||||
|
lua_pushvalue(L, 2);
|
||||||
|
lua_replace(L, 1);
|
||||||
|
lua_replace(L, 2);
|
||||||
|
// at this point the stack looks like err, f, args
|
||||||
|
|
||||||
|
// any errors from this point on are handled by continuation
|
||||||
|
L->ci->flags |= LUA_CALLINFO_HANDLE;
|
||||||
|
|
||||||
|
StkId errf = L->base;
|
||||||
|
StkId func = L->base + 1;
|
||||||
|
|
||||||
|
// maintain yieldable invariant (baseCcalls <= nCcalls)
|
||||||
|
L->baseCcalls++;
|
||||||
|
int status = luaD_pcall(L, luaB_pcallrun, func, savestack(L, func), savestack(L, errf));
|
||||||
|
L->baseCcalls--;
|
||||||
|
|
||||||
|
// necessary to accomodate functions that return lots of values
|
||||||
|
expandstacklimit(L, L->top);
|
||||||
|
|
||||||
|
// yielding means we need to propagate yield; resume will call continuation function later
|
||||||
|
if (status == 0 && (L->status == LUA_YIELD || L->status == LUA_BREAK))
|
||||||
|
return -1; // -1 is a marker for yielding from C
|
||||||
|
|
||||||
|
// immediate return (error or success)
|
||||||
|
lua_rawcheckstack(L, 1);
|
||||||
|
lua_pushboolean(L, status == 0);
|
||||||
|
lua_replace(L, 1); // replace error function with status
|
||||||
|
return lua_gettop(L); // return status + all results
|
||||||
|
}
|
||||||
|
|
||||||
|
static void luaB_xpcallerr(lua_State* L, void* ud)
|
||||||
|
{
|
||||||
|
StkId func = (StkId)ud;
|
||||||
|
|
||||||
|
luaD_call(L, func, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int luaB_xpcallcont(lua_State* L, int status)
|
||||||
|
{
|
||||||
|
if (status == 0)
|
||||||
|
{
|
||||||
|
lua_rawcheckstack(L, 1);
|
||||||
|
lua_pushboolean(L, true);
|
||||||
|
lua_replace(L, 1); // replace error function with status
|
||||||
|
return lua_gettop(L); // return status + all results
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lua_rawcheckstack(L, 3);
|
||||||
|
lua_pushboolean(L, false);
|
||||||
|
lua_pushvalue(L, 1); // push error function on top of the stack
|
||||||
|
lua_pushvalue(L, -3); // push error object (that was on top of the stack before)
|
||||||
|
|
||||||
|
StkId res = L->top - 3;
|
||||||
|
StkId errf = L->top - 2;
|
||||||
|
|
||||||
|
// note: we pass res as errfunc as a short cut; if errf generates an error, we'll try to execute res (boolean) and fail
|
||||||
|
luaD_pcall(L, luaB_xpcallerr, errf, savestack(L, errf), savestack(L, res));
|
||||||
|
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int luaB_tostring(lua_State* L)
|
||||||
|
{
|
||||||
|
luaL_checkany(L, 1);
|
||||||
|
luaL_tolstring(L, 1, NULL);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int luaB_newproxy(lua_State* L)
|
||||||
|
{
|
||||||
|
int t = lua_type(L, 1);
|
||||||
|
luaL_argexpected(L, t == LUA_TNONE || t == LUA_TNIL || t == LUA_TBOOLEAN, 1, "nil or boolean");
|
||||||
|
|
||||||
|
bool needsmt = lua_toboolean(L, 1);
|
||||||
|
|
||||||
|
lua_newuserdatatagged(L, 0, UTAG_PROXY);
|
||||||
|
|
||||||
|
if (needsmt)
|
||||||
|
{
|
||||||
|
lua_newtable(L);
|
||||||
|
lua_setmetatable(L, -2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const luaL_Reg base_funcs[] = {
|
||||||
|
{"assert", luaB_assert},
|
||||||
|
{"error", luaB_error},
|
||||||
|
{"gcinfo", luaB_gcinfo},
|
||||||
|
{"getfenv", luaB_getfenv},
|
||||||
|
{"getmetatable", luaB_getmetatable},
|
||||||
|
{"next", luaB_next},
|
||||||
|
{"newproxy", luaB_newproxy},
|
||||||
|
{"print", luaB_print},
|
||||||
|
{"rawequal", luaB_rawequal},
|
||||||
|
{"rawget", luaB_rawget},
|
||||||
|
{"rawset", luaB_rawset},
|
||||||
|
{"rawlen", luaB_rawlen},
|
||||||
|
{"select", luaB_select},
|
||||||
|
{"setfenv", luaB_setfenv},
|
||||||
|
{"setmetatable", luaB_setmetatable},
|
||||||
|
{"tonumber", luaB_tonumber},
|
||||||
|
{"tostring", luaB_tostring},
|
||||||
|
{"type", luaB_type},
|
||||||
|
{"typeof", luaB_typeof},
|
||||||
|
{NULL, NULL},
|
||||||
|
};
|
||||||
|
|
||||||
|
static void auxopen(lua_State* L, const char* name, lua_CFunction f, lua_CFunction u)
|
||||||
|
{
|
||||||
|
lua_pushcfunction(L, u, NULL);
|
||||||
|
lua_pushcclosure(L, f, name, 1);
|
||||||
|
lua_setfield(L, -2, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
int luaopen_base(lua_State* L)
|
||||||
|
{
|
||||||
|
// set global _G
|
||||||
|
lua_pushvalue(L, LUA_GLOBALSINDEX);
|
||||||
|
lua_setglobal(L, "_G");
|
||||||
|
|
||||||
|
// open lib into global table
|
||||||
|
luaL_register(L, "_G", base_funcs);
|
||||||
|
lua_pushliteral(L, "Luau");
|
||||||
|
lua_setglobal(L, "_VERSION"); // set global _VERSION
|
||||||
|
|
||||||
|
// `ipairs' and `pairs' need auxiliary functions as upvalues
|
||||||
|
auxopen(L, "ipairs", luaB_ipairs, luaB_inext);
|
||||||
|
auxopen(L, "pairs", luaB_pairs, luaB_next);
|
||||||
|
|
||||||
|
lua_pushcclosurek(L, luaB_pcally, "pcall", 0, luaB_pcallcont);
|
||||||
|
lua_setfield(L, -2, "pcall");
|
||||||
|
|
||||||
|
lua_pushcclosurek(L, luaB_xpcally, "xpcall", 0, luaB_xpcallcont);
|
||||||
|
lua_setfield(L, -2, "xpcall");
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
|
@ -0,0 +1,236 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
||||||
|
#include "lualib.h"
|
||||||
|
|
||||||
|
#include "lcommon.h"
|
||||||
|
#include "lnumutils.h"
|
||||||
|
|
||||||
|
#define ALLONES ~0u
|
||||||
|
#define NBITS int(8 * sizeof(unsigned))
|
||||||
|
|
||||||
|
// macro to trim extra bits
|
||||||
|
#define trim(x) ((x)&ALLONES)
|
||||||
|
|
||||||
|
// builds a number with 'n' ones (1 <= n <= NBITS)
|
||||||
|
#define mask(n) (~((ALLONES << 1) << ((n)-1)))
|
||||||
|
|
||||||
|
typedef unsigned b_uint;
|
||||||
|
|
||||||
|
static b_uint andaux(lua_State* L)
|
||||||
|
{
|
||||||
|
int i, n = lua_gettop(L);
|
||||||
|
b_uint r = ~(b_uint)0;
|
||||||
|
for (i = 1; i <= n; i++)
|
||||||
|
r &= luaL_checkunsigned(L, i);
|
||||||
|
return trim(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b_and(lua_State* L)
|
||||||
|
{
|
||||||
|
b_uint r = andaux(L);
|
||||||
|
lua_pushunsigned(L, r);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b_test(lua_State* L)
|
||||||
|
{
|
||||||
|
b_uint r = andaux(L);
|
||||||
|
lua_pushboolean(L, r != 0);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b_or(lua_State* L)
|
||||||
|
{
|
||||||
|
int i, n = lua_gettop(L);
|
||||||
|
b_uint r = 0;
|
||||||
|
for (i = 1; i <= n; i++)
|
||||||
|
r |= luaL_checkunsigned(L, i);
|
||||||
|
lua_pushunsigned(L, trim(r));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b_xor(lua_State* L)
|
||||||
|
{
|
||||||
|
int i, n = lua_gettop(L);
|
||||||
|
b_uint r = 0;
|
||||||
|
for (i = 1; i <= n; i++)
|
||||||
|
r ^= luaL_checkunsigned(L, i);
|
||||||
|
lua_pushunsigned(L, trim(r));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b_not(lua_State* L)
|
||||||
|
{
|
||||||
|
b_uint r = ~luaL_checkunsigned(L, 1);
|
||||||
|
lua_pushunsigned(L, trim(r));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b_shift(lua_State* L, b_uint r, int i)
|
||||||
|
{
|
||||||
|
if (i < 0)
|
||||||
|
{ // shift right?
|
||||||
|
i = -i;
|
||||||
|
r = trim(r);
|
||||||
|
if (i >= NBITS)
|
||||||
|
r = 0;
|
||||||
|
else
|
||||||
|
r >>= i;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{ // shift left
|
||||||
|
if (i >= NBITS)
|
||||||
|
r = 0;
|
||||||
|
else
|
||||||
|
r <<= i;
|
||||||
|
r = trim(r);
|
||||||
|
}
|
||||||
|
lua_pushunsigned(L, r);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b_lshift(lua_State* L)
|
||||||
|
{
|
||||||
|
return b_shift(L, luaL_checkunsigned(L, 1), luaL_checkinteger(L, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b_rshift(lua_State* L)
|
||||||
|
{
|
||||||
|
return b_shift(L, luaL_checkunsigned(L, 1), -luaL_checkinteger(L, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b_arshift(lua_State* L)
|
||||||
|
{
|
||||||
|
b_uint r = luaL_checkunsigned(L, 1);
|
||||||
|
int i = luaL_checkinteger(L, 2);
|
||||||
|
if (i < 0 || !(r & ((b_uint)1 << (NBITS - 1))))
|
||||||
|
return b_shift(L, r, -i);
|
||||||
|
else
|
||||||
|
{ // arithmetic shift for 'negative' number
|
||||||
|
if (i >= NBITS)
|
||||||
|
r = ALLONES;
|
||||||
|
else
|
||||||
|
r = trim((r >> i) | ~(~(b_uint)0 >> i)); // add signal bit
|
||||||
|
lua_pushunsigned(L, r);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b_rot(lua_State* L, int i)
|
||||||
|
{
|
||||||
|
b_uint r = luaL_checkunsigned(L, 1);
|
||||||
|
i &= (NBITS - 1); // i = i % NBITS
|
||||||
|
r = trim(r);
|
||||||
|
if (i != 0) // avoid undefined shift of NBITS when i == 0
|
||||||
|
r = (r << i) | (r >> (NBITS - i));
|
||||||
|
lua_pushunsigned(L, trim(r));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b_lrot(lua_State* L)
|
||||||
|
{
|
||||||
|
return b_rot(L, luaL_checkinteger(L, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b_rrot(lua_State* L)
|
||||||
|
{
|
||||||
|
return b_rot(L, -luaL_checkinteger(L, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** get field and width arguments for field-manipulation functions,
|
||||||
|
** checking whether they are valid.
|
||||||
|
** ('luaL_error' called without 'return' to avoid later warnings about
|
||||||
|
** 'width' being used uninitialized.)
|
||||||
|
*/
|
||||||
|
static int fieldargs(lua_State* L, int farg, int* width)
|
||||||
|
{
|
||||||
|
int f = luaL_checkinteger(L, farg);
|
||||||
|
int w = luaL_optinteger(L, farg + 1, 1);
|
||||||
|
luaL_argcheck(L, 0 <= f, farg, "field cannot be negative");
|
||||||
|
luaL_argcheck(L, 0 < w, farg + 1, "width must be positive");
|
||||||
|
if (f + w > NBITS)
|
||||||
|
luaL_error(L, "trying to access non-existent bits");
|
||||||
|
*width = w;
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b_extract(lua_State* L)
|
||||||
|
{
|
||||||
|
int w;
|
||||||
|
b_uint r = luaL_checkunsigned(L, 1);
|
||||||
|
int f = fieldargs(L, 2, &w);
|
||||||
|
r = (r >> f) & mask(w);
|
||||||
|
lua_pushunsigned(L, r);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b_replace(lua_State* L)
|
||||||
|
{
|
||||||
|
int w;
|
||||||
|
b_uint r = luaL_checkunsigned(L, 1);
|
||||||
|
b_uint v = luaL_checkunsigned(L, 2);
|
||||||
|
int f = fieldargs(L, 3, &w);
|
||||||
|
int m = mask(w);
|
||||||
|
v &= m; // erase bits outside given width
|
||||||
|
r = (r & ~(m << f)) | (v << f);
|
||||||
|
lua_pushunsigned(L, r);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b_countlz(lua_State* L)
|
||||||
|
{
|
||||||
|
b_uint v = luaL_checkunsigned(L, 1);
|
||||||
|
|
||||||
|
b_uint r = NBITS;
|
||||||
|
for (int i = 0; i < NBITS; ++i)
|
||||||
|
if (v & (1u << (NBITS - 1 - i)))
|
||||||
|
{
|
||||||
|
r = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
lua_pushunsigned(L, r);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b_countrz(lua_State* L)
|
||||||
|
{
|
||||||
|
b_uint v = luaL_checkunsigned(L, 1);
|
||||||
|
|
||||||
|
b_uint r = NBITS;
|
||||||
|
for (int i = 0; i < NBITS; ++i)
|
||||||
|
if (v & (1u << i))
|
||||||
|
{
|
||||||
|
r = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
lua_pushunsigned(L, r);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const luaL_Reg bitlib[] = {
|
||||||
|
{"arshift", b_arshift},
|
||||||
|
{"band", b_and},
|
||||||
|
{"bnot", b_not},
|
||||||
|
{"bor", b_or},
|
||||||
|
{"bxor", b_xor},
|
||||||
|
{"btest", b_test},
|
||||||
|
{"extract", b_extract},
|
||||||
|
{"lrotate", b_lrot},
|
||||||
|
{"lshift", b_lshift},
|
||||||
|
{"replace", b_replace},
|
||||||
|
{"rrotate", b_rrot},
|
||||||
|
{"rshift", b_rshift},
|
||||||
|
{"countlz", b_countlz},
|
||||||
|
{"countrz", b_countrz},
|
||||||
|
{NULL, NULL},
|
||||||
|
};
|
||||||
|
|
||||||
|
int luaopen_bit32(lua_State* L)
|
||||||
|
{
|
||||||
|
luaL_register(L, LUA_BITLIBNAME, bitlib);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,9 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "lobject.h"
|
||||||
|
|
||||||
|
typedef int (*luau_FastFunction)(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams);
|
||||||
|
|
||||||
|
extern const luau_FastFunction luauF_table[256];
|
|
@ -0,0 +1,6 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// This is a forwarding header for Luau bytecode definition
|
||||||
|
#include "Luau/Bytecode.h"
|
|
@ -0,0 +1,48 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <limits.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "luaconf.h"
|
||||||
|
|
||||||
|
#include "Luau/Common.h"
|
||||||
|
|
||||||
|
typedef LUAI_USER_ALIGNMENT_T L_Umaxalign;
|
||||||
|
|
||||||
|
// internal assertions for in-house debugging
|
||||||
|
#define check_exp(c, e) (LUAU_ASSERT(c), (e))
|
||||||
|
#define api_check(l, e) LUAU_ASSERT(e)
|
||||||
|
|
||||||
|
#ifndef cast_to
|
||||||
|
#define cast_to(t, exp) ((t)(exp))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define cast_byte(i) cast_to(uint8_t, (i))
|
||||||
|
#define cast_num(i) cast_to(double, (i))
|
||||||
|
#define cast_int(i) cast_to(int, (i))
|
||||||
|
|
||||||
|
/*
|
||||||
|
** type for virtual-machine instructions
|
||||||
|
** must be an unsigned with (at least) 4 bytes (see details in lopcodes.h)
|
||||||
|
*/
|
||||||
|
typedef uint32_t Instruction;
|
||||||
|
|
||||||
|
/*
|
||||||
|
** macro to control inclusion of some hard tests on stack reallocation
|
||||||
|
*/
|
||||||
|
#if defined(HARDSTACKTESTS) && HARDSTACKTESTS
|
||||||
|
#define condhardstacktests(x) (x)
|
||||||
|
#else
|
||||||
|
#define condhardstacktests(x) ((void)0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
** macro to control inclusion of some hard tests on garbage collection
|
||||||
|
*/
|
||||||
|
#if defined(HARDMEMTESTS) && HARDMEMTESTS
|
||||||
|
#define condhardmemtests(x, l) (HARDMEMTESTS >= l ? (x) : (void)0)
|
||||||
|
#else
|
||||||
|
#define condhardmemtests(x, l) ((void)0)
|
||||||
|
#endif
|
|
@ -0,0 +1,256 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
||||||
|
#include "lualib.h"
|
||||||
|
|
||||||
|
#include "lstate.h"
|
||||||
|
#include "lvm.h"
|
||||||
|
|
||||||
|
#define CO_STATUS_ERROR -1
|
||||||
|
#define CO_STATUS_BREAK -2
|
||||||
|
|
||||||
|
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[lua_costatus(L, co)]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int auxresume(lua_State* L, lua_State* co, int narg)
|
||||||
|
{
|
||||||
|
// error handling for edge cases
|
||||||
|
if (co->status != LUA_YIELD)
|
||||||
|
{
|
||||||
|
int status = lua_costatus(L, co);
|
||||||
|
if (status != LUA_COSUS)
|
||||||
|
{
|
||||||
|
lua_pushfstring(L, "cannot resume %s coroutine", statnames[status]);
|
||||||
|
return CO_STATUS_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (narg)
|
||||||
|
{
|
||||||
|
if (!lua_checkstack(co, narg))
|
||||||
|
luaL_error(L, "too many arguments to resume");
|
||||||
|
lua_xmove(L, co, narg);
|
||||||
|
}
|
||||||
|
|
||||||
|
co->singlestep = L->singlestep;
|
||||||
|
|
||||||
|
int status = lua_resume(co, L, narg);
|
||||||
|
if (status == 0 || status == LUA_YIELD)
|
||||||
|
{
|
||||||
|
int nres = cast_int(co->top - co->base);
|
||||||
|
if (nres)
|
||||||
|
{
|
||||||
|
// +1 accounts for true/false status in resumefinish
|
||||||
|
if (nres + 1 > LUA_MINSTACK && !lua_checkstack(L, nres + 1))
|
||||||
|
luaL_error(L, "too many results to resume");
|
||||||
|
lua_xmove(co, L, nres); // move yielded values
|
||||||
|
}
|
||||||
|
return nres;
|
||||||
|
}
|
||||||
|
else if (status == LUA_BREAK)
|
||||||
|
{
|
||||||
|
return CO_STATUS_BREAK;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lua_xmove(co, L, 1); // move error message
|
||||||
|
return CO_STATUS_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int interruptThread(lua_State* L, lua_State* co)
|
||||||
|
{
|
||||||
|
// notify the debugger that the thread was suspended
|
||||||
|
if (L->global->cb.debuginterrupt)
|
||||||
|
luau_callhook(L, L->global->cb.debuginterrupt, co);
|
||||||
|
|
||||||
|
return lua_break(L);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int auxresumecont(lua_State* L, lua_State* co)
|
||||||
|
{
|
||||||
|
if (co->status == 0 || co->status == LUA_YIELD)
|
||||||
|
{
|
||||||
|
int nres = cast_int(co->top - co->base);
|
||||||
|
if (!lua_checkstack(L, nres + 1))
|
||||||
|
luaL_error(L, "too many results to resume");
|
||||||
|
lua_xmove(co, L, nres); // move yielded values
|
||||||
|
return nres;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lua_rawcheckstack(L, 2);
|
||||||
|
lua_xmove(co, L, 1); // move error message
|
||||||
|
return CO_STATUS_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int coresumefinish(lua_State* L, int r)
|
||||||
|
{
|
||||||
|
if (r < 0)
|
||||||
|
{
|
||||||
|
lua_pushboolean(L, 0);
|
||||||
|
lua_insert(L, -2);
|
||||||
|
return 2; // return false + error message
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lua_pushboolean(L, 1);
|
||||||
|
lua_insert(L, -(r + 1));
|
||||||
|
return r + 1; // return true + `resume' returns
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int coresumey(lua_State* L)
|
||||||
|
{
|
||||||
|
lua_State* co = lua_tothread(L, 1);
|
||||||
|
luaL_argexpected(L, co, 1, "thread");
|
||||||
|
int narg = cast_int(L->top - L->base) - 1;
|
||||||
|
int r = auxresume(L, co, narg);
|
||||||
|
|
||||||
|
if (r == CO_STATUS_BREAK)
|
||||||
|
return interruptThread(L, co);
|
||||||
|
|
||||||
|
return coresumefinish(L, r);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int coresumecont(lua_State* L, int status)
|
||||||
|
{
|
||||||
|
lua_State* co = lua_tothread(L, 1);
|
||||||
|
luaL_argexpected(L, co, 1, "thread");
|
||||||
|
|
||||||
|
// if coroutine still hasn't yielded after the break, break current thread again
|
||||||
|
if (co->status == LUA_BREAK)
|
||||||
|
return interruptThread(L, co);
|
||||||
|
|
||||||
|
int r = auxresumecont(L, co);
|
||||||
|
|
||||||
|
return coresumefinish(L, r);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int auxwrapfinish(lua_State* L, int r)
|
||||||
|
{
|
||||||
|
if (r < 0)
|
||||||
|
{
|
||||||
|
if (lua_isstring(L, -1))
|
||||||
|
{ // error object is a string?
|
||||||
|
luaL_where(L, 1); // add extra info
|
||||||
|
lua_insert(L, -2);
|
||||||
|
lua_concat(L, 2);
|
||||||
|
}
|
||||||
|
lua_error(L); // propagate error
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int auxwrapy(lua_State* L)
|
||||||
|
{
|
||||||
|
lua_State* co = lua_tothread(L, lua_upvalueindex(1));
|
||||||
|
int narg = cast_int(L->top - L->base);
|
||||||
|
int r = auxresume(L, co, narg);
|
||||||
|
|
||||||
|
if (r == CO_STATUS_BREAK)
|
||||||
|
return interruptThread(L, co);
|
||||||
|
|
||||||
|
return auxwrapfinish(L, r);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int auxwrapcont(lua_State* L, int status)
|
||||||
|
{
|
||||||
|
lua_State* co = lua_tothread(L, lua_upvalueindex(1));
|
||||||
|
|
||||||
|
// if coroutine still hasn't yielded after the break, break current thread again
|
||||||
|
if (co->status == LUA_BREAK)
|
||||||
|
return interruptThread(L, co);
|
||||||
|
|
||||||
|
int r = auxresumecont(L, co);
|
||||||
|
|
||||||
|
return auxwrapfinish(L, r);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cocreate(lua_State* L)
|
||||||
|
{
|
||||||
|
luaL_checktype(L, 1, LUA_TFUNCTION);
|
||||||
|
lua_State* NL = lua_newthread(L);
|
||||||
|
lua_xpush(L, NL, 1); // push function on top of NL
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cowrap(lua_State* L)
|
||||||
|
{
|
||||||
|
cocreate(L);
|
||||||
|
|
||||||
|
lua_pushcclosurek(L, auxwrapy, NULL, 1, auxwrapcont);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int coyield(lua_State* L)
|
||||||
|
{
|
||||||
|
int nres = cast_int(L->top - L->base);
|
||||||
|
return lua_yield(L, nres);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int corunning(lua_State* L)
|
||||||
|
{
|
||||||
|
if (lua_pushthread(L))
|
||||||
|
lua_pushnil(L); // main thread is not a coroutine
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int coyieldable(lua_State* L)
|
||||||
|
{
|
||||||
|
lua_pushboolean(L, lua_isyieldable(L));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int coclose(lua_State* L)
|
||||||
|
{
|
||||||
|
lua_State* co = lua_tothread(L, 1);
|
||||||
|
luaL_argexpected(L, co, 1, "thread");
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
lua_pushboolean(L, true);
|
||||||
|
lua_resetthread(co);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lua_pushboolean(L, false);
|
||||||
|
if (lua_gettop(co))
|
||||||
|
lua_xmove(co, L, 1); // move error message
|
||||||
|
lua_resetthread(co);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const luaL_Reg co_funcs[] = {
|
||||||
|
{"create", cocreate},
|
||||||
|
{"running", corunning},
|
||||||
|
{"status", costatus},
|
||||||
|
{"wrap", cowrap},
|
||||||
|
{"yield", coyield},
|
||||||
|
{"isyieldable", coyieldable},
|
||||||
|
{"close", coclose},
|
||||||
|
{NULL, NULL},
|
||||||
|
};
|
||||||
|
|
||||||
|
int luaopen_coroutine(lua_State* L)
|
||||||
|
{
|
||||||
|
luaL_register(L, LUA_COLIBNAME, co_funcs);
|
||||||
|
|
||||||
|
lua_pushcclosurek(L, coresumey, "resume", 0, coresumecont);
|
||||||
|
lua_setfield(L, -2, "resume");
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
|
@ -0,0 +1,166 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
||||||
|
#include "lualib.h"
|
||||||
|
|
||||||
|
#include "lvm.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
static lua_State* getthread(lua_State* L, int* arg)
|
||||||
|
{
|
||||||
|
if (lua_isthread(L, 1))
|
||||||
|
{
|
||||||
|
*arg = 1;
|
||||||
|
return lua_tothread(L, 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
*arg = 0;
|
||||||
|
return L;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int db_info(lua_State* L)
|
||||||
|
{
|
||||||
|
int arg;
|
||||||
|
lua_State* L1 = getthread(L, &arg);
|
||||||
|
|
||||||
|
// If L1 != L, L1 can be in any state, and therefore there are no guarantees about its stack space
|
||||||
|
if (L != L1)
|
||||||
|
lua_rawcheckstack(L1, 1); // for 'f' option
|
||||||
|
|
||||||
|
int level;
|
||||||
|
if (lua_isnumber(L, arg + 1))
|
||||||
|
{
|
||||||
|
level = (int)lua_tointeger(L, arg + 1);
|
||||||
|
luaL_argcheck(L, level >= 0, arg + 1, "level can't be negative");
|
||||||
|
}
|
||||||
|
else if (arg == 0 && lua_isfunction(L, 1))
|
||||||
|
{
|
||||||
|
// convert absolute index to relative index
|
||||||
|
level = -lua_gettop(L);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
luaL_argerror(L, arg + 1, "function or level expected");
|
||||||
|
|
||||||
|
const char* options = luaL_checkstring(L, arg + 2);
|
||||||
|
|
||||||
|
lua_Debug ar;
|
||||||
|
if (!lua_getinfo(L1, level, options, &ar))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
int results = 0;
|
||||||
|
bool occurs[26] = {};
|
||||||
|
|
||||||
|
for (const char* it = options; *it; ++it)
|
||||||
|
{
|
||||||
|
if (unsigned(*it - 'a') < 26)
|
||||||
|
{
|
||||||
|
if (occurs[*it - 'a'])
|
||||||
|
luaL_argerror(L, arg + 2, "duplicate option");
|
||||||
|
occurs[*it - 'a'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (*it)
|
||||||
|
{
|
||||||
|
case 's':
|
||||||
|
lua_pushstring(L, ar.short_src);
|
||||||
|
results++;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'l':
|
||||||
|
lua_pushinteger(L, ar.currentline);
|
||||||
|
results++;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'n':
|
||||||
|
lua_pushstring(L, ar.name ? ar.name : "");
|
||||||
|
results++;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'f':
|
||||||
|
if (L1 == L)
|
||||||
|
lua_pushvalue(L, -1 - results); // function is right before results
|
||||||
|
else
|
||||||
|
lua_xmove(L1, L, 1); // function is at top of L1
|
||||||
|
results++;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'a':
|
||||||
|
lua_pushinteger(L, ar.nparams);
|
||||||
|
lua_pushboolean(L, ar.isvararg);
|
||||||
|
results += 2;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
luaL_argerror(L, arg + 2, "invalid option");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int db_traceback(lua_State* L)
|
||||||
|
{
|
||||||
|
int arg;
|
||||||
|
lua_State* L1 = getthread(L, &arg);
|
||||||
|
const char* msg = luaL_optstring(L, arg + 1, NULL);
|
||||||
|
int level = luaL_optinteger(L, arg + 2, (L == L1) ? 1 : 0);
|
||||||
|
luaL_argcheck(L, level >= 0, arg + 2, "level can't be negative");
|
||||||
|
|
||||||
|
luaL_Buffer buf;
|
||||||
|
luaL_buffinit(L, &buf);
|
||||||
|
|
||||||
|
if (msg)
|
||||||
|
{
|
||||||
|
luaL_addstring(&buf, msg);
|
||||||
|
luaL_addstring(&buf, "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
lua_Debug ar;
|
||||||
|
for (int i = level; lua_getinfo(L1, i, "sln", &ar); ++i)
|
||||||
|
{
|
||||||
|
if (strcmp(ar.what, "C") == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (ar.source)
|
||||||
|
luaL_addstring(&buf, ar.short_src);
|
||||||
|
|
||||||
|
if (ar.currentline > 0)
|
||||||
|
{
|
||||||
|
char line[32]; // manual conversion for performance
|
||||||
|
char* lineend = line + sizeof(line);
|
||||||
|
char* lineptr = lineend;
|
||||||
|
for (unsigned int r = ar.currentline; r > 0; r /= 10)
|
||||||
|
*--lineptr = '0' + (r % 10);
|
||||||
|
|
||||||
|
luaL_addchar(&buf, ':');
|
||||||
|
luaL_addlstring(&buf, lineptr, lineend - lineptr, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ar.name)
|
||||||
|
{
|
||||||
|
luaL_addstring(&buf, " function ");
|
||||||
|
luaL_addstring(&buf, ar.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
luaL_addchar(&buf, '\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
luaL_pushresult(&buf);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const luaL_Reg dblib[] = {
|
||||||
|
{"info", db_info},
|
||||||
|
{"traceback", db_traceback},
|
||||||
|
{NULL, NULL},
|
||||||
|
};
|
||||||
|
|
||||||
|
int luaopen_debug(lua_State* L)
|
||||||
|
{
|
||||||
|
luaL_register(L, LUA_DBLIBNAME, dblib);
|
||||||
|
return 1;
|
||||||
|
}
|
|
@ -0,0 +1,565 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
||||||
|
#include "ldebug.h"
|
||||||
|
|
||||||
|
#include "lapi.h"
|
||||||
|
#include "lfunc.h"
|
||||||
|
#include "lmem.h"
|
||||||
|
#include "lgc.h"
|
||||||
|
#include "ldo.h"
|
||||||
|
#include "lbytecode.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
static const char* getfuncname(Closure* f);
|
||||||
|
|
||||||
|
static int currentpc(lua_State* L, CallInfo* ci)
|
||||||
|
{
|
||||||
|
return pcRel(ci->savedpc, ci_func(ci)->l.p);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int currentline(lua_State* L, CallInfo* ci)
|
||||||
|
{
|
||||||
|
return luaG_getline(ci_func(ci)->l.p, currentpc(L, ci));
|
||||||
|
}
|
||||||
|
|
||||||
|
static Proto* getluaproto(CallInfo* ci)
|
||||||
|
{
|
||||||
|
return (isLua(ci) ? cast_to(Proto*, ci_func(ci)->l.p) : NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
int lua_getargument(lua_State* L, int level, int n)
|
||||||
|
{
|
||||||
|
if (unsigned(level) >= unsigned(L->ci - L->base_ci))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
CallInfo* ci = L->ci - level;
|
||||||
|
Proto* fp = getluaproto(ci);
|
||||||
|
int res = 0;
|
||||||
|
|
||||||
|
if (fp && n > 0)
|
||||||
|
{
|
||||||
|
if (n <= fp->numparams)
|
||||||
|
{
|
||||||
|
luaC_threadbarrier(L);
|
||||||
|
luaA_pushobject(L, ci->base + (n - 1));
|
||||||
|
res = 1;
|
||||||
|
}
|
||||||
|
else if (fp->is_vararg && n < ci->base - ci->func)
|
||||||
|
{
|
||||||
|
luaC_threadbarrier(L);
|
||||||
|
luaA_pushobject(L, ci->func + n);
|
||||||
|
res = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* lua_getlocal(lua_State* L, int level, int n)
|
||||||
|
{
|
||||||
|
if (unsigned(level) >= unsigned(L->ci - L->base_ci))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
CallInfo* ci = L->ci - level;
|
||||||
|
Proto* fp = getluaproto(ci);
|
||||||
|
const LocVar* var = fp ? luaF_getlocal(fp, n, currentpc(L, ci)) : NULL;
|
||||||
|
if (var)
|
||||||
|
{
|
||||||
|
luaC_threadbarrier(L);
|
||||||
|
luaA_pushobject(L, ci->base + var->reg);
|
||||||
|
}
|
||||||
|
const char* name = var ? getstr(var->varname) : NULL;
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* lua_setlocal(lua_State* L, int level, int n)
|
||||||
|
{
|
||||||
|
if (unsigned(level) >= unsigned(L->ci - L->base_ci))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
CallInfo* ci = L->ci - level;
|
||||||
|
Proto* fp = getluaproto(ci);
|
||||||
|
const LocVar* var = fp ? luaF_getlocal(fp, n, currentpc(L, ci)) : NULL;
|
||||||
|
if (var)
|
||||||
|
setobj2s(L, ci->base + var->reg, L->top - 1);
|
||||||
|
L->top--; // pop value
|
||||||
|
const char* name = var ? getstr(var->varname) : NULL;
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Closure* auxgetinfo(lua_State* L, const char* what, lua_Debug* ar, Closure* f, CallInfo* ci)
|
||||||
|
{
|
||||||
|
Closure* cl = NULL;
|
||||||
|
for (; *what; what++)
|
||||||
|
{
|
||||||
|
switch (*what)
|
||||||
|
{
|
||||||
|
case 's':
|
||||||
|
{
|
||||||
|
if (f->isC)
|
||||||
|
{
|
||||||
|
ar->source = "=[C]";
|
||||||
|
ar->what = "C";
|
||||||
|
ar->linedefined = -1;
|
||||||
|
ar->short_src = "[C]";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TString* source = f->l.p->source;
|
||||||
|
ar->source = getstr(source);
|
||||||
|
ar->what = "Lua";
|
||||||
|
ar->linedefined = f->l.p->linedefined;
|
||||||
|
ar->short_src = luaO_chunkid(ar->ssbuf, sizeof(ar->ssbuf), getstr(source), source->len);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'l':
|
||||||
|
{
|
||||||
|
if (ci)
|
||||||
|
{
|
||||||
|
ar->currentline = isLua(ci) ? currentline(L, ci) : -1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ar->currentline = f->isC ? -1 : f->l.p->linedefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'u':
|
||||||
|
{
|
||||||
|
ar->nupvals = f->nupvalues;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'a':
|
||||||
|
{
|
||||||
|
if (f->isC)
|
||||||
|
{
|
||||||
|
ar->isvararg = 1;
|
||||||
|
ar->nparams = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ar->isvararg = f->l.p->is_vararg;
|
||||||
|
ar->nparams = f->l.p->numparams;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'n':
|
||||||
|
{
|
||||||
|
ar->name = ci ? getfuncname(ci_func(ci)) : getfuncname(f);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'f':
|
||||||
|
{
|
||||||
|
cl = f;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cl;
|
||||||
|
}
|
||||||
|
|
||||||
|
int lua_stackdepth(lua_State* L)
|
||||||
|
{
|
||||||
|
return int(L->ci - L->base_ci);
|
||||||
|
}
|
||||||
|
|
||||||
|
int lua_getinfo(lua_State* L, int level, const char* what, lua_Debug* ar)
|
||||||
|
{
|
||||||
|
Closure* f = NULL;
|
||||||
|
CallInfo* ci = NULL;
|
||||||
|
if (level < 0)
|
||||||
|
{
|
||||||
|
const TValue* func = luaA_toobject(L, level);
|
||||||
|
api_check(L, ttisfunction(func));
|
||||||
|
f = clvalue(func);
|
||||||
|
}
|
||||||
|
else if (unsigned(level) < unsigned(L->ci - L->base_ci))
|
||||||
|
{
|
||||||
|
ci = L->ci - level;
|
||||||
|
LUAU_ASSERT(ttisfunction(ci->func));
|
||||||
|
f = clvalue(ci->func);
|
||||||
|
}
|
||||||
|
if (f)
|
||||||
|
{
|
||||||
|
// auxgetinfo fills ar and optionally requests to put closure on stack
|
||||||
|
if (Closure* fcl = auxgetinfo(L, what, ar, f, ci))
|
||||||
|
{
|
||||||
|
luaC_threadbarrier(L);
|
||||||
|
setclvalue(L, L->top, fcl);
|
||||||
|
incr_top(L);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return f ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char* getfuncname(Closure* cl)
|
||||||
|
{
|
||||||
|
if (cl->isC)
|
||||||
|
{
|
||||||
|
if (cl->c.debugname)
|
||||||
|
{
|
||||||
|
return cl->c.debugname;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Proto* p = cl->l.p;
|
||||||
|
|
||||||
|
if (p->debugname)
|
||||||
|
{
|
||||||
|
return getstr(p->debugname);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
l_noret luaG_typeerrorL(lua_State* L, const TValue* o, const char* op)
|
||||||
|
{
|
||||||
|
const char* t = luaT_objtypename(L, o);
|
||||||
|
|
||||||
|
luaG_runerror(L, "attempt to %s a %s value", op, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
l_noret luaG_forerrorL(lua_State* L, const TValue* o, const char* what)
|
||||||
|
{
|
||||||
|
const char* t = luaT_objtypename(L, o);
|
||||||
|
|
||||||
|
luaG_runerror(L, "invalid 'for' %s (number expected, got %s)", what, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
l_noret luaG_concaterror(lua_State* L, StkId p1, StkId p2)
|
||||||
|
{
|
||||||
|
const char* t1 = luaT_objtypename(L, p1);
|
||||||
|
const char* t2 = luaT_objtypename(L, p2);
|
||||||
|
|
||||||
|
luaG_runerror(L, "attempt to concatenate %s with %s", t1, t2);
|
||||||
|
}
|
||||||
|
|
||||||
|
l_noret luaG_aritherror(lua_State* L, const TValue* p1, const TValue* p2, TMS op)
|
||||||
|
{
|
||||||
|
const char* t1 = luaT_objtypename(L, p1);
|
||||||
|
const char* t2 = luaT_objtypename(L, p2);
|
||||||
|
const char* opname = luaT_eventname[op] + 2; // skip __ from metamethod name
|
||||||
|
|
||||||
|
if (t1 == t2)
|
||||||
|
luaG_runerror(L, "attempt to perform arithmetic (%s) on %s", opname, t1);
|
||||||
|
else
|
||||||
|
luaG_runerror(L, "attempt to perform arithmetic (%s) on %s and %s", opname, t1, t2);
|
||||||
|
}
|
||||||
|
|
||||||
|
l_noret luaG_ordererror(lua_State* L, const TValue* p1, const TValue* p2, TMS op)
|
||||||
|
{
|
||||||
|
const char* t1 = luaT_objtypename(L, p1);
|
||||||
|
const char* t2 = luaT_objtypename(L, p2);
|
||||||
|
const char* opname = (op == TM_LT) ? "<" : (op == TM_LE) ? "<=" : "==";
|
||||||
|
|
||||||
|
luaG_runerror(L, "attempt to compare %s %s %s", t1, opname, t2);
|
||||||
|
}
|
||||||
|
|
||||||
|
l_noret luaG_indexerror(lua_State* L, const TValue* p1, const TValue* p2)
|
||||||
|
{
|
||||||
|
const char* t1 = luaT_objtypename(L, p1);
|
||||||
|
const char* t2 = luaT_objtypename(L, p2);
|
||||||
|
const TString* key = ttisstring(p2) ? tsvalue(p2) : 0;
|
||||||
|
|
||||||
|
if (key && key->len <= 64) // limit length to make sure we don't generate very long error messages for very long keys
|
||||||
|
luaG_runerror(L, "attempt to index %s with '%s'", t1, getstr(key));
|
||||||
|
else
|
||||||
|
luaG_runerror(L, "attempt to index %s with %s", t1, t2);
|
||||||
|
}
|
||||||
|
|
||||||
|
l_noret luaG_methoderror(lua_State* L, const TValue* p1, const TString* p2)
|
||||||
|
{
|
||||||
|
const char* t1 = luaT_objtypename(L, p1);
|
||||||
|
|
||||||
|
luaG_runerror(L, "attempt to call missing method '%s' of %s", getstr(p2), t1);
|
||||||
|
}
|
||||||
|
|
||||||
|
l_noret luaG_readonlyerror(lua_State* L)
|
||||||
|
{
|
||||||
|
luaG_runerror(L, "attempt to modify a readonly table");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void pusherror(lua_State* L, const char* msg)
|
||||||
|
{
|
||||||
|
CallInfo* ci = L->ci;
|
||||||
|
if (isLua(ci))
|
||||||
|
{
|
||||||
|
TString* source = getluaproto(ci)->source;
|
||||||
|
char chunkbuf[LUA_IDSIZE]; // add file:line information
|
||||||
|
const char* chunkid = luaO_chunkid(chunkbuf, sizeof(chunkbuf), getstr(source), source->len);
|
||||||
|
int line = currentline(L, ci);
|
||||||
|
luaO_pushfstring(L, "%s:%d: %s", chunkid, line, msg);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lua_pushstring(L, msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
l_noret luaG_runerrorL(lua_State* L, const char* fmt, ...)
|
||||||
|
{
|
||||||
|
va_list argp;
|
||||||
|
va_start(argp, fmt);
|
||||||
|
char result[LUA_BUFFERSIZE];
|
||||||
|
vsnprintf(result, sizeof(result), fmt, argp);
|
||||||
|
va_end(argp);
|
||||||
|
|
||||||
|
pusherror(L, result);
|
||||||
|
luaD_throw(L, LUA_ERRRUN);
|
||||||
|
}
|
||||||
|
|
||||||
|
void luaG_pusherror(lua_State* L, const char* error)
|
||||||
|
{
|
||||||
|
pusherror(L, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
void luaG_breakpoint(lua_State* L, Proto* p, int line, bool enable)
|
||||||
|
{
|
||||||
|
if (p->lineinfo)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < p->sizecode; ++i)
|
||||||
|
{
|
||||||
|
// note: we keep prologue as is, instead opting to break at the first meaningful instruction
|
||||||
|
if (LUAU_INSN_OP(p->code[i]) == LOP_PREPVARARGS)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (luaG_getline(p, i) != line)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// lazy copy of the original opcode array; done when the first breakpoint is set
|
||||||
|
if (!p->debuginsn)
|
||||||
|
{
|
||||||
|
p->debuginsn = luaM_newarray(L, p->sizecode, uint8_t, p->memcat);
|
||||||
|
for (int j = 0; j < p->sizecode; ++j)
|
||||||
|
p->debuginsn[j] = LUAU_INSN_OP(p->code[j]);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t op = enable ? LOP_BREAK : LUAU_INSN_OP(p->debuginsn[i]);
|
||||||
|
|
||||||
|
// patch just the opcode byte, leave arguments alone
|
||||||
|
p->code[i] &= ~0xff;
|
||||||
|
p->code[i] |= op;
|
||||||
|
LUAU_ASSERT(LUAU_INSN_OP(p->code[i]) == op);
|
||||||
|
|
||||||
|
#if LUA_CUSTOM_EXECUTION
|
||||||
|
if (L->global->ecb.setbreakpoint)
|
||||||
|
L->global->ecb.setbreakpoint(L, p, i);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// note: this is important!
|
||||||
|
// we only patch the *first* instruction in each proto that's attributed to a given line
|
||||||
|
// this can be changed, but if requires making patching a bit more nuanced so that we don't patch AUX words
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < p->sizep; ++i)
|
||||||
|
{
|
||||||
|
luaG_breakpoint(L, p->p[i], line, enable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool luaG_onbreak(lua_State* L)
|
||||||
|
{
|
||||||
|
if (L->ci == L->base_ci)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!isLua(L->ci))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return LUAU_INSN_OP(*L->ci->savedpc) == LOP_BREAK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int luaG_getline(Proto* p, int pc)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(pc >= 0 && pc < p->sizecode);
|
||||||
|
|
||||||
|
if (!p->lineinfo)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return p->abslineinfo[pc >> p->linegaplog2] + p->lineinfo[pc];
|
||||||
|
}
|
||||||
|
|
||||||
|
void lua_singlestep(lua_State* L, int enabled)
|
||||||
|
{
|
||||||
|
L->singlestep = bool(enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int getmaxline(Proto* p)
|
||||||
|
{
|
||||||
|
int result = -1;
|
||||||
|
|
||||||
|
for (int i = 0; i < p->sizecode; ++i)
|
||||||
|
{
|
||||||
|
int line = luaG_getline(p, i);
|
||||||
|
result = result < line ? line : result;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < p->sizep; ++i)
|
||||||
|
{
|
||||||
|
int psize = getmaxline(p->p[i]);
|
||||||
|
result = result < psize ? psize : result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the line number with instructions. If the provided line doesn't have any instruction, it should return the next line number with
|
||||||
|
// instructions.
|
||||||
|
static int getnextline(Proto* p, int line)
|
||||||
|
{
|
||||||
|
int closest = -1;
|
||||||
|
if (p->lineinfo)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < p->sizecode; ++i)
|
||||||
|
{
|
||||||
|
// note: we keep prologue as is, instead opting to break at the first meaningful instruction
|
||||||
|
if (LUAU_INSN_OP(p->code[i]) == LOP_PREPVARARGS)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
int current = luaG_getline(p, i);
|
||||||
|
if (current >= line)
|
||||||
|
{
|
||||||
|
closest = current;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < p->sizep; ++i)
|
||||||
|
{
|
||||||
|
// Find the closest line number to the intended one.
|
||||||
|
int candidate = getnextline(p->p[i], line);
|
||||||
|
if (closest == -1 || (candidate >= line && candidate < closest))
|
||||||
|
{
|
||||||
|
closest = candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return closest;
|
||||||
|
}
|
||||||
|
|
||||||
|
int lua_breakpoint(lua_State* L, int funcindex, int line, int enabled)
|
||||||
|
{
|
||||||
|
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.
|
||||||
|
int target = getnextline(p, line);
|
||||||
|
|
||||||
|
if (target != -1)
|
||||||
|
{
|
||||||
|
// Add breakpoint on the exact line
|
||||||
|
luaG_breakpoint(L, p, target, bool(enabled));
|
||||||
|
}
|
||||||
|
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void getcoverage(Proto* p, int depth, int* buffer, size_t size, void* context, lua_Coverage callback)
|
||||||
|
{
|
||||||
|
memset(buffer, -1, size * sizeof(int));
|
||||||
|
|
||||||
|
for (int i = 0; i < p->sizecode; ++i)
|
||||||
|
{
|
||||||
|
Instruction insn = p->code[i];
|
||||||
|
if (LUAU_INSN_OP(insn) != LOP_COVERAGE)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
int line = luaG_getline(p, i);
|
||||||
|
int hits = LUAU_INSN_E(insn);
|
||||||
|
|
||||||
|
LUAU_ASSERT(size_t(line) < size);
|
||||||
|
buffer[line] = buffer[line] < hits ? hits : buffer[line];
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* debugname = p->debugname ? getstr(p->debugname) : NULL;
|
||||||
|
int linedefined = p->linedefined;
|
||||||
|
|
||||||
|
callback(context, debugname, linedefined, depth, buffer, size);
|
||||||
|
|
||||||
|
for (int i = 0; i < p->sizep; ++i)
|
||||||
|
getcoverage(p->p[i], depth + 1, buffer, size, context, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
void lua_getcoverage(lua_State* L, int funcindex, void* context, lua_Coverage callback)
|
||||||
|
{
|
||||||
|
const TValue* func = luaA_toobject(L, funcindex);
|
||||||
|
api_check(L, ttisfunction(func) && !clvalue(func)->isC);
|
||||||
|
|
||||||
|
Proto* p = clvalue(func)->l.p;
|
||||||
|
|
||||||
|
size_t size = getmaxline(p) + 1;
|
||||||
|
if (size == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int* buffer = luaM_newarray(L, size, int, 0);
|
||||||
|
|
||||||
|
getcoverage(p, 0, buffer, size, context, callback);
|
||||||
|
|
||||||
|
luaM_freearray(L, buffer, size, int, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t append(char* buf, size_t bufsize, size_t offset, const char* data)
|
||||||
|
{
|
||||||
|
size_t size = strlen(data);
|
||||||
|
size_t copy = offset + size >= bufsize ? bufsize - offset - 1 : size;
|
||||||
|
memcpy(buf + offset, data, copy);
|
||||||
|
return offset + copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* lua_debugtrace(lua_State* L)
|
||||||
|
{
|
||||||
|
static char buf[4096];
|
||||||
|
|
||||||
|
const int limit1 = 10;
|
||||||
|
const int limit2 = 10;
|
||||||
|
|
||||||
|
int depth = int(L->ci - L->base_ci);
|
||||||
|
size_t offset = 0;
|
||||||
|
|
||||||
|
lua_Debug ar;
|
||||||
|
for (int level = 0; lua_getinfo(L, level, "sln", &ar); ++level)
|
||||||
|
{
|
||||||
|
if (ar.source)
|
||||||
|
offset = append(buf, sizeof(buf), offset, ar.short_src);
|
||||||
|
|
||||||
|
if (ar.currentline > 0)
|
||||||
|
{
|
||||||
|
char line[32];
|
||||||
|
snprintf(line, sizeof(line), ":%d", ar.currentline);
|
||||||
|
|
||||||
|
offset = append(buf, sizeof(buf), offset, line);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ar.name)
|
||||||
|
{
|
||||||
|
offset = append(buf, sizeof(buf), offset, " function ");
|
||||||
|
offset = append(buf, sizeof(buf), offset, ar.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
offset = append(buf, sizeof(buf), offset, "\n");
|
||||||
|
|
||||||
|
if (depth > limit1 + limit2 && level == limit1 - 1)
|
||||||
|
{
|
||||||
|
char skip[32];
|
||||||
|
snprintf(skip, sizeof(skip), "... (+%d frames)\n", int(depth - limit1 - limit2));
|
||||||
|
|
||||||
|
offset = append(buf, sizeof(buf), offset, skip);
|
||||||
|
|
||||||
|
level = depth - limit2 - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LUAU_ASSERT(offset < sizeof(buf));
|
||||||
|
buf[offset] = '\0';
|
||||||
|
|
||||||
|
return buf;
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "lstate.h"
|
||||||
|
|
||||||
|
#define pcRel(pc, p) ((pc) ? cast_to(int, (pc) - (p)->code) - 1 : 0)
|
||||||
|
|
||||||
|
#define luaG_typeerror(L, o, opname) luaG_typeerrorL(L, o, opname)
|
||||||
|
#define luaG_forerror(L, o, what) luaG_forerrorL(L, o, what)
|
||||||
|
#define luaG_runerror(L, fmt, ...) luaG_runerrorL(L, fmt, ##__VA_ARGS__)
|
||||||
|
|
||||||
|
#define LUA_MEMERRMSG "not enough memory"
|
||||||
|
#define LUA_ERRERRMSG "error in error handling"
|
||||||
|
|
||||||
|
LUAI_FUNC l_noret luaG_typeerrorL(lua_State* L, const TValue* o, const char* opname);
|
||||||
|
LUAI_FUNC l_noret luaG_forerrorL(lua_State* L, const TValue* o, const char* what);
|
||||||
|
LUAI_FUNC l_noret luaG_concaterror(lua_State* L, StkId p1, StkId p2);
|
||||||
|
LUAI_FUNC l_noret luaG_aritherror(lua_State* L, const TValue* p1, const TValue* p2, TMS op);
|
||||||
|
LUAI_FUNC l_noret luaG_ordererror(lua_State* L, const TValue* p1, const TValue* p2, TMS op);
|
||||||
|
LUAI_FUNC l_noret luaG_indexerror(lua_State* L, const TValue* p1, const TValue* p2);
|
||||||
|
LUAI_FUNC l_noret luaG_methoderror(lua_State* L, const TValue* p1, const TString* p2);
|
||||||
|
LUAI_FUNC l_noret luaG_readonlyerror(lua_State* L);
|
||||||
|
|
||||||
|
LUAI_FUNC LUA_PRINTF_ATTR(2, 3) l_noret luaG_runerrorL(lua_State* L, const char* fmt, ...);
|
||||||
|
LUAI_FUNC void luaG_pusherror(lua_State* L, const char* error);
|
||||||
|
|
||||||
|
LUAI_FUNC void luaG_breakpoint(lua_State* L, Proto* p, int line, bool enable);
|
||||||
|
LUAI_FUNC bool luaG_onbreak(lua_State* L);
|
||||||
|
|
||||||
|
LUAI_FUNC int luaG_getline(Proto* p, int pc);
|
|
@ -0,0 +1,606 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
||||||
|
#include "ldo.h"
|
||||||
|
|
||||||
|
#include "lstring.h"
|
||||||
|
#include "lfunc.h"
|
||||||
|
#include "lgc.h"
|
||||||
|
#include "lmem.h"
|
||||||
|
#include "lvm.h"
|
||||||
|
|
||||||
|
#if LUA_USE_LONGJMP
|
||||||
|
#include <setjmp.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#else
|
||||||
|
#include <stdexcept>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauBetterOOMHandling, false)
|
||||||
|
|
||||||
|
/*
|
||||||
|
** {======================================================
|
||||||
|
** Error-recovery functions
|
||||||
|
** =======================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if LUA_USE_LONGJMP
|
||||||
|
struct lua_jmpbuf
|
||||||
|
{
|
||||||
|
lua_jmpbuf* volatile prev;
|
||||||
|
volatile int status;
|
||||||
|
jmp_buf buf;
|
||||||
|
};
|
||||||
|
|
||||||
|
// use POSIX versions of setjmp/longjmp if possible: they don't save/restore signal mask and are therefore faster
|
||||||
|
#if defined(__linux__) || defined(__APPLE__)
|
||||||
|
#define LUAU_SETJMP(buf) _setjmp(buf)
|
||||||
|
#define LUAU_LONGJMP(buf, code) _longjmp(buf, code)
|
||||||
|
#else
|
||||||
|
#define LUAU_SETJMP(buf) setjmp(buf)
|
||||||
|
#define LUAU_LONGJMP(buf, code) longjmp(buf, code)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int luaD_rawrunprotected(lua_State* L, Pfunc f, void* ud)
|
||||||
|
{
|
||||||
|
lua_jmpbuf jb;
|
||||||
|
jb.prev = L->global->errorjmp;
|
||||||
|
jb.status = 0;
|
||||||
|
L->global->errorjmp = &jb;
|
||||||
|
|
||||||
|
if (LUAU_SETJMP(jb.buf) == 0)
|
||||||
|
f(L, ud);
|
||||||
|
|
||||||
|
L->global->errorjmp = jb.prev;
|
||||||
|
return jb.status;
|
||||||
|
}
|
||||||
|
|
||||||
|
l_noret luaD_throw(lua_State* L, int errcode)
|
||||||
|
{
|
||||||
|
if (lua_jmpbuf* jb = L->global->errorjmp)
|
||||||
|
{
|
||||||
|
jb->status = errcode;
|
||||||
|
LUAU_LONGJMP(jb->buf, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (L->global->cb.panic)
|
||||||
|
L->global->cb.panic(L, errcode);
|
||||||
|
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
class lua_exception : public std::exception
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
lua_exception(lua_State* L, int status)
|
||||||
|
: L(L)
|
||||||
|
, status(status)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* what() const throw() override
|
||||||
|
{
|
||||||
|
// LUA_ERRRUN passes error object on the stack
|
||||||
|
if (status == LUA_ERRRUN || (status == LUA_ERRSYNTAX && !FFlag::LuauBetterOOMHandling))
|
||||||
|
if (const char* str = lua_tostring(L, -1))
|
||||||
|
return str;
|
||||||
|
|
||||||
|
switch (status)
|
||||||
|
{
|
||||||
|
case LUA_ERRRUN:
|
||||||
|
return "lua_exception: runtime error";
|
||||||
|
case LUA_ERRSYNTAX:
|
||||||
|
return "lua_exception: syntax error";
|
||||||
|
case LUA_ERRMEM:
|
||||||
|
return "lua_exception: " LUA_MEMERRMSG;
|
||||||
|
case LUA_ERRERR:
|
||||||
|
return "lua_exception: " LUA_ERRERRMSG;
|
||||||
|
default:
|
||||||
|
return "lua_exception: unexpected exception status";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int getStatus() const
|
||||||
|
{
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
lua_State* L;
|
||||||
|
int status;
|
||||||
|
};
|
||||||
|
|
||||||
|
int luaD_rawrunprotected(lua_State* L, Pfunc f, void* ud)
|
||||||
|
{
|
||||||
|
int status = 0;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
f(L, ud);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
catch (lua_exception& e)
|
||||||
|
{
|
||||||
|
// lua_exception means that luaD_throw was called and an exception object is on stack if status is ERRRUN
|
||||||
|
status = e.getStatus();
|
||||||
|
}
|
||||||
|
catch (std::exception& e)
|
||||||
|
{
|
||||||
|
// Luau will never throw this, but this can catch exceptions that escape from C++ implementations of external functions
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// there's no exception object on stack; let's push the error on stack so that error handling below can proceed
|
||||||
|
luaG_pusherror(L, e.what());
|
||||||
|
status = LUA_ERRRUN;
|
||||||
|
}
|
||||||
|
catch (std::exception&)
|
||||||
|
{
|
||||||
|
// out of memory while allocating error string
|
||||||
|
status = LUA_ERRMEM;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
l_noret luaD_throw(lua_State* L, int errcode)
|
||||||
|
{
|
||||||
|
throw lua_exception(L, errcode);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// }======================================================
|
||||||
|
|
||||||
|
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.open.threadnext)
|
||||||
|
up->v = (up->v - oldstack) + L->stack;
|
||||||
|
for (CallInfo* ci = L->base_ci; ci <= L->ci; ci++)
|
||||||
|
{
|
||||||
|
ci->top = (ci->top - oldstack) + L->stack;
|
||||||
|
ci->base = (ci->base - oldstack) + L->stack;
|
||||||
|
ci->func = (ci->func - oldstack) + L->stack;
|
||||||
|
}
|
||||||
|
L->base = (L->base - oldstack) + L->stack;
|
||||||
|
}
|
||||||
|
|
||||||
|
void luaD_reallocstack(lua_State* L, int newsize)
|
||||||
|
{
|
||||||
|
TValue* oldstack = L->stack;
|
||||||
|
int realsize = newsize + EXTRA_STACK;
|
||||||
|
LUAU_ASSERT(L->stack_last - L->stack == L->stacksize - EXTRA_STACK);
|
||||||
|
luaM_reallocarray(L, L->stack, L->stacksize, realsize, TValue, L->memcat);
|
||||||
|
TValue* newstack = L->stack;
|
||||||
|
for (int i = L->stacksize; i < realsize; i++)
|
||||||
|
setnilvalue(newstack + i); // erase new segment
|
||||||
|
L->stacksize = realsize;
|
||||||
|
L->stack_last = newstack + newsize;
|
||||||
|
correctstack(L, oldstack);
|
||||||
|
}
|
||||||
|
|
||||||
|
void luaD_reallocCI(lua_State* L, int newsize)
|
||||||
|
{
|
||||||
|
CallInfo* oldci = L->base_ci;
|
||||||
|
luaM_reallocarray(L, L->base_ci, L->size_ci, newsize, CallInfo, L->memcat);
|
||||||
|
L->size_ci = newsize;
|
||||||
|
L->ci = (L->ci - oldci) + L->base_ci;
|
||||||
|
L->end_ci = L->base_ci + L->size_ci - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void luaD_growstack(lua_State* L, int n)
|
||||||
|
{
|
||||||
|
if (n <= L->stacksize) // double size is enough?
|
||||||
|
luaD_reallocstack(L, 2 * L->stacksize);
|
||||||
|
else
|
||||||
|
luaD_reallocstack(L, L->stacksize + n);
|
||||||
|
}
|
||||||
|
|
||||||
|
CallInfo* luaD_growCI(lua_State* L)
|
||||||
|
{
|
||||||
|
// allow extra stack space to handle stack overflow in xpcall
|
||||||
|
const int hardlimit = LUAI_MAXCALLS + (LUAI_MAXCALLS >> 3);
|
||||||
|
|
||||||
|
if (L->size_ci >= hardlimit)
|
||||||
|
luaD_throw(L, LUA_ERRERR); // error while handling stack error
|
||||||
|
|
||||||
|
int request = L->size_ci * 2;
|
||||||
|
luaD_reallocCI(L, L->size_ci >= LUAI_MAXCALLS ? hardlimit : request < LUAI_MAXCALLS ? request : LUAI_MAXCALLS);
|
||||||
|
|
||||||
|
if (L->size_ci > LUAI_MAXCALLS)
|
||||||
|
luaG_runerror(L, "stack overflow");
|
||||||
|
|
||||||
|
return ++L->ci;
|
||||||
|
}
|
||||||
|
|
||||||
|
void luaD_checkCstack(lua_State* L)
|
||||||
|
{
|
||||||
|
// allow extra stack space to handle stack overflow in xpcall
|
||||||
|
const int hardlimit = LUAI_MAXCCALLS + (LUAI_MAXCCALLS >> 3);
|
||||||
|
|
||||||
|
if (L->nCcalls == LUAI_MAXCCALLS)
|
||||||
|
luaG_runerror(L, "C stack overflow");
|
||||||
|
else if (L->nCcalls >= hardlimit)
|
||||||
|
luaD_throw(L, LUA_ERRERR); // error while handling stack error
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Call a function (C or Lua). The function to be called is at *func.
|
||||||
|
** The arguments are on the stack, right after the function.
|
||||||
|
** When returns, all the results are on the stack, starting at the original
|
||||||
|
** function position.
|
||||||
|
*/
|
||||||
|
void luaD_call(lua_State* L, StkId func, int nResults)
|
||||||
|
{
|
||||||
|
if (++L->nCcalls >= LUAI_MAXCCALLS)
|
||||||
|
luaD_checkCstack(L);
|
||||||
|
|
||||||
|
if (luau_precall(L, func, nResults) == PCRLUA)
|
||||||
|
{ // is a Lua function?
|
||||||
|
L->ci->flags |= LUA_CALLINFO_RETURN; // luau_execute will stop after returning from the stack frame
|
||||||
|
|
||||||
|
bool oldactive = L->isactive;
|
||||||
|
L->isactive = true;
|
||||||
|
luaC_threadbarrier(L);
|
||||||
|
|
||||||
|
luau_execute(L); // call it
|
||||||
|
|
||||||
|
if (!oldactive)
|
||||||
|
L->isactive = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
L->nCcalls--;
|
||||||
|
luaC_checkGC(L);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void seterrorobj(lua_State* L, int errcode, StkId oldtop)
|
||||||
|
{
|
||||||
|
switch (errcode)
|
||||||
|
{
|
||||||
|
case LUA_ERRMEM:
|
||||||
|
{
|
||||||
|
setsvalue(L, oldtop, luaS_newliteral(L, LUA_MEMERRMSG)); // can not fail because string is pinned in luaopen
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case LUA_ERRERR:
|
||||||
|
{
|
||||||
|
setsvalue(L, oldtop, luaS_newliteral(L, LUA_ERRERRMSG)); // can not fail because string is pinned in luaopen
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case LUA_ERRSYNTAX:
|
||||||
|
case LUA_ERRRUN:
|
||||||
|
{
|
||||||
|
setobj2s(L, oldtop, L->top - 1); // error message on current top
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
L->top = oldtop + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void resume_continue(lua_State* L)
|
||||||
|
{
|
||||||
|
// unroll Lua/C combined stack, processing continuations
|
||||||
|
while (L->status == 0 && L->ci > L->base_ci)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(L->baseCcalls == L->nCcalls);
|
||||||
|
|
||||||
|
Closure* cl = curr_func(L);
|
||||||
|
|
||||||
|
if (cl->isC)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(cl->c.cont);
|
||||||
|
|
||||||
|
// C continuation; we expect this to be followed by Lua continuations
|
||||||
|
int n = cl->c.cont(L, 0);
|
||||||
|
|
||||||
|
// Continuation can break again
|
||||||
|
if (L->status == LUA_BREAK)
|
||||||
|
break;
|
||||||
|
|
||||||
|
luau_poscall(L, L->top - n);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Lua continuation; it terminates at the end of the stack or at another C continuation
|
||||||
|
luau_execute(L);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void resume(lua_State* L, void* ud)
|
||||||
|
{
|
||||||
|
StkId firstArg = cast_to(StkId, ud);
|
||||||
|
|
||||||
|
if (L->status == 0)
|
||||||
|
{
|
||||||
|
// start coroutine
|
||||||
|
LUAU_ASSERT(L->ci == L->base_ci && firstArg >= L->base);
|
||||||
|
if (firstArg == L->base)
|
||||||
|
luaG_runerror(L, "cannot resume dead coroutine");
|
||||||
|
|
||||||
|
if (luau_precall(L, firstArg - 1, LUA_MULTRET) != PCRLUA)
|
||||||
|
return;
|
||||||
|
|
||||||
|
L->ci->flags |= LUA_CALLINFO_RETURN;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// resume from previous yield or break
|
||||||
|
LUAU_ASSERT(L->status == LUA_YIELD || L->status == LUA_BREAK);
|
||||||
|
L->status = 0;
|
||||||
|
|
||||||
|
Closure* cl = curr_func(L);
|
||||||
|
|
||||||
|
if (cl->isC)
|
||||||
|
{
|
||||||
|
// if the top stack frame is a C call continuation, resume_continue will handle that case
|
||||||
|
if (!cl->c.cont)
|
||||||
|
{
|
||||||
|
// finish interrupted execution of `OP_CALL'
|
||||||
|
luau_poscall(L, firstArg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// yielded inside a hook: just continue its execution
|
||||||
|
L->base = L->ci->base;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// run continuations from the stack; typically resumes Lua code and pcalls
|
||||||
|
resume_continue(L);
|
||||||
|
}
|
||||||
|
|
||||||
|
static CallInfo* resume_findhandler(lua_State* L)
|
||||||
|
{
|
||||||
|
CallInfo* ci = L->ci;
|
||||||
|
|
||||||
|
while (ci > L->base_ci)
|
||||||
|
{
|
||||||
|
if (ci->flags & LUA_CALLINFO_HANDLE)
|
||||||
|
return ci;
|
||||||
|
|
||||||
|
ci--;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void resume_handle(lua_State* L, void* ud)
|
||||||
|
{
|
||||||
|
CallInfo* ci = (CallInfo*)ud;
|
||||||
|
Closure* cl = ci_func(ci);
|
||||||
|
|
||||||
|
LUAU_ASSERT(ci->flags & LUA_CALLINFO_HANDLE);
|
||||||
|
LUAU_ASSERT(cl->isC && cl->c.cont);
|
||||||
|
LUAU_ASSERT(L->status != 0);
|
||||||
|
|
||||||
|
// restore nCcalls back to base since this might not have happened during error handling
|
||||||
|
L->nCcalls = L->baseCcalls;
|
||||||
|
|
||||||
|
// make sure we don't run the handler the second time
|
||||||
|
ci->flags &= ~LUA_CALLINFO_HANDLE;
|
||||||
|
|
||||||
|
// restore thread status to 0 since we're handling the error
|
||||||
|
int status = L->status;
|
||||||
|
L->status = 0;
|
||||||
|
|
||||||
|
// push error object to stack top if it's not already there
|
||||||
|
if (status != LUA_ERRRUN)
|
||||||
|
seterrorobj(L, status, L->top);
|
||||||
|
|
||||||
|
// adjust the stack frame for ci to prepare for cont call
|
||||||
|
L->base = ci->base;
|
||||||
|
ci->top = L->top;
|
||||||
|
|
||||||
|
// save ci pointer - it will be invalidated by cont call!
|
||||||
|
ptrdiff_t old_ci = saveci(L, ci);
|
||||||
|
|
||||||
|
// handle the error in continuation; note that this executes on top of original stack!
|
||||||
|
int n = cl->c.cont(L, status);
|
||||||
|
|
||||||
|
// restore the stack frame to the frame with continuation
|
||||||
|
L->ci = restoreci(L, old_ci);
|
||||||
|
|
||||||
|
// close eventual pending closures; this means it's now safe to restore stack
|
||||||
|
luaF_close(L, L->base);
|
||||||
|
|
||||||
|
// finish cont call and restore stack to previous ci top
|
||||||
|
luau_poscall(L, L->top - n);
|
||||||
|
|
||||||
|
// run remaining continuations from the stack; typically resumes pcalls
|
||||||
|
resume_continue(L);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int resume_error(lua_State* L, const char* msg)
|
||||||
|
{
|
||||||
|
L->top = L->ci->base;
|
||||||
|
setsvalue(L, L->top, luaS_new(L, msg));
|
||||||
|
incr_top(L);
|
||||||
|
return LUA_ERRRUN;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void resume_finish(lua_State* L, int status)
|
||||||
|
{
|
||||||
|
L->nCcalls = L->baseCcalls;
|
||||||
|
L->isactive = false;
|
||||||
|
|
||||||
|
if (status != 0)
|
||||||
|
{ // error?
|
||||||
|
L->status = cast_byte(status); // mark thread as `dead'
|
||||||
|
seterrorobj(L, status, L->top);
|
||||||
|
L->ci->top = L->top;
|
||||||
|
}
|
||||||
|
else if (L->status == 0)
|
||||||
|
{
|
||||||
|
expandstacklimit(L, L->top);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int lua_resume(lua_State* L, lua_State* from, int nargs)
|
||||||
|
{
|
||||||
|
int status;
|
||||||
|
if (L->status != LUA_YIELD && L->status != LUA_BREAK && (L->status != 0 || L->ci != L->base_ci))
|
||||||
|
return resume_error(L, "cannot resume non-suspended coroutine");
|
||||||
|
|
||||||
|
L->nCcalls = from ? from->nCcalls : 0;
|
||||||
|
if (L->nCcalls >= LUAI_MAXCCALLS)
|
||||||
|
return resume_error(L, "C stack overflow");
|
||||||
|
|
||||||
|
L->baseCcalls = ++L->nCcalls;
|
||||||
|
L->isactive = true;
|
||||||
|
|
||||||
|
luaC_threadbarrier(L);
|
||||||
|
|
||||||
|
status = luaD_rawrunprotected(L, resume, L->top - nargs);
|
||||||
|
|
||||||
|
CallInfo* ch = NULL;
|
||||||
|
while (status != 0 && (ch = resume_findhandler(L)) != NULL)
|
||||||
|
{
|
||||||
|
L->status = cast_byte(status);
|
||||||
|
status = luaD_rawrunprotected(L, resume_handle, ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
resume_finish(L, status);
|
||||||
|
--L->nCcalls;
|
||||||
|
return L->status;
|
||||||
|
}
|
||||||
|
|
||||||
|
int lua_resumeerror(lua_State* L, lua_State* from)
|
||||||
|
{
|
||||||
|
int status;
|
||||||
|
if (L->status != LUA_YIELD && L->status != LUA_BREAK && (L->status != 0 || L->ci != L->base_ci))
|
||||||
|
return resume_error(L, "cannot resume non-suspended coroutine");
|
||||||
|
|
||||||
|
L->nCcalls = from ? from->nCcalls : 0;
|
||||||
|
if (L->nCcalls >= LUAI_MAXCCALLS)
|
||||||
|
return resume_error(L, "C stack overflow");
|
||||||
|
|
||||||
|
L->baseCcalls = ++L->nCcalls;
|
||||||
|
L->isactive = true;
|
||||||
|
|
||||||
|
luaC_threadbarrier(L);
|
||||||
|
|
||||||
|
status = LUA_ERRRUN;
|
||||||
|
|
||||||
|
CallInfo* ch = NULL;
|
||||||
|
while (status != 0 && (ch = resume_findhandler(L)) != NULL)
|
||||||
|
{
|
||||||
|
L->status = cast_byte(status);
|
||||||
|
status = luaD_rawrunprotected(L, resume_handle, ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
resume_finish(L, status);
|
||||||
|
--L->nCcalls;
|
||||||
|
return L->status;
|
||||||
|
}
|
||||||
|
|
||||||
|
int lua_yield(lua_State* L, int nresults)
|
||||||
|
{
|
||||||
|
if (L->nCcalls > L->baseCcalls)
|
||||||
|
luaG_runerror(L, "attempt to yield across metamethod/C-call boundary");
|
||||||
|
L->base = L->top - nresults; // protect stack slots below
|
||||||
|
L->status = LUA_YIELD;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int lua_break(lua_State* L)
|
||||||
|
{
|
||||||
|
if (L->nCcalls > L->baseCcalls)
|
||||||
|
luaG_runerror(L, "attempt to break across metamethod/C-call boundary");
|
||||||
|
L->status = LUA_BREAK;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int lua_isyieldable(lua_State* L)
|
||||||
|
{
|
||||||
|
return (L->nCcalls <= L->baseCcalls);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void callerrfunc(lua_State* L, void* ud)
|
||||||
|
{
|
||||||
|
StkId errfunc = cast_to(StkId, ud);
|
||||||
|
|
||||||
|
setobj2s(L, L->top, L->top - 1);
|
||||||
|
setobj2s(L, L->top - 1, errfunc);
|
||||||
|
incr_top(L);
|
||||||
|
luaD_call(L, L->top - 2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void restore_stack_limit(lua_State* L)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(L->stack_last - L->stack == L->stacksize - EXTRA_STACK);
|
||||||
|
if (L->size_ci > LUAI_MAXCALLS)
|
||||||
|
{ // there was an overflow?
|
||||||
|
int inuse = cast_int(L->ci - L->base_ci);
|
||||||
|
if (inuse + 1 < LUAI_MAXCALLS) // can `undo' overflow?
|
||||||
|
luaD_reallocCI(L, LUAI_MAXCALLS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int luaD_pcall(lua_State* L, Pfunc func, void* u, ptrdiff_t old_top, ptrdiff_t ef)
|
||||||
|
{
|
||||||
|
unsigned short oldnCcalls = L->nCcalls;
|
||||||
|
ptrdiff_t old_ci = saveci(L, L->ci);
|
||||||
|
bool oldactive = L->isactive;
|
||||||
|
int status = luaD_rawrunprotected(L, func, u);
|
||||||
|
if (status != 0)
|
||||||
|
{
|
||||||
|
int errstatus = status;
|
||||||
|
|
||||||
|
// call user-defined error function (used in xpcall)
|
||||||
|
if (ef)
|
||||||
|
{
|
||||||
|
if (FFlag::LuauBetterOOMHandling)
|
||||||
|
{
|
||||||
|
// push error object to stack top if it's not already there
|
||||||
|
if (status != LUA_ERRRUN)
|
||||||
|
seterrorobj(L, status, L->top);
|
||||||
|
|
||||||
|
// if errfunc fails, we fail with "error in error handling" or "not enough memory"
|
||||||
|
int err = luaD_rawrunprotected(L, callerrfunc, restorestack(L, ef));
|
||||||
|
|
||||||
|
// in general we preserve the status, except for cases when the error handler fails
|
||||||
|
// out of memory is treated specially because it's common for it to be cascading, in which case we preserve the code
|
||||||
|
if (err == 0)
|
||||||
|
errstatus = LUA_ERRRUN;
|
||||||
|
else if (status == LUA_ERRMEM && err == LUA_ERRMEM)
|
||||||
|
errstatus = LUA_ERRMEM;
|
||||||
|
else
|
||||||
|
errstatus = status = LUA_ERRERR;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// if errfunc fails, we fail with "error in error handling"
|
||||||
|
if (luaD_rawrunprotected(L, callerrfunc, restorestack(L, ef)) != 0)
|
||||||
|
status = LUA_ERRERR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// since the call failed with an error, we might have to reset the 'active' thread state
|
||||||
|
if (!oldactive)
|
||||||
|
L->isactive = false;
|
||||||
|
|
||||||
|
// restore nCcalls before calling the debugprotectederror callback which may rely on the proper value to have been restored.
|
||||||
|
L->nCcalls = oldnCcalls;
|
||||||
|
|
||||||
|
// an error occurred, check if we have a protected error callback
|
||||||
|
if (L->global->cb.debugprotectederror)
|
||||||
|
{
|
||||||
|
L->global->cb.debugprotectederror(L);
|
||||||
|
|
||||||
|
// debug hook is only allowed to break
|
||||||
|
if (L->status == LUA_BREAK)
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
StkId oldtop = restorestack(L, old_top);
|
||||||
|
luaF_close(L, oldtop); // close eventual pending closures
|
||||||
|
seterrorobj(L, FFlag::LuauBetterOOMHandling ? errstatus : status, oldtop);
|
||||||
|
L->ci = restoreci(L, old_ci);
|
||||||
|
L->base = L->ci->base;
|
||||||
|
restore_stack_limit(L);
|
||||||
|
}
|
||||||
|
return status;
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "lobject.h"
|
||||||
|
#include "lstate.h"
|
||||||
|
#include "luaconf.h"
|
||||||
|
#include "ldebug.h"
|
||||||
|
|
||||||
|
#define luaD_checkstack(L, n) \
|
||||||
|
if ((char*)L->stack_last - (char*)L->top <= (n) * (int)sizeof(TValue)) \
|
||||||
|
luaD_growstack(L, n); \
|
||||||
|
else \
|
||||||
|
condhardstacktests(luaD_reallocstack(L, L->stacksize - EXTRA_STACK));
|
||||||
|
|
||||||
|
#define incr_top(L) \
|
||||||
|
{ \
|
||||||
|
luaD_checkstack(L, 1); \
|
||||||
|
L->top++; \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define savestack(L, p) ((char*)(p) - (char*)L->stack)
|
||||||
|
#define restorestack(L, n) ((TValue*)((char*)L->stack + (n)))
|
||||||
|
|
||||||
|
#define expandstacklimit(L, p) \
|
||||||
|
{ \
|
||||||
|
LUAU_ASSERT((p) <= (L)->stack_last); \
|
||||||
|
if ((L)->ci->top < (p)) \
|
||||||
|
(L)->ci->top = (p); \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define incr_ci(L) ((L->ci == L->end_ci) ? luaD_growCI(L) : (condhardstacktests(luaD_reallocCI(L, L->size_ci)), ++L->ci))
|
||||||
|
|
||||||
|
#define saveci(L, p) ((char*)(p) - (char*)L->base_ci)
|
||||||
|
#define restoreci(L, n) ((CallInfo*)((char*)L->base_ci + (n)))
|
||||||
|
|
||||||
|
// results from luaD_precall
|
||||||
|
#define PCRLUA 0 // initiated a call to a Lua function
|
||||||
|
#define PCRC 1 // did a call to a C function
|
||||||
|
#define PCRYIELD 2 // C function yielded
|
||||||
|
|
||||||
|
// type of protected functions, to be ran by `runprotected'
|
||||||
|
typedef void (*Pfunc)(lua_State* L, void* ud);
|
||||||
|
|
||||||
|
LUAI_FUNC CallInfo* luaD_growCI(lua_State* L);
|
||||||
|
|
||||||
|
LUAI_FUNC void luaD_call(lua_State* L, StkId func, int nResults);
|
||||||
|
LUAI_FUNC int luaD_pcall(lua_State* L, Pfunc func, void* u, ptrdiff_t oldtop, ptrdiff_t ef);
|
||||||
|
LUAI_FUNC void luaD_reallocCI(lua_State* L, int newsize);
|
||||||
|
LUAI_FUNC void luaD_reallocstack(lua_State* L, int newsize);
|
||||||
|
LUAI_FUNC void luaD_growstack(lua_State* L, int n);
|
||||||
|
LUAI_FUNC void luaD_checkCstack(lua_State* L);
|
||||||
|
|
||||||
|
LUAI_FUNC l_noret luaD_throw(lua_State* L, int errcode);
|
||||||
|
LUAI_FUNC int luaD_rawrunprotected(lua_State* L, Pfunc f, void* ud);
|
|
@ -0,0 +1,197 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
||||||
|
#include "lfunc.h"
|
||||||
|
|
||||||
|
#include "lstate.h"
|
||||||
|
#include "lmem.h"
|
||||||
|
#include "lgc.h"
|
||||||
|
|
||||||
|
Proto* luaF_newproto(lua_State* L)
|
||||||
|
{
|
||||||
|
Proto* f = luaM_newgco(L, Proto, sizeof(Proto), L->activememcat);
|
||||||
|
luaC_init(L, f, LUA_TPROTO);
|
||||||
|
f->k = NULL;
|
||||||
|
f->sizek = 0;
|
||||||
|
f->p = NULL;
|
||||||
|
f->sizep = 0;
|
||||||
|
f->code = NULL;
|
||||||
|
f->sizecode = 0;
|
||||||
|
f->sizeupvalues = 0;
|
||||||
|
f->nups = 0;
|
||||||
|
f->upvalues = NULL;
|
||||||
|
f->numparams = 0;
|
||||||
|
f->is_vararg = 0;
|
||||||
|
f->maxstacksize = 0;
|
||||||
|
f->sizelineinfo = 0;
|
||||||
|
f->linegaplog2 = 0;
|
||||||
|
f->lineinfo = NULL;
|
||||||
|
f->abslineinfo = NULL;
|
||||||
|
f->sizelocvars = 0;
|
||||||
|
f->locvars = NULL;
|
||||||
|
f->source = NULL;
|
||||||
|
f->debugname = NULL;
|
||||||
|
f->debuginsn = NULL;
|
||||||
|
|
||||||
|
#if LUA_CUSTOM_EXECUTION
|
||||||
|
f->execdata = NULL;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
Closure* luaF_newLclosure(lua_State* L, int nelems, Table* e, Proto* p)
|
||||||
|
{
|
||||||
|
Closure* c = luaM_newgco(L, Closure, sizeLclosure(nelems), L->activememcat);
|
||||||
|
luaC_init(L, c, LUA_TFUNCTION);
|
||||||
|
c->isC = 0;
|
||||||
|
c->env = e;
|
||||||
|
c->nupvalues = cast_byte(nelems);
|
||||||
|
c->stacksize = p->maxstacksize;
|
||||||
|
c->preload = 0;
|
||||||
|
c->l.p = p;
|
||||||
|
for (int i = 0; i < nelems; ++i)
|
||||||
|
setnilvalue(&c->l.uprefs[i]);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
Closure* luaF_newCclosure(lua_State* L, int nelems, Table* e)
|
||||||
|
{
|
||||||
|
Closure* c = luaM_newgco(L, Closure, sizeCclosure(nelems), L->activememcat);
|
||||||
|
luaC_init(L, c, LUA_TFUNCTION);
|
||||||
|
c->isC = 1;
|
||||||
|
c->env = e;
|
||||||
|
c->nupvalues = cast_byte(nelems);
|
||||||
|
c->stacksize = LUA_MINSTACK;
|
||||||
|
c->preload = 0;
|
||||||
|
c->c.f = NULL;
|
||||||
|
c->c.cont = NULL;
|
||||||
|
c->c.debugname = NULL;
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
UpVal* luaF_findupval(lua_State* L, StkId level)
|
||||||
|
{
|
||||||
|
global_State* g = L->global;
|
||||||
|
UpVal** pp = &L->openupval;
|
||||||
|
UpVal* p;
|
||||||
|
while (*pp != NULL && (p = *pp)->v >= level)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(!isdead(g, obj2gco(p)));
|
||||||
|
LUAU_ASSERT(upisopen(p));
|
||||||
|
if (p->v == level)
|
||||||
|
return p;
|
||||||
|
|
||||||
|
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
|
||||||
|
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
|
||||||
|
uv->u.open.threadnext = *pp;
|
||||||
|
*pp = uv;
|
||||||
|
|
||||||
|
// double link the upvalue in the global open upvalue list
|
||||||
|
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_freeupval(lua_State* L, UpVal* uv, lua_Page* page)
|
||||||
|
{
|
||||||
|
luaM_freegco(L, uv, sizeof(UpVal), uv->memcat, page); // free upvalue
|
||||||
|
}
|
||||||
|
|
||||||
|
void luaF_close(lua_State* L, StkId level)
|
||||||
|
{
|
||||||
|
global_State* g = L->global;
|
||||||
|
UpVal* uv;
|
||||||
|
while (L->openupval != NULL && (uv = L->openupval)->v >= level)
|
||||||
|
{
|
||||||
|
GCObject* o = obj2gco(uv);
|
||||||
|
LUAU_ASSERT(!isblack(o) && upisopen(uv));
|
||||||
|
LUAU_ASSERT(!isdead(g, o));
|
||||||
|
|
||||||
|
// unlink value *before* closing it since value storage overlaps
|
||||||
|
L->openupval = uv->u.open.threadnext;
|
||||||
|
|
||||||
|
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);
|
||||||
|
luaM_freearray(L, f->p, f->sizep, Proto*, f->memcat);
|
||||||
|
luaM_freearray(L, f->k, f->sizek, TValue, f->memcat);
|
||||||
|
if (f->lineinfo)
|
||||||
|
luaM_freearray(L, f->lineinfo, f->sizelineinfo, uint8_t, f->memcat);
|
||||||
|
luaM_freearray(L, f->locvars, f->sizelocvars, struct LocVar, f->memcat);
|
||||||
|
luaM_freearray(L, f->upvalues, f->sizeupvalues, TString*, f->memcat);
|
||||||
|
if (f->debuginsn)
|
||||||
|
luaM_freearray(L, f->debuginsn, f->sizecode, uint8_t, f->memcat);
|
||||||
|
|
||||||
|
#if LUA_CUSTOM_EXECUTION
|
||||||
|
if (f->execdata)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(L->global->ecb.destroy);
|
||||||
|
L->global->ecb.destroy(L, f);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
luaM_freegco(L, f, sizeof(Proto), f->memcat, page);
|
||||||
|
}
|
||||||
|
|
||||||
|
void luaF_freeclosure(lua_State* L, Closure* c, lua_Page* page)
|
||||||
|
{
|
||||||
|
int size = c->isC ? sizeCclosure(c->nupvalues) : sizeLclosure(c->nupvalues);
|
||||||
|
luaM_freegco(L, c, size, c->memcat, page);
|
||||||
|
}
|
||||||
|
|
||||||
|
const LocVar* luaF_getlocal(const Proto* f, int local_number, int pc)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < f->sizelocvars; i++)
|
||||||
|
{
|
||||||
|
if (pc >= f->locvars[i].startpc && pc < f->locvars[i].endpc)
|
||||||
|
{ // is variable active?
|
||||||
|
local_number--;
|
||||||
|
if (local_number == 0)
|
||||||
|
return &f->locvars[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL; // not found
|
||||||
|
}
|
||||||
|
|
||||||
|
const LocVar* luaF_findlocal(const Proto* f, int local_reg, int pc)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < f->sizelocvars; i++)
|
||||||
|
if (local_reg == f->locvars[i].reg && pc >= f->locvars[i].startpc && pc < f->locvars[i].endpc)
|
||||||
|
return &f->locvars[i];
|
||||||
|
|
||||||
|
return NULL; // not found
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "lobject.h"
|
||||||
|
|
||||||
|
#define sizeCclosure(n) (offsetof(Closure, c.upvals) + sizeof(TValue) * (n))
|
||||||
|
#define sizeLclosure(n) (offsetof(Closure, l.uprefs) + sizeof(TValue) * (n))
|
||||||
|
|
||||||
|
LUAI_FUNC Proto* luaF_newproto(lua_State* L);
|
||||||
|
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_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);
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,138 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ldo.h"
|
||||||
|
#include "lobject.h"
|
||||||
|
#include "lstate.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Default settings for GC tunables (settable via lua_gc)
|
||||||
|
*/
|
||||||
|
#define LUAI_GCGOAL 200 // 200% (allow heap to double compared to live heap size)
|
||||||
|
#define LUAI_GCSTEPMUL 200 // GC runs 'twice the speed' of memory allocation
|
||||||
|
#define LUAI_GCSTEPSIZE 1 // GC runs every KB of memory allocation
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Possible states of the Garbage Collector
|
||||||
|
*/
|
||||||
|
#define GCSpause 0
|
||||||
|
#define GCSpropagate 1
|
||||||
|
#define GCSpropagateagain 2
|
||||||
|
#define GCSatomic 3
|
||||||
|
#define GCSsweep 4
|
||||||
|
|
||||||
|
/*
|
||||||
|
** macro to tell when main invariant (white objects cannot point to black
|
||||||
|
** ones) must be kept. During a collection, the sweep
|
||||||
|
** phase may break the invariant, as objects turned white may point to
|
||||||
|
** still-black objects. The invariant is restored when sweep ends and
|
||||||
|
** all objects are white again.
|
||||||
|
*/
|
||||||
|
#define keepinvariant(g) ((g)->gcstate == GCSpropagate || (g)->gcstate == GCSpropagateagain || (g)->gcstate == GCSatomic)
|
||||||
|
|
||||||
|
/*
|
||||||
|
** some useful bit tricks
|
||||||
|
*/
|
||||||
|
#define resetbits(x, m) ((x) &= cast_to(uint8_t, ~(m)))
|
||||||
|
#define setbits(x, m) ((x) |= (m))
|
||||||
|
#define testbits(x, m) ((x) & (m))
|
||||||
|
#define bitmask(b) (1 << (b))
|
||||||
|
#define bit2mask(b1, b2) (bitmask(b1) | bitmask(b2))
|
||||||
|
#define l_setbit(x, b) setbits(x, bitmask(b))
|
||||||
|
#define resetbit(x, b) resetbits(x, bitmask(b))
|
||||||
|
#define testbit(x, b) testbits(x, bitmask(b))
|
||||||
|
#define set2bits(x, b1, b2) setbits(x, (bit2mask(b1, b2)))
|
||||||
|
#define reset2bits(x, b1, b2) resetbits(x, (bit2mask(b1, b2)))
|
||||||
|
#define test2bits(x, b1, b2) testbits(x, (bit2mask(b1, b2)))
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Layout for bit use in `marked' field:
|
||||||
|
** bit 0 - object is white (type 0)
|
||||||
|
** bit 1 - object is white (type 1)
|
||||||
|
** bit 2 - object is black
|
||||||
|
** bit 3 - object is fixed (should not be collected)
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define WHITE0BIT 0
|
||||||
|
#define WHITE1BIT 1
|
||||||
|
#define BLACKBIT 2
|
||||||
|
#define FIXEDBIT 3
|
||||||
|
#define WHITEBITS bit2mask(WHITE0BIT, WHITE1BIT)
|
||||||
|
|
||||||
|
#define iswhite(x) test2bits((x)->gch.marked, WHITE0BIT, WHITE1BIT)
|
||||||
|
#define isblack(x) testbit((x)->gch.marked, BLACKBIT)
|
||||||
|
#define isgray(x) (!testbits((x)->gch.marked, WHITEBITS | bitmask(BLACKBIT)))
|
||||||
|
#define isfixed(x) testbit((x)->gch.marked, FIXEDBIT)
|
||||||
|
|
||||||
|
#define otherwhite(g) (g->currentwhite ^ WHITEBITS)
|
||||||
|
#define isdead(g, v) (((v)->gch.marked & (WHITEBITS | bitmask(FIXEDBIT))) == (otherwhite(g) & WHITEBITS))
|
||||||
|
|
||||||
|
#define changewhite(x) ((x)->gch.marked ^= WHITEBITS)
|
||||||
|
#define gray2black(x) l_setbit((x)->gch.marked, BLACKBIT)
|
||||||
|
|
||||||
|
#define luaC_white(g) cast_to(uint8_t, ((g)->currentwhite) & WHITEBITS)
|
||||||
|
|
||||||
|
#define luaC_checkGC(L) \
|
||||||
|
{ \
|
||||||
|
condhardstacktests(luaD_reallocstack(L, L->stacksize - EXTRA_STACK)); \
|
||||||
|
if (L->global->totalbytes >= L->global->GCthreshold) \
|
||||||
|
{ \
|
||||||
|
condhardmemtests(luaC_validate(L), 1); \
|
||||||
|
luaC_step(L, true); \
|
||||||
|
} \
|
||||||
|
else \
|
||||||
|
{ \
|
||||||
|
condhardmemtests(luaC_validate(L), 2); \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define luaC_barrier(L, p, v) \
|
||||||
|
{ \
|
||||||
|
if (iscollectable(v) && isblack(obj2gco(p)) && iswhite(gcvalue(v))) \
|
||||||
|
luaC_barrierf(L, obj2gco(p), gcvalue(v)); \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define luaC_barriert(L, t, v) \
|
||||||
|
{ \
|
||||||
|
if (iscollectable(v) && isblack(obj2gco(t)) && iswhite(gcvalue(v))) \
|
||||||
|
luaC_barriertable(L, t, gcvalue(v)); \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define luaC_barrierfast(L, t) \
|
||||||
|
{ \
|
||||||
|
if (isblack(obj2gco(t))) \
|
||||||
|
luaC_barrierback(L, obj2gco(t), &t->gclist); \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define luaC_objbarrier(L, p, o) \
|
||||||
|
{ \
|
||||||
|
if (isblack(obj2gco(p)) && iswhite(obj2gco(o))) \
|
||||||
|
luaC_barrierf(L, obj2gco(p), obj2gco(o)); \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define luaC_threadbarrier(L) \
|
||||||
|
{ \
|
||||||
|
if (isblack(obj2gco(L))) \
|
||||||
|
luaC_barrierback(L, obj2gco(L), &L->gclist); \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define luaC_init(L, o, tt_) \
|
||||||
|
{ \
|
||||||
|
o->marked = luaC_white(L->global); \
|
||||||
|
o->tt = tt_; \
|
||||||
|
o->memcat = L->activememcat; \
|
||||||
|
}
|
||||||
|
|
||||||
|
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_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, 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 const char* luaC_statename(int state);
|
|
@ -0,0 +1,604 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
||||||
|
#include "lgc.h"
|
||||||
|
|
||||||
|
#include "lfunc.h"
|
||||||
|
#include "lmem.h"
|
||||||
|
#include "lobject.h"
|
||||||
|
#include "lstate.h"
|
||||||
|
#include "lstring.h"
|
||||||
|
#include "ltable.h"
|
||||||
|
#include "ludata.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
static void validateobjref(global_State* g, GCObject* f, GCObject* t)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(!isdead(g, t));
|
||||||
|
|
||||||
|
if (keepinvariant(g))
|
||||||
|
{
|
||||||
|
// basic incremental invariant: black can't point to white
|
||||||
|
LUAU_ASSERT(!(isblack(f) && iswhite(t)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void validateref(global_State* g, GCObject* f, TValue* v)
|
||||||
|
{
|
||||||
|
if (iscollectable(v))
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(ttype(v) == gcvalue(v)->gch.tt);
|
||||||
|
validateobjref(g, f, gcvalue(v));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void validatetable(global_State* g, Table* h)
|
||||||
|
{
|
||||||
|
int sizenode = 1 << h->lsizenode;
|
||||||
|
|
||||||
|
LUAU_ASSERT(h->lastfree <= sizenode);
|
||||||
|
|
||||||
|
if (h->metatable)
|
||||||
|
validateobjref(g, obj2gco(h), obj2gco(h->metatable));
|
||||||
|
|
||||||
|
for (int i = 0; i < h->sizearray; ++i)
|
||||||
|
validateref(g, obj2gco(h), &h->array[i]);
|
||||||
|
|
||||||
|
for (int i = 0; i < sizenode; ++i)
|
||||||
|
{
|
||||||
|
LuaNode* n = &h->node[i];
|
||||||
|
|
||||||
|
LUAU_ASSERT(ttype(gkey(n)) != LUA_TDEADKEY || ttisnil(gval(n)));
|
||||||
|
LUAU_ASSERT(i + gnext(n) >= 0 && i + gnext(n) < sizenode);
|
||||||
|
|
||||||
|
if (!ttisnil(gval(n)))
|
||||||
|
{
|
||||||
|
TValue k = {};
|
||||||
|
k.tt = gkey(n)->tt;
|
||||||
|
k.value = gkey(n)->value;
|
||||||
|
|
||||||
|
validateref(g, obj2gco(h), &k);
|
||||||
|
validateref(g, obj2gco(h), gval(n));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void validateclosure(global_State* g, Closure* cl)
|
||||||
|
{
|
||||||
|
validateobjref(g, obj2gco(cl), obj2gco(cl->env));
|
||||||
|
|
||||||
|
if (cl->isC)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < cl->nupvalues; ++i)
|
||||||
|
validateref(g, obj2gco(cl), &cl->c.upvals[i]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(cl->nupvalues == cl->l.p->nups);
|
||||||
|
|
||||||
|
validateobjref(g, obj2gco(cl), obj2gco(cl->l.p));
|
||||||
|
|
||||||
|
for (int i = 0; i < cl->nupvalues; ++i)
|
||||||
|
validateref(g, obj2gco(cl), &cl->l.uprefs[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void validatestack(global_State* g, lua_State* l)
|
||||||
|
{
|
||||||
|
validateobjref(g, obj2gco(l), obj2gco(l->gt));
|
||||||
|
|
||||||
|
for (CallInfo* ci = l->base_ci; ci <= l->ci; ++ci)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(l->stack <= ci->base);
|
||||||
|
LUAU_ASSERT(ci->func <= ci->base && ci->base <= ci->top);
|
||||||
|
LUAU_ASSERT(ci->top <= l->stack_last);
|
||||||
|
}
|
||||||
|
|
||||||
|
// note: stack refs can violate gc invariant so we only check for liveness
|
||||||
|
for (StkId o = l->stack; o < l->top; ++o)
|
||||||
|
checkliveness(g, o);
|
||||||
|
|
||||||
|
if (l->namecall)
|
||||||
|
validateobjref(g, obj2gco(l), obj2gco(l->namecall));
|
||||||
|
|
||||||
|
for (UpVal* uv = l->openupval; uv; uv = uv->u.open.threadnext)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(uv->tt == LUA_TUPVAL);
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void validateproto(global_State* g, Proto* f)
|
||||||
|
{
|
||||||
|
if (f->source)
|
||||||
|
validateobjref(g, obj2gco(f), obj2gco(f->source));
|
||||||
|
|
||||||
|
if (f->debugname)
|
||||||
|
validateobjref(g, obj2gco(f), obj2gco(f->debugname));
|
||||||
|
|
||||||
|
for (int i = 0; i < f->sizek; ++i)
|
||||||
|
validateref(g, obj2gco(f), &f->k[i]);
|
||||||
|
|
||||||
|
for (int i = 0; i < f->sizeupvalues; ++i)
|
||||||
|
if (f->upvalues[i])
|
||||||
|
validateobjref(g, obj2gco(f), obj2gco(f->upvalues[i]));
|
||||||
|
|
||||||
|
for (int i = 0; i < f->sizep; ++i)
|
||||||
|
if (f->p[i])
|
||||||
|
validateobjref(g, obj2gco(f), obj2gco(f->p[i]));
|
||||||
|
|
||||||
|
for (int i = 0; i < f->sizelocvars; i++)
|
||||||
|
if (f->locvars[i].varname)
|
||||||
|
validateobjref(g, obj2gco(f), obj2gco(f->locvars[i].varname));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void validateobj(global_State* g, GCObject* o)
|
||||||
|
{
|
||||||
|
// dead objects can only occur during sweep
|
||||||
|
if (isdead(g, o))
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(g->gcstate == GCSsweep);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (o->gch.tt)
|
||||||
|
{
|
||||||
|
case LUA_TSTRING:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LUA_TTABLE:
|
||||||
|
validatetable(g, gco2h(o));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LUA_TFUNCTION:
|
||||||
|
validateclosure(g, gco2cl(o));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LUA_TUSERDATA:
|
||||||
|
if (gco2u(o)->metatable)
|
||||||
|
validateobjref(g, o, obj2gco(gco2u(o)->metatable));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LUA_TTHREAD:
|
||||||
|
validatestack(g, gco2th(o));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LUA_TPROTO:
|
||||||
|
validateproto(g, gco2p(o));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LUA_TUPVAL:
|
||||||
|
validateref(g, o, gco2uv(o)->v);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
LUAU_ASSERT(!"unexpected object type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void validategraylist(global_State* g, GCObject* o)
|
||||||
|
{
|
||||||
|
if (!keepinvariant(g))
|
||||||
|
return;
|
||||||
|
|
||||||
|
while (o)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(isgray(o));
|
||||||
|
|
||||||
|
switch (o->gch.tt)
|
||||||
|
{
|
||||||
|
case LUA_TTABLE:
|
||||||
|
o = gco2h(o)->gclist;
|
||||||
|
break;
|
||||||
|
case LUA_TFUNCTION:
|
||||||
|
o = gco2cl(o)->gclist;
|
||||||
|
break;
|
||||||
|
case LUA_TTHREAD:
|
||||||
|
o = gco2th(o)->gclist;
|
||||||
|
break;
|
||||||
|
case LUA_TPROTO:
|
||||||
|
o = gco2p(o)->gclist;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LUAU_ASSERT(!"unknown object in gray list");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool validategco(void* context, lua_Page* page, GCObject* gco)
|
||||||
|
{
|
||||||
|
lua_State* L = (lua_State*)context;
|
||||||
|
global_State* g = L->global;
|
||||||
|
|
||||||
|
validateobj(g, gco);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void luaC_validate(lua_State* L)
|
||||||
|
{
|
||||||
|
global_State* g = L->global;
|
||||||
|
|
||||||
|
LUAU_ASSERT(!isdead(g, obj2gco(g->mainthread)));
|
||||||
|
checkliveness(g, &g->registry);
|
||||||
|
|
||||||
|
for (int i = 0; i < LUA_T_COUNT; ++i)
|
||||||
|
if (g->mt[i])
|
||||||
|
LUAU_ASSERT(!isdead(g, obj2gco(g->mt[i])));
|
||||||
|
|
||||||
|
validategraylist(g, g->weak);
|
||||||
|
validategraylist(g, g->gray);
|
||||||
|
validategraylist(g, g->grayagain);
|
||||||
|
|
||||||
|
validategco(L, NULL, obj2gco(g->mainthread));
|
||||||
|
|
||||||
|
luaM_visitgco(L, L, validategco);
|
||||||
|
|
||||||
|
for (UpVal* uv = g->uvhead.u.open.next; uv != &g->uvhead; uv = uv->u.open.next)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(uv->tt == LUA_TUPVAL);
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool safejson(char ch)
|
||||||
|
{
|
||||||
|
return unsigned(ch) < 128 && ch >= 32 && ch != '\\' && ch != '\"';
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dumpref(FILE* f, GCObject* o)
|
||||||
|
{
|
||||||
|
fprintf(f, "\"%p\"", o);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dumprefs(FILE* f, TValue* data, size_t size)
|
||||||
|
{
|
||||||
|
bool first = true;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < size; ++i)
|
||||||
|
{
|
||||||
|
if (iscollectable(&data[i]))
|
||||||
|
{
|
||||||
|
if (!first)
|
||||||
|
fputc(',', f);
|
||||||
|
first = false;
|
||||||
|
|
||||||
|
dumpref(f, gcvalue(&data[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dumpstringdata(FILE* f, const char* data, size_t len)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < len; ++i)
|
||||||
|
fputc(safejson(data[i]) ? data[i] : '?', f);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dumpstring(FILE* f, TString* ts)
|
||||||
|
{
|
||||||
|
fprintf(f, "{\"type\":\"string\",\"cat\":%d,\"size\":%d,\"data\":\"", ts->memcat, int(sizestring(ts->len)));
|
||||||
|
dumpstringdata(f, ts->data, ts->len);
|
||||||
|
fprintf(f, "\"}");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dumptable(FILE* f, Table* h)
|
||||||
|
{
|
||||||
|
size_t size = sizeof(Table) + (h->node == &luaH_dummynode ? 0 : sizenode(h) * sizeof(LuaNode)) + h->sizearray * sizeof(TValue);
|
||||||
|
|
||||||
|
fprintf(f, "{\"type\":\"table\",\"cat\":%d,\"size\":%d", h->memcat, int(size));
|
||||||
|
|
||||||
|
if (h->node != &luaH_dummynode)
|
||||||
|
{
|
||||||
|
fprintf(f, ",\"pairs\":[");
|
||||||
|
|
||||||
|
bool first = true;
|
||||||
|
|
||||||
|
for (int i = 0; i < sizenode(h); ++i)
|
||||||
|
{
|
||||||
|
const LuaNode& n = h->node[i];
|
||||||
|
|
||||||
|
if (!ttisnil(&n.val) && (iscollectable(&n.key) || iscollectable(&n.val)))
|
||||||
|
{
|
||||||
|
if (!first)
|
||||||
|
fputc(',', f);
|
||||||
|
first = false;
|
||||||
|
|
||||||
|
if (iscollectable(&n.key))
|
||||||
|
dumpref(f, gcvalue(&n.key));
|
||||||
|
else
|
||||||
|
fprintf(f, "null");
|
||||||
|
|
||||||
|
fputc(',', f);
|
||||||
|
|
||||||
|
if (iscollectable(&n.val))
|
||||||
|
dumpref(f, gcvalue(&n.val));
|
||||||
|
else
|
||||||
|
fprintf(f, "null");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(f, "]");
|
||||||
|
}
|
||||||
|
if (h->sizearray)
|
||||||
|
{
|
||||||
|
fprintf(f, ",\"array\":[");
|
||||||
|
dumprefs(f, h->array, h->sizearray);
|
||||||
|
fprintf(f, "]");
|
||||||
|
}
|
||||||
|
if (h->metatable)
|
||||||
|
{
|
||||||
|
fprintf(f, ",\"metatable\":");
|
||||||
|
dumpref(f, obj2gco(h->metatable));
|
||||||
|
}
|
||||||
|
fprintf(f, "}");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dumpclosure(FILE* f, Closure* cl)
|
||||||
|
{
|
||||||
|
fprintf(f, "{\"type\":\"function\",\"cat\":%d,\"size\":%d", cl->memcat,
|
||||||
|
cl->isC ? int(sizeCclosure(cl->nupvalues)) : int(sizeLclosure(cl->nupvalues)));
|
||||||
|
|
||||||
|
fprintf(f, ",\"env\":");
|
||||||
|
dumpref(f, obj2gco(cl->env));
|
||||||
|
|
||||||
|
if (cl->isC)
|
||||||
|
{
|
||||||
|
if (cl->c.debugname)
|
||||||
|
fprintf(f, ",\"name\":\"%s\"", cl->c.debugname + 0);
|
||||||
|
|
||||||
|
if (cl->nupvalues)
|
||||||
|
{
|
||||||
|
fprintf(f, ",\"upvalues\":[");
|
||||||
|
dumprefs(f, cl->c.upvals, cl->nupvalues);
|
||||||
|
fprintf(f, "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (cl->l.p->debugname)
|
||||||
|
fprintf(f, ",\"name\":\"%s\"", getstr(cl->l.p->debugname));
|
||||||
|
|
||||||
|
fprintf(f, ",\"proto\":");
|
||||||
|
dumpref(f, obj2gco(cl->l.p));
|
||||||
|
if (cl->nupvalues)
|
||||||
|
{
|
||||||
|
fprintf(f, ",\"upvalues\":[");
|
||||||
|
dumprefs(f, cl->l.uprefs, cl->nupvalues);
|
||||||
|
fprintf(f, "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fprintf(f, "}");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dumpudata(FILE* f, Udata* u)
|
||||||
|
{
|
||||||
|
fprintf(f, "{\"type\":\"userdata\",\"cat\":%d,\"size\":%d,\"tag\":%d", u->memcat, int(sizeudata(u->len)), u->tag);
|
||||||
|
|
||||||
|
if (u->metatable)
|
||||||
|
{
|
||||||
|
fprintf(f, ",\"metatable\":");
|
||||||
|
dumpref(f, obj2gco(u->metatable));
|
||||||
|
}
|
||||||
|
fprintf(f, "}");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dumpthread(FILE* f, lua_State* th)
|
||||||
|
{
|
||||||
|
size_t size = sizeof(lua_State) + sizeof(TValue) * th->stacksize + sizeof(CallInfo) * th->size_ci;
|
||||||
|
|
||||||
|
fprintf(f, "{\"type\":\"thread\",\"cat\":%d,\"size\":%d", th->memcat, int(size));
|
||||||
|
|
||||||
|
fprintf(f, ",\"env\":");
|
||||||
|
dumpref(f, obj2gco(th->gt));
|
||||||
|
|
||||||
|
Closure* tcl = 0;
|
||||||
|
for (CallInfo* ci = th->base_ci; ci <= th->ci; ++ci)
|
||||||
|
{
|
||||||
|
if (ttisfunction(ci->func))
|
||||||
|
{
|
||||||
|
tcl = clvalue(ci->func);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tcl && !tcl->isC && tcl->l.p->source)
|
||||||
|
{
|
||||||
|
Proto* p = tcl->l.p;
|
||||||
|
|
||||||
|
fprintf(f, ",\"source\":\"");
|
||||||
|
dumpstringdata(f, p->source->data, p->source->len);
|
||||||
|
fprintf(f, "\",\"line\":%d", p->linedefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (th->top > th->stack)
|
||||||
|
{
|
||||||
|
fprintf(f, ",\"stack\":[");
|
||||||
|
dumprefs(f, th->stack, th->top - th->stack);
|
||||||
|
fprintf(f, "]");
|
||||||
|
|
||||||
|
CallInfo* ci = th->base_ci;
|
||||||
|
bool first = true;
|
||||||
|
|
||||||
|
fprintf(f, ",\"stacknames\":[");
|
||||||
|
for (StkId v = th->stack; v < th->top; ++v)
|
||||||
|
{
|
||||||
|
if (!iscollectable(v))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
while (ci < th->ci && v >= (ci + 1)->func)
|
||||||
|
ci++;
|
||||||
|
|
||||||
|
if (!first)
|
||||||
|
fputc(',', f);
|
||||||
|
first = false;
|
||||||
|
|
||||||
|
if (v == ci->func)
|
||||||
|
{
|
||||||
|
Closure* cl = ci_func(ci);
|
||||||
|
|
||||||
|
if (cl->isC)
|
||||||
|
{
|
||||||
|
fprintf(f, "\"frame:%s\"", cl->c.debugname ? cl->c.debugname : "[C]");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Proto* p = cl->l.p;
|
||||||
|
fprintf(f, "\"frame:");
|
||||||
|
if (p->source)
|
||||||
|
dumpstringdata(f, p->source->data, p->source->len);
|
||||||
|
fprintf(f, ":%d:%s\"", p->linedefined, p->debugname ? getstr(p->debugname) : "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (isLua(ci))
|
||||||
|
{
|
||||||
|
Proto* p = ci_func(ci)->l.p;
|
||||||
|
int pc = pcRel(ci->savedpc, p);
|
||||||
|
const LocVar* var = luaF_findlocal(p, int(v - ci->base), pc);
|
||||||
|
|
||||||
|
if (var && var->varname)
|
||||||
|
fprintf(f, "\"%s\"", getstr(var->varname));
|
||||||
|
else
|
||||||
|
fprintf(f, "null");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
fprintf(f, "null");
|
||||||
|
}
|
||||||
|
fprintf(f, "]");
|
||||||
|
}
|
||||||
|
fprintf(f, "}");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dumpproto(FILE* f, Proto* p)
|
||||||
|
{
|
||||||
|
size_t size = sizeof(Proto) + sizeof(Instruction) * p->sizecode + sizeof(Proto*) * p->sizep + sizeof(TValue) * p->sizek + p->sizelineinfo +
|
||||||
|
sizeof(LocVar) * p->sizelocvars + sizeof(TString*) * p->sizeupvalues;
|
||||||
|
|
||||||
|
fprintf(f, "{\"type\":\"proto\",\"cat\":%d,\"size\":%d", p->memcat, int(size));
|
||||||
|
|
||||||
|
if (p->source)
|
||||||
|
{
|
||||||
|
fprintf(f, ",\"source\":\"");
|
||||||
|
dumpstringdata(f, p->source->data, p->source->len);
|
||||||
|
fprintf(f, "\",\"line\":%d", p->abslineinfo ? p->abslineinfo[0] : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p->sizek)
|
||||||
|
{
|
||||||
|
fprintf(f, ",\"constants\":[");
|
||||||
|
dumprefs(f, p->k, p->sizek);
|
||||||
|
fprintf(f, "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p->sizep)
|
||||||
|
{
|
||||||
|
fprintf(f, ",\"protos\":[");
|
||||||
|
for (int i = 0; i < p->sizep; ++i)
|
||||||
|
{
|
||||||
|
if (i != 0)
|
||||||
|
fputc(',', f);
|
||||||
|
dumpref(f, obj2gco(p->p[i]));
|
||||||
|
}
|
||||||
|
fprintf(f, "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(f, "}");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dumpupval(FILE* f, UpVal* uv)
|
||||||
|
{
|
||||||
|
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, "}");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dumpobj(FILE* f, GCObject* o)
|
||||||
|
{
|
||||||
|
switch (o->gch.tt)
|
||||||
|
{
|
||||||
|
case LUA_TSTRING:
|
||||||
|
return dumpstring(f, gco2ts(o));
|
||||||
|
|
||||||
|
case LUA_TTABLE:
|
||||||
|
return dumptable(f, gco2h(o));
|
||||||
|
|
||||||
|
case LUA_TFUNCTION:
|
||||||
|
return dumpclosure(f, gco2cl(o));
|
||||||
|
|
||||||
|
case LUA_TUSERDATA:
|
||||||
|
return dumpudata(f, gco2u(o));
|
||||||
|
|
||||||
|
case LUA_TTHREAD:
|
||||||
|
return dumpthread(f, gco2th(o));
|
||||||
|
|
||||||
|
case LUA_TPROTO:
|
||||||
|
return dumpproto(f, gco2p(o));
|
||||||
|
|
||||||
|
case LUA_TUPVAL:
|
||||||
|
return dumpupval(f, gco2uv(o));
|
||||||
|
|
||||||
|
default:
|
||||||
|
LUAU_ASSERT(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool dumpgco(void* context, lua_Page* page, GCObject* gco)
|
||||||
|
{
|
||||||
|
FILE* f = (FILE*)context;
|
||||||
|
|
||||||
|
dumpref(f, gco);
|
||||||
|
fputc(':', f);
|
||||||
|
dumpobj(f, gco);
|
||||||
|
fputc(',', f);
|
||||||
|
fputc('\n', f);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void luaC_dump(lua_State* L, void* file, const char* (*categoryName)(lua_State* L, uint8_t memcat))
|
||||||
|
{
|
||||||
|
global_State* g = L->global;
|
||||||
|
FILE* f = static_cast<FILE*>(file);
|
||||||
|
|
||||||
|
fprintf(f, "{\"objects\":{\n");
|
||||||
|
|
||||||
|
dumpgco(f, NULL, obj2gco(g->mainthread));
|
||||||
|
|
||||||
|
luaM_visitgco(L, f, dumpgco);
|
||||||
|
|
||||||
|
fprintf(f, "\"0\":{\"type\":\"userdata\",\"cat\":0,\"size\":0}\n"); // to avoid issues with trailing ,
|
||||||
|
fprintf(f, "},\"roots\":{\n");
|
||||||
|
fprintf(f, "\"mainthread\":");
|
||||||
|
dumpref(f, obj2gco(g->mainthread));
|
||||||
|
fprintf(f, ",\"registry\":");
|
||||||
|
dumpref(f, gcvalue(&g->registry));
|
||||||
|
|
||||||
|
fprintf(f, "},\"stats\":{\n");
|
||||||
|
|
||||||
|
fprintf(f, "\"size\":%d,\n", int(g->totalbytes));
|
||||||
|
|
||||||
|
fprintf(f, "\"categories\":{\n");
|
||||||
|
for (int i = 0; i < LUA_MEMORY_CATEGORIES; i++)
|
||||||
|
{
|
||||||
|
if (size_t bytes = g->memcatbytes[i])
|
||||||
|
{
|
||||||
|
if (categoryName)
|
||||||
|
fprintf(f, "\"%d\":{\"name\":\"%s\", \"size\":%d},\n", i, categoryName(L, i), int(bytes));
|
||||||
|
else
|
||||||
|
fprintf(f, "\"%d\":{\"size\":%d},\n", i, int(bytes));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fprintf(f, "\"none\":{}\n"); // to avoid issues with trailing ,
|
||||||
|
fprintf(f, "}\n");
|
||||||
|
fprintf(f, "}}\n");
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
||||||
|
#include "lualib.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
static const luaL_Reg lualibs[] = {
|
||||||
|
{"", luaopen_base},
|
||||||
|
{LUA_COLIBNAME, luaopen_coroutine},
|
||||||
|
{LUA_TABLIBNAME, luaopen_table},
|
||||||
|
{LUA_OSLIBNAME, luaopen_os},
|
||||||
|
{LUA_STRLIBNAME, luaopen_string},
|
||||||
|
{LUA_MATHLIBNAME, luaopen_math},
|
||||||
|
{LUA_DBLIBNAME, luaopen_debug},
|
||||||
|
{LUA_UTF8LIBNAME, luaopen_utf8},
|
||||||
|
{LUA_BITLIBNAME, luaopen_bit32},
|
||||||
|
{NULL, NULL},
|
||||||
|
};
|
||||||
|
|
||||||
|
void luaL_openlibs(lua_State* L)
|
||||||
|
{
|
||||||
|
const luaL_Reg* lib = lualibs;
|
||||||
|
for (; lib->func; lib++)
|
||||||
|
{
|
||||||
|
lua_pushcfunction(L, lib->func, NULL);
|
||||||
|
lua_pushstring(L, lib->name);
|
||||||
|
lua_call(L, 1, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void luaL_sandbox(lua_State* L)
|
||||||
|
{
|
||||||
|
// set all libraries to read-only
|
||||||
|
lua_pushnil(L);
|
||||||
|
while (lua_next(L, LUA_GLOBALSINDEX) != 0)
|
||||||
|
{
|
||||||
|
if (lua_istable(L, -1))
|
||||||
|
lua_setreadonly(L, -1, true);
|
||||||
|
|
||||||
|
lua_pop(L, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// set all builtin metatables to read-only
|
||||||
|
lua_pushliteral(L, "");
|
||||||
|
lua_getmetatable(L, -1);
|
||||||
|
lua_setreadonly(L, -1, true);
|
||||||
|
lua_pop(L, 2);
|
||||||
|
|
||||||
|
// set globals to readonly and activate safeenv since the env is immutable
|
||||||
|
lua_setreadonly(L, LUA_GLOBALSINDEX, true);
|
||||||
|
lua_setsafeenv(L, LUA_GLOBALSINDEX, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void luaL_sandboxthread(lua_State* L)
|
||||||
|
{
|
||||||
|
// create new global table that proxies reads to original table
|
||||||
|
lua_newtable(L);
|
||||||
|
|
||||||
|
lua_newtable(L);
|
||||||
|
lua_pushvalue(L, LUA_GLOBALSINDEX);
|
||||||
|
lua_setfield(L, -2, "__index");
|
||||||
|
lua_setreadonly(L, -1, true);
|
||||||
|
|
||||||
|
lua_setmetatable(L, -2);
|
||||||
|
|
||||||
|
// we can set safeenv now although it's important to set it to false if code is loaded twice into the thread
|
||||||
|
lua_replace(L, LUA_GLOBALSINDEX);
|
||||||
|
lua_setsafeenv(L, LUA_GLOBALSINDEX, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void* l_alloc(void* ud, void* ptr, size_t osize, size_t nsize)
|
||||||
|
{
|
||||||
|
(void)ud;
|
||||||
|
(void)osize;
|
||||||
|
if (nsize == 0)
|
||||||
|
{
|
||||||
|
free(ptr);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return realloc(ptr, nsize);
|
||||||
|
}
|
||||||
|
|
||||||
|
lua_State* luaL_newstate(void)
|
||||||
|
{
|
||||||
|
return lua_newstate(l_alloc, NULL);
|
||||||
|
}
|
|
@ -0,0 +1,446 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
||||||
|
#include "lualib.h"
|
||||||
|
|
||||||
|
#include "lstate.h"
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
#undef PI
|
||||||
|
#define PI (3.14159265358979323846)
|
||||||
|
#define RADIANS_PER_DEGREE (PI / 180.0)
|
||||||
|
|
||||||
|
#define PCG32_INC 105
|
||||||
|
|
||||||
|
static uint32_t pcg32_random(uint64_t* state)
|
||||||
|
{
|
||||||
|
uint64_t oldstate = *state;
|
||||||
|
*state = oldstate * 6364136223846793005ULL + (PCG32_INC | 1);
|
||||||
|
uint32_t xorshifted = uint32_t(((oldstate >> 18u) ^ oldstate) >> 27u);
|
||||||
|
uint32_t rot = uint32_t(oldstate >> 59u);
|
||||||
|
return (xorshifted >> rot) | (xorshifted << ((-int32_t(rot)) & 31));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void pcg32_seed(uint64_t* state, uint64_t seed)
|
||||||
|
{
|
||||||
|
*state = 0;
|
||||||
|
pcg32_random(state);
|
||||||
|
*state += seed;
|
||||||
|
pcg32_random(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int math_abs(lua_State* L)
|
||||||
|
{
|
||||||
|
lua_pushnumber(L, fabs(luaL_checknumber(L, 1)));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int math_sin(lua_State* L)
|
||||||
|
{
|
||||||
|
lua_pushnumber(L, sin(luaL_checknumber(L, 1)));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int math_sinh(lua_State* L)
|
||||||
|
{
|
||||||
|
lua_pushnumber(L, sinh(luaL_checknumber(L, 1)));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int math_cos(lua_State* L)
|
||||||
|
{
|
||||||
|
lua_pushnumber(L, cos(luaL_checknumber(L, 1)));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int math_cosh(lua_State* L)
|
||||||
|
{
|
||||||
|
lua_pushnumber(L, cosh(luaL_checknumber(L, 1)));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int math_tan(lua_State* L)
|
||||||
|
{
|
||||||
|
lua_pushnumber(L, tan(luaL_checknumber(L, 1)));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int math_tanh(lua_State* L)
|
||||||
|
{
|
||||||
|
lua_pushnumber(L, tanh(luaL_checknumber(L, 1)));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int math_asin(lua_State* L)
|
||||||
|
{
|
||||||
|
lua_pushnumber(L, asin(luaL_checknumber(L, 1)));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int math_acos(lua_State* L)
|
||||||
|
{
|
||||||
|
lua_pushnumber(L, acos(luaL_checknumber(L, 1)));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int math_atan(lua_State* L)
|
||||||
|
{
|
||||||
|
lua_pushnumber(L, atan(luaL_checknumber(L, 1)));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int math_atan2(lua_State* L)
|
||||||
|
{
|
||||||
|
lua_pushnumber(L, atan2(luaL_checknumber(L, 1), luaL_checknumber(L, 2)));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int math_ceil(lua_State* L)
|
||||||
|
{
|
||||||
|
lua_pushnumber(L, ceil(luaL_checknumber(L, 1)));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int math_floor(lua_State* L)
|
||||||
|
{
|
||||||
|
lua_pushnumber(L, floor(luaL_checknumber(L, 1)));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int math_fmod(lua_State* L)
|
||||||
|
{
|
||||||
|
lua_pushnumber(L, fmod(luaL_checknumber(L, 1), luaL_checknumber(L, 2)));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int math_modf(lua_State* L)
|
||||||
|
{
|
||||||
|
double ip;
|
||||||
|
double fp = modf(luaL_checknumber(L, 1), &ip);
|
||||||
|
lua_pushnumber(L, ip);
|
||||||
|
lua_pushnumber(L, fp);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int math_sqrt(lua_State* L)
|
||||||
|
{
|
||||||
|
lua_pushnumber(L, sqrt(luaL_checknumber(L, 1)));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int math_pow(lua_State* L)
|
||||||
|
{
|
||||||
|
lua_pushnumber(L, pow(luaL_checknumber(L, 1), luaL_checknumber(L, 2)));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int math_log(lua_State* L)
|
||||||
|
{
|
||||||
|
double x = luaL_checknumber(L, 1);
|
||||||
|
double res;
|
||||||
|
if (lua_isnoneornil(L, 2))
|
||||||
|
res = log(x);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
double base = luaL_checknumber(L, 2);
|
||||||
|
if (base == 2.0)
|
||||||
|
res = log2(x);
|
||||||
|
else if (base == 10.0)
|
||||||
|
res = log10(x);
|
||||||
|
else
|
||||||
|
res = log(x) / log(base);
|
||||||
|
}
|
||||||
|
lua_pushnumber(L, res);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int math_log10(lua_State* L)
|
||||||
|
{
|
||||||
|
lua_pushnumber(L, log10(luaL_checknumber(L, 1)));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int math_exp(lua_State* L)
|
||||||
|
{
|
||||||
|
lua_pushnumber(L, exp(luaL_checknumber(L, 1)));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int math_deg(lua_State* L)
|
||||||
|
{
|
||||||
|
lua_pushnumber(L, luaL_checknumber(L, 1) / RADIANS_PER_DEGREE);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int math_rad(lua_State* L)
|
||||||
|
{
|
||||||
|
lua_pushnumber(L, luaL_checknumber(L, 1) * RADIANS_PER_DEGREE);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int math_frexp(lua_State* L)
|
||||||
|
{
|
||||||
|
int e;
|
||||||
|
lua_pushnumber(L, frexp(luaL_checknumber(L, 1), &e));
|
||||||
|
lua_pushinteger(L, e);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int math_ldexp(lua_State* L)
|
||||||
|
{
|
||||||
|
lua_pushnumber(L, ldexp(luaL_checknumber(L, 1), luaL_checkinteger(L, 2)));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int math_min(lua_State* L)
|
||||||
|
{
|
||||||
|
int n = lua_gettop(L); // number of arguments
|
||||||
|
double dmin = luaL_checknumber(L, 1);
|
||||||
|
int i;
|
||||||
|
for (i = 2; i <= n; i++)
|
||||||
|
{
|
||||||
|
double d = luaL_checknumber(L, i);
|
||||||
|
if (d < dmin)
|
||||||
|
dmin = d;
|
||||||
|
}
|
||||||
|
lua_pushnumber(L, dmin);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int math_max(lua_State* L)
|
||||||
|
{
|
||||||
|
int n = lua_gettop(L); // number of arguments
|
||||||
|
double dmax = luaL_checknumber(L, 1);
|
||||||
|
int i;
|
||||||
|
for (i = 2; i <= n; i++)
|
||||||
|
{
|
||||||
|
double d = luaL_checknumber(L, i);
|
||||||
|
if (d > dmax)
|
||||||
|
dmax = d;
|
||||||
|
}
|
||||||
|
lua_pushnumber(L, dmax);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int math_random(lua_State* L)
|
||||||
|
{
|
||||||
|
global_State* g = L->global;
|
||||||
|
switch (lua_gettop(L))
|
||||||
|
{ // check number of arguments
|
||||||
|
case 0:
|
||||||
|
{ // no arguments
|
||||||
|
// Using ldexp instead of division for speed & clarity.
|
||||||
|
// See http://mumble.net/~campbell/tmp/random_real.c for details on generating doubles from integer ranges.
|
||||||
|
uint32_t rl = pcg32_random(&g->rngstate);
|
||||||
|
uint32_t rh = pcg32_random(&g->rngstate);
|
||||||
|
double rd = ldexp(double(rl | (uint64_t(rh) << 32)), -64);
|
||||||
|
lua_pushnumber(L, rd); // number between 0 and 1
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 1:
|
||||||
|
{ // only upper limit
|
||||||
|
int u = luaL_checkinteger(L, 1);
|
||||||
|
luaL_argcheck(L, 1 <= u, 1, "interval is empty");
|
||||||
|
|
||||||
|
uint64_t x = uint64_t(u) * pcg32_random(&g->rngstate);
|
||||||
|
int r = int(1 + (x >> 32));
|
||||||
|
lua_pushinteger(L, r); // int between 1 and `u'
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 2:
|
||||||
|
{ // lower and upper limits
|
||||||
|
int l = luaL_checkinteger(L, 1);
|
||||||
|
int u = luaL_checkinteger(L, 2);
|
||||||
|
luaL_argcheck(L, l <= u, 2, "interval is empty");
|
||||||
|
|
||||||
|
uint32_t ul = uint32_t(u) - uint32_t(l);
|
||||||
|
luaL_argcheck(L, ul < UINT_MAX, 2, "interval is too large"); // -INT_MIN..INT_MAX interval can result in integer overflow
|
||||||
|
uint64_t x = uint64_t(ul + 1) * pcg32_random(&g->rngstate);
|
||||||
|
int r = int(l + (x >> 32));
|
||||||
|
lua_pushinteger(L, r); // int between `l' and `u'
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
luaL_error(L, "wrong number of arguments");
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int math_randomseed(lua_State* L)
|
||||||
|
{
|
||||||
|
int seed = luaL_checkinteger(L, 1);
|
||||||
|
|
||||||
|
pcg32_seed(&L->global->rngstate, seed);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const unsigned char kPerlin[512] = {151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99,
|
||||||
|
37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174,
|
||||||
|
20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41,
|
||||||
|
55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86,
|
||||||
|
164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17,
|
||||||
|
182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110,
|
||||||
|
79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14,
|
||||||
|
239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24,
|
||||||
|
72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180,
|
||||||
|
|
||||||
|
151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247,
|
||||||
|
120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74,
|
||||||
|
165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54, 65,
|
||||||
|
25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52,
|
||||||
|
217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213,
|
||||||
|
119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112,
|
||||||
|
104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199,
|
||||||
|
106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61,
|
||||||
|
156, 180};
|
||||||
|
|
||||||
|
static float fade(float t)
|
||||||
|
{
|
||||||
|
return t * t * t * (t * (t * 6 - 15) + 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
static float math_lerp(float t, float a, float b)
|
||||||
|
{
|
||||||
|
return a + t * (b - a);
|
||||||
|
}
|
||||||
|
|
||||||
|
static float grad(unsigned char hash, float x, float y, float z)
|
||||||
|
{
|
||||||
|
unsigned char h = hash & 15;
|
||||||
|
float u = (h < 8) ? x : y;
|
||||||
|
float v = (h < 4) ? y : (h == 12 || h == 14) ? x : z;
|
||||||
|
|
||||||
|
return (h & 1 ? -u : u) + (h & 2 ? -v : v);
|
||||||
|
}
|
||||||
|
|
||||||
|
static float perlin(float x, float y, float z)
|
||||||
|
{
|
||||||
|
float xflr = floorf(x);
|
||||||
|
float yflr = floorf(y);
|
||||||
|
float zflr = floorf(z);
|
||||||
|
|
||||||
|
int xi = int(xflr) & 255;
|
||||||
|
int yi = int(yflr) & 255;
|
||||||
|
int zi = int(zflr) & 255;
|
||||||
|
|
||||||
|
float xf = x - xflr;
|
||||||
|
float yf = y - yflr;
|
||||||
|
float zf = z - zflr;
|
||||||
|
|
||||||
|
float u = fade(xf);
|
||||||
|
float v = fade(yf);
|
||||||
|
float w = fade(zf);
|
||||||
|
|
||||||
|
const unsigned char* p = kPerlin;
|
||||||
|
|
||||||
|
int a = p[xi] + yi;
|
||||||
|
int aa = p[a] + zi;
|
||||||
|
int ab = p[a + 1] + zi;
|
||||||
|
|
||||||
|
int b = p[xi + 1] + yi;
|
||||||
|
int ba = p[b] + zi;
|
||||||
|
int bb = p[b + 1] + zi;
|
||||||
|
|
||||||
|
return math_lerp(w,
|
||||||
|
math_lerp(v, math_lerp(u, grad(p[aa], xf, yf, zf), grad(p[ba], xf - 1, yf, zf)),
|
||||||
|
math_lerp(u, grad(p[ab], xf, yf - 1, zf), grad(p[bb], xf - 1, yf - 1, zf))),
|
||||||
|
math_lerp(v, math_lerp(u, grad(p[aa + 1], xf, yf, zf - 1), grad(p[ba + 1], xf - 1, yf, zf - 1)),
|
||||||
|
math_lerp(u, grad(p[ab + 1], xf, yf - 1, zf - 1), grad(p[bb + 1], xf - 1, yf - 1, zf - 1))));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int math_noise(lua_State* L)
|
||||||
|
{
|
||||||
|
double x = luaL_checknumber(L, 1);
|
||||||
|
double y = luaL_optnumber(L, 2, 0.0);
|
||||||
|
double z = luaL_optnumber(L, 3, 0.0);
|
||||||
|
|
||||||
|
double r = perlin((float)x, (float)y, (float)z);
|
||||||
|
|
||||||
|
lua_pushnumber(L, r);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int math_clamp(lua_State* L)
|
||||||
|
{
|
||||||
|
double v = luaL_checknumber(L, 1);
|
||||||
|
double min = luaL_checknumber(L, 2);
|
||||||
|
double max = luaL_checknumber(L, 3);
|
||||||
|
|
||||||
|
luaL_argcheck(L, min <= max, 3, "max must be greater than or equal to min");
|
||||||
|
|
||||||
|
double r = v < min ? min : v;
|
||||||
|
r = r > max ? max : r;
|
||||||
|
|
||||||
|
lua_pushnumber(L, r);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int math_sign(lua_State* L)
|
||||||
|
{
|
||||||
|
double v = luaL_checknumber(L, 1);
|
||||||
|
lua_pushnumber(L, v > 0.0 ? 1.0 : v < 0.0 ? -1.0 : 0.0);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int math_round(lua_State* L)
|
||||||
|
{
|
||||||
|
lua_pushnumber(L, round(luaL_checknumber(L, 1)));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const luaL_Reg mathlib[] = {
|
||||||
|
{"abs", math_abs},
|
||||||
|
{"acos", math_acos},
|
||||||
|
{"asin", math_asin},
|
||||||
|
{"atan2", math_atan2},
|
||||||
|
{"atan", math_atan},
|
||||||
|
{"ceil", math_ceil},
|
||||||
|
{"cosh", math_cosh},
|
||||||
|
{"cos", math_cos},
|
||||||
|
{"deg", math_deg},
|
||||||
|
{"exp", math_exp},
|
||||||
|
{"floor", math_floor},
|
||||||
|
{"fmod", math_fmod},
|
||||||
|
{"frexp", math_frexp},
|
||||||
|
{"ldexp", math_ldexp},
|
||||||
|
{"log10", math_log10},
|
||||||
|
{"log", math_log},
|
||||||
|
{"max", math_max},
|
||||||
|
{"min", math_min},
|
||||||
|
{"modf", math_modf},
|
||||||
|
{"pow", math_pow},
|
||||||
|
{"rad", math_rad},
|
||||||
|
{"random", math_random},
|
||||||
|
{"randomseed", math_randomseed},
|
||||||
|
{"sinh", math_sinh},
|
||||||
|
{"sin", math_sin},
|
||||||
|
{"sqrt", math_sqrt},
|
||||||
|
{"tanh", math_tanh},
|
||||||
|
{"tan", math_tan},
|
||||||
|
{"noise", math_noise},
|
||||||
|
{"clamp", math_clamp},
|
||||||
|
{"sign", math_sign},
|
||||||
|
{"round", math_round},
|
||||||
|
{NULL, NULL},
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Open math library
|
||||||
|
*/
|
||||||
|
int luaopen_math(lua_State* L)
|
||||||
|
{
|
||||||
|
uint64_t seed = uintptr_t(L);
|
||||||
|
seed ^= time(NULL);
|
||||||
|
seed ^= clock();
|
||||||
|
|
||||||
|
pcg32_seed(&L->global->rngstate, seed);
|
||||||
|
|
||||||
|
luaL_register(L, LUA_MATHLIBNAME, mathlib);
|
||||||
|
lua_pushnumber(L, PI);
|
||||||
|
lua_setfield(L, -2, "pi");
|
||||||
|
lua_pushnumber(L, HUGE_VAL);
|
||||||
|
lua_setfield(L, -2, "huge");
|
||||||
|
return 1;
|
||||||
|
}
|
|
@ -0,0 +1,646 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
||||||
|
#include "lmem.h"
|
||||||
|
|
||||||
|
#include "lstate.h"
|
||||||
|
#include "ldo.h"
|
||||||
|
#include "ldebug.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Luau heap uses a size-segregated page structure, with individual pages and large allocations
|
||||||
|
* allocated using system heap (via frealloc callback).
|
||||||
|
*
|
||||||
|
* frealloc callback serves as a general, if slow, allocation callback that can allocate, free or
|
||||||
|
* resize allocations:
|
||||||
|
*
|
||||||
|
* void* frealloc(void* ud, void* ptr, size_t oldsize, size_t newsize);
|
||||||
|
*
|
||||||
|
* frealloc(ud, NULL, 0, x) creates a new block of size x
|
||||||
|
* frealloc(ud, p, x, 0) frees the block p (must return NULL)
|
||||||
|
* frealloc(ud, NULL, 0, 0) does nothing, equivalent to free(NULL)
|
||||||
|
*
|
||||||
|
* frealloc returns NULL if it cannot create or reallocate the area
|
||||||
|
* (any reallocation to an equal or smaller size cannot fail!)
|
||||||
|
*
|
||||||
|
* On top of this, Luau implements heap storage which is split into two types of allocations:
|
||||||
|
*
|
||||||
|
* - GCO, short for "garbage collected objects"
|
||||||
|
* - other objects (for example, arrays stored inside table objects)
|
||||||
|
*
|
||||||
|
* The heap layout for these two allocation types is a bit different.
|
||||||
|
*
|
||||||
|
* All GCO are allocated in pages, which is a block of memory of ~16K in size that has a page header
|
||||||
|
* (lua_Page). Each page contains 1..N blocks of the same size, where N is selected to fill the page
|
||||||
|
* completely. This amortizes the allocation cost and increases locality. Each GCO block starts with
|
||||||
|
* the GC header (GCheader) which contains the object type, mark bits and other GC metadata. If the
|
||||||
|
* GCO block is free (not used), then it must have the type set to TNIL; in this case the block can
|
||||||
|
* be part of the per-page free list, the link for that list is stored after the header (freegcolink).
|
||||||
|
*
|
||||||
|
* Importantly, the GCO block doesn't have any back references to the page it's allocated in, so it's
|
||||||
|
* impossible to free it in isolation - GCO blocks are freed by sweeping the pages they belong to,
|
||||||
|
* using luaM_freegco which must specify the page; this is called by page sweeper that traverses the
|
||||||
|
* entire page's worth of objects. For this reason it's also important that freed GCO blocks keep the
|
||||||
|
* GC header intact and accessible (with type = NIL) so that the sweeper can access it.
|
||||||
|
*
|
||||||
|
* Some GCOs are too large to fit in a 16K page without excessive fragmentation (the size threshold is
|
||||||
|
* currently 512 bytes); in this case, we allocate a dedicated small page with just a single block's worth
|
||||||
|
* storage space, but that requires allocating an extra page header. In effect large GCOs are a little bit
|
||||||
|
* less memory efficient, but this allows us to uniformly sweep small and large GCOs using page lists.
|
||||||
|
*
|
||||||
|
* All GCO pages are linked in a large intrusive linked list (global_State::allgcopages). Additionally,
|
||||||
|
* for each block size there's a page free list that contains pages that have at least one free block
|
||||||
|
* (global_State::freegcopages). This free list is used to make sure object allocation is O(1).
|
||||||
|
*
|
||||||
|
* Compared to GCOs, regular allocations have two important differences: they can be freed in isolation,
|
||||||
|
* and they don't start with a GC header. Because of this, each allocation is prefixed with block metadata,
|
||||||
|
* which contains the pointer to the page for allocated blocks, and the pointer to the next free block
|
||||||
|
* inside the page for freed blocks.
|
||||||
|
* For regular allocations that are too large to fit in a page (using the same threshold of 512 bytes),
|
||||||
|
* we don't allocate a separate page, instead simply using frealloc to allocate a vanilla block of memory.
|
||||||
|
*
|
||||||
|
* Just like GCO pages, we store a page free list (global_State::freepages) that allows O(1) allocation;
|
||||||
|
* there is no global list for non-GCO pages since we never need to traverse them directly.
|
||||||
|
*
|
||||||
|
* In both cases, we pick the page by computing the size class from the block size which rounds the block
|
||||||
|
* size up to reduce the chance that we'll allocate pages that have very few allocated blocks. The size
|
||||||
|
* class strategy is determined by SizeClassConfig constructor.
|
||||||
|
*
|
||||||
|
* Note that when the last block in a page is freed, we immediately free the page with frealloc - the
|
||||||
|
* memory manager doesn't currently attempt to keep unused memory around. This can result in excessive
|
||||||
|
* allocation traffic and can be mitigated by adding a page cache in the future.
|
||||||
|
*
|
||||||
|
* For both GCO and non-GCO pages, the per-page block allocation combines bump pointer style allocation
|
||||||
|
* (lua_Page::freeNext) and per-page free list (lua_Page::freeList). We use the bump allocator to allocate
|
||||||
|
* the contents of the page, and the free list for further reuse; this allows shorter page setup times
|
||||||
|
* which results in less variance between allocation cost, as well as tighter sweep bounds for newly
|
||||||
|
* allocated pages.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __has_feature
|
||||||
|
#define __has_feature(x) 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if __has_feature(address_sanitizer) || defined(LUAU_ENABLE_ASAN)
|
||||||
|
#include <sanitizer/asan_interface.h>
|
||||||
|
#define ASAN_POISON_MEMORY_REGION(addr, size) __asan_poison_memory_region((addr), (size))
|
||||||
|
#define ASAN_UNPOISON_MEMORY_REGION(addr, size) __asan_unpoison_memory_region((addr), (size))
|
||||||
|
#else
|
||||||
|
#define ASAN_POISON_MEMORY_REGION(addr, size) (void)0
|
||||||
|
#define ASAN_UNPOISON_MEMORY_REGION(addr, size) (void)0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The sizes of Luau objects aren't crucial for code correctness, but they are crucial for memory efficiency
|
||||||
|
* To prevent some of them accidentally growing and us losing memory without realizing it, we're going to lock
|
||||||
|
* the sizes of all critical structures down.
|
||||||
|
*/
|
||||||
|
#if defined(__APPLE__)
|
||||||
|
#define ABISWITCH(x64, ms32, gcc32) (sizeof(void*) == 8 ? x64 : gcc32)
|
||||||
|
#elif defined(__i386__) && !defined(_MSC_VER)
|
||||||
|
#define ABISWITCH(x64, ms32, gcc32) (gcc32)
|
||||||
|
#else
|
||||||
|
// Android somehow uses a similar ABI to MSVC, *not* to iOS...
|
||||||
|
#define ABISWITCH(x64, ms32, gcc32) (sizeof(void*) == 8 ? x64 : ms32)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if LUA_VECTOR_SIZE == 4
|
||||||
|
static_assert(sizeof(TValue) == ABISWITCH(24, 24, 24), "size mismatch for value");
|
||||||
|
static_assert(sizeof(LuaNode) == ABISWITCH(48, 48, 48), "size mismatch for table entry");
|
||||||
|
#else
|
||||||
|
static_assert(sizeof(TValue) == ABISWITCH(16, 16, 16), "size mismatch for value");
|
||||||
|
static_assert(sizeof(LuaNode) == ABISWITCH(32, 32, 32), "size mismatch for table entry");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static_assert(offsetof(TString, data) == ABISWITCH(24, 20, 20), "size mismatch for string header");
|
||||||
|
static_assert(offsetof(Udata, data) == ABISWITCH(16, 16, 12), "size mismatch for userdata header");
|
||||||
|
static_assert(sizeof(Table) == ABISWITCH(48, 32, 32), "size mismatch for table header");
|
||||||
|
|
||||||
|
const size_t kSizeClasses = LUA_SIZECLASSES;
|
||||||
|
const size_t kMaxSmallSize = 512;
|
||||||
|
const size_t kPageSize = 16 * 1024 - 24; // slightly under 16KB since that results in less fragmentation due to heap metadata
|
||||||
|
|
||||||
|
const size_t kBlockHeader = sizeof(double) > sizeof(void*) ? sizeof(double) : sizeof(void*); // suitable for aligning double & void* on all platforms
|
||||||
|
const size_t kGCOLinkOffset = (sizeof(GCheader) + sizeof(void*) - 1) & ~(sizeof(void*) - 1); // GCO pages contain freelist links after the GC header
|
||||||
|
|
||||||
|
struct SizeClassConfig
|
||||||
|
{
|
||||||
|
int sizeOfClass[kSizeClasses];
|
||||||
|
int8_t classForSize[kMaxSmallSize + 1];
|
||||||
|
int classCount = 0;
|
||||||
|
|
||||||
|
SizeClassConfig()
|
||||||
|
{
|
||||||
|
memset(sizeOfClass, 0, sizeof(sizeOfClass));
|
||||||
|
memset(classForSize, -1, sizeof(classForSize));
|
||||||
|
|
||||||
|
// we use a progressive size class scheme:
|
||||||
|
// - all size classes are aligned by 8b to satisfy pointer alignment requirements
|
||||||
|
// - we first allocate sizes classes in multiples of 8
|
||||||
|
// - after the first cutoff we allocate size classes in multiples of 16
|
||||||
|
// - after the second cutoff we allocate size classes in multiples of 32
|
||||||
|
// this balances internal fragmentation vs external fragmentation
|
||||||
|
for (int size = 8; size < 64; size += 8)
|
||||||
|
sizeOfClass[classCount++] = size;
|
||||||
|
|
||||||
|
for (int size = 64; size < 256; size += 16)
|
||||||
|
sizeOfClass[classCount++] = size;
|
||||||
|
|
||||||
|
for (int size = 256; size <= 512; size += 32)
|
||||||
|
sizeOfClass[classCount++] = size;
|
||||||
|
|
||||||
|
LUAU_ASSERT(size_t(classCount) <= kSizeClasses);
|
||||||
|
|
||||||
|
// fill the lookup table for all classes
|
||||||
|
for (int klass = 0; klass < classCount; ++klass)
|
||||||
|
classForSize[sizeOfClass[klass]] = int8_t(klass);
|
||||||
|
|
||||||
|
// fill the gaps in lookup table
|
||||||
|
for (int size = kMaxSmallSize - 1; size >= 0; --size)
|
||||||
|
if (classForSize[size] < 0)
|
||||||
|
classForSize[size] = classForSize[size + 1];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const SizeClassConfig kSizeClassConfig;
|
||||||
|
|
||||||
|
// size class for a block of size sz; returns -1 for size=0 because empty allocations take no space
|
||||||
|
#define sizeclass(sz) (size_t((sz)-1) < kMaxSmallSize ? kSizeClassConfig.classForSize[sz] : -1)
|
||||||
|
|
||||||
|
// metadata for a block is stored in the first pointer of the block
|
||||||
|
#define metadata(block) (*(void**)(block))
|
||||||
|
#define freegcolink(block) (*(void**)((char*)block + kGCOLinkOffset))
|
||||||
|
|
||||||
|
struct lua_Page
|
||||||
|
{
|
||||||
|
// list of pages with free blocks
|
||||||
|
lua_Page* prev;
|
||||||
|
lua_Page* next;
|
||||||
|
|
||||||
|
// list of all gco pages
|
||||||
|
lua_Page* gcolistprev;
|
||||||
|
lua_Page* gcolistnext;
|
||||||
|
|
||||||
|
int pageSize; // page size in bytes, including page header
|
||||||
|
int blockSize; // block size in bytes, including block header (for non-GCO)
|
||||||
|
|
||||||
|
void* freeList; // next free block in this page; linked with metadata()/freegcolink()
|
||||||
|
int freeNext; // next free block offset in this page, in bytes; when negative, freeList is used instead
|
||||||
|
int busyBlocks; // number of blocks allocated out of this page
|
||||||
|
|
||||||
|
union
|
||||||
|
{
|
||||||
|
char data[1];
|
||||||
|
double align1;
|
||||||
|
void* align2;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
l_noret luaM_toobig(lua_State* L)
|
||||||
|
{
|
||||||
|
luaG_runerror(L, "memory allocation error: block too big");
|
||||||
|
}
|
||||||
|
|
||||||
|
static lua_Page* newpage(lua_State* L, lua_Page** gcopageset, int pageSize, int blockSize, int blockCount)
|
||||||
|
{
|
||||||
|
global_State* g = L->global;
|
||||||
|
|
||||||
|
LUAU_ASSERT(pageSize - int(offsetof(lua_Page, data)) >= blockSize * blockCount);
|
||||||
|
|
||||||
|
lua_Page* page = (lua_Page*)(*g->frealloc)(g->ud, NULL, 0, pageSize);
|
||||||
|
if (!page)
|
||||||
|
luaD_throw(L, LUA_ERRMEM);
|
||||||
|
|
||||||
|
ASAN_POISON_MEMORY_REGION(page->data, blockSize * blockCount);
|
||||||
|
|
||||||
|
// setup page header
|
||||||
|
page->prev = NULL;
|
||||||
|
page->next = NULL;
|
||||||
|
|
||||||
|
page->gcolistprev = NULL;
|
||||||
|
page->gcolistnext = NULL;
|
||||||
|
|
||||||
|
page->pageSize = pageSize;
|
||||||
|
page->blockSize = blockSize;
|
||||||
|
|
||||||
|
// note: we start with the last block in the page and move downward
|
||||||
|
// either order would work, but that way we don't need to store the block count in the page
|
||||||
|
// additionally, GC stores objects in singly linked lists, and this way the GC lists end up in increasing pointer order
|
||||||
|
page->freeList = NULL;
|
||||||
|
page->freeNext = (blockCount - 1) * blockSize;
|
||||||
|
page->busyBlocks = 0;
|
||||||
|
|
||||||
|
if (gcopageset)
|
||||||
|
{
|
||||||
|
page->gcolistnext = *gcopageset;
|
||||||
|
if (page->gcolistnext)
|
||||||
|
page->gcolistnext->gcolistprev = page;
|
||||||
|
*gcopageset = page;
|
||||||
|
}
|
||||||
|
|
||||||
|
return page;
|
||||||
|
}
|
||||||
|
|
||||||
|
static lua_Page* newclasspage(lua_State* L, lua_Page** freepageset, lua_Page** gcopageset, uint8_t sizeClass, bool storeMetadata)
|
||||||
|
{
|
||||||
|
int blockSize = kSizeClassConfig.sizeOfClass[sizeClass] + (storeMetadata ? kBlockHeader : 0);
|
||||||
|
int blockCount = (kPageSize - offsetof(lua_Page, data)) / blockSize;
|
||||||
|
|
||||||
|
lua_Page* page = newpage(L, gcopageset, kPageSize, blockSize, blockCount);
|
||||||
|
|
||||||
|
// prepend a page to page freelist (which is empty because we only ever allocate a new page when it is!)
|
||||||
|
LUAU_ASSERT(!freepageset[sizeClass]);
|
||||||
|
freepageset[sizeClass] = page;
|
||||||
|
|
||||||
|
return page;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void freepage(lua_State* L, lua_Page** gcopageset, lua_Page* page)
|
||||||
|
{
|
||||||
|
global_State* g = L->global;
|
||||||
|
|
||||||
|
if (gcopageset)
|
||||||
|
{
|
||||||
|
// remove page from alllist
|
||||||
|
if (page->gcolistnext)
|
||||||
|
page->gcolistnext->gcolistprev = page->gcolistprev;
|
||||||
|
|
||||||
|
if (page->gcolistprev)
|
||||||
|
page->gcolistprev->gcolistnext = page->gcolistnext;
|
||||||
|
else if (*gcopageset == page)
|
||||||
|
*gcopageset = page->gcolistnext;
|
||||||
|
}
|
||||||
|
|
||||||
|
// so long
|
||||||
|
(*g->frealloc)(g->ud, page, page->pageSize, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void freeclasspage(lua_State* L, lua_Page** freepageset, lua_Page** gcopageset, lua_Page* page, uint8_t sizeClass)
|
||||||
|
{
|
||||||
|
// remove page from freelist
|
||||||
|
if (page->next)
|
||||||
|
page->next->prev = page->prev;
|
||||||
|
|
||||||
|
if (page->prev)
|
||||||
|
page->prev->next = page->next;
|
||||||
|
else if (freepageset[sizeClass] == page)
|
||||||
|
freepageset[sizeClass] = page->next;
|
||||||
|
|
||||||
|
freepage(L, gcopageset, page);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void* newblock(lua_State* L, int sizeClass)
|
||||||
|
{
|
||||||
|
global_State* g = L->global;
|
||||||
|
lua_Page* page = g->freepages[sizeClass];
|
||||||
|
|
||||||
|
// slow path: no page in the freelist, allocate a new one
|
||||||
|
if (!page)
|
||||||
|
page = newclasspage(L, g->freepages, NULL, sizeClass, true);
|
||||||
|
|
||||||
|
LUAU_ASSERT(!page->prev);
|
||||||
|
LUAU_ASSERT(page->freeList || page->freeNext >= 0);
|
||||||
|
LUAU_ASSERT(size_t(page->blockSize) == kSizeClassConfig.sizeOfClass[sizeClass] + kBlockHeader);
|
||||||
|
|
||||||
|
void* block;
|
||||||
|
|
||||||
|
if (page->freeNext >= 0)
|
||||||
|
{
|
||||||
|
block = &page->data + page->freeNext;
|
||||||
|
ASAN_UNPOISON_MEMORY_REGION(block, page->blockSize);
|
||||||
|
|
||||||
|
page->freeNext -= page->blockSize;
|
||||||
|
page->busyBlocks++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
block = page->freeList;
|
||||||
|
ASAN_UNPOISON_MEMORY_REGION(block, page->blockSize);
|
||||||
|
|
||||||
|
page->freeList = metadata(block);
|
||||||
|
page->busyBlocks++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// the first word in a block point back to the page
|
||||||
|
metadata(block) = page;
|
||||||
|
|
||||||
|
// if we allocate the last block out of a page, we need to remove it from free list
|
||||||
|
if (!page->freeList && page->freeNext < 0)
|
||||||
|
{
|
||||||
|
g->freepages[sizeClass] = page->next;
|
||||||
|
if (page->next)
|
||||||
|
page->next->prev = NULL;
|
||||||
|
page->next = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// the user data is right after the metadata
|
||||||
|
return (char*)block + kBlockHeader;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void* newgcoblock(lua_State* L, int sizeClass)
|
||||||
|
{
|
||||||
|
global_State* g = L->global;
|
||||||
|
lua_Page* page = g->freegcopages[sizeClass];
|
||||||
|
|
||||||
|
// slow path: no page in the freelist, allocate a new one
|
||||||
|
if (!page)
|
||||||
|
page = newclasspage(L, g->freegcopages, &g->allgcopages, sizeClass, false);
|
||||||
|
|
||||||
|
LUAU_ASSERT(!page->prev);
|
||||||
|
LUAU_ASSERT(page->freeList || page->freeNext >= 0);
|
||||||
|
LUAU_ASSERT(page->blockSize == kSizeClassConfig.sizeOfClass[sizeClass]);
|
||||||
|
|
||||||
|
void* block;
|
||||||
|
|
||||||
|
if (page->freeNext >= 0)
|
||||||
|
{
|
||||||
|
block = &page->data + page->freeNext;
|
||||||
|
ASAN_UNPOISON_MEMORY_REGION(block, page->blockSize);
|
||||||
|
|
||||||
|
page->freeNext -= page->blockSize;
|
||||||
|
page->busyBlocks++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
block = page->freeList;
|
||||||
|
ASAN_UNPOISON_MEMORY_REGION((char*)block + sizeof(GCheader), page->blockSize - sizeof(GCheader));
|
||||||
|
|
||||||
|
// when separate block metadata is not used, free list link is stored inside the block data itself
|
||||||
|
page->freeList = freegcolink(block);
|
||||||
|
page->busyBlocks++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we allocate the last block out of a page, we need to remove it from free list
|
||||||
|
if (!page->freeList && page->freeNext < 0)
|
||||||
|
{
|
||||||
|
g->freegcopages[sizeClass] = page->next;
|
||||||
|
if (page->next)
|
||||||
|
page->next->prev = NULL;
|
||||||
|
page->next = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return block;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void freeblock(lua_State* L, int sizeClass, void* block)
|
||||||
|
{
|
||||||
|
global_State* g = L->global;
|
||||||
|
|
||||||
|
// the user data is right after the metadata
|
||||||
|
LUAU_ASSERT(block);
|
||||||
|
block = (char*)block - kBlockHeader;
|
||||||
|
|
||||||
|
lua_Page* page = (lua_Page*)metadata(block);
|
||||||
|
LUAU_ASSERT(page && page->busyBlocks > 0);
|
||||||
|
LUAU_ASSERT(size_t(page->blockSize) == kSizeClassConfig.sizeOfClass[sizeClass] + kBlockHeader);
|
||||||
|
LUAU_ASSERT(block >= page->data && block < (char*)page + page->pageSize);
|
||||||
|
|
||||||
|
// if the page wasn't in the page free list, it should be now since it got a block!
|
||||||
|
if (!page->freeList && page->freeNext < 0)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(!page->prev);
|
||||||
|
LUAU_ASSERT(!page->next);
|
||||||
|
|
||||||
|
page->next = g->freepages[sizeClass];
|
||||||
|
if (page->next)
|
||||||
|
page->next->prev = page;
|
||||||
|
g->freepages[sizeClass] = page;
|
||||||
|
}
|
||||||
|
|
||||||
|
// add the block to the free list inside the page
|
||||||
|
metadata(block) = page->freeList;
|
||||||
|
page->freeList = block;
|
||||||
|
|
||||||
|
ASAN_POISON_MEMORY_REGION(block, page->blockSize);
|
||||||
|
|
||||||
|
page->busyBlocks--;
|
||||||
|
|
||||||
|
// if it's the last block in the page, we don't need the page
|
||||||
|
if (page->busyBlocks == 0)
|
||||||
|
freeclasspage(L, g->freepages, NULL, page, sizeClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void freegcoblock(lua_State* L, int sizeClass, void* block, lua_Page* page)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(page && page->busyBlocks > 0);
|
||||||
|
LUAU_ASSERT(page->blockSize == kSizeClassConfig.sizeOfClass[sizeClass]);
|
||||||
|
LUAU_ASSERT(block >= page->data && block < (char*)page + page->pageSize);
|
||||||
|
|
||||||
|
global_State* g = L->global;
|
||||||
|
|
||||||
|
// if the page wasn't in the page free list, it should be now since it got a block!
|
||||||
|
if (!page->freeList && page->freeNext < 0)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(!page->prev);
|
||||||
|
LUAU_ASSERT(!page->next);
|
||||||
|
|
||||||
|
page->next = g->freegcopages[sizeClass];
|
||||||
|
if (page->next)
|
||||||
|
page->next->prev = page;
|
||||||
|
g->freegcopages[sizeClass] = page;
|
||||||
|
}
|
||||||
|
|
||||||
|
// when separate block metadata is not used, free list link is stored inside the block data itself
|
||||||
|
freegcolink(block) = page->freeList;
|
||||||
|
page->freeList = block;
|
||||||
|
|
||||||
|
ASAN_POISON_MEMORY_REGION((char*)block + sizeof(GCheader), page->blockSize - sizeof(GCheader));
|
||||||
|
|
||||||
|
page->busyBlocks--;
|
||||||
|
|
||||||
|
// if it's the last block in the page, we don't need the page
|
||||||
|
if (page->busyBlocks == 0)
|
||||||
|
freeclasspage(L, g->freegcopages, &g->allgcopages, page, sizeClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
void* luaM_new_(lua_State* L, size_t nsize, uint8_t memcat)
|
||||||
|
{
|
||||||
|
global_State* g = L->global;
|
||||||
|
|
||||||
|
int nclass = sizeclass(nsize);
|
||||||
|
|
||||||
|
void* block = nclass >= 0 ? newblock(L, nclass) : (*g->frealloc)(g->ud, NULL, 0, nsize);
|
||||||
|
if (block == NULL && nsize > 0)
|
||||||
|
luaD_throw(L, LUA_ERRMEM);
|
||||||
|
|
||||||
|
g->totalbytes += nsize;
|
||||||
|
g->memcatbytes[memcat] += nsize;
|
||||||
|
|
||||||
|
return block;
|
||||||
|
}
|
||||||
|
|
||||||
|
GCObject* luaM_newgco_(lua_State* L, size_t nsize, uint8_t memcat)
|
||||||
|
{
|
||||||
|
// we need to accommodate space for link for free blocks (freegcolink)
|
||||||
|
LUAU_ASSERT(nsize >= kGCOLinkOffset + sizeof(void*));
|
||||||
|
|
||||||
|
global_State* g = L->global;
|
||||||
|
|
||||||
|
int nclass = sizeclass(nsize);
|
||||||
|
|
||||||
|
void* block = NULL;
|
||||||
|
|
||||||
|
if (nclass >= 0)
|
||||||
|
{
|
||||||
|
block = newgcoblock(L, nclass);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lua_Page* page = newpage(L, &g->allgcopages, offsetof(lua_Page, data) + int(nsize), int(nsize), 1);
|
||||||
|
|
||||||
|
block = &page->data;
|
||||||
|
ASAN_UNPOISON_MEMORY_REGION(block, page->blockSize);
|
||||||
|
|
||||||
|
page->freeNext -= page->blockSize;
|
||||||
|
page->busyBlocks++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (block == NULL && nsize > 0)
|
||||||
|
luaD_throw(L, LUA_ERRMEM);
|
||||||
|
|
||||||
|
g->totalbytes += nsize;
|
||||||
|
g->memcatbytes[memcat] += nsize;
|
||||||
|
|
||||||
|
return (GCObject*)block;
|
||||||
|
}
|
||||||
|
|
||||||
|
void luaM_free_(lua_State* L, void* block, size_t osize, uint8_t memcat)
|
||||||
|
{
|
||||||
|
global_State* g = L->global;
|
||||||
|
LUAU_ASSERT((osize == 0) == (block == NULL));
|
||||||
|
|
||||||
|
int oclass = sizeclass(osize);
|
||||||
|
|
||||||
|
if (oclass >= 0)
|
||||||
|
freeblock(L, oclass, block);
|
||||||
|
else
|
||||||
|
(*g->frealloc)(g->ud, block, osize, 0);
|
||||||
|
|
||||||
|
g->totalbytes -= osize;
|
||||||
|
g->memcatbytes[memcat] -= osize;
|
||||||
|
}
|
||||||
|
|
||||||
|
void luaM_freegco_(lua_State* L, GCObject* block, size_t osize, uint8_t memcat, lua_Page* page)
|
||||||
|
{
|
||||||
|
global_State* g = L->global;
|
||||||
|
LUAU_ASSERT((osize == 0) == (block == NULL));
|
||||||
|
|
||||||
|
int oclass = sizeclass(osize);
|
||||||
|
|
||||||
|
if (oclass >= 0)
|
||||||
|
{
|
||||||
|
block->gch.tt = LUA_TNIL;
|
||||||
|
|
||||||
|
freegcoblock(L, oclass, block, page);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(page->busyBlocks == 1);
|
||||||
|
LUAU_ASSERT(size_t(page->blockSize) == osize);
|
||||||
|
LUAU_ASSERT((void*)block == page->data);
|
||||||
|
|
||||||
|
freepage(L, &g->allgcopages, page);
|
||||||
|
}
|
||||||
|
|
||||||
|
g->totalbytes -= osize;
|
||||||
|
g->memcatbytes[memcat] -= osize;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* luaM_realloc_(lua_State* L, void* block, size_t osize, size_t nsize, uint8_t memcat)
|
||||||
|
{
|
||||||
|
global_State* g = L->global;
|
||||||
|
LUAU_ASSERT((osize == 0) == (block == NULL));
|
||||||
|
|
||||||
|
int nclass = sizeclass(nsize);
|
||||||
|
int oclass = sizeclass(osize);
|
||||||
|
void* result;
|
||||||
|
|
||||||
|
// if either block needs to be allocated using a block allocator, we can't use realloc directly
|
||||||
|
if (nclass >= 0 || oclass >= 0)
|
||||||
|
{
|
||||||
|
result = nclass >= 0 ? newblock(L, nclass) : (*g->frealloc)(g->ud, NULL, 0, nsize);
|
||||||
|
if (result == NULL && nsize > 0)
|
||||||
|
luaD_throw(L, LUA_ERRMEM);
|
||||||
|
|
||||||
|
if (osize > 0 && nsize > 0)
|
||||||
|
memcpy(result, block, osize < nsize ? osize : nsize);
|
||||||
|
|
||||||
|
if (oclass >= 0)
|
||||||
|
freeblock(L, oclass, block);
|
||||||
|
else
|
||||||
|
(*g->frealloc)(g->ud, block, osize, 0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = (*g->frealloc)(g->ud, block, osize, nsize);
|
||||||
|
if (result == NULL && nsize > 0)
|
||||||
|
luaD_throw(L, LUA_ERRMEM);
|
||||||
|
}
|
||||||
|
|
||||||
|
LUAU_ASSERT((nsize == 0) == (result == NULL));
|
||||||
|
g->totalbytes = (g->totalbytes - osize) + nsize;
|
||||||
|
g->memcatbytes[memcat] += nsize - osize;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void luaM_getpagewalkinfo(lua_Page* page, char** start, char** end, int* busyBlocks, int* blockSize)
|
||||||
|
{
|
||||||
|
int blockCount = (page->pageSize - offsetof(lua_Page, data)) / page->blockSize;
|
||||||
|
|
||||||
|
LUAU_ASSERT(page->freeNext >= -page->blockSize && page->freeNext <= (blockCount - 1) * page->blockSize);
|
||||||
|
|
||||||
|
char* data = page->data; // silences ubsan when indexing page->data
|
||||||
|
|
||||||
|
*start = data + page->freeNext + page->blockSize;
|
||||||
|
*end = data + blockCount * page->blockSize;
|
||||||
|
*busyBlocks = page->busyBlocks;
|
||||||
|
*blockSize = page->blockSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
lua_Page* luaM_getnextgcopage(lua_Page* page)
|
||||||
|
{
|
||||||
|
return page->gcolistnext;
|
||||||
|
}
|
||||||
|
|
||||||
|
void luaM_visitpage(lua_Page* page, void* context, bool (*visitor)(void* context, lua_Page* page, GCObject* gco))
|
||||||
|
{
|
||||||
|
char* start;
|
||||||
|
char* end;
|
||||||
|
int busyBlocks;
|
||||||
|
int blockSize;
|
||||||
|
luaM_getpagewalkinfo(page, &start, &end, &busyBlocks, &blockSize);
|
||||||
|
|
||||||
|
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 (visitor(context, page, gco))
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(busyBlocks > 0);
|
||||||
|
|
||||||
|
// if the last block was removed, page would be removed as well
|
||||||
|
if (--busyBlocks == 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void luaM_visitgco(lua_State* L, void* context, bool (*visitor)(void* context, lua_Page* page, GCObject* gco))
|
||||||
|
{
|
||||||
|
global_State* g = L->global;
|
||||||
|
|
||||||
|
for (lua_Page* curr = g->allgcopages; curr;)
|
||||||
|
{
|
||||||
|
lua_Page* next = curr->gcolistnext; // block visit might destroy the page
|
||||||
|
|
||||||
|
luaM_visitpage(curr, context, visitor);
|
||||||
|
|
||||||
|
curr = next;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "lua.h"
|
||||||
|
|
||||||
|
struct lua_Page;
|
||||||
|
union GCObject;
|
||||||
|
|
||||||
|
#define luaM_newgco(L, t, size, memcat) cast_to(t*, luaM_newgco_(L, size, memcat))
|
||||||
|
#define luaM_freegco(L, p, size, memcat, page) luaM_freegco_(L, obj2gco(p), size, memcat, page)
|
||||||
|
|
||||||
|
#define luaM_arraysize_(L, n, e) ((cast_to(size_t, (n)) <= SIZE_MAX / (e)) ? (n) * (e) : (luaM_toobig(L), SIZE_MAX))
|
||||||
|
|
||||||
|
#define luaM_newarray(L, n, t, memcat) cast_to(t*, luaM_new_(L, luaM_arraysize_(L, n, sizeof(t)), memcat))
|
||||||
|
#define luaM_freearray(L, b, n, t, memcat) luaM_free_(L, (b), (n) * sizeof(t), memcat)
|
||||||
|
#define luaM_reallocarray(L, v, oldn, n, t, memcat) \
|
||||||
|
((v) = cast_to(t*, luaM_realloc_(L, v, (oldn) * sizeof(t), luaM_arraysize_(L, n, sizeof(t)), memcat)))
|
||||||
|
|
||||||
|
LUAI_FUNC void* luaM_new_(lua_State* L, size_t nsize, uint8_t memcat);
|
||||||
|
LUAI_FUNC GCObject* luaM_newgco_(lua_State* L, size_t nsize, uint8_t memcat);
|
||||||
|
LUAI_FUNC void luaM_free_(lua_State* L, void* block, size_t osize, uint8_t memcat);
|
||||||
|
LUAI_FUNC void luaM_freegco_(lua_State* L, GCObject* block, size_t osize, uint8_t memcat, lua_Page* page);
|
||||||
|
LUAI_FUNC void* luaM_realloc_(lua_State* L, void* block, size_t osize, size_t nsize, uint8_t memcat);
|
||||||
|
|
||||||
|
LUAI_FUNC l_noret luaM_toobig(lua_State* L);
|
||||||
|
|
||||||
|
LUAI_FUNC void luaM_getpagewalkinfo(lua_Page* page, char** start, char** end, int* busyBlocks, int* blockSize);
|
||||||
|
LUAI_FUNC lua_Page* luaM_getnextgcopage(lua_Page* page);
|
||||||
|
|
||||||
|
LUAI_FUNC void luaM_visitpage(lua_Page* page, void* context, bool (*visitor)(void* context, lua_Page* page, GCObject* gco));
|
||||||
|
LUAI_FUNC void luaM_visitgco(lua_State* L, void* context, bool (*visitor)(void* context, lua_Page* page, GCObject* gco));
|
|
@ -0,0 +1,366 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
||||||
|
#include "luaconf.h"
|
||||||
|
#include "lnumutils.h"
|
||||||
|
|
||||||
|
#include "lcommon.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#include <intrin.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// This work is based on:
|
||||||
|
// Raffaello Giulietti. The Schubfach way to render doubles. 2021
|
||||||
|
// https://drive.google.com/file/d/1IEeATSVnEE6TkrHlCYNY2GjaraBjOT4f/edit
|
||||||
|
|
||||||
|
// The code uses the notation from the paper for local variables where appropriate, and refers to paper sections/figures/results.
|
||||||
|
|
||||||
|
// 9.8.2. Precomputed table for 128-bit overestimates of powers of 10 (see figure 3 for table bounds)
|
||||||
|
// To avoid storing 616 128-bit numbers directly we use a technique inspired by Dragonbox implementation and store 16 consecutive
|
||||||
|
// powers using a 128-bit baseline and a bitvector with 1-bit scale and 3-bit offset for the delta between each entry and base*5^k
|
||||||
|
static const int kPow10TableMin = -292;
|
||||||
|
static const int kPow10TableMax = 324;
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
static const uint64_t kPow5Table[16] = {
|
||||||
|
0x8000000000000000, 0xa000000000000000, 0xc800000000000000, 0xfa00000000000000, 0x9c40000000000000, 0xc350000000000000,
|
||||||
|
0xf424000000000000, 0x9896800000000000, 0xbebc200000000000, 0xee6b280000000000, 0x9502f90000000000, 0xba43b74000000000,
|
||||||
|
0xe8d4a51000000000, 0x9184e72a00000000, 0xb5e620f480000000, 0xe35fa931a0000000,
|
||||||
|
};
|
||||||
|
static const uint64_t kPow10Table[(kPow10TableMax - kPow10TableMin + 1 + 15) / 16][3] = {
|
||||||
|
{0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b, 0x333443443333443b}, {0x8dd01fad907ffc3b, 0xae3da7d97f6792e4, 0xbbb3ab3cb3ba3cbc},
|
||||||
|
{0x9d71ac8fada6c9b5, 0x6f773fc3603db4aa, 0x4ba4bc4bb4bb4bcc}, {0xaecc49914078536d, 0x58fae9f773886e19, 0x3ba3bc33b43b43bb},
|
||||||
|
{0xc21094364dfb5636, 0x985915fc12f542e5, 0x33b43b43a33b33cb}, {0xd77485cb25823ac7, 0x7d633293366b828c, 0x34b44c444343443c},
|
||||||
|
{0xef340a98172aace4, 0x86fb897116c87c35, 0x333343333343334b}, {0x84c8d4dfd2c63f3b, 0x29ecd9f40041e074, 0xccaccbbcbcbb4bbc},
|
||||||
|
{0x936b9fcebb25c995, 0xcab10dd900beec35, 0x3ab3ab3ab3bb3bbb}, {0xa3ab66580d5fdaf5, 0xc13e60d0d2e0ebbb, 0x4cc3dc4db4db4dbb},
|
||||||
|
{0xb5b5ada8aaff80b8, 0x0d819992132456bb, 0x33b33a34c33b34ab}, {0xc9bcff6034c13052, 0xfc89b393dd02f0b6, 0x33c33b44b43c34bc},
|
||||||
|
{0xdff9772470297ebd, 0x59787e2b93bc56f8, 0x43b444444443434c}, {0xf8a95fcf88747d94, 0x75a44c6397ce912b, 0x443334343443343b},
|
||||||
|
{0x8a08f0f8bf0f156b, 0x1b8e9ecb641b5900, 0xbbabab3aa3ab4ccc}, {0x993fe2c6d07b7fab, 0xe546a8038efe402a, 0x4cb4bc4db4db4bcc},
|
||||||
|
{0xaa242499697392d2, 0xdde50bd1d5d0b9ea, 0x3ba3ba3bb33b33bc}, {0xbce5086492111aea, 0x88f4bb1ca6bcf585, 0x44b44c44c44c43cb},
|
||||||
|
{0xd1b71758e219652b, 0xd3c36113404ea4a9, 0x44c44c44c444443b}, {0xe8d4a51000000000, 0x0000000000000000, 0x444444444444444c},
|
||||||
|
{0x813f3978f8940984, 0x4000000000000000, 0xcccccccccccccccc}, {0x8f7e32ce7bea5c6f, 0xe4820023a2000000, 0xbba3bc4cc4cc4ccc},
|
||||||
|
{0x9f4f2726179a2245, 0x01d762422c946591, 0x4aa3bb3aa3ba3bab}, {0xb0de65388cc8ada8, 0x3b25a55f43294bcc, 0x3ca33b33b44b43bc},
|
||||||
|
{0xc45d1df942711d9a, 0x3ba5d0bd324f8395, 0x44c44c34c44b44cb}, {0xda01ee641a708de9, 0xe80e6f4820cc9496, 0x33b33b343333333c},
|
||||||
|
{0xf209787bb47d6b84, 0xc0678c5dbd23a49b, 0x443444444443443b}, {0x865b86925b9bc5c2, 0x0b8a2392ba45a9b3, 0xdbccbcccb4cb3bbb},
|
||||||
|
{0x952ab45cfa97a0b2, 0xdd945a747bf26184, 0x3bc4bb4ab3ca3cbc}, {0xa59bc234db398c25, 0x43fab9837e699096, 0x3bb3ac3ab3bb33ac},
|
||||||
|
{0xb7dcbf5354e9bece, 0x0c11ed6d538aeb30, 0x33b43b43b34c34dc}, {0xcc20ce9bd35c78a5, 0x31ec038df7b441f5, 0x34c44c43c44b44cb},
|
||||||
|
{0xe2a0b5dc971f303a, 0x2e44ae64840fd61e, 0x333333333333333c}, {0xfb9b7cd9a4a7443c, 0x169840ef017da3b2, 0x433344443333344c},
|
||||||
|
{0x8bab8eefb6409c1a, 0x1ad089b6c2f7548f, 0xdcbdcc3cc4cc4bcb}, {0x9b10a4e5e9913128, 0xca7cf2b4191c8327, 0x3ab3cb3bc3bb4bbb},
|
||||||
|
{0xac2820d9623bf429, 0x546345fa9fbdcd45, 0x3bb3cc43c43c43cb}, {0xbf21e44003acdd2c, 0xe0470a63e6bd56c4, 0x44b34a43b44c44bc},
|
||||||
|
{0xd433179d9c8cb841, 0x5fa60692a46151ec, 0x43a33a33a333333c},
|
||||||
|
};
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
static const char kDigitTable[] = "0001020304050607080910111213141516171819202122232425262728293031323334353637383940414243444546474849"
|
||||||
|
"5051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899";
|
||||||
|
|
||||||
|
// x*y => 128-bit product (lo+hi)
|
||||||
|
inline uint64_t mul128(uint64_t x, uint64_t y, uint64_t* hi)
|
||||||
|
{
|
||||||
|
#if defined(_MSC_VER) && defined(_M_X64)
|
||||||
|
return _umul128(x, y, hi);
|
||||||
|
#elif defined(__SIZEOF_INT128__)
|
||||||
|
unsigned __int128 r = x;
|
||||||
|
r *= y;
|
||||||
|
*hi = uint64_t(r >> 64);
|
||||||
|
return uint64_t(r);
|
||||||
|
#else
|
||||||
|
uint32_t x0 = uint32_t(x), x1 = uint32_t(x >> 32);
|
||||||
|
uint32_t y0 = uint32_t(y), y1 = uint32_t(y >> 32);
|
||||||
|
uint64_t p11 = uint64_t(x1) * y1, p01 = uint64_t(x0) * y1;
|
||||||
|
uint64_t p10 = uint64_t(x1) * y0, p00 = uint64_t(x0) * y0;
|
||||||
|
uint64_t mid = p10 + (p00 >> 32) + uint32_t(p01);
|
||||||
|
uint64_t r0 = (mid << 32) | uint32_t(p00);
|
||||||
|
uint64_t r1 = p11 + (mid >> 32) + (p01 >> 32);
|
||||||
|
*hi = r1;
|
||||||
|
return r0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// (x*y)>>64 => 128-bit product (lo+hi)
|
||||||
|
inline uint64_t mul192hi(uint64_t xhi, uint64_t xlo, uint64_t y, uint64_t* hi)
|
||||||
|
{
|
||||||
|
uint64_t z2;
|
||||||
|
uint64_t z1 = mul128(xhi, y, &z2);
|
||||||
|
|
||||||
|
uint64_t z1c;
|
||||||
|
uint64_t z0 = mul128(xlo, y, &z1c);
|
||||||
|
(void)z0;
|
||||||
|
|
||||||
|
z1 += z1c;
|
||||||
|
z2 += (z1 < z1c);
|
||||||
|
|
||||||
|
*hi = z2;
|
||||||
|
return z1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 9.3. Rounding to odd (+ figure 8 + result 23)
|
||||||
|
inline uint64_t roundodd(uint64_t ghi, uint64_t glo, uint64_t cp)
|
||||||
|
{
|
||||||
|
uint64_t xhi;
|
||||||
|
uint64_t xlo = mul128(glo, cp, &xhi);
|
||||||
|
(void)xlo;
|
||||||
|
|
||||||
|
uint64_t yhi;
|
||||||
|
uint64_t ylo = mul128(ghi, cp, &yhi);
|
||||||
|
|
||||||
|
uint64_t z = ylo + xhi;
|
||||||
|
return (yhi + (z < xhi)) | (z > 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Decimal
|
||||||
|
{
|
||||||
|
uint64_t s;
|
||||||
|
int k;
|
||||||
|
};
|
||||||
|
|
||||||
|
static Decimal schubfach(int exponent, uint64_t fraction)
|
||||||
|
{
|
||||||
|
// Extract c & q such that c*2^q == |v|
|
||||||
|
uint64_t c = fraction;
|
||||||
|
int q = exponent - 1023 - 51;
|
||||||
|
|
||||||
|
if (exponent != 0) // normal numbers have implicit leading 1
|
||||||
|
{
|
||||||
|
c |= (1ull << 52);
|
||||||
|
q--;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8.3. Fast path for integers
|
||||||
|
if (unsigned(-q) < 53 && (c & ((1ull << (-q)) - 1)) == 0)
|
||||||
|
return {c >> (-q), 0};
|
||||||
|
|
||||||
|
// 5. Rounding interval
|
||||||
|
int irr = (c == (1ull << 52) && q != -1074); // Qmin
|
||||||
|
int out = int(c & 1);
|
||||||
|
|
||||||
|
// 9.8.1. Boundaries for c
|
||||||
|
uint64_t cbl = 4 * c - 2 + irr;
|
||||||
|
uint64_t cb = 4 * c;
|
||||||
|
uint64_t cbr = 4 * c + 2;
|
||||||
|
|
||||||
|
// 9.1. Computing k and h
|
||||||
|
const int Q = 20;
|
||||||
|
const int C = 315652; // floor(2^Q * log10(2))
|
||||||
|
const int A = -131008; // floor(2^Q * log10(3/4))
|
||||||
|
const int C2 = 3483294; // floor(2^Q * log2(10))
|
||||||
|
int k = (q * C + (irr ? A : 0)) >> Q;
|
||||||
|
int h = q + ((-k * C2) >> Q) + 1; // see (9) in 9.9
|
||||||
|
|
||||||
|
// 9.8.2. Overestimates of powers of 10
|
||||||
|
// Recover 10^-k fraction using compact tables generated by tools/numutils.py
|
||||||
|
// The 128-bit fraction is encoded as 128-bit baseline * power-of-5 * scale + offset
|
||||||
|
LUAU_ASSERT(-k >= kPow10TableMin && -k <= kPow10TableMax);
|
||||||
|
int gtoff = -k - kPow10TableMin;
|
||||||
|
const uint64_t* gt = kPow10Table[gtoff >> 4];
|
||||||
|
|
||||||
|
uint64_t ghi;
|
||||||
|
uint64_t glo = mul192hi(gt[0], gt[1], kPow5Table[gtoff & 15], &ghi);
|
||||||
|
|
||||||
|
// Apply 1-bit scale + 3-bit offset; note, offset is intentionally applied without carry, numutils.py validates that this is sufficient
|
||||||
|
int gterr = (gt[2] >> ((gtoff & 15) * 4)) & 15;
|
||||||
|
int gtscale = gterr >> 3;
|
||||||
|
|
||||||
|
ghi <<= gtscale;
|
||||||
|
ghi += (glo >> 63) & gtscale;
|
||||||
|
glo <<= gtscale;
|
||||||
|
glo -= (gterr & 7) - 4;
|
||||||
|
|
||||||
|
// 9.9. Boundaries for v
|
||||||
|
uint64_t vbl = roundodd(ghi, glo, cbl << h);
|
||||||
|
uint64_t vb = roundodd(ghi, glo, cb << h);
|
||||||
|
uint64_t vbr = roundodd(ghi, glo, cbr << h);
|
||||||
|
|
||||||
|
// Main algorithm; see figure 7 + figure 9
|
||||||
|
uint64_t s = vb / 4;
|
||||||
|
|
||||||
|
if (s >= 10)
|
||||||
|
{
|
||||||
|
uint64_t sp = s / 10;
|
||||||
|
|
||||||
|
bool upin = vbl + out <= 40 * sp;
|
||||||
|
bool wpin = vbr >= 40 * sp + 40 + out;
|
||||||
|
|
||||||
|
if (upin != wpin)
|
||||||
|
return {sp + wpin, k + 1};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Figure 7 contains the algorithm to select between u (s) and w (s+1)
|
||||||
|
// rup computes the last 4 conditions in that algorithm
|
||||||
|
// rup is only used when uin == win, but since these branches predict poorly we use branchless selects
|
||||||
|
bool uin = vbl + out <= 4 * s;
|
||||||
|
bool win = 4 * s + 4 + out <= vbr;
|
||||||
|
bool rup = vb >= 4 * s + 2 + 1 - (s & 1);
|
||||||
|
|
||||||
|
return {s + (uin != win ? win : rup), k};
|
||||||
|
}
|
||||||
|
|
||||||
|
static char* printspecial(char* buf, int sign, uint64_t fraction)
|
||||||
|
{
|
||||||
|
if (fraction == 0)
|
||||||
|
{
|
||||||
|
memcpy(buf, ("-inf") + (1 - sign), 4);
|
||||||
|
return buf + 3 + sign;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
memcpy(buf, "nan", 4);
|
||||||
|
return buf + 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static char* printunsignedrev(char* end, uint64_t num)
|
||||||
|
{
|
||||||
|
while (num >= 10000)
|
||||||
|
{
|
||||||
|
unsigned int tail = unsigned(num % 10000);
|
||||||
|
|
||||||
|
memcpy(end - 4, &kDigitTable[int(tail / 100) * 2], 2);
|
||||||
|
memcpy(end - 2, &kDigitTable[int(tail % 100) * 2], 2);
|
||||||
|
num /= 10000;
|
||||||
|
end -= 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int rest = unsigned(num);
|
||||||
|
|
||||||
|
while (rest >= 10)
|
||||||
|
{
|
||||||
|
memcpy(end - 2, &kDigitTable[int(rest % 100) * 2], 2);
|
||||||
|
rest /= 100;
|
||||||
|
end -= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rest)
|
||||||
|
{
|
||||||
|
end[-1] = '0' + int(rest);
|
||||||
|
end -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return end;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char* printexp(char* buf, int num)
|
||||||
|
{
|
||||||
|
*buf++ = 'e';
|
||||||
|
*buf++ = num < 0 ? '-' : '+';
|
||||||
|
|
||||||
|
int v = num < 0 ? -num : num;
|
||||||
|
|
||||||
|
if (v >= 100)
|
||||||
|
{
|
||||||
|
*buf++ = '0' + (v / 100);
|
||||||
|
v %= 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(buf, &kDigitTable[v * 2], 2);
|
||||||
|
return buf + 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline char* trimzero(char* end)
|
||||||
|
{
|
||||||
|
while (end[-1] == '0')
|
||||||
|
end--;
|
||||||
|
|
||||||
|
return end;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We use fixed-length memcpy/memset since they lower to fast SIMD+scalar writes; the target buffers should have padding space
|
||||||
|
#define fastmemcpy(dst, src, size, sizefast) check_exp((size) <= sizefast, memcpy(dst, src, sizefast))
|
||||||
|
#define fastmemset(dst, val, size, sizefast) check_exp((size) <= sizefast, memset(dst, val, sizefast))
|
||||||
|
|
||||||
|
char* luai_num2str(char* buf, double n)
|
||||||
|
{
|
||||||
|
// IEEE-754
|
||||||
|
union
|
||||||
|
{
|
||||||
|
double v;
|
||||||
|
uint64_t bits;
|
||||||
|
} v = {n};
|
||||||
|
int sign = int(v.bits >> 63);
|
||||||
|
int exponent = int(v.bits >> 52) & 2047;
|
||||||
|
uint64_t fraction = v.bits & ((1ull << 52) - 1);
|
||||||
|
|
||||||
|
// specials
|
||||||
|
if (LUAU_UNLIKELY(exponent == 0x7ff))
|
||||||
|
return printspecial(buf, sign, fraction);
|
||||||
|
|
||||||
|
// sign bit
|
||||||
|
*buf = '-';
|
||||||
|
buf += sign;
|
||||||
|
|
||||||
|
// zero
|
||||||
|
if (exponent == 0 && fraction == 0)
|
||||||
|
{
|
||||||
|
buf[0] = '0';
|
||||||
|
return buf + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert binary to decimal using Schubfach
|
||||||
|
Decimal d = schubfach(exponent, fraction);
|
||||||
|
LUAU_ASSERT(d.s < uint64_t(1e17));
|
||||||
|
|
||||||
|
// print the decimal to a temporary buffer; we'll need to insert the decimal point and figure out the format
|
||||||
|
char decbuf[40];
|
||||||
|
char* decend = decbuf + 20; // significand needs at most 17 digits; the rest of the buffer may be copied using fixed length memcpy
|
||||||
|
char* dec = printunsignedrev(decend, d.s);
|
||||||
|
|
||||||
|
int declen = int(decend - dec);
|
||||||
|
LUAU_ASSERT(declen <= 17);
|
||||||
|
|
||||||
|
int dot = declen + d.k;
|
||||||
|
|
||||||
|
// the limits are somewhat arbitrary but changing them may require changing fastmemset/fastmemcpy sizes below
|
||||||
|
if (dot >= -5 && dot <= 21)
|
||||||
|
{
|
||||||
|
// fixed point format
|
||||||
|
if (dot <= 0)
|
||||||
|
{
|
||||||
|
buf[0] = '0';
|
||||||
|
buf[1] = '.';
|
||||||
|
|
||||||
|
fastmemset(buf + 2, '0', -dot, 5);
|
||||||
|
fastmemcpy(buf + 2 + (-dot), dec, declen, 17);
|
||||||
|
|
||||||
|
return trimzero(buf + 2 + (-dot) + declen);
|
||||||
|
}
|
||||||
|
else if (dot == declen)
|
||||||
|
{
|
||||||
|
// no dot
|
||||||
|
fastmemcpy(buf, dec, dot, 17);
|
||||||
|
|
||||||
|
return buf + dot;
|
||||||
|
}
|
||||||
|
else if (dot < declen)
|
||||||
|
{
|
||||||
|
// dot in the middle
|
||||||
|
fastmemcpy(buf, dec, dot, 16);
|
||||||
|
|
||||||
|
buf[dot] = '.';
|
||||||
|
|
||||||
|
fastmemcpy(buf + dot + 1, dec + dot, declen - dot, 16);
|
||||||
|
|
||||||
|
return trimzero(buf + declen + 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// no dot, zero padding
|
||||||
|
fastmemcpy(buf, dec, declen, 17);
|
||||||
|
fastmemset(buf + declen, '0', dot - declen, 8);
|
||||||
|
|
||||||
|
return buf + dot;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// scientific format
|
||||||
|
buf[0] = dec[0];
|
||||||
|
buf[1] = '.';
|
||||||
|
fastmemcpy(buf + 2, dec + 1, declen - 1, 16);
|
||||||
|
|
||||||
|
char* exp = trimzero(buf + declen + 1);
|
||||||
|
|
||||||
|
return printexp(exp, dot - 1);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
#define luai_numadd(a, b) ((a) + (b))
|
||||||
|
#define luai_numsub(a, b) ((a) - (b))
|
||||||
|
#define luai_nummul(a, b) ((a) * (b))
|
||||||
|
#define luai_numdiv(a, b) ((a) / (b))
|
||||||
|
#define luai_numpow(a, b) (pow(a, b))
|
||||||
|
#define luai_numunm(a) (-(a))
|
||||||
|
#define luai_numisnan(a) ((a) != (a))
|
||||||
|
#define luai_numeq(a, b) ((a) == (b))
|
||||||
|
#define luai_numlt(a, b) ((a) < (b))
|
||||||
|
#define luai_numle(a, b) ((a) <= (b))
|
||||||
|
|
||||||
|
inline bool luai_veceq(const float* a, const float* b)
|
||||||
|
{
|
||||||
|
#if LUA_VECTOR_SIZE == 4
|
||||||
|
return a[0] == b[0] && a[1] == b[1] && a[2] == b[2] && a[3] == b[3];
|
||||||
|
#else
|
||||||
|
return a[0] == b[0] && a[1] == b[1] && a[2] == b[2];
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool luai_vecisnan(const float* a)
|
||||||
|
{
|
||||||
|
#if LUA_VECTOR_SIZE == 4
|
||||||
|
return a[0] != a[0] || a[1] != a[1] || a[2] != a[2] || a[3] != a[3];
|
||||||
|
#else
|
||||||
|
return a[0] != a[0] || a[1] != a[1] || a[2] != a[2];
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
LUAU_FASTMATH_BEGIN
|
||||||
|
inline double luai_nummod(double a, double b)
|
||||||
|
{
|
||||||
|
return a - floor(a / b) * b;
|
||||||
|
}
|
||||||
|
LUAU_FASTMATH_END
|
||||||
|
|
||||||
|
#define luai_num2int(i, d) ((i) = (int)(d))
|
||||||
|
|
||||||
|
// On MSVC in 32-bit, double to unsigned cast compiles into a call to __dtoui3, so we invoke x87->int64 conversion path manually
|
||||||
|
#if defined(_MSC_VER) && defined(_M_IX86)
|
||||||
|
#define luai_num2unsigned(i, n) \
|
||||||
|
{ \
|
||||||
|
__int64 l; \
|
||||||
|
__asm { __asm fld n __asm fistp l} \
|
||||||
|
; \
|
||||||
|
i = (unsigned int)l; \
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
#define luai_num2unsigned(i, n) ((i) = (unsigned)(long long)(n))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define LUAI_MAXNUM2STR 48
|
||||||
|
|
||||||
|
LUAI_FUNC char* luai_num2str(char* buf, double n);
|
||||||
|
|
||||||
|
#define luai_str2num(s, p) strtod((s), (p))
|
|
@ -0,0 +1,154 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
||||||
|
#include "lobject.h"
|
||||||
|
|
||||||
|
#include "lstate.h"
|
||||||
|
#include "lstring.h"
|
||||||
|
#include "lgc.h"
|
||||||
|
#include "ldo.h"
|
||||||
|
#include "lnumutils.h"
|
||||||
|
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
const TValue luaO_nilobject_ = {{NULL}, {0}, LUA_TNIL};
|
||||||
|
|
||||||
|
int luaO_log2(unsigned int x)
|
||||||
|
{
|
||||||
|
static const uint8_t log_2[256] = {0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6,
|
||||||
|
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
|
||||||
|
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8,
|
||||||
|
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
|
||||||
|
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
|
||||||
|
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8};
|
||||||
|
int l = -1;
|
||||||
|
while (x >= 256)
|
||||||
|
{
|
||||||
|
l += 8;
|
||||||
|
x >>= 8;
|
||||||
|
}
|
||||||
|
return l + log_2[x];
|
||||||
|
}
|
||||||
|
|
||||||
|
int luaO_rawequalObj(const TValue* t1, const TValue* t2)
|
||||||
|
{
|
||||||
|
if (ttype(t1) != ttype(t2))
|
||||||
|
return 0;
|
||||||
|
else
|
||||||
|
switch (ttype(t1))
|
||||||
|
{
|
||||||
|
case LUA_TNIL:
|
||||||
|
return 1;
|
||||||
|
case LUA_TNUMBER:
|
||||||
|
return luai_numeq(nvalue(t1), nvalue(t2));
|
||||||
|
case LUA_TVECTOR:
|
||||||
|
return luai_veceq(vvalue(t1), vvalue(t2));
|
||||||
|
case LUA_TBOOLEAN:
|
||||||
|
return bvalue(t1) == bvalue(t2); // boolean true must be 1 !!
|
||||||
|
case LUA_TLIGHTUSERDATA:
|
||||||
|
return pvalue(t1) == pvalue(t2);
|
||||||
|
default:
|
||||||
|
LUAU_ASSERT(iscollectable(t1));
|
||||||
|
return gcvalue(t1) == gcvalue(t2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int luaO_rawequalKey(const TKey* t1, const TValue* t2)
|
||||||
|
{
|
||||||
|
if (ttype(t1) != ttype(t2))
|
||||||
|
return 0;
|
||||||
|
else
|
||||||
|
switch (ttype(t1))
|
||||||
|
{
|
||||||
|
case LUA_TNIL:
|
||||||
|
return 1;
|
||||||
|
case LUA_TNUMBER:
|
||||||
|
return luai_numeq(nvalue(t1), nvalue(t2));
|
||||||
|
case LUA_TVECTOR:
|
||||||
|
return luai_veceq(vvalue(t1), vvalue(t2));
|
||||||
|
case LUA_TBOOLEAN:
|
||||||
|
return bvalue(t1) == bvalue(t2); // boolean true must be 1 !!
|
||||||
|
case LUA_TLIGHTUSERDATA:
|
||||||
|
return pvalue(t1) == pvalue(t2);
|
||||||
|
default:
|
||||||
|
LUAU_ASSERT(iscollectable(t1));
|
||||||
|
return gcvalue(t1) == gcvalue(t2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int luaO_str2d(const char* s, double* result)
|
||||||
|
{
|
||||||
|
char* endptr;
|
||||||
|
*result = luai_str2num(s, &endptr);
|
||||||
|
if (endptr == s)
|
||||||
|
return 0; // conversion failed
|
||||||
|
if (*endptr == 'x' || *endptr == 'X') // maybe an hexadecimal constant?
|
||||||
|
*result = cast_num(strtoul(s, &endptr, 16));
|
||||||
|
if (*endptr == '\0')
|
||||||
|
return 1; // most common case
|
||||||
|
while (isspace(cast_to(unsigned char, *endptr)))
|
||||||
|
endptr++;
|
||||||
|
if (*endptr != '\0')
|
||||||
|
return 0; // invalid trailing characters?
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* luaO_pushvfstring(lua_State* L, const char* fmt, va_list argp)
|
||||||
|
{
|
||||||
|
char result[LUA_BUFFERSIZE];
|
||||||
|
vsnprintf(result, sizeof(result), fmt, argp);
|
||||||
|
|
||||||
|
setsvalue(L, L->top, luaS_new(L, result));
|
||||||
|
incr_top(L);
|
||||||
|
return svalue(L->top - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* luaO_pushfstring(lua_State* L, const char* fmt, ...)
|
||||||
|
{
|
||||||
|
const char* msg;
|
||||||
|
va_list argp;
|
||||||
|
va_start(argp, fmt);
|
||||||
|
msg = luaO_pushvfstring(L, fmt, argp);
|
||||||
|
va_end(argp);
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* luaO_chunkid(char* buf, size_t buflen, const char* source, size_t srclen)
|
||||||
|
{
|
||||||
|
if (*source == '=')
|
||||||
|
{
|
||||||
|
if (srclen <= buflen)
|
||||||
|
return source + 1;
|
||||||
|
// truncate the part after =
|
||||||
|
memcpy(buf, source + 1, buflen - 1);
|
||||||
|
buf[buflen - 1] = '\0';
|
||||||
|
}
|
||||||
|
else if (*source == '@')
|
||||||
|
{
|
||||||
|
if (srclen <= buflen)
|
||||||
|
return source + 1;
|
||||||
|
// truncate the part after @
|
||||||
|
memcpy(buf, "...", 3);
|
||||||
|
memcpy(buf + 3, source + srclen - (buflen - 4), buflen - 4);
|
||||||
|
buf[buflen - 1] = '\0';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{ // buf = [string "string"]
|
||||||
|
size_t len = strcspn(source, "\n\r"); // stop at first newline
|
||||||
|
buflen -= sizeof("[string \"...\"]");
|
||||||
|
if (len > buflen)
|
||||||
|
len = buflen;
|
||||||
|
strcpy(buf, "[string \"");
|
||||||
|
if (source[len] != '\0')
|
||||||
|
{ // must truncate?
|
||||||
|
strncat(buf, source, len);
|
||||||
|
strcat(buf, "...");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
strcat(buf, source);
|
||||||
|
strcat(buf, "\"]");
|
||||||
|
}
|
||||||
|
return buf;
|
||||||
|
}
|
|
@ -0,0 +1,464 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "lua.h"
|
||||||
|
#include "lcommon.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Union of all collectible objects
|
||||||
|
*/
|
||||||
|
typedef union GCObject GCObject;
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Common Header for all collectible objects (in macro form, to be included in other objects)
|
||||||
|
*/
|
||||||
|
// clang-format off
|
||||||
|
#define CommonHeader \
|
||||||
|
uint8_t tt; uint8_t marked; uint8_t memcat
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Common header in struct form
|
||||||
|
*/
|
||||||
|
typedef struct GCheader
|
||||||
|
{
|
||||||
|
CommonHeader;
|
||||||
|
} GCheader;
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Union of all Lua values
|
||||||
|
*/
|
||||||
|
typedef union
|
||||||
|
{
|
||||||
|
GCObject* gc;
|
||||||
|
void* p;
|
||||||
|
double n;
|
||||||
|
int b;
|
||||||
|
float v[2]; // v[0], v[1] live here; v[2] lives in TValue::extra
|
||||||
|
} Value;
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Tagged Values
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef struct lua_TValue
|
||||||
|
{
|
||||||
|
Value value;
|
||||||
|
int extra[LUA_EXTRA_SIZE];
|
||||||
|
int tt;
|
||||||
|
} TValue;
|
||||||
|
|
||||||
|
// Macros to test type
|
||||||
|
#define ttisnil(o) (ttype(o) == LUA_TNIL)
|
||||||
|
#define ttisnumber(o) (ttype(o) == LUA_TNUMBER)
|
||||||
|
#define ttisstring(o) (ttype(o) == LUA_TSTRING)
|
||||||
|
#define ttistable(o) (ttype(o) == LUA_TTABLE)
|
||||||
|
#define ttisfunction(o) (ttype(o) == LUA_TFUNCTION)
|
||||||
|
#define ttisboolean(o) (ttype(o) == LUA_TBOOLEAN)
|
||||||
|
#define ttisuserdata(o) (ttype(o) == LUA_TUSERDATA)
|
||||||
|
#define ttisthread(o) (ttype(o) == LUA_TTHREAD)
|
||||||
|
#define ttislightuserdata(o) (ttype(o) == LUA_TLIGHTUSERDATA)
|
||||||
|
#define ttisvector(o) (ttype(o) == LUA_TVECTOR)
|
||||||
|
#define ttisupval(o) (ttype(o) == LUA_TUPVAL)
|
||||||
|
|
||||||
|
// Macros to access values
|
||||||
|
#define ttype(o) ((o)->tt)
|
||||||
|
#define gcvalue(o) check_exp(iscollectable(o), (o)->value.gc)
|
||||||
|
#define pvalue(o) check_exp(ttislightuserdata(o), (o)->value.p)
|
||||||
|
#define nvalue(o) check_exp(ttisnumber(o), (o)->value.n)
|
||||||
|
#define vvalue(o) check_exp(ttisvector(o), (o)->value.v)
|
||||||
|
#define tsvalue(o) check_exp(ttisstring(o), &(o)->value.gc->ts)
|
||||||
|
#define uvalue(o) check_exp(ttisuserdata(o), &(o)->value.gc->u)
|
||||||
|
#define clvalue(o) check_exp(ttisfunction(o), &(o)->value.gc->cl)
|
||||||
|
#define hvalue(o) check_exp(ttistable(o), &(o)->value.gc->h)
|
||||||
|
#define bvalue(o) check_exp(ttisboolean(o), (o)->value.b)
|
||||||
|
#define thvalue(o) check_exp(ttisthread(o), &(o)->value.gc->th)
|
||||||
|
#define upvalue(o) check_exp(ttisupval(o), &(o)->value.gc->uv)
|
||||||
|
|
||||||
|
#define l_isfalse(o) (ttisnil(o) || (ttisboolean(o) && bvalue(o) == 0))
|
||||||
|
|
||||||
|
/*
|
||||||
|
** for internal debug only
|
||||||
|
*/
|
||||||
|
#define checkconsistency(obj) LUAU_ASSERT(!iscollectable(obj) || (ttype(obj) == (obj)->value.gc->gch.tt))
|
||||||
|
|
||||||
|
#define checkliveness(g, obj) LUAU_ASSERT(!iscollectable(obj) || ((ttype(obj) == (obj)->value.gc->gch.tt) && !isdead(g, (obj)->value.gc)))
|
||||||
|
|
||||||
|
// Macros to set values
|
||||||
|
#define setnilvalue(obj) ((obj)->tt = LUA_TNIL)
|
||||||
|
|
||||||
|
#define setnvalue(obj, x) \
|
||||||
|
{ \
|
||||||
|
TValue* i_o = (obj); \
|
||||||
|
i_o->value.n = (x); \
|
||||||
|
i_o->tt = LUA_TNUMBER; \
|
||||||
|
}
|
||||||
|
|
||||||
|
#if LUA_VECTOR_SIZE == 4
|
||||||
|
#define setvvalue(obj, x, y, z, w) \
|
||||||
|
{ \
|
||||||
|
TValue* i_o = (obj); \
|
||||||
|
float* i_v = i_o->value.v; \
|
||||||
|
i_v[0] = (x); \
|
||||||
|
i_v[1] = (y); \
|
||||||
|
i_v[2] = (z); \
|
||||||
|
i_v[3] = (w); \
|
||||||
|
i_o->tt = LUA_TVECTOR; \
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
#define setvvalue(obj, x, y, z, w) \
|
||||||
|
{ \
|
||||||
|
TValue* i_o = (obj); \
|
||||||
|
float* i_v = i_o->value.v; \
|
||||||
|
i_v[0] = (x); \
|
||||||
|
i_v[1] = (y); \
|
||||||
|
i_v[2] = (z); \
|
||||||
|
i_o->tt = LUA_TVECTOR; \
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define setpvalue(obj, x) \
|
||||||
|
{ \
|
||||||
|
TValue* i_o = (obj); \
|
||||||
|
i_o->value.p = (x); \
|
||||||
|
i_o->tt = LUA_TLIGHTUSERDATA; \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define setbvalue(obj, x) \
|
||||||
|
{ \
|
||||||
|
TValue* i_o = (obj); \
|
||||||
|
i_o->value.b = (x); \
|
||||||
|
i_o->tt = LUA_TBOOLEAN; \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define setsvalue(L, obj, x) \
|
||||||
|
{ \
|
||||||
|
TValue* i_o = (obj); \
|
||||||
|
i_o->value.gc = cast_to(GCObject*, (x)); \
|
||||||
|
i_o->tt = LUA_TSTRING; \
|
||||||
|
checkliveness(L->global, i_o); \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define setuvalue(L, obj, x) \
|
||||||
|
{ \
|
||||||
|
TValue* i_o = (obj); \
|
||||||
|
i_o->value.gc = cast_to(GCObject*, (x)); \
|
||||||
|
i_o->tt = LUA_TUSERDATA; \
|
||||||
|
checkliveness(L->global, i_o); \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define setthvalue(L, obj, x) \
|
||||||
|
{ \
|
||||||
|
TValue* i_o = (obj); \
|
||||||
|
i_o->value.gc = cast_to(GCObject*, (x)); \
|
||||||
|
i_o->tt = LUA_TTHREAD; \
|
||||||
|
checkliveness(L->global, i_o); \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define setclvalue(L, obj, x) \
|
||||||
|
{ \
|
||||||
|
TValue* i_o = (obj); \
|
||||||
|
i_o->value.gc = cast_to(GCObject*, (x)); \
|
||||||
|
i_o->tt = LUA_TFUNCTION; \
|
||||||
|
checkliveness(L->global, i_o); \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define sethvalue(L, obj, x) \
|
||||||
|
{ \
|
||||||
|
TValue* i_o = (obj); \
|
||||||
|
i_o->value.gc = cast_to(GCObject*, (x)); \
|
||||||
|
i_o->tt = LUA_TTABLE; \
|
||||||
|
checkliveness(L->global, i_o); \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define setptvalue(L, obj, x) \
|
||||||
|
{ \
|
||||||
|
TValue* i_o = (obj); \
|
||||||
|
i_o->value.gc = cast_to(GCObject*, (x)); \
|
||||||
|
i_o->tt = LUA_TPROTO; \
|
||||||
|
checkliveness(L->global, i_o); \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define setupvalue(L, obj, x) \
|
||||||
|
{ \
|
||||||
|
TValue* i_o = (obj); \
|
||||||
|
i_o->value.gc = cast_to(GCObject*, (x)); \
|
||||||
|
i_o->tt = LUA_TUPVAL; \
|
||||||
|
checkliveness(L->global, i_o); \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define setobj(L, obj1, obj2) \
|
||||||
|
{ \
|
||||||
|
const TValue* o2 = (obj2); \
|
||||||
|
TValue* o1 = (obj1); \
|
||||||
|
*o1 = *o2; \
|
||||||
|
checkliveness(L->global, o1); \
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** different types of sets, according to destination
|
||||||
|
*/
|
||||||
|
|
||||||
|
// to stack
|
||||||
|
#define setobj2s setobj
|
||||||
|
// from table to same table (no barrier)
|
||||||
|
#define setobjt2t setobj
|
||||||
|
// to table (needs barrier)
|
||||||
|
#define setobj2t setobj
|
||||||
|
// to new object (no barrier)
|
||||||
|
#define setobj2n setobj
|
||||||
|
|
||||||
|
#define setttype(obj, tt) (ttype(obj) = (tt))
|
||||||
|
|
||||||
|
#define iscollectable(o) (ttype(o) >= LUA_TSTRING)
|
||||||
|
|
||||||
|
typedef TValue* StkId; // index to stack elements
|
||||||
|
|
||||||
|
/*
|
||||||
|
** String headers for string table
|
||||||
|
*/
|
||||||
|
typedef struct TString
|
||||||
|
{
|
||||||
|
CommonHeader;
|
||||||
|
// 1 byte padding
|
||||||
|
|
||||||
|
int16_t atom;
|
||||||
|
// 2 byte padding
|
||||||
|
|
||||||
|
TString* next; // next string in the hash table bucket
|
||||||
|
|
||||||
|
unsigned int hash;
|
||||||
|
unsigned int len;
|
||||||
|
|
||||||
|
char data[1]; // string data is allocated right after the header
|
||||||
|
} TString;
|
||||||
|
|
||||||
|
#define getstr(ts) (ts)->data
|
||||||
|
#define svalue(o) getstr(tsvalue(o))
|
||||||
|
|
||||||
|
typedef struct Udata
|
||||||
|
{
|
||||||
|
CommonHeader;
|
||||||
|
|
||||||
|
uint8_t tag;
|
||||||
|
|
||||||
|
int len;
|
||||||
|
|
||||||
|
struct Table* metatable;
|
||||||
|
|
||||||
|
union
|
||||||
|
{
|
||||||
|
char data[1]; // userdata is allocated right after the header
|
||||||
|
L_Umaxalign dummy; // ensures maximum alignment for data
|
||||||
|
};
|
||||||
|
} Udata;
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Function Prototypes
|
||||||
|
*/
|
||||||
|
// clang-format off
|
||||||
|
typedef struct Proto
|
||||||
|
{
|
||||||
|
CommonHeader;
|
||||||
|
|
||||||
|
|
||||||
|
TValue* k; // constants used by the function
|
||||||
|
Instruction* code; // function bytecode
|
||||||
|
struct Proto** p; // functions defined inside the function
|
||||||
|
uint8_t* lineinfo; // for each instruction, line number as a delta from baseline
|
||||||
|
int* abslineinfo; // baseline line info, one entry for each 1<<linegaplog2 instructions; allocated after lineinfo
|
||||||
|
struct LocVar* locvars; // information about local variables
|
||||||
|
TString** upvalues; // upvalue names
|
||||||
|
TString* source;
|
||||||
|
|
||||||
|
TString* debugname;
|
||||||
|
uint8_t* debuginsn; // a copy of code[] array with just opcodes
|
||||||
|
|
||||||
|
#if LUA_CUSTOM_EXECUTION
|
||||||
|
void* execdata;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
GCObject* gclist;
|
||||||
|
|
||||||
|
|
||||||
|
int sizecode;
|
||||||
|
int sizep;
|
||||||
|
int sizelocvars;
|
||||||
|
int sizeupvalues;
|
||||||
|
int sizek;
|
||||||
|
int sizelineinfo;
|
||||||
|
int linegaplog2;
|
||||||
|
int linedefined;
|
||||||
|
int bytecodeid;
|
||||||
|
|
||||||
|
|
||||||
|
uint8_t nups; // number of upvalues
|
||||||
|
uint8_t numparams;
|
||||||
|
uint8_t is_vararg;
|
||||||
|
uint8_t maxstacksize;
|
||||||
|
} Proto;
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
typedef struct LocVar
|
||||||
|
{
|
||||||
|
TString* varname;
|
||||||
|
int startpc; // first point where variable is active
|
||||||
|
int endpc; // first point where variable is dead
|
||||||
|
uint8_t reg; // register slot, relative to base, where variable is stored
|
||||||
|
} LocVar;
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Upvalues
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef struct UpVal
|
||||||
|
{
|
||||||
|
CommonHeader;
|
||||||
|
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
|
||||||
|
{
|
||||||
|
TValue value; // the value (when closed)
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
// global double linked list (when open)
|
||||||
|
struct UpVal* prev;
|
||||||
|
struct UpVal* next;
|
||||||
|
|
||||||
|
// thread linked list (when open)
|
||||||
|
struct UpVal* threadnext;
|
||||||
|
} open;
|
||||||
|
} u;
|
||||||
|
} UpVal;
|
||||||
|
|
||||||
|
#define upisopen(up) ((up)->v != &(up)->u.value)
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Closures
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef struct Closure
|
||||||
|
{
|
||||||
|
CommonHeader;
|
||||||
|
|
||||||
|
uint8_t isC;
|
||||||
|
uint8_t nupvalues;
|
||||||
|
uint8_t stacksize;
|
||||||
|
uint8_t preload;
|
||||||
|
|
||||||
|
GCObject* gclist;
|
||||||
|
struct Table* env;
|
||||||
|
|
||||||
|
union
|
||||||
|
{
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
lua_CFunction f;
|
||||||
|
lua_Continuation cont;
|
||||||
|
const char* debugname;
|
||||||
|
TValue upvals[1];
|
||||||
|
} c;
|
||||||
|
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
struct Proto* p;
|
||||||
|
TValue uprefs[1];
|
||||||
|
} l;
|
||||||
|
};
|
||||||
|
} Closure;
|
||||||
|
|
||||||
|
#define iscfunction(o) (ttype(o) == LUA_TFUNCTION && clvalue(o)->isC)
|
||||||
|
#define isLfunction(o) (ttype(o) == LUA_TFUNCTION && !clvalue(o)->isC)
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Tables
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef struct TKey
|
||||||
|
{
|
||||||
|
::Value value;
|
||||||
|
int extra[LUA_EXTRA_SIZE];
|
||||||
|
unsigned tt : 4;
|
||||||
|
int next : 28; // for chaining
|
||||||
|
} TKey;
|
||||||
|
|
||||||
|
typedef struct LuaNode
|
||||||
|
{
|
||||||
|
TValue val;
|
||||||
|
TKey key;
|
||||||
|
} LuaNode;
|
||||||
|
|
||||||
|
// copy a value into a key
|
||||||
|
#define setnodekey(L, node, obj) \
|
||||||
|
{ \
|
||||||
|
LuaNode* n_ = (node); \
|
||||||
|
const TValue* i_o = (obj); \
|
||||||
|
n_->key.value = i_o->value; \
|
||||||
|
memcpy(n_->key.extra, i_o->extra, sizeof(n_->key.extra)); \
|
||||||
|
n_->key.tt = i_o->tt; \
|
||||||
|
checkliveness(L->global, i_o); \
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy a value from a key
|
||||||
|
#define getnodekey(L, obj, node) \
|
||||||
|
{ \
|
||||||
|
TValue* i_o = (obj); \
|
||||||
|
const LuaNode* n_ = (node); \
|
||||||
|
i_o->value = n_->key.value; \
|
||||||
|
memcpy(i_o->extra, n_->key.extra, sizeof(i_o->extra)); \
|
||||||
|
i_o->tt = n_->key.tt; \
|
||||||
|
checkliveness(L->global, i_o); \
|
||||||
|
}
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
typedef struct Table
|
||||||
|
{
|
||||||
|
CommonHeader;
|
||||||
|
|
||||||
|
|
||||||
|
uint8_t tmcache; // 1<<p means tagmethod(p) is not present
|
||||||
|
uint8_t readonly; // sandboxing feature to prohibit writes to table
|
||||||
|
uint8_t safeenv; // environment doesn't share globals with other scripts
|
||||||
|
uint8_t lsizenode; // log2 of size of `node' array
|
||||||
|
uint8_t nodemask8; // (1<<lsizenode)-1, truncated to 8 bits
|
||||||
|
|
||||||
|
int sizearray; // size of `array' array
|
||||||
|
union
|
||||||
|
{
|
||||||
|
int lastfree; // any free position is before this position
|
||||||
|
int aboundary; // negated 'boundary' of `array' array; iff aboundary < 0
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct Table* metatable;
|
||||||
|
TValue* array; // array part
|
||||||
|
LuaNode* node;
|
||||||
|
GCObject* gclist;
|
||||||
|
} Table;
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
/*
|
||||||
|
** `module' operation for hashing (size is always a power of 2)
|
||||||
|
*/
|
||||||
|
#define lmod(s, size) (check_exp((size & (size - 1)) == 0, (cast_to(int, (s) & ((size)-1)))))
|
||||||
|
|
||||||
|
#define twoto(x) ((int)(1 << (x)))
|
||||||
|
#define sizenode(t) (twoto((t)->lsizenode))
|
||||||
|
|
||||||
|
#define luaO_nilobject (&luaO_nilobject_)
|
||||||
|
|
||||||
|
LUAI_DATA const TValue luaO_nilobject_;
|
||||||
|
|
||||||
|
#define ceillog2(x) (luaO_log2((x)-1) + 1)
|
||||||
|
|
||||||
|
LUAI_FUNC int luaO_log2(unsigned int x);
|
||||||
|
LUAI_FUNC int luaO_rawequalObj(const TValue* t1, const TValue* t2);
|
||||||
|
LUAI_FUNC int luaO_rawequalKey(const TKey* t1, const TValue* t2);
|
||||||
|
LUAI_FUNC int luaO_str2d(const char* s, double* result);
|
||||||
|
LUAI_FUNC const char* luaO_pushvfstring(lua_State* L, const char* fmt, va_list argp);
|
||||||
|
LUAI_FUNC const char* luaO_pushfstring(lua_State* L, const char* fmt, ...);
|
||||||
|
LUAI_FUNC const char* luaO_chunkid(char* buf, size_t buflen, const char* source, size_t srclen);
|
|
@ -0,0 +1,208 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
||||||
|
#include "lualib.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
#define LUA_STRFTIMEOPTIONS "aAbBcdHIjmMpSUwWxXyYzZ%"
|
||||||
|
|
||||||
|
#if defined(_WIN32)
|
||||||
|
static tm* gmtime_r(const time_t* timep, tm* result)
|
||||||
|
{
|
||||||
|
return gmtime_s(result, timep) == 0 ? result : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static tm* localtime_r(const time_t* timep, tm* result)
|
||||||
|
{
|
||||||
|
return localtime_s(result, timep) == 0 ? result : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static time_t timegm(struct tm* timep)
|
||||||
|
{
|
||||||
|
return _mkgmtime(timep);
|
||||||
|
}
|
||||||
|
#elif defined(__FreeBSD__)
|
||||||
|
static tm* gmtime_r(const time_t* timep, tm* result)
|
||||||
|
{
|
||||||
|
return gmtime_s(timep, result) == 0 ? result : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static tm* localtime_r(const time_t* timep, tm* result)
|
||||||
|
{
|
||||||
|
return localtime_s(timep, result) == 0 ? result : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static time_t timegm(struct tm* timep)
|
||||||
|
{
|
||||||
|
return mktime(timep);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static int os_clock(lua_State* L)
|
||||||
|
{
|
||||||
|
lua_pushnumber(L, lua_clock());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** {======================================================
|
||||||
|
** Time/Date operations
|
||||||
|
** { year=%Y, month=%m, day=%d, hour=%H, min=%M, sec=%S,
|
||||||
|
** wday=%w+1, yday=%j, isdst=? }
|
||||||
|
** =======================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void setfield(lua_State* L, const char* key, int value)
|
||||||
|
{
|
||||||
|
lua_pushinteger(L, value);
|
||||||
|
lua_setfield(L, -2, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void setboolfield(lua_State* L, const char* key, int value)
|
||||||
|
{
|
||||||
|
if (value < 0) // undefined?
|
||||||
|
return; // does not set field
|
||||||
|
lua_pushboolean(L, value);
|
||||||
|
lua_setfield(L, -2, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int getboolfield(lua_State* L, const char* key)
|
||||||
|
{
|
||||||
|
int res;
|
||||||
|
lua_rawgetfield(L, -1, key);
|
||||||
|
res = lua_isnil(L, -1) ? -1 : lua_toboolean(L, -1);
|
||||||
|
lua_pop(L, 1);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int getfield(lua_State* L, const char* key, int d)
|
||||||
|
{
|
||||||
|
int res;
|
||||||
|
lua_rawgetfield(L, -1, key);
|
||||||
|
if (lua_isnumber(L, -1))
|
||||||
|
res = (int)lua_tointeger(L, -1);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (d < 0)
|
||||||
|
luaL_error(L, "field '%s' missing in date table", key);
|
||||||
|
res = d;
|
||||||
|
}
|
||||||
|
lua_pop(L, 1);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int os_date(lua_State* L)
|
||||||
|
{
|
||||||
|
const char* s = luaL_optstring(L, 1, "%c");
|
||||||
|
time_t t = luaL_opt(L, (time_t)luaL_checknumber, 2, time(NULL));
|
||||||
|
|
||||||
|
struct tm tm;
|
||||||
|
struct tm* stm;
|
||||||
|
if (*s == '!')
|
||||||
|
{ // UTC?
|
||||||
|
stm = gmtime_r(&t, &tm);
|
||||||
|
s++; // skip `!'
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// on Windows, localtime() fails with dates before epoch start so we disallow that
|
||||||
|
stm = t < 0 ? NULL : localtime_r(&t, &tm);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stm == NULL) // invalid date?
|
||||||
|
{
|
||||||
|
lua_pushnil(L);
|
||||||
|
}
|
||||||
|
else if (strcmp(s, "*t") == 0)
|
||||||
|
{
|
||||||
|
lua_createtable(L, 0, 9); // 9 = number of fields
|
||||||
|
setfield(L, "sec", stm->tm_sec);
|
||||||
|
setfield(L, "min", stm->tm_min);
|
||||||
|
setfield(L, "hour", stm->tm_hour);
|
||||||
|
setfield(L, "day", stm->tm_mday);
|
||||||
|
setfield(L, "month", stm->tm_mon + 1);
|
||||||
|
setfield(L, "year", stm->tm_year + 1900);
|
||||||
|
setfield(L, "wday", stm->tm_wday + 1);
|
||||||
|
setfield(L, "yday", stm->tm_yday + 1);
|
||||||
|
setboolfield(L, "isdst", stm->tm_isdst);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
char cc[3];
|
||||||
|
cc[0] = '%';
|
||||||
|
cc[2] = '\0';
|
||||||
|
|
||||||
|
luaL_Buffer b;
|
||||||
|
luaL_buffinit(L, &b);
|
||||||
|
for (; *s; s++)
|
||||||
|
{
|
||||||
|
if (*s != '%' || *(s + 1) == '\0') // no conversion specifier?
|
||||||
|
{
|
||||||
|
luaL_addchar(&b, *s);
|
||||||
|
}
|
||||||
|
else if (strchr(LUA_STRFTIMEOPTIONS, *(s + 1)) == 0)
|
||||||
|
{
|
||||||
|
luaL_argerror(L, 1, "invalid conversion specifier");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
size_t reslen;
|
||||||
|
char buff[200]; // should be big enough for any conversion result
|
||||||
|
cc[1] = *(++s);
|
||||||
|
reslen = strftime(buff, sizeof(buff), cc, stm);
|
||||||
|
luaL_addlstring(&b, buff, reslen, -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
luaL_pushresult(&b);
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int os_time(lua_State* L)
|
||||||
|
{
|
||||||
|
time_t t;
|
||||||
|
if (lua_isnoneornil(L, 1)) // called without args?
|
||||||
|
t = time(NULL); // get current time
|
||||||
|
else
|
||||||
|
{
|
||||||
|
struct tm ts;
|
||||||
|
luaL_checktype(L, 1, LUA_TTABLE);
|
||||||
|
lua_settop(L, 1); // make sure table is at the top
|
||||||
|
ts.tm_sec = getfield(L, "sec", 0);
|
||||||
|
ts.tm_min = getfield(L, "min", 0);
|
||||||
|
ts.tm_hour = getfield(L, "hour", 12);
|
||||||
|
ts.tm_mday = getfield(L, "day", -1);
|
||||||
|
ts.tm_mon = getfield(L, "month", -1) - 1;
|
||||||
|
ts.tm_year = getfield(L, "year", -1) - 1900;
|
||||||
|
ts.tm_isdst = getboolfield(L, "isdst");
|
||||||
|
|
||||||
|
// Note: upstream Lua uses mktime() here which assumes input is local time, but we prefer UTC for consistency
|
||||||
|
t = timegm(&ts);
|
||||||
|
}
|
||||||
|
if (t == (time_t)(-1))
|
||||||
|
lua_pushnil(L);
|
||||||
|
else
|
||||||
|
lua_pushnumber(L, (double)t);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int os_difftime(lua_State* L)
|
||||||
|
{
|
||||||
|
lua_pushnumber(L, difftime((time_t)(luaL_checknumber(L, 1)), (time_t)(luaL_optnumber(L, 2, 0))));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const luaL_Reg syslib[] = {
|
||||||
|
{"clock", os_clock},
|
||||||
|
{"date", os_date},
|
||||||
|
{"difftime", os_difftime},
|
||||||
|
{"time", os_time},
|
||||||
|
{NULL, NULL},
|
||||||
|
};
|
||||||
|
|
||||||
|
int luaopen_os(lua_State* L)
|
||||||
|
{
|
||||||
|
luaL_register(L, LUA_OSLIBNAME, syslib);
|
||||||
|
return 1;
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
||||||
|
#include "lua.h"
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#ifndef WIN32_LEAN_AND_MEAN
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#endif
|
||||||
|
#ifndef NOMINMAX
|
||||||
|
#define NOMINMAX
|
||||||
|
#endif
|
||||||
|
#include <Windows.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __APPLE__
|
||||||
|
#include <mach/mach.h>
|
||||||
|
#include <mach/mach_time.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
static double clock_period()
|
||||||
|
{
|
||||||
|
#if defined(_WIN32)
|
||||||
|
LARGE_INTEGER result = {};
|
||||||
|
QueryPerformanceFrequency(&result);
|
||||||
|
return 1.0 / double(result.QuadPart);
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
mach_timebase_info_data_t result = {};
|
||||||
|
mach_timebase_info(&result);
|
||||||
|
return double(result.numer) / double(result.denom) * 1e-9;
|
||||||
|
#elif defined(__linux__)
|
||||||
|
return 1e-9;
|
||||||
|
#else
|
||||||
|
return 1.0 / double(CLOCKS_PER_SEC);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static double clock_timestamp()
|
||||||
|
{
|
||||||
|
#if defined(_WIN32)
|
||||||
|
LARGE_INTEGER result = {};
|
||||||
|
QueryPerformanceCounter(&result);
|
||||||
|
return double(result.QuadPart);
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
return double(mach_absolute_time());
|
||||||
|
#elif defined(__linux__)
|
||||||
|
timespec now;
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||||
|
return now.tv_sec * 1e9 + now.tv_nsec;
|
||||||
|
#else
|
||||||
|
return double(clock());
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
double lua_clock()
|
||||||
|
{
|
||||||
|
static double period = clock_period();
|
||||||
|
|
||||||
|
return clock_timestamp() * period;
|
||||||
|
}
|
|
@ -0,0 +1,246 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
||||||
|
#include "lstate.h"
|
||||||
|
|
||||||
|
#include "ltable.h"
|
||||||
|
#include "lstring.h"
|
||||||
|
#include "lfunc.h"
|
||||||
|
#include "lmem.h"
|
||||||
|
#include "lgc.h"
|
||||||
|
#include "ldo.h"
|
||||||
|
#include "ldebug.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Main thread combines a thread state and the global state
|
||||||
|
*/
|
||||||
|
typedef struct LG
|
||||||
|
{
|
||||||
|
lua_State l;
|
||||||
|
global_State g;
|
||||||
|
} LG;
|
||||||
|
|
||||||
|
static void stack_init(lua_State* L1, lua_State* L)
|
||||||
|
{
|
||||||
|
// initialize CallInfo array
|
||||||
|
L1->base_ci = luaM_newarray(L, BASIC_CI_SIZE, CallInfo, L1->memcat);
|
||||||
|
L1->ci = L1->base_ci;
|
||||||
|
L1->size_ci = BASIC_CI_SIZE;
|
||||||
|
L1->end_ci = L1->base_ci + L1->size_ci - 1;
|
||||||
|
// initialize stack array
|
||||||
|
L1->stack = luaM_newarray(L, BASIC_STACK_SIZE + EXTRA_STACK, TValue, L1->memcat);
|
||||||
|
L1->stacksize = BASIC_STACK_SIZE + EXTRA_STACK;
|
||||||
|
TValue* stack = L1->stack;
|
||||||
|
for (int i = 0; i < BASIC_STACK_SIZE + EXTRA_STACK; i++)
|
||||||
|
setnilvalue(stack + i); // erase new stack
|
||||||
|
L1->top = stack;
|
||||||
|
L1->stack_last = stack + (L1->stacksize - EXTRA_STACK);
|
||||||
|
// initialize first ci
|
||||||
|
L1->ci->func = L1->top;
|
||||||
|
setnilvalue(L1->top++); // `function' entry for this `ci'
|
||||||
|
L1->base = L1->ci->base = L1->top;
|
||||||
|
L1->ci->top = L1->top + LUA_MINSTACK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void freestack(lua_State* L, lua_State* L1)
|
||||||
|
{
|
||||||
|
luaM_freearray(L, L1->base_ci, L1->size_ci, CallInfo, L1->memcat);
|
||||||
|
luaM_freearray(L, L1->stack, L1->stacksize, TValue, L1->memcat);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** open parts that may cause memory-allocation errors
|
||||||
|
*/
|
||||||
|
static void f_luaopen(lua_State* L, void* ud)
|
||||||
|
{
|
||||||
|
global_State* g = L->global;
|
||||||
|
stack_init(L, L); // init stack
|
||||||
|
L->gt = luaH_new(L, 0, 2); // table of globals
|
||||||
|
sethvalue(L, registry(L), luaH_new(L, 0, 2)); // registry
|
||||||
|
luaS_resize(L, LUA_MINSTRTABSIZE); // initial size of string table
|
||||||
|
luaT_init(L);
|
||||||
|
luaS_fix(luaS_newliteral(L, LUA_MEMERRMSG)); // pin to make sure we can always throw this error
|
||||||
|
luaS_fix(luaS_newliteral(L, LUA_ERRERRMSG)); // pin to make sure we can always throw this error
|
||||||
|
g->GCthreshold = 4 * g->totalbytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void preinit_state(lua_State* L, global_State* g)
|
||||||
|
{
|
||||||
|
L->global = g;
|
||||||
|
L->stack = NULL;
|
||||||
|
L->stacksize = 0;
|
||||||
|
L->gt = NULL;
|
||||||
|
L->openupval = NULL;
|
||||||
|
L->size_ci = 0;
|
||||||
|
L->nCcalls = L->baseCcalls = 0;
|
||||||
|
L->status = 0;
|
||||||
|
L->base_ci = L->ci = NULL;
|
||||||
|
L->namecall = NULL;
|
||||||
|
L->cachedslot = 0;
|
||||||
|
L->singlestep = false;
|
||||||
|
L->isactive = false;
|
||||||
|
L->activememcat = 0;
|
||||||
|
L->userdata = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
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->strt.nuse == 0);
|
||||||
|
luaM_freearray(L, L->global->strt.hash, L->global->strt.size, TString*, 0);
|
||||||
|
freestack(L, L);
|
||||||
|
for (int i = 0; i < LUA_SIZECLASSES; i++)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(g->freepages[i] == NULL);
|
||||||
|
LUAU_ASSERT(g->freegcopages[i] == NULL);
|
||||||
|
}
|
||||||
|
LUAU_ASSERT(g->allgcopages == NULL);
|
||||||
|
LUAU_ASSERT(g->totalbytes == sizeof(LG));
|
||||||
|
LUAU_ASSERT(g->memcatbytes[0] == sizeof(LG));
|
||||||
|
for (int i = 1; i < LUA_MEMORY_CATEGORIES; i++)
|
||||||
|
LUAU_ASSERT(g->memcatbytes[i] == 0);
|
||||||
|
|
||||||
|
#if LUA_CUSTOM_EXECUTION
|
||||||
|
if (L->global->ecb.close)
|
||||||
|
L->global->ecb.close(L);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
(*g->frealloc)(g->ud, L, sizeof(LG), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
lua_State* luaE_newthread(lua_State* L)
|
||||||
|
{
|
||||||
|
lua_State* L1 = luaM_newgco(L, lua_State, sizeof(lua_State), L->activememcat);
|
||||||
|
luaC_init(L, L1, LUA_TTHREAD);
|
||||||
|
preinit_state(L1, L->global);
|
||||||
|
L1->activememcat = L->activememcat; // inherit the active memory category
|
||||||
|
stack_init(L1, L); // init stack
|
||||||
|
L1->gt = L->gt; // share table of globals
|
||||||
|
L1->singlestep = L->singlestep;
|
||||||
|
LUAU_ASSERT(iswhite(obj2gco(L1)));
|
||||||
|
return L1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void luaE_freethread(lua_State* L, lua_State* L1, lua_Page* page)
|
||||||
|
{
|
||||||
|
global_State* g = L->global;
|
||||||
|
if (g->cb.userthread)
|
||||||
|
g->cb.userthread(NULL, L1);
|
||||||
|
freestack(L, L1);
|
||||||
|
luaM_freegco(L, L1, sizeof(lua_State), L1->memcat, page);
|
||||||
|
}
|
||||||
|
|
||||||
|
void lua_resetthread(lua_State* L)
|
||||||
|
{
|
||||||
|
// close upvalues before clearing anything
|
||||||
|
luaF_close(L, L->stack);
|
||||||
|
// clear call frames
|
||||||
|
CallInfo* ci = L->base_ci;
|
||||||
|
ci->func = L->stack;
|
||||||
|
ci->base = ci->func + 1;
|
||||||
|
ci->top = ci->base + LUA_MINSTACK;
|
||||||
|
setnilvalue(ci->func);
|
||||||
|
L->ci = ci;
|
||||||
|
if (L->size_ci != BASIC_CI_SIZE)
|
||||||
|
luaD_reallocCI(L, BASIC_CI_SIZE);
|
||||||
|
// clear thread state
|
||||||
|
L->status = LUA_OK;
|
||||||
|
L->base = L->ci->base;
|
||||||
|
L->top = L->ci->base;
|
||||||
|
L->nCcalls = L->baseCcalls = 0;
|
||||||
|
// clear thread stack
|
||||||
|
if (L->stacksize != BASIC_STACK_SIZE + EXTRA_STACK)
|
||||||
|
luaD_reallocstack(L, BASIC_STACK_SIZE);
|
||||||
|
for (int i = 0; i < L->stacksize; i++)
|
||||||
|
setnilvalue(L->stack + i);
|
||||||
|
}
|
||||||
|
|
||||||
|
int lua_isthreadreset(lua_State* L)
|
||||||
|
{
|
||||||
|
return L->ci == L->base_ci && L->base == L->top && L->status == LUA_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
lua_State* lua_newstate(lua_Alloc f, void* ud)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
lua_State* L;
|
||||||
|
global_State* g;
|
||||||
|
void* l = (*f)(ud, NULL, 0, sizeof(LG));
|
||||||
|
if (l == NULL)
|
||||||
|
return NULL;
|
||||||
|
L = (lua_State*)l;
|
||||||
|
g = &((LG*)L)->g;
|
||||||
|
L->tt = LUA_TTHREAD;
|
||||||
|
L->marked = g->currentwhite = bit2mask(WHITE0BIT, FIXEDBIT);
|
||||||
|
L->memcat = 0;
|
||||||
|
preinit_state(L, g);
|
||||||
|
g->frealloc = f;
|
||||||
|
g->ud = ud;
|
||||||
|
g->mainthread = L;
|
||||||
|
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;
|
||||||
|
g->rngstate = 0;
|
||||||
|
g->ptrenckey[0] = 1;
|
||||||
|
g->ptrenckey[1] = 0;
|
||||||
|
g->ptrenckey[2] = 0;
|
||||||
|
g->ptrenckey[3] = 0;
|
||||||
|
g->strt.size = 0;
|
||||||
|
g->strt.nuse = 0;
|
||||||
|
g->strt.hash = NULL;
|
||||||
|
setnilvalue(&g->pseudotemp);
|
||||||
|
setnilvalue(registry(L));
|
||||||
|
g->gcstate = GCSpause;
|
||||||
|
g->gray = NULL;
|
||||||
|
g->grayagain = NULL;
|
||||||
|
g->weak = NULL;
|
||||||
|
g->totalbytes = sizeof(LG);
|
||||||
|
g->gcgoal = LUAI_GCGOAL;
|
||||||
|
g->gcstepmul = LUAI_GCSTEPMUL;
|
||||||
|
g->gcstepsize = LUAI_GCSTEPSIZE << 10;
|
||||||
|
for (i = 0; i < LUA_SIZECLASSES; i++)
|
||||||
|
{
|
||||||
|
g->freepages[i] = NULL;
|
||||||
|
g->freegcopages[i] = NULL;
|
||||||
|
}
|
||||||
|
g->allgcopages = NULL;
|
||||||
|
g->sweepgcopage = NULL;
|
||||||
|
for (i = 0; i < LUA_T_COUNT; i++)
|
||||||
|
g->mt[i] = NULL;
|
||||||
|
for (i = 0; i < LUA_UTAG_LIMIT; i++)
|
||||||
|
g->udatagc[i] = NULL;
|
||||||
|
for (i = 0; i < LUA_MEMORY_CATEGORIES; i++)
|
||||||
|
g->memcatbytes[i] = 0;
|
||||||
|
|
||||||
|
g->memcatbytes[0] = sizeof(LG);
|
||||||
|
|
||||||
|
g->cb = lua_Callbacks();
|
||||||
|
|
||||||
|
#if LUA_CUSTOM_EXECUTION
|
||||||
|
g->ecb = lua_ExecutionCallbacks();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
g->gcstats = GCStats();
|
||||||
|
|
||||||
|
#ifdef LUAI_GCMETRICS
|
||||||
|
g->gcmetrics = GCMetrics();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (luaD_rawrunprotected(L, f_luaopen, NULL) != 0)
|
||||||
|
{
|
||||||
|
// memory allocation error: free partial state
|
||||||
|
close_state(L);
|
||||||
|
L = NULL;
|
||||||
|
}
|
||||||
|
return L;
|
||||||
|
}
|
||||||
|
|
||||||
|
void lua_close(lua_State* L)
|
||||||
|
{
|
||||||
|
L = L->global->mainthread; // only the main thread can be closed
|
||||||
|
luaF_close(L, L->stack); // close all upvalues for this thread
|
||||||
|
close_state(L);
|
||||||
|
}
|
|
@ -0,0 +1,302 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "lobject.h"
|
||||||
|
#include "ltm.h"
|
||||||
|
|
||||||
|
// registry
|
||||||
|
#define registry(L) (&L->global->registry)
|
||||||
|
|
||||||
|
// extra stack space to handle TM calls and some other extras
|
||||||
|
#define EXTRA_STACK 5
|
||||||
|
|
||||||
|
#define BASIC_CI_SIZE 8
|
||||||
|
|
||||||
|
#define BASIC_STACK_SIZE (2 * LUA_MINSTACK)
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
typedef struct stringtable
|
||||||
|
{
|
||||||
|
|
||||||
|
TString** hash;
|
||||||
|
uint32_t nuse; // number of elements
|
||||||
|
int size;
|
||||||
|
} stringtable;
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
/*
|
||||||
|
** informations about a call
|
||||||
|
**
|
||||||
|
** the general Lua stack frame structure is as follows:
|
||||||
|
** - each function gets a stack frame, with function "registers" being stack slots on the frame
|
||||||
|
** - function arguments are associated with registers 0+
|
||||||
|
** - function locals and temporaries follow after; usually locals are a consecutive block per scope, and temporaries are allocated after this, but
|
||||||
|
*this is up to the compiler
|
||||||
|
**
|
||||||
|
** when function doesn't have varargs, the stack layout is as follows:
|
||||||
|
** ^ (func) ^^ [fixed args] [locals + temporaries]
|
||||||
|
** where ^ is the 'func' pointer in CallInfo struct, and ^^ is the 'base' pointer (which is what registers are relative to)
|
||||||
|
**
|
||||||
|
** when function *does* have varargs, the stack layout is more complex - the runtime has to copy the fixed arguments so that the 0+ addressing still
|
||||||
|
*works as follows:
|
||||||
|
** ^ (func) [fixed args] [varargs] ^^ [fixed args] [locals + temporaries]
|
||||||
|
**
|
||||||
|
** computing the sizes of these individual blocks works as follows:
|
||||||
|
** - the number of fixed args is always matching the `numparams` in a function's Proto object; runtime adds `nil` during the call execution as
|
||||||
|
*necessary
|
||||||
|
** - the number of variadic args can be computed by evaluating (ci->base - ci->func - 1 - numparams)
|
||||||
|
**
|
||||||
|
** the CallInfo structures are allocated as an array, with each subsequent call being *appended* to this array (so if f calls g, CallInfo for g
|
||||||
|
*immediately follows CallInfo for f)
|
||||||
|
** the `nresults` field in CallInfo is set by the caller to tell the function how many arguments the caller is expecting on the stack after the
|
||||||
|
*function returns
|
||||||
|
** the `flags` field in CallInfo contains internal execution flags that are important for pcall/etc, see LUA_CALLINFO_*
|
||||||
|
*/
|
||||||
|
// clang-format off
|
||||||
|
typedef struct CallInfo
|
||||||
|
{
|
||||||
|
|
||||||
|
StkId base; // base for this function
|
||||||
|
StkId func; // function index in the stack
|
||||||
|
StkId top; // top for this function
|
||||||
|
const Instruction* savedpc;
|
||||||
|
|
||||||
|
int nresults; // expected number of results from this function
|
||||||
|
unsigned int flags; // call frame flags, see LUA_CALLINFO_*
|
||||||
|
} CallInfo;
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
#define LUA_CALLINFO_RETURN (1 << 0) // should the interpreter return after returning from this callinfo? first frame must have this set
|
||||||
|
#define LUA_CALLINFO_HANDLE (1 << 1) // should the error thrown during execution get handled by continuation from this callinfo? func must be C
|
||||||
|
|
||||||
|
#define curr_func(L) (clvalue(L->ci->func))
|
||||||
|
#define ci_func(ci) (clvalue((ci)->func))
|
||||||
|
#define f_isLua(ci) (!ci_func(ci)->isC)
|
||||||
|
#define isLua(ci) (ttisfunction((ci)->func) && f_isLua(ci))
|
||||||
|
|
||||||
|
struct GCStats
|
||||||
|
{
|
||||||
|
// data for proportional-integral controller of heap trigger value
|
||||||
|
int32_t triggerterms[32] = {0};
|
||||||
|
uint32_t triggertermpos = 0;
|
||||||
|
int32_t triggerintegral = 0;
|
||||||
|
|
||||||
|
size_t atomicstarttotalsizebytes = 0;
|
||||||
|
size_t endtotalsizebytes = 0;
|
||||||
|
size_t heapgoalsizebytes = 0;
|
||||||
|
|
||||||
|
double starttimestamp = 0;
|
||||||
|
double atomicstarttimestamp = 0;
|
||||||
|
double endtimestamp = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef LUAI_GCMETRICS
|
||||||
|
struct GCCycleMetrics
|
||||||
|
{
|
||||||
|
size_t starttotalsizebytes = 0;
|
||||||
|
size_t heaptriggersizebytes = 0;
|
||||||
|
|
||||||
|
double pausetime = 0.0; // time from end of the last cycle to the start of a new one
|
||||||
|
|
||||||
|
double starttimestamp = 0.0;
|
||||||
|
double endtimestamp = 0.0;
|
||||||
|
|
||||||
|
double marktime = 0.0;
|
||||||
|
double markassisttime = 0.0;
|
||||||
|
double markmaxexplicittime = 0.0;
|
||||||
|
size_t markexplicitsteps = 0;
|
||||||
|
size_t markwork = 0;
|
||||||
|
|
||||||
|
double atomicstarttimestamp = 0.0;
|
||||||
|
size_t atomicstarttotalsizebytes = 0;
|
||||||
|
double atomictime = 0.0;
|
||||||
|
|
||||||
|
// specific atomic stage parts
|
||||||
|
double atomictimeupval = 0.0;
|
||||||
|
double atomictimeweak = 0.0;
|
||||||
|
double atomictimegray = 0.0;
|
||||||
|
double atomictimeclear = 0.0;
|
||||||
|
|
||||||
|
double sweeptime = 0.0;
|
||||||
|
double sweepassisttime = 0.0;
|
||||||
|
double sweepmaxexplicittime = 0.0;
|
||||||
|
size_t sweepexplicitsteps = 0;
|
||||||
|
size_t sweepwork = 0;
|
||||||
|
|
||||||
|
size_t assistwork = 0;
|
||||||
|
size_t explicitwork = 0;
|
||||||
|
|
||||||
|
size_t propagatework = 0;
|
||||||
|
size_t propagateagainwork = 0;
|
||||||
|
|
||||||
|
size_t endtotalsizebytes = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GCMetrics
|
||||||
|
{
|
||||||
|
double stepexplicittimeacc = 0.0;
|
||||||
|
double stepassisttimeacc = 0.0;
|
||||||
|
|
||||||
|
// when cycle is completed, last cycle values are updated
|
||||||
|
uint64_t completedcycles = 0;
|
||||||
|
|
||||||
|
GCCycleMetrics lastcycle;
|
||||||
|
GCCycleMetrics currcycle;
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Callbacks that can be used to to redirect code execution from Luau bytecode VM to a custom implementation (AoT/JiT/sandboxing/...)
|
||||||
|
struct lua_ExecutionCallbacks
|
||||||
|
{
|
||||||
|
void* context;
|
||||||
|
void (*close)(lua_State* L); // called when global VM state is closed
|
||||||
|
void (*destroy)(lua_State* L, Proto* proto); // called when function is destroyed
|
||||||
|
int (*enter)(lua_State* L, Proto* proto); // called when function is about to start/resume (when execdata is present), return 0 to exit VM
|
||||||
|
void (*setbreakpoint)(lua_State* L, Proto* proto, int line); // called when a breakpoint is set in a function
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
** `global state', shared by all threads of this state
|
||||||
|
*/
|
||||||
|
// clang-format off
|
||||||
|
typedef struct global_State
|
||||||
|
{
|
||||||
|
stringtable strt; // hash table for strings
|
||||||
|
|
||||||
|
|
||||||
|
lua_Alloc frealloc; // function to reallocate memory
|
||||||
|
void* ud; // auxiliary data to `frealloc'
|
||||||
|
|
||||||
|
|
||||||
|
uint8_t currentwhite;
|
||||||
|
uint8_t gcstate; // state of garbage collector
|
||||||
|
|
||||||
|
|
||||||
|
GCObject* gray; // list of gray objects
|
||||||
|
GCObject* grayagain; // list of objects to be traversed atomically
|
||||||
|
GCObject* weak; // list of weak tables (to be cleared)
|
||||||
|
|
||||||
|
|
||||||
|
size_t GCthreshold; // when totalbytes > GCthreshold, run GC step
|
||||||
|
size_t totalbytes; // number of bytes currently allocated
|
||||||
|
int gcgoal; // see LUAI_GCGOAL
|
||||||
|
int gcstepmul; // see LUAI_GCSTEPMUL
|
||||||
|
int gcstepsize; // see LUAI_GCSTEPSIZE
|
||||||
|
|
||||||
|
struct lua_Page* freepages[LUA_SIZECLASSES]; // free page linked list for each size class for non-collectable objects
|
||||||
|
struct lua_Page* freegcopages[LUA_SIZECLASSES]; // free page linked list for each size class for collectable objects
|
||||||
|
struct lua_Page* allgcopages; // page linked list with all pages for all classes
|
||||||
|
struct lua_Page* sweepgcopage; // position of the sweep in `allgcopages'
|
||||||
|
|
||||||
|
size_t memcatbytes[LUA_MEMORY_CATEGORIES]; // total amount of memory used by each memory category
|
||||||
|
|
||||||
|
|
||||||
|
struct lua_State* mainthread;
|
||||||
|
UpVal uvhead; // head of double-linked list of all open upvalues
|
||||||
|
struct Table* mt[LUA_T_COUNT]; // metatables for basic types
|
||||||
|
TString* ttname[LUA_T_COUNT]; // names for basic types
|
||||||
|
TString* tmname[TM_N]; // array with tag-method names
|
||||||
|
|
||||||
|
TValue pseudotemp; // storage for temporary values used in pseudo2addr
|
||||||
|
|
||||||
|
TValue registry; // registry table, used by lua_ref and LUA_REGISTRYINDEX
|
||||||
|
int registryfree; // next free slot in registry
|
||||||
|
|
||||||
|
struct lua_jmpbuf* errorjmp; // jump buffer data for longjmp-style error handling
|
||||||
|
|
||||||
|
uint64_t rngstate; // PCG random number generator state
|
||||||
|
uint64_t ptrenckey[4]; // pointer encoding key for display
|
||||||
|
|
||||||
|
lua_Callbacks cb;
|
||||||
|
|
||||||
|
#if LUA_CUSTOM_EXECUTION
|
||||||
|
lua_ExecutionCallbacks ecb;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void (*udatagc[LUA_UTAG_LIMIT])(lua_State*, void*); // for each userdata tag, a gc callback to be called immediately before freeing memory
|
||||||
|
|
||||||
|
GCStats gcstats;
|
||||||
|
|
||||||
|
#ifdef LUAI_GCMETRICS
|
||||||
|
GCMetrics gcmetrics;
|
||||||
|
#endif
|
||||||
|
} global_State;
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
/*
|
||||||
|
** `per thread' state
|
||||||
|
*/
|
||||||
|
// clang-format off
|
||||||
|
struct lua_State
|
||||||
|
{
|
||||||
|
CommonHeader;
|
||||||
|
uint8_t status;
|
||||||
|
|
||||||
|
uint8_t activememcat; // memory category that is used for new GC object allocations
|
||||||
|
|
||||||
|
bool isactive; // thread is currently executing, stack may be mutated without barriers
|
||||||
|
bool singlestep; // call debugstep hook after each instruction
|
||||||
|
|
||||||
|
|
||||||
|
StkId top; // first free slot in the stack
|
||||||
|
StkId base; // base of current function
|
||||||
|
global_State* global;
|
||||||
|
CallInfo* ci; // call info for current function
|
||||||
|
StkId stack_last; // last free slot in the stack
|
||||||
|
StkId stack; // stack base
|
||||||
|
|
||||||
|
|
||||||
|
CallInfo* end_ci; // points after end of ci array
|
||||||
|
CallInfo* base_ci; // array of CallInfo's
|
||||||
|
|
||||||
|
|
||||||
|
int stacksize;
|
||||||
|
int size_ci; // size of array `base_ci'
|
||||||
|
|
||||||
|
|
||||||
|
unsigned short nCcalls; // number of nested C calls
|
||||||
|
unsigned short baseCcalls; // nested C calls when resuming coroutine
|
||||||
|
|
||||||
|
int cachedslot; // when table operations or INDEX/NEWINDEX is invoked from Luau, what is the expected slot for lookup?
|
||||||
|
|
||||||
|
|
||||||
|
Table* gt; // table of globals
|
||||||
|
UpVal* openupval; // list of open upvalues in this stack
|
||||||
|
GCObject* gclist;
|
||||||
|
|
||||||
|
TString* namecall; // when invoked from Luau using NAMECALL, what method do we need to invoke?
|
||||||
|
|
||||||
|
void* userdata;
|
||||||
|
};
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Union of all collectible objects
|
||||||
|
*/
|
||||||
|
union GCObject
|
||||||
|
{
|
||||||
|
GCheader gch;
|
||||||
|
struct TString ts;
|
||||||
|
struct Udata u;
|
||||||
|
struct Closure cl;
|
||||||
|
struct Table h;
|
||||||
|
struct Proto p;
|
||||||
|
struct UpVal uv;
|
||||||
|
struct lua_State th; // thread
|
||||||
|
};
|
||||||
|
|
||||||
|
// macros to convert a GCObject into a specific value
|
||||||
|
#define gco2ts(o) check_exp((o)->gch.tt == LUA_TSTRING, &((o)->ts))
|
||||||
|
#define gco2u(o) check_exp((o)->gch.tt == LUA_TUSERDATA, &((o)->u))
|
||||||
|
#define gco2cl(o) check_exp((o)->gch.tt == LUA_TFUNCTION, &((o)->cl))
|
||||||
|
#define gco2h(o) check_exp((o)->gch.tt == LUA_TTABLE, &((o)->h))
|
||||||
|
#define gco2p(o) check_exp((o)->gch.tt == LUA_TPROTO, &((o)->p))
|
||||||
|
#define gco2uv(o) check_exp((o)->gch.tt == LUA_TUPVAL, &((o)->uv))
|
||||||
|
#define gco2th(o) check_exp((o)->gch.tt == LUA_TTHREAD, &((o)->th))
|
||||||
|
|
||||||
|
// macro to convert any Lua object into a GCObject
|
||||||
|
#define obj2gco(v) check_exp(iscollectable(v), cast_to(GCObject*, (v) + 0))
|
||||||
|
|
||||||
|
LUAI_FUNC lua_State* luaE_newthread(lua_State* L);
|
||||||
|
LUAI_FUNC void luaE_freethread(lua_State* L, lua_State* L1, struct lua_Page* page);
|
|
@ -0,0 +1,193 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
||||||
|
#include "lstring.h"
|
||||||
|
|
||||||
|
#include "lgc.h"
|
||||||
|
#include "lmem.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
unsigned int luaS_hash(const char* str, size_t len)
|
||||||
|
{
|
||||||
|
// Note that this hashing algorithm is replicated in BytecodeBuilder.cpp, BytecodeBuilder::getStringHash
|
||||||
|
unsigned int a = 0, b = 0;
|
||||||
|
unsigned int h = unsigned(len);
|
||||||
|
|
||||||
|
// hash prefix in 12b chunks (using aligned reads) with ARX based hash (LuaJIT v2.1, lookup3)
|
||||||
|
// note that we stop at length<32 to maintain compatibility with Lua 5.1
|
||||||
|
while (len >= 32)
|
||||||
|
{
|
||||||
|
#define rol(x, s) ((x >> s) | (x << (32 - s)))
|
||||||
|
#define mix(u, v, w) a ^= h, a -= rol(h, u), b ^= a, b -= rol(a, v), h ^= b, h -= rol(b, w)
|
||||||
|
|
||||||
|
// should compile into fast unaligned reads
|
||||||
|
uint32_t block[3];
|
||||||
|
memcpy(block, str, 12);
|
||||||
|
|
||||||
|
a += block[0];
|
||||||
|
b += block[1];
|
||||||
|
h += block[2];
|
||||||
|
mix(14, 11, 25);
|
||||||
|
str += 12;
|
||||||
|
len -= 12;
|
||||||
|
|
||||||
|
#undef mix
|
||||||
|
#undef rol
|
||||||
|
}
|
||||||
|
|
||||||
|
// original Lua 5.1 hash for compatibility (exact match when len<32)
|
||||||
|
for (size_t i = len; i > 0; --i)
|
||||||
|
h ^= (h << 5) + (h >> 2) + (uint8_t)str[i - 1];
|
||||||
|
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
void luaS_resize(lua_State* L, int newsize)
|
||||||
|
{
|
||||||
|
TString** newhash = luaM_newarray(L, newsize, TString*, 0);
|
||||||
|
stringtable* tb = &L->global->strt;
|
||||||
|
for (int i = 0; i < newsize; i++)
|
||||||
|
newhash[i] = NULL;
|
||||||
|
// rehash
|
||||||
|
for (int i = 0; i < tb->size; i++)
|
||||||
|
{
|
||||||
|
TString* p = tb->hash[i];
|
||||||
|
while (p)
|
||||||
|
{ // for each node in the list
|
||||||
|
TString* next = p->next; // save next
|
||||||
|
unsigned int h = p->hash;
|
||||||
|
int h1 = lmod(h, newsize); // new position
|
||||||
|
LUAU_ASSERT(cast_int(h % newsize) == lmod(h, newsize));
|
||||||
|
p->next = newhash[h1]; // chain it
|
||||||
|
newhash[h1] = p;
|
||||||
|
p = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
luaM_freearray(L, tb->hash, tb->size, TString*, 0);
|
||||||
|
tb->size = newsize;
|
||||||
|
tb->hash = newhash;
|
||||||
|
}
|
||||||
|
|
||||||
|
static TString* newlstr(lua_State* L, const char* str, size_t l, unsigned int h)
|
||||||
|
{
|
||||||
|
if (l > MAXSSIZE)
|
||||||
|
luaM_toobig(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->len = unsigned(l);
|
||||||
|
|
||||||
|
memcpy(ts->data, str, l);
|
||||||
|
ts->data[l] = '\0'; // ending 0
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
TString* luaS_buffinish(lua_State* L, TString* ts)
|
||||||
|
{
|
||||||
|
unsigned int h = luaS_hash(ts->data, ts->len);
|
||||||
|
stringtable* tb = &L->global->strt;
|
||||||
|
int bucket = lmod(h, tb->size);
|
||||||
|
|
||||||
|
// search if we already have this string in the hash table
|
||||||
|
for (TString* el = tb->hash[bucket]; el != NULL; el = el->next)
|
||||||
|
{
|
||||||
|
if (el->len == ts->len && memcmp(el->data, ts->data, ts->len) == 0)
|
||||||
|
{
|
||||||
|
// string may be dead
|
||||||
|
if (isdead(L->global, obj2gco(el)))
|
||||||
|
changewhite(obj2gco(el));
|
||||||
|
|
||||||
|
return el;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LUAU_ASSERT(ts->next == NULL);
|
||||||
|
|
||||||
|
ts->hash = h;
|
||||||
|
ts->data[ts->len] = '\0'; // ending 0
|
||||||
|
ts->atom = ATOM_UNDEF;
|
||||||
|
ts->next = tb->hash[bucket]; // chain new entry
|
||||||
|
tb->hash[bucket] = 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
TString* luaS_newlstr(lua_State* L, const char* str, size_t l)
|
||||||
|
{
|
||||||
|
unsigned int h = luaS_hash(str, l);
|
||||||
|
for (TString* el = L->global->strt.hash[lmod(h, L->global->strt.size)]; el != NULL; el = el->next)
|
||||||
|
{
|
||||||
|
if (el->len == l && (memcmp(str, getstr(el), l) == 0))
|
||||||
|
{
|
||||||
|
// string may be dead
|
||||||
|
if (isdead(L->global, obj2gco(el)))
|
||||||
|
changewhite(obj2gco(el));
|
||||||
|
return el;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newlstr(L, str, l, h); // not found
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool unlinkstr(lua_State* L, TString* ts)
|
||||||
|
{
|
||||||
|
global_State* g = L->global;
|
||||||
|
|
||||||
|
TString** p = &g->strt.hash[lmod(ts->hash, g->strt.size)];
|
||||||
|
|
||||||
|
while (TString* curr = *p)
|
||||||
|
{
|
||||||
|
if (curr == ts)
|
||||||
|
{
|
||||||
|
*p = curr->next;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
p = &curr->next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void luaS_free(lua_State* L, TString* ts, lua_Page* page)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "lobject.h"
|
||||||
|
#include "lstate.h"
|
||||||
|
|
||||||
|
// string size limit
|
||||||
|
#define MAXSSIZE (1 << 30)
|
||||||
|
|
||||||
|
// string atoms are not defined by default; the storage is 16-bit integer
|
||||||
|
#define ATOM_UNDEF -32768
|
||||||
|
|
||||||
|
#define sizestring(len) (offsetof(TString, data) + len + 1)
|
||||||
|
|
||||||
|
#define luaS_new(L, s) (luaS_newlstr(L, s, strlen(s)))
|
||||||
|
#define luaS_newliteral(L, s) (luaS_newlstr(L, "" s, (sizeof(s) / sizeof(char)) - 1))
|
||||||
|
|
||||||
|
#define luaS_fix(s) l_setbit((s)->marked, FIXEDBIT)
|
||||||
|
|
||||||
|
LUAI_FUNC unsigned int luaS_hash(const char* str, size_t len);
|
||||||
|
|
||||||
|
LUAI_FUNC void luaS_resize(lua_State* L, int newsize);
|
||||||
|
|
||||||
|
LUAI_FUNC TString* luaS_newlstr(lua_State* L, const char* str, size_t l);
|
||||||
|
LUAI_FUNC void luaS_free(lua_State* L, TString* ts, struct lua_Page* page);
|
||||||
|
|
||||||
|
LUAI_FUNC TString* luaS_bufstart(lua_State* L, size_t size);
|
||||||
|
LUAI_FUNC TString* luaS_buffinish(lua_State* L, TString* ts);
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,869 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Implementation of tables (aka arrays, objects, or hash tables).
|
||||||
|
*
|
||||||
|
* Tables keep the elements in two parts: an array part and a hash part.
|
||||||
|
* Integer keys >=1 are all candidates to be kept in the array part. The actual size of the array is the
|
||||||
|
* largest n such that at least half the slots between 0 and n are in use.
|
||||||
|
* Hash uses a mix of chained scatter table with Brent's variation.
|
||||||
|
*
|
||||||
|
* A main invariant of these tables is that, if an element is not in its main position (i.e. the original
|
||||||
|
* position that its hash gives to it), then the colliding element is in its own main position.
|
||||||
|
* Hence even when the load factor reaches 100%, performance remains good.
|
||||||
|
*
|
||||||
|
* Table keys can be arbitrary values unless they contain NaN. Keys are hashed and compared using raw equality,
|
||||||
|
* so even if the key is a userdata with an overridden __eq, it's not used during hash lookups.
|
||||||
|
*
|
||||||
|
* Each table has a "boundary", defined as the index k where t[k] ~= nil and t[k+1] == nil. The boundary can be
|
||||||
|
* computed using a binary search and can be adjusted when the table is modified; crucially, Luau enforces an
|
||||||
|
* invariant where the boundary must be in the array part - this enforces a consistent iteration order through the
|
||||||
|
* prefix of the table when using pairs(), and allows to implement algorithms that access elements in 1..#t range
|
||||||
|
* more efficiently.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ltable.h"
|
||||||
|
|
||||||
|
#include "lstate.h"
|
||||||
|
#include "ldebug.h"
|
||||||
|
#include "lgc.h"
|
||||||
|
#include "lmem.h"
|
||||||
|
#include "lnumutils.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauArrBoundResizeFix, false)
|
||||||
|
|
||||||
|
// max size of both array and hash part is 2^MAXBITS
|
||||||
|
#define MAXBITS 26
|
||||||
|
#define MAXSIZE (1 << MAXBITS)
|
||||||
|
|
||||||
|
static_assert(offsetof(LuaNode, val) == 0, "Unexpected Node memory layout, pointer cast in gval2slot is incorrect");
|
||||||
|
|
||||||
|
// TKey is bitpacked for memory efficiency so we need to validate bit counts for worst case
|
||||||
|
static_assert(TKey{{NULL}, {0}, LUA_TDEADKEY, 0}.tt == LUA_TDEADKEY, "not enough bits for tt");
|
||||||
|
static_assert(TKey{{NULL}, {0}, LUA_TNIL, MAXSIZE - 1}.next == MAXSIZE - 1, "not enough bits for next");
|
||||||
|
static_assert(TKey{{NULL}, {0}, LUA_TNIL, -(MAXSIZE - 1)}.next == -(MAXSIZE - 1), "not enough bits for next");
|
||||||
|
|
||||||
|
// empty hash data points to dummynode so that we can always dereference it
|
||||||
|
const LuaNode luaH_dummynode = {
|
||||||
|
{{NULL}, {0}, LUA_TNIL}, // value
|
||||||
|
{{NULL}, {0}, LUA_TNIL, 0} // key
|
||||||
|
};
|
||||||
|
|
||||||
|
#define dummynode (&luaH_dummynode)
|
||||||
|
|
||||||
|
// hash is always reduced mod 2^k
|
||||||
|
#define hashpow2(t, n) (gnode(t, lmod((n), sizenode(t))))
|
||||||
|
|
||||||
|
#define hashstr(t, str) hashpow2(t, (str)->hash)
|
||||||
|
#define hashboolean(t, p) hashpow2(t, p)
|
||||||
|
|
||||||
|
static LuaNode* hashpointer(const Table* t, const void* p)
|
||||||
|
{
|
||||||
|
// we discard the high 32-bit portion of the pointer on 64-bit platforms as it doesn't carry much entropy anyway
|
||||||
|
unsigned int h = unsigned(uintptr_t(p));
|
||||||
|
|
||||||
|
// MurmurHash3 32-bit finalizer
|
||||||
|
h ^= h >> 16;
|
||||||
|
h *= 0x85ebca6bu;
|
||||||
|
h ^= h >> 13;
|
||||||
|
h *= 0xc2b2ae35u;
|
||||||
|
h ^= h >> 16;
|
||||||
|
|
||||||
|
return hashpow2(t, h);
|
||||||
|
}
|
||||||
|
|
||||||
|
static LuaNode* hashnum(const Table* t, double n)
|
||||||
|
{
|
||||||
|
static_assert(sizeof(double) == sizeof(unsigned int) * 2, "expected a 8-byte double");
|
||||||
|
unsigned int i[2];
|
||||||
|
memcpy(i, &n, sizeof(i));
|
||||||
|
|
||||||
|
// mask out sign bit to make sure -0 and 0 hash to the same value
|
||||||
|
uint32_t h1 = i[0];
|
||||||
|
uint32_t h2 = i[1] & 0x7fffffff;
|
||||||
|
|
||||||
|
// finalizer from MurmurHash64B
|
||||||
|
const uint32_t m = 0x5bd1e995;
|
||||||
|
|
||||||
|
h1 ^= h2 >> 18;
|
||||||
|
h1 *= m;
|
||||||
|
h2 ^= h1 >> 22;
|
||||||
|
h2 *= m;
|
||||||
|
h1 ^= h2 >> 17;
|
||||||
|
h1 *= m;
|
||||||
|
h2 ^= h1 >> 19;
|
||||||
|
h2 *= m;
|
||||||
|
|
||||||
|
// ... truncated to 32-bit output (normally hash is equal to (uint64_t(h1) << 32) | h2, but we only really need the lower 32-bit half)
|
||||||
|
return hashpow2(t, h2);
|
||||||
|
}
|
||||||
|
|
||||||
|
static LuaNode* hashvec(const Table* t, const float* v)
|
||||||
|
{
|
||||||
|
unsigned int i[LUA_VECTOR_SIZE];
|
||||||
|
memcpy(i, v, sizeof(i));
|
||||||
|
|
||||||
|
// convert -0 to 0 to make sure they hash to the same value
|
||||||
|
i[0] = (i[0] == 0x80000000) ? 0 : i[0];
|
||||||
|
i[1] = (i[1] == 0x80000000) ? 0 : i[1];
|
||||||
|
i[2] = (i[2] == 0x80000000) ? 0 : i[2];
|
||||||
|
|
||||||
|
// scramble bits to make sure that integer coordinates have entropy in lower bits
|
||||||
|
i[0] ^= i[0] >> 17;
|
||||||
|
i[1] ^= i[1] >> 17;
|
||||||
|
i[2] ^= i[2] >> 17;
|
||||||
|
|
||||||
|
// Optimized Spatial Hashing for Collision Detection of Deformable Objects
|
||||||
|
unsigned int h = (i[0] * 73856093) ^ (i[1] * 19349663) ^ (i[2] * 83492791);
|
||||||
|
|
||||||
|
#if LUA_VECTOR_SIZE == 4
|
||||||
|
i[3] = (i[3] == 0x80000000) ? 0 : i[3];
|
||||||
|
i[3] ^= i[3] >> 17;
|
||||||
|
h ^= i[3] * 39916801;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return hashpow2(t, h);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** returns the `main' position of an element in a table (that is, the index
|
||||||
|
** of its hash value)
|
||||||
|
*/
|
||||||
|
static LuaNode* mainposition(const Table* t, const TValue* key)
|
||||||
|
{
|
||||||
|
switch (ttype(key))
|
||||||
|
{
|
||||||
|
case LUA_TNUMBER:
|
||||||
|
return hashnum(t, nvalue(key));
|
||||||
|
case LUA_TVECTOR:
|
||||||
|
return hashvec(t, vvalue(key));
|
||||||
|
case LUA_TSTRING:
|
||||||
|
return hashstr(t, tsvalue(key));
|
||||||
|
case LUA_TBOOLEAN:
|
||||||
|
return hashboolean(t, bvalue(key));
|
||||||
|
case LUA_TLIGHTUSERDATA:
|
||||||
|
return hashpointer(t, pvalue(key));
|
||||||
|
default:
|
||||||
|
return hashpointer(t, gcvalue(key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** returns the index for `key' if `key' is an appropriate key to live in
|
||||||
|
** the array part of the table, -1 otherwise.
|
||||||
|
*/
|
||||||
|
static int arrayindex(double key)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
luai_num2int(i, key);
|
||||||
|
|
||||||
|
return luai_numeq(cast_num(i), key) ? i : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** returns the index of a `key' for table traversals. First goes all
|
||||||
|
** elements in the array part, then elements in the hash part. The
|
||||||
|
** beginning of a traversal is signalled by -1.
|
||||||
|
*/
|
||||||
|
static int findindex(lua_State* L, Table* t, StkId key)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
if (ttisnil(key))
|
||||||
|
return -1; // first iteration
|
||||||
|
i = ttisnumber(key) ? arrayindex(nvalue(key)) : -1;
|
||||||
|
if (0 < i && i <= t->sizearray) // is `key' inside array part?
|
||||||
|
return i - 1; // yes; that's the index (corrected to C)
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LuaNode* n = mainposition(t, key);
|
||||||
|
for (;;)
|
||||||
|
{ // check whether `key' is somewhere in the chain
|
||||||
|
// key may be dead already, but it is ok to use it in `next'
|
||||||
|
if (luaO_rawequalKey(gkey(n), key) || (ttype(gkey(n)) == LUA_TDEADKEY && iscollectable(key) && gcvalue(gkey(n)) == gcvalue(key)))
|
||||||
|
{
|
||||||
|
i = cast_int(n - gnode(t, 0)); // key index in hash table
|
||||||
|
// hash elements are numbered after array ones
|
||||||
|
return i + t->sizearray;
|
||||||
|
}
|
||||||
|
if (gnext(n) == 0)
|
||||||
|
break;
|
||||||
|
n += gnext(n);
|
||||||
|
}
|
||||||
|
luaG_runerror(L, "invalid key to 'next'"); // key not found
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int luaH_next(lua_State* L, Table* t, StkId key)
|
||||||
|
{
|
||||||
|
int i = findindex(L, t, key); // find original element
|
||||||
|
for (i++; i < t->sizearray; i++)
|
||||||
|
{ // try first array part
|
||||||
|
if (!ttisnil(&t->array[i]))
|
||||||
|
{ // a non-nil value?
|
||||||
|
setnvalue(key, cast_num(i + 1));
|
||||||
|
setobj2s(L, key + 1, &t->array[i]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (i -= t->sizearray; i < sizenode(t); i++)
|
||||||
|
{ // then hash part
|
||||||
|
if (!ttisnil(gval(gnode(t, i))))
|
||||||
|
{ // a non-nil value?
|
||||||
|
getnodekey(L, key, gnode(t, i));
|
||||||
|
setobj2s(L, key + 1, gval(gnode(t, i)));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0; // no more elements
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** {=============================================================
|
||||||
|
** Rehash
|
||||||
|
** ==============================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define maybesetaboundary(t, boundary) \
|
||||||
|
{ \
|
||||||
|
if (t->aboundary <= 0) \
|
||||||
|
t->aboundary = -int(boundary); \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define getaboundary(t) (t->aboundary < 0 ? -t->aboundary : t->sizearray)
|
||||||
|
|
||||||
|
static int computesizes(int nums[], int* narray)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
int twotoi; // 2^i
|
||||||
|
int a = 0; // number of elements smaller than 2^i
|
||||||
|
int na = 0; // number of elements to go to array part
|
||||||
|
int n = 0; // optimal size for array part
|
||||||
|
for (i = 0, twotoi = 1; twotoi / 2 < *narray; i++, twotoi *= 2)
|
||||||
|
{
|
||||||
|
if (nums[i] > 0)
|
||||||
|
{
|
||||||
|
a += nums[i];
|
||||||
|
if (a > twotoi / 2)
|
||||||
|
{ // more than half elements present?
|
||||||
|
n = twotoi; // optimal size (till now)
|
||||||
|
na = a; // all elements smaller than n will go to array part
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (a == *narray)
|
||||||
|
break; // all elements already counted
|
||||||
|
}
|
||||||
|
*narray = n;
|
||||||
|
LUAU_ASSERT(*narray / 2 <= na && na <= *narray);
|
||||||
|
return na;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int countint(double key, int* nums)
|
||||||
|
{
|
||||||
|
int k = arrayindex(key);
|
||||||
|
if (0 < k && k <= MAXSIZE)
|
||||||
|
{ // is `key' an appropriate array index?
|
||||||
|
nums[ceillog2(k)]++; // count as such
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int numusearray(const Table* t, int* nums)
|
||||||
|
{
|
||||||
|
int lg;
|
||||||
|
int ttlg; // 2^lg
|
||||||
|
int ause = 0; // summation of `nums'
|
||||||
|
int i = 1; // count to traverse all array keys
|
||||||
|
for (lg = 0, ttlg = 1; lg <= MAXBITS; lg++, ttlg *= 2)
|
||||||
|
{ // for each slice
|
||||||
|
int lc = 0; // counter
|
||||||
|
int lim = ttlg;
|
||||||
|
if (lim > t->sizearray)
|
||||||
|
{
|
||||||
|
lim = t->sizearray; // adjust upper limit
|
||||||
|
if (i > lim)
|
||||||
|
break; // no more elements to count
|
||||||
|
}
|
||||||
|
// count elements in range (2^(lg-1), 2^lg]
|
||||||
|
for (; i <= lim; i++)
|
||||||
|
{
|
||||||
|
if (!ttisnil(&t->array[i - 1]))
|
||||||
|
lc++;
|
||||||
|
}
|
||||||
|
nums[lg] += lc;
|
||||||
|
ause += lc;
|
||||||
|
}
|
||||||
|
return ause;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int numusehash(const Table* t, int* nums, int* pnasize)
|
||||||
|
{
|
||||||
|
int totaluse = 0; // total number of elements
|
||||||
|
int ause = 0; // summation of `nums'
|
||||||
|
int i = sizenode(t);
|
||||||
|
while (i--)
|
||||||
|
{
|
||||||
|
LuaNode* n = &t->node[i];
|
||||||
|
if (!ttisnil(gval(n)))
|
||||||
|
{
|
||||||
|
if (ttisnumber(gkey(n)))
|
||||||
|
ause += countint(nvalue(gkey(n)), nums);
|
||||||
|
totaluse++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*pnasize += ause;
|
||||||
|
return totaluse;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void setarrayvector(lua_State* L, Table* t, int size)
|
||||||
|
{
|
||||||
|
if (size > MAXSIZE)
|
||||||
|
luaG_runerror(L, "table overflow");
|
||||||
|
luaM_reallocarray(L, t->array, t->sizearray, size, TValue, t->memcat);
|
||||||
|
TValue* array = t->array;
|
||||||
|
for (int i = t->sizearray; i < size; i++)
|
||||||
|
setnilvalue(&array[i]);
|
||||||
|
t->sizearray = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void setnodevector(lua_State* L, Table* t, int size)
|
||||||
|
{
|
||||||
|
int lsize;
|
||||||
|
if (size == 0)
|
||||||
|
{ // no elements to hash part?
|
||||||
|
t->node = cast_to(LuaNode*, dummynode); // use common `dummynode'
|
||||||
|
lsize = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
lsize = ceillog2(size);
|
||||||
|
if (lsize > MAXBITS)
|
||||||
|
luaG_runerror(L, "table overflow");
|
||||||
|
size = twoto(lsize);
|
||||||
|
t->node = luaM_newarray(L, size, LuaNode, t->memcat);
|
||||||
|
for (i = 0; i < size; i++)
|
||||||
|
{
|
||||||
|
LuaNode* n = gnode(t, i);
|
||||||
|
gnext(n) = 0;
|
||||||
|
setnilvalue(gkey(n));
|
||||||
|
setnilvalue(gval(n));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t->lsizenode = cast_byte(lsize);
|
||||||
|
t->nodemask8 = cast_byte((1 << lsize) - 1);
|
||||||
|
t->lastfree = size; // all positions are free
|
||||||
|
}
|
||||||
|
|
||||||
|
static TValue* newkey(lua_State* L, Table* t, const TValue* key);
|
||||||
|
|
||||||
|
static TValue* arrayornewkey(lua_State* L, Table* t, const TValue* key)
|
||||||
|
{
|
||||||
|
if (ttisnumber(key))
|
||||||
|
{
|
||||||
|
int k;
|
||||||
|
double n = nvalue(key);
|
||||||
|
luai_num2int(k, n);
|
||||||
|
if (luai_numeq(cast_num(k), n) && cast_to(unsigned int, k - 1) < cast_to(unsigned int, t->sizearray))
|
||||||
|
return &t->array[k - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return newkey(L, t, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void resize(lua_State* L, Table* t, int nasize, int nhsize)
|
||||||
|
{
|
||||||
|
if (nasize > MAXSIZE || nhsize > MAXSIZE)
|
||||||
|
luaG_runerror(L, "table overflow");
|
||||||
|
int oldasize = t->sizearray;
|
||||||
|
int oldhsize = t->lsizenode;
|
||||||
|
LuaNode* nold = t->node; // save old hash ...
|
||||||
|
if (nasize > oldasize) // array part must grow?
|
||||||
|
setarrayvector(L, t, nasize);
|
||||||
|
// create new hash part with appropriate size
|
||||||
|
setnodevector(L, t, nhsize);
|
||||||
|
// used for the migration check at the end
|
||||||
|
LuaNode* nnew = t->node;
|
||||||
|
if (nasize < oldasize)
|
||||||
|
{ // array part must shrink?
|
||||||
|
t->sizearray = nasize;
|
||||||
|
// re-insert elements from vanishing slice
|
||||||
|
for (int i = nasize; i < oldasize; i++)
|
||||||
|
{
|
||||||
|
if (!ttisnil(&t->array[i]))
|
||||||
|
{
|
||||||
|
TValue ok;
|
||||||
|
setnvalue(&ok, cast_num(i + 1));
|
||||||
|
setobjt2t(L, newkey(L, t, &ok), &t->array[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// shrink array
|
||||||
|
luaM_reallocarray(L, t->array, oldasize, nasize, TValue, t->memcat);
|
||||||
|
}
|
||||||
|
// used for the migration check at the end
|
||||||
|
TValue* anew = t->array;
|
||||||
|
// re-insert elements from hash part
|
||||||
|
for (int i = twoto(oldhsize) - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
LuaNode* old = nold + i;
|
||||||
|
if (!ttisnil(gval(old)))
|
||||||
|
{
|
||||||
|
TValue ok;
|
||||||
|
getnodekey(L, &ok, old);
|
||||||
|
setobjt2t(L, arrayornewkey(L, t, &ok), gval(old));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure we haven't recursively rehashed during element migration
|
||||||
|
LUAU_ASSERT(nnew == t->node);
|
||||||
|
LUAU_ASSERT(anew == t->array);
|
||||||
|
|
||||||
|
if (nold != dummynode)
|
||||||
|
luaM_freearray(L, nold, twoto(oldhsize), LuaNode, t->memcat); // free old array
|
||||||
|
}
|
||||||
|
|
||||||
|
static int adjustasize(Table* t, int size, const TValue* ek)
|
||||||
|
{
|
||||||
|
bool tbound = t->node != dummynode || size < t->sizearray;
|
||||||
|
int ekindex = ek && ttisnumber(ek) ? arrayindex(nvalue(ek)) : -1;
|
||||||
|
// move the array size up until the boundary is guaranteed to be inside the array part
|
||||||
|
while (size + 1 == ekindex || (tbound && !ttisnil(luaH_getnum(t, size + 1))))
|
||||||
|
size++;
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
void luaH_resizearray(lua_State* L, Table* t, int nasize)
|
||||||
|
{
|
||||||
|
int nsize = (t->node == dummynode) ? 0 : sizenode(t);
|
||||||
|
int asize = adjustasize(t, nasize, NULL);
|
||||||
|
resize(L, t, asize, nsize);
|
||||||
|
}
|
||||||
|
|
||||||
|
void luaH_resizehash(lua_State* L, Table* t, int nhsize)
|
||||||
|
{
|
||||||
|
resize(L, t, t->sizearray, nhsize);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rehash(lua_State* L, Table* t, const TValue* ek)
|
||||||
|
{
|
||||||
|
int nums[MAXBITS + 1]; // nums[i] = number of keys between 2^(i-1) and 2^i
|
||||||
|
for (int i = 0; i <= MAXBITS; i++)
|
||||||
|
nums[i] = 0; // reset counts
|
||||||
|
int nasize = numusearray(t, nums); // count keys in array part
|
||||||
|
int totaluse = nasize; // all those keys are integer keys
|
||||||
|
totaluse += numusehash(t, nums, &nasize); // count keys in hash part
|
||||||
|
|
||||||
|
// count extra key
|
||||||
|
if (ttisnumber(ek))
|
||||||
|
nasize += countint(nvalue(ek), nums);
|
||||||
|
totaluse++;
|
||||||
|
|
||||||
|
// compute new size for array part
|
||||||
|
int na = computesizes(nums, &nasize);
|
||||||
|
int nh = totaluse - na;
|
||||||
|
|
||||||
|
if (FFlag::LuauArrBoundResizeFix)
|
||||||
|
{
|
||||||
|
// enforce the boundary invariant; for performance, only do hash lookups if we must
|
||||||
|
int nadjusted = adjustasize(t, nasize, ek);
|
||||||
|
|
||||||
|
// count how many extra elements belong to array part instead of hash part
|
||||||
|
int aextra = nadjusted - nasize;
|
||||||
|
|
||||||
|
if (aextra != 0)
|
||||||
|
{
|
||||||
|
// we no longer need to store those extra array elements in hash part
|
||||||
|
nh -= aextra;
|
||||||
|
|
||||||
|
// because hash nodes are twice as large as array nodes, the memory we saved for hash parts can be used by array part
|
||||||
|
// this follows the general sparse array part optimization where array is allocated when 50% occupation is reached
|
||||||
|
nasize = nadjusted + aextra;
|
||||||
|
|
||||||
|
// since the size was changed, it's again important to enforce the boundary invariant at the new size
|
||||||
|
nasize = adjustasize(t, nasize, ek);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// enforce the boundary invariant; for performance, only do hash lookups if we must
|
||||||
|
nasize = adjustasize(t, nasize, ek);
|
||||||
|
}
|
||||||
|
|
||||||
|
// resize the table to new computed sizes
|
||||||
|
resize(L, t, nasize, nh);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** }=============================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
Table* luaH_new(lua_State* L, int narray, int nhash)
|
||||||
|
{
|
||||||
|
Table* t = luaM_newgco(L, Table, sizeof(Table), L->activememcat);
|
||||||
|
luaC_init(L, t, LUA_TTABLE);
|
||||||
|
t->metatable = NULL;
|
||||||
|
t->tmcache = cast_byte(~0);
|
||||||
|
t->array = NULL;
|
||||||
|
t->sizearray = 0;
|
||||||
|
t->lastfree = 0;
|
||||||
|
t->lsizenode = 0;
|
||||||
|
t->readonly = 0;
|
||||||
|
t->safeenv = 0;
|
||||||
|
t->nodemask8 = 0;
|
||||||
|
t->node = cast_to(LuaNode*, dummynode);
|
||||||
|
if (narray > 0)
|
||||||
|
setarrayvector(L, t, narray);
|
||||||
|
if (nhash > 0)
|
||||||
|
setnodevector(L, t, nhash);
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
void luaH_free(lua_State* L, Table* t, lua_Page* page)
|
||||||
|
{
|
||||||
|
if (t->node != dummynode)
|
||||||
|
luaM_freearray(L, t->node, sizenode(t), LuaNode, t->memcat);
|
||||||
|
if (t->array)
|
||||||
|
luaM_freearray(L, t->array, t->sizearray, TValue, t->memcat);
|
||||||
|
luaM_freegco(L, t, sizeof(Table), t->memcat, page);
|
||||||
|
}
|
||||||
|
|
||||||
|
static LuaNode* getfreepos(Table* t)
|
||||||
|
{
|
||||||
|
while (t->lastfree > 0)
|
||||||
|
{
|
||||||
|
t->lastfree--;
|
||||||
|
|
||||||
|
LuaNode* n = gnode(t, t->lastfree);
|
||||||
|
if (ttisnil(gkey(n)))
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
return NULL; // could not find a free place
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** inserts a new key into a hash table; first, check whether key's main
|
||||||
|
** position is free. If not, check whether colliding node is in its main
|
||||||
|
** position or not: if it is not, move colliding node to an empty place and
|
||||||
|
** put new key in its main position; otherwise (colliding node is in its main
|
||||||
|
** position), new key goes to an empty position.
|
||||||
|
*/
|
||||||
|
static TValue* newkey(lua_State* L, Table* t, const TValue* key)
|
||||||
|
{
|
||||||
|
// enforce boundary invariant
|
||||||
|
if (ttisnumber(key) && nvalue(key) == t->sizearray + 1)
|
||||||
|
{
|
||||||
|
rehash(L, t, key); // grow table
|
||||||
|
|
||||||
|
// after rehash, numeric keys might be located in the new array part, but won't be found in the node part
|
||||||
|
return arrayornewkey(L, t, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
LuaNode* mp = mainposition(t, key);
|
||||||
|
if (!ttisnil(gval(mp)) || mp == dummynode)
|
||||||
|
{
|
||||||
|
LuaNode* n = getfreepos(t); // get a free place
|
||||||
|
if (n == NULL)
|
||||||
|
{ // cannot find a free place?
|
||||||
|
rehash(L, t, key); // grow table
|
||||||
|
|
||||||
|
// after rehash, numeric keys might be located in the new array part, but won't be found in the node part
|
||||||
|
return arrayornewkey(L, t, key);
|
||||||
|
}
|
||||||
|
LUAU_ASSERT(n != dummynode);
|
||||||
|
TValue mk;
|
||||||
|
getnodekey(L, &mk, mp);
|
||||||
|
LuaNode* othern = mainposition(t, &mk);
|
||||||
|
if (othern != mp)
|
||||||
|
{ // is colliding node out of its main position?
|
||||||
|
// yes; move colliding node into free position
|
||||||
|
while (othern + gnext(othern) != mp)
|
||||||
|
othern += gnext(othern); // find previous
|
||||||
|
gnext(othern) = cast_int(n - othern); // redo the chain with `n' in place of `mp'
|
||||||
|
*n = *mp; // copy colliding node into free pos. (mp->next also goes)
|
||||||
|
if (gnext(mp) != 0)
|
||||||
|
{
|
||||||
|
gnext(n) += cast_int(mp - n); // correct 'next'
|
||||||
|
gnext(mp) = 0; // now 'mp' is free
|
||||||
|
}
|
||||||
|
setnilvalue(gval(mp));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{ // colliding node is in its own main position
|
||||||
|
// new node will go into free position
|
||||||
|
if (gnext(mp) != 0)
|
||||||
|
gnext(n) = cast_int((mp + gnext(mp)) - n); // chain new position
|
||||||
|
else
|
||||||
|
LUAU_ASSERT(gnext(n) == 0);
|
||||||
|
gnext(mp) = cast_int(n - mp);
|
||||||
|
mp = n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setnodekey(L, mp, key);
|
||||||
|
luaC_barriert(L, t, key);
|
||||||
|
LUAU_ASSERT(ttisnil(gval(mp)));
|
||||||
|
return gval(mp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** search function for integers
|
||||||
|
*/
|
||||||
|
const TValue* luaH_getnum(Table* t, int key)
|
||||||
|
{
|
||||||
|
// (1 <= key && key <= t->sizearray)
|
||||||
|
if (cast_to(unsigned int, key - 1) < cast_to(unsigned int, t->sizearray))
|
||||||
|
return &t->array[key - 1];
|
||||||
|
else if (t->node != dummynode)
|
||||||
|
{
|
||||||
|
double nk = cast_num(key);
|
||||||
|
LuaNode* n = hashnum(t, nk);
|
||||||
|
for (;;)
|
||||||
|
{ // check whether `key' is somewhere in the chain
|
||||||
|
if (ttisnumber(gkey(n)) && luai_numeq(nvalue(gkey(n)), nk))
|
||||||
|
return gval(n); // that's it
|
||||||
|
if (gnext(n) == 0)
|
||||||
|
break;
|
||||||
|
n += gnext(n);
|
||||||
|
}
|
||||||
|
return luaO_nilobject;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return luaO_nilobject;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** search function for strings
|
||||||
|
*/
|
||||||
|
const TValue* luaH_getstr(Table* t, TString* key)
|
||||||
|
{
|
||||||
|
LuaNode* n = hashstr(t, key);
|
||||||
|
for (;;)
|
||||||
|
{ // check whether `key' is somewhere in the chain
|
||||||
|
if (ttisstring(gkey(n)) && tsvalue(gkey(n)) == key)
|
||||||
|
return gval(n); // that's it
|
||||||
|
if (gnext(n) == 0)
|
||||||
|
break;
|
||||||
|
n += gnext(n);
|
||||||
|
}
|
||||||
|
return luaO_nilobject;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** main search function
|
||||||
|
*/
|
||||||
|
const TValue* luaH_get(Table* t, const TValue* key)
|
||||||
|
{
|
||||||
|
switch (ttype(key))
|
||||||
|
{
|
||||||
|
case LUA_TNIL:
|
||||||
|
return luaO_nilobject;
|
||||||
|
case LUA_TSTRING:
|
||||||
|
return luaH_getstr(t, tsvalue(key));
|
||||||
|
case LUA_TNUMBER:
|
||||||
|
{
|
||||||
|
int k;
|
||||||
|
double n = nvalue(key);
|
||||||
|
luai_num2int(k, n);
|
||||||
|
if (luai_numeq(cast_num(k), nvalue(key))) // index is int?
|
||||||
|
return luaH_getnum(t, k); // use specialized version
|
||||||
|
// else go through
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
LuaNode* n = mainposition(t, key);
|
||||||
|
for (;;)
|
||||||
|
{ // check whether `key' is somewhere in the chain
|
||||||
|
if (luaO_rawequalKey(gkey(n), key))
|
||||||
|
return gval(n); // that's it
|
||||||
|
if (gnext(n) == 0)
|
||||||
|
break;
|
||||||
|
n += gnext(n);
|
||||||
|
}
|
||||||
|
return luaO_nilobject;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TValue* luaH_set(lua_State* L, Table* t, const TValue* key)
|
||||||
|
{
|
||||||
|
const TValue* p = luaH_get(t, key);
|
||||||
|
invalidateTMcache(t);
|
||||||
|
if (p != luaO_nilobject)
|
||||||
|
return cast_to(TValue*, p);
|
||||||
|
else
|
||||||
|
return luaH_newkey(L, t, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
TValue* luaH_newkey(lua_State* L, Table* t, const TValue* key)
|
||||||
|
{
|
||||||
|
if (ttisnil(key))
|
||||||
|
luaG_runerror(L, "table index is nil");
|
||||||
|
else if (ttisnumber(key) && luai_numisnan(nvalue(key)))
|
||||||
|
luaG_runerror(L, "table index is NaN");
|
||||||
|
else if (ttisvector(key) && luai_vecisnan(vvalue(key)))
|
||||||
|
luaG_runerror(L, "table index contains NaN");
|
||||||
|
return newkey(L, t, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
TValue* luaH_setnum(lua_State* L, Table* t, int key)
|
||||||
|
{
|
||||||
|
// (1 <= key && key <= t->sizearray)
|
||||||
|
if (cast_to(unsigned int, key - 1) < cast_to(unsigned int, t->sizearray))
|
||||||
|
return &t->array[key - 1];
|
||||||
|
// hash fallback
|
||||||
|
const TValue* p = luaH_getnum(t, key);
|
||||||
|
if (p != luaO_nilobject)
|
||||||
|
return cast_to(TValue*, p);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TValue k;
|
||||||
|
setnvalue(&k, cast_num(key));
|
||||||
|
return newkey(L, t, &k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TValue* luaH_setstr(lua_State* L, Table* t, TString* key)
|
||||||
|
{
|
||||||
|
const TValue* p = luaH_getstr(t, key);
|
||||||
|
invalidateTMcache(t);
|
||||||
|
if (p != luaO_nilobject)
|
||||||
|
return cast_to(TValue*, p);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TValue k;
|
||||||
|
setsvalue(L, &k, key);
|
||||||
|
return newkey(L, t, &k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int updateaboundary(Table* t, int boundary)
|
||||||
|
{
|
||||||
|
if (boundary < t->sizearray && ttisnil(&t->array[boundary - 1]))
|
||||||
|
{
|
||||||
|
if (boundary >= 2 && !ttisnil(&t->array[boundary - 2]))
|
||||||
|
{
|
||||||
|
maybesetaboundary(t, boundary - 1);
|
||||||
|
return boundary - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (boundary + 1 < t->sizearray && !ttisnil(&t->array[boundary]) && ttisnil(&t->array[boundary + 1]))
|
||||||
|
{
|
||||||
|
maybesetaboundary(t, boundary + 1);
|
||||||
|
return boundary + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Try to find a boundary in table `t'. A `boundary' is an integer index
|
||||||
|
** such that t[i] is non-nil and t[i+1] is nil (and 0 if t[1] is nil).
|
||||||
|
*/
|
||||||
|
int luaH_getn(Table* t)
|
||||||
|
{
|
||||||
|
int boundary = getaboundary(t);
|
||||||
|
|
||||||
|
if (boundary > 0)
|
||||||
|
{
|
||||||
|
if (!ttisnil(&t->array[t->sizearray - 1]) && t->node == dummynode)
|
||||||
|
return t->sizearray; // fast-path: the end of the array in `t' already refers to a boundary
|
||||||
|
if (boundary < t->sizearray && !ttisnil(&t->array[boundary - 1]) && ttisnil(&t->array[boundary]))
|
||||||
|
return boundary; // fast-path: boundary already refers to a boundary in `t'
|
||||||
|
|
||||||
|
int foundboundary = updateaboundary(t, boundary);
|
||||||
|
if (foundboundary > 0)
|
||||||
|
return foundboundary;
|
||||||
|
}
|
||||||
|
|
||||||
|
int j = t->sizearray;
|
||||||
|
|
||||||
|
if (j > 0 && ttisnil(&t->array[j - 1]))
|
||||||
|
{
|
||||||
|
// "branchless" binary search from Array Layouts for Comparison-Based Searching, Paul Khuong, Pat Morin, 2017.
|
||||||
|
// note that clang is cmov-shy on cmovs around memory operands, so it will compile this to a branchy loop.
|
||||||
|
TValue* base = t->array;
|
||||||
|
int rest = j;
|
||||||
|
while (int half = rest >> 1)
|
||||||
|
{
|
||||||
|
base = ttisnil(&base[half]) ? base : base + half;
|
||||||
|
rest -= half;
|
||||||
|
}
|
||||||
|
int boundary = !ttisnil(base) + int(base - t->array);
|
||||||
|
maybesetaboundary(t, boundary);
|
||||||
|
return boundary;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// validate boundary invariant
|
||||||
|
LUAU_ASSERT(t->node == dummynode || ttisnil(luaH_getnum(t, j + 1)));
|
||||||
|
return j;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Table* luaH_clone(lua_State* L, Table* tt)
|
||||||
|
{
|
||||||
|
Table* t = luaM_newgco(L, Table, sizeof(Table), L->activememcat);
|
||||||
|
luaC_init(L, t, LUA_TTABLE);
|
||||||
|
t->metatable = tt->metatable;
|
||||||
|
t->tmcache = tt->tmcache;
|
||||||
|
t->array = NULL;
|
||||||
|
t->sizearray = 0;
|
||||||
|
t->lsizenode = 0;
|
||||||
|
t->nodemask8 = 0;
|
||||||
|
t->readonly = 0;
|
||||||
|
t->safeenv = 0;
|
||||||
|
t->node = cast_to(LuaNode*, dummynode);
|
||||||
|
t->lastfree = 0;
|
||||||
|
|
||||||
|
if (tt->sizearray)
|
||||||
|
{
|
||||||
|
t->array = luaM_newarray(L, tt->sizearray, TValue, t->memcat);
|
||||||
|
maybesetaboundary(t, getaboundary(tt));
|
||||||
|
t->sizearray = tt->sizearray;
|
||||||
|
|
||||||
|
memcpy(t->array, tt->array, t->sizearray * sizeof(TValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tt->node != dummynode)
|
||||||
|
{
|
||||||
|
int size = 1 << tt->lsizenode;
|
||||||
|
t->node = luaM_newarray(L, size, LuaNode, t->memcat);
|
||||||
|
t->lsizenode = tt->lsizenode;
|
||||||
|
t->nodemask8 = tt->nodemask8;
|
||||||
|
memcpy(t->node, tt->node, size * sizeof(LuaNode));
|
||||||
|
t->lastfree = tt->lastfree;
|
||||||
|
}
|
||||||
|
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
void luaH_clear(Table* tt)
|
||||||
|
{
|
||||||
|
// clear array part
|
||||||
|
for (int i = 0; i < tt->sizearray; ++i)
|
||||||
|
{
|
||||||
|
setnilvalue(&tt->array[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
maybesetaboundary(tt, 0);
|
||||||
|
|
||||||
|
// clear hash part
|
||||||
|
if (tt->node != dummynode)
|
||||||
|
{
|
||||||
|
int size = sizenode(tt);
|
||||||
|
tt->lastfree = size;
|
||||||
|
for (int i = 0; i < size; ++i)
|
||||||
|
{
|
||||||
|
LuaNode* n = gnode(tt, i);
|
||||||
|
setnilvalue(gkey(n));
|
||||||
|
setnilvalue(gval(n));
|
||||||
|
gnext(n) = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// back to empty -> no tag methods present
|
||||||
|
tt->tmcache = cast_byte(~0);
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "lobject.h"
|
||||||
|
|
||||||
|
#define gnode(t, i) (&(t)->node[i])
|
||||||
|
#define gkey(n) (&(n)->key)
|
||||||
|
#define gval(n) (&(n)->val)
|
||||||
|
#define gnext(n) ((n)->key.next)
|
||||||
|
|
||||||
|
#define gval2slot(t, v) int(cast_to(LuaNode*, static_cast<const TValue*>(v)) - t->node)
|
||||||
|
|
||||||
|
// reset cache of absent metamethods, cache is updated in luaT_gettm
|
||||||
|
#define invalidateTMcache(t) t->tmcache = 0
|
||||||
|
|
||||||
|
LUAI_FUNC const TValue* luaH_getnum(Table* t, int key);
|
||||||
|
LUAI_FUNC TValue* luaH_setnum(lua_State* L, Table* t, int key);
|
||||||
|
LUAI_FUNC const TValue* luaH_getstr(Table* t, TString* key);
|
||||||
|
LUAI_FUNC TValue* luaH_setstr(lua_State* L, Table* t, TString* key);
|
||||||
|
LUAI_FUNC const TValue* luaH_get(Table* t, const TValue* key);
|
||||||
|
LUAI_FUNC TValue* luaH_set(lua_State* L, Table* t, const TValue* key);
|
||||||
|
LUAI_FUNC TValue* luaH_newkey(lua_State* L, Table* t, const TValue* key);
|
||||||
|
LUAI_FUNC Table* luaH_new(lua_State* L, int narray, int lnhash);
|
||||||
|
LUAI_FUNC void luaH_resizearray(lua_State* L, Table* t, int nasize);
|
||||||
|
LUAI_FUNC void luaH_resizehash(lua_State* L, Table* t, int nhsize);
|
||||||
|
LUAI_FUNC void luaH_free(lua_State* L, Table* t, struct lua_Page* page);
|
||||||
|
LUAI_FUNC int luaH_next(lua_State* L, Table* t, StkId key);
|
||||||
|
LUAI_FUNC int luaH_getn(Table* t);
|
||||||
|
LUAI_FUNC Table* luaH_clone(lua_State* L, Table* tt);
|
||||||
|
LUAI_FUNC void luaH_clear(Table* tt);
|
||||||
|
|
||||||
|
#define luaH_setslot(L, t, slot, key) (invalidateTMcache(t), (slot == luaO_nilobject ? luaH_newkey(L, t, key) : cast_to(TValue*, slot)))
|
||||||
|
|
||||||
|
extern const LuaNode luaH_dummynode;
|
|
@ -0,0 +1,631 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
||||||
|
#include "lualib.h"
|
||||||
|
|
||||||
|
#include "lapi.h"
|
||||||
|
#include "lstate.h"
|
||||||
|
#include "ltable.h"
|
||||||
|
#include "lstring.h"
|
||||||
|
#include "lgc.h"
|
||||||
|
#include "ldebug.h"
|
||||||
|
#include "lvm.h"
|
||||||
|
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauIntrosort, false)
|
||||||
|
|
||||||
|
static int foreachi(lua_State* L)
|
||||||
|
{
|
||||||
|
luaL_checktype(L, 1, LUA_TTABLE);
|
||||||
|
luaL_checktype(L, 2, LUA_TFUNCTION);
|
||||||
|
int i;
|
||||||
|
int n = lua_objlen(L, 1);
|
||||||
|
for (i = 1; i <= n; i++)
|
||||||
|
{
|
||||||
|
lua_pushvalue(L, 2); // function
|
||||||
|
lua_pushinteger(L, i); // 1st argument
|
||||||
|
lua_rawgeti(L, 1, i); // 2nd argument
|
||||||
|
lua_call(L, 2, 1);
|
||||||
|
if (!lua_isnil(L, -1))
|
||||||
|
return 1;
|
||||||
|
lua_pop(L, 1); // remove nil result
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int foreach (lua_State* L)
|
||||||
|
{
|
||||||
|
luaL_checktype(L, 1, LUA_TTABLE);
|
||||||
|
luaL_checktype(L, 2, LUA_TFUNCTION);
|
||||||
|
lua_pushnil(L); // first key
|
||||||
|
while (lua_next(L, 1))
|
||||||
|
{
|
||||||
|
lua_pushvalue(L, 2); // function
|
||||||
|
lua_pushvalue(L, -3); // key
|
||||||
|
lua_pushvalue(L, -3); // value
|
||||||
|
lua_call(L, 2, 1);
|
||||||
|
if (!lua_isnil(L, -1))
|
||||||
|
return 1;
|
||||||
|
lua_pop(L, 2); // remove value and result
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int maxn(lua_State* L)
|
||||||
|
{
|
||||||
|
double max = 0;
|
||||||
|
luaL_checktype(L, 1, LUA_TTABLE);
|
||||||
|
lua_pushnil(L); // first key
|
||||||
|
while (lua_next(L, 1))
|
||||||
|
{
|
||||||
|
lua_pop(L, 1); // remove value
|
||||||
|
if (lua_type(L, -1) == LUA_TNUMBER)
|
||||||
|
{
|
||||||
|
double v = lua_tonumber(L, -1);
|
||||||
|
if (v > max)
|
||||||
|
max = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lua_pushnumber(L, max);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int getn(lua_State* L)
|
||||||
|
{
|
||||||
|
luaL_checktype(L, 1, LUA_TTABLE);
|
||||||
|
lua_pushinteger(L, lua_objlen(L, 1));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void moveelements(lua_State* L, int srct, int dstt, int f, int e, int t)
|
||||||
|
{
|
||||||
|
Table* src = hvalue(L->base + (srct - 1));
|
||||||
|
Table* dst = hvalue(L->base + (dstt - 1));
|
||||||
|
|
||||||
|
if (dst->readonly)
|
||||||
|
luaG_readonlyerror(L);
|
||||||
|
|
||||||
|
int n = e - f + 1; // number of elements to move
|
||||||
|
|
||||||
|
if (cast_to(unsigned int, f - 1) < cast_to(unsigned int, src->sizearray) &&
|
||||||
|
cast_to(unsigned int, t - 1) < cast_to(unsigned int, dst->sizearray) &&
|
||||||
|
cast_to(unsigned int, f - 1 + n) <= cast_to(unsigned int, src->sizearray) &&
|
||||||
|
cast_to(unsigned int, t - 1 + n) <= cast_to(unsigned int, dst->sizearray))
|
||||||
|
{
|
||||||
|
TValue* srcarray = src->array;
|
||||||
|
TValue* dstarray = dst->array;
|
||||||
|
|
||||||
|
if (t > e || t <= f || (dstt != srct && dst != src))
|
||||||
|
{
|
||||||
|
for (int i = 0; i < n; ++i)
|
||||||
|
{
|
||||||
|
TValue* s = &srcarray[f + i - 1];
|
||||||
|
TValue* d = &dstarray[t + i - 1];
|
||||||
|
setobj2t(L, d, s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (int i = n - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
TValue* s = &srcarray[(f + i) - 1];
|
||||||
|
TValue* d = &dstarray[(t + i) - 1];
|
||||||
|
setobj2t(L, d, s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
luaC_barrierfast(L, dst);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (t > e || t <= f || dst != src)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < n; ++i)
|
||||||
|
{
|
||||||
|
lua_rawgeti(L, srct, f + i);
|
||||||
|
lua_rawseti(L, dstt, t + i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (int i = n - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
lua_rawgeti(L, srct, f + i);
|
||||||
|
lua_rawseti(L, dstt, t + i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int tinsert(lua_State* L)
|
||||||
|
{
|
||||||
|
luaL_checktype(L, 1, LUA_TTABLE);
|
||||||
|
int n = lua_objlen(L, 1);
|
||||||
|
int pos; // where to insert new element
|
||||||
|
switch (lua_gettop(L))
|
||||||
|
{
|
||||||
|
case 2:
|
||||||
|
{ // called with only 2 arguments
|
||||||
|
pos = n + 1; // insert new element at the end
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 3:
|
||||||
|
{
|
||||||
|
pos = luaL_checkinteger(L, 2); // 2nd argument is the position
|
||||||
|
|
||||||
|
// move up elements if necessary
|
||||||
|
if (1 <= pos && pos <= n)
|
||||||
|
moveelements(L, 1, 1, pos, n, pos + 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
luaL_error(L, "wrong number of arguments to 'insert'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lua_rawseti(L, 1, pos); // t[pos] = v
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int tremove(lua_State* L)
|
||||||
|
{
|
||||||
|
luaL_checktype(L, 1, LUA_TTABLE);
|
||||||
|
int n = lua_objlen(L, 1);
|
||||||
|
int pos = luaL_optinteger(L, 2, n);
|
||||||
|
|
||||||
|
if (!(1 <= pos && pos <= n)) // position is outside bounds?
|
||||||
|
return 0; // nothing to remove
|
||||||
|
lua_rawgeti(L, 1, pos); // result = t[pos]
|
||||||
|
|
||||||
|
moveelements(L, 1, 1, pos + 1, n, pos);
|
||||||
|
|
||||||
|
lua_pushnil(L);
|
||||||
|
lua_rawseti(L, 1, n); // t[n] = nil
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Copy elements (1[f], ..., 1[e]) into (tt[t], tt[t+1], ...). Whenever
|
||||||
|
** possible, copy in increasing order, which is better for rehashing.
|
||||||
|
** "possible" means destination after original range, or smaller
|
||||||
|
** than origin, or copying to another table.
|
||||||
|
*/
|
||||||
|
static int tmove(lua_State* L)
|
||||||
|
{
|
||||||
|
luaL_checktype(L, 1, LUA_TTABLE);
|
||||||
|
int f = luaL_checkinteger(L, 2);
|
||||||
|
int e = luaL_checkinteger(L, 3);
|
||||||
|
int t = luaL_checkinteger(L, 4);
|
||||||
|
int tt = !lua_isnoneornil(L, 5) ? 5 : 1; // destination table
|
||||||
|
luaL_checktype(L, tt, LUA_TTABLE);
|
||||||
|
|
||||||
|
if (e >= f)
|
||||||
|
{ // otherwise, nothing to move
|
||||||
|
luaL_argcheck(L, f > 0 || e < INT_MAX + f, 3, "too many elements to move");
|
||||||
|
int n = e - f + 1; // number of elements to move
|
||||||
|
luaL_argcheck(L, t <= INT_MAX - n + 1, 4, "destination wrap around");
|
||||||
|
|
||||||
|
Table* dst = hvalue(L->base + (tt - 1));
|
||||||
|
|
||||||
|
if (dst->readonly) // also checked in moveelements, but this blocks resizes of r/o tables
|
||||||
|
luaG_readonlyerror(L);
|
||||||
|
|
||||||
|
if (t > 0 && (t - 1) <= dst->sizearray && (t - 1 + n) > dst->sizearray)
|
||||||
|
{ // grow the destination table array
|
||||||
|
luaH_resizearray(L, dst, t - 1 + n);
|
||||||
|
}
|
||||||
|
|
||||||
|
moveelements(L, 1, tt, f, e, t);
|
||||||
|
}
|
||||||
|
lua_pushvalue(L, tt); // return destination table
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void addfield(lua_State* L, luaL_Buffer* b, int i)
|
||||||
|
{
|
||||||
|
lua_rawgeti(L, 1, i);
|
||||||
|
if (!lua_isstring(L, -1))
|
||||||
|
luaL_error(L, "invalid value (%s) at index %d in table for 'concat'", luaL_typename(L, -1), i);
|
||||||
|
luaL_addvalue(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int tconcat(lua_State* L)
|
||||||
|
{
|
||||||
|
luaL_Buffer b;
|
||||||
|
size_t lsep;
|
||||||
|
int i, last;
|
||||||
|
const char* sep = luaL_optlstring(L, 2, "", &lsep);
|
||||||
|
luaL_checktype(L, 1, LUA_TTABLE);
|
||||||
|
i = luaL_optinteger(L, 3, 1);
|
||||||
|
last = luaL_opt(L, luaL_checkinteger, 4, lua_objlen(L, 1));
|
||||||
|
luaL_buffinit(L, &b);
|
||||||
|
for (; i < last; i++)
|
||||||
|
{
|
||||||
|
addfield(L, &b, i);
|
||||||
|
luaL_addlstring(&b, sep, lsep, -1);
|
||||||
|
}
|
||||||
|
if (i == last) // add last value (if interval was not empty)
|
||||||
|
addfield(L, &b, i);
|
||||||
|
luaL_pushresult(&b);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int tpack(lua_State* L)
|
||||||
|
{
|
||||||
|
int n = lua_gettop(L); // number of elements to pack
|
||||||
|
lua_createtable(L, n, 1); // create result table
|
||||||
|
|
||||||
|
Table* t = hvalue(L->top - 1);
|
||||||
|
|
||||||
|
for (int i = 0; i < n; ++i)
|
||||||
|
{
|
||||||
|
TValue* e = &t->array[i];
|
||||||
|
setobj2t(L, e, L->base + i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// t.n = number of elements
|
||||||
|
TValue* nv = luaH_setstr(L, t, luaS_newliteral(L, "n"));
|
||||||
|
setnvalue(nv, n);
|
||||||
|
|
||||||
|
return 1; // return table
|
||||||
|
}
|
||||||
|
|
||||||
|
static int tunpack(lua_State* L)
|
||||||
|
{
|
||||||
|
luaL_checktype(L, 1, LUA_TTABLE);
|
||||||
|
Table* t = hvalue(L->base);
|
||||||
|
|
||||||
|
int i = luaL_optinteger(L, 2, 1);
|
||||||
|
int e = luaL_opt(L, luaL_checkinteger, 3, lua_objlen(L, 1));
|
||||||
|
if (i > e)
|
||||||
|
return 0; // empty range
|
||||||
|
unsigned n = (unsigned)e - i; // number of elements minus 1 (avoid overflows)
|
||||||
|
if (n >= (unsigned int)INT_MAX || !lua_checkstack(L, (int)(++n)))
|
||||||
|
luaL_error(L, "too many results to unpack");
|
||||||
|
|
||||||
|
// fast-path: direct array-to-stack copy
|
||||||
|
if (i == 1 && int(n) <= t->sizearray)
|
||||||
|
{
|
||||||
|
for (i = 0; i < int(n); i++)
|
||||||
|
setobj2s(L, L->top + i, &t->array[i]);
|
||||||
|
L->top += n;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// push arg[i..e - 1] (to avoid overflows)
|
||||||
|
for (; i < e; i++)
|
||||||
|
lua_rawgeti(L, 1, i);
|
||||||
|
lua_rawgeti(L, 1, e); // push last element
|
||||||
|
}
|
||||||
|
return (int)n;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef int (*SortPredicate)(lua_State* L, const TValue* l, const TValue* r);
|
||||||
|
|
||||||
|
static int sort_func(lua_State* L, const TValue* l, const TValue* r)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(L->top == L->base + 2); // table, function
|
||||||
|
|
||||||
|
setobj2s(L, L->top, &L->base[1]);
|
||||||
|
setobj2s(L, L->top + 1, l);
|
||||||
|
setobj2s(L, L->top + 2, r);
|
||||||
|
L->top += 3; // safe because of LUA_MINSTACK guarantee
|
||||||
|
luaD_call(L, L->top - 3, 1);
|
||||||
|
L->top -= 1; // maintain stack depth
|
||||||
|
|
||||||
|
return !l_isfalse(L->top);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void sort_swap(lua_State* L, Table* t, int i, int j)
|
||||||
|
{
|
||||||
|
TValue* arr = t->array;
|
||||||
|
int n = t->sizearray;
|
||||||
|
LUAU_ASSERT(unsigned(i) < unsigned(n) && unsigned(j) < unsigned(n)); // contract maintained in sort_less after predicate call
|
||||||
|
|
||||||
|
// no barrier required because both elements are in the array before and after the swap
|
||||||
|
TValue temp;
|
||||||
|
setobj2s(L, &temp, &arr[i]);
|
||||||
|
setobj2t(L, &arr[i], &arr[j]);
|
||||||
|
setobj2t(L, &arr[j], &temp);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int sort_less(lua_State* L, Table* t, int i, int j, SortPredicate pred)
|
||||||
|
{
|
||||||
|
TValue* arr = t->array;
|
||||||
|
int n = t->sizearray;
|
||||||
|
LUAU_ASSERT(unsigned(i) < unsigned(n) && unsigned(j) < unsigned(n)); // contract maintained in sort_less after predicate call
|
||||||
|
|
||||||
|
int res = pred(L, &arr[i], &arr[j]);
|
||||||
|
|
||||||
|
// predicate call may resize the table, which is invalid
|
||||||
|
if (t->sizearray != n)
|
||||||
|
luaL_error(L, "table modified during sorting");
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sort_siftheap(lua_State* L, Table* t, int l, int u, SortPredicate pred, int root)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(l <= u);
|
||||||
|
int count = u - l + 1;
|
||||||
|
|
||||||
|
// process all elements with two children
|
||||||
|
while (root * 2 + 2 < count)
|
||||||
|
{
|
||||||
|
int left = root * 2 + 1, right = root * 2 + 2;
|
||||||
|
int next = root;
|
||||||
|
next = sort_less(L, t, l + next, l + left, pred) ? left : next;
|
||||||
|
next = sort_less(L, t, l + next, l + right, pred) ? right : next;
|
||||||
|
|
||||||
|
if (next == root)
|
||||||
|
break;
|
||||||
|
|
||||||
|
sort_swap(L, t, l + root, l + next);
|
||||||
|
root = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
// process last element if it has just one child
|
||||||
|
int lastleft = root * 2 + 1;
|
||||||
|
if (lastleft == count - 1 && sort_less(L, t, l + root, l + lastleft, pred))
|
||||||
|
sort_swap(L, t, l + root, l + lastleft);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sort_heap(lua_State* L, Table* t, int l, int u, SortPredicate pred)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(l <= u);
|
||||||
|
int count = u - l + 1;
|
||||||
|
|
||||||
|
for (int i = count / 2 - 1; i >= 0; --i)
|
||||||
|
sort_siftheap(L, t, l, u, pred, i);
|
||||||
|
|
||||||
|
for (int i = count - 1; i > 0; --i)
|
||||||
|
{
|
||||||
|
sort_swap(L, t, l, l + i);
|
||||||
|
sort_siftheap(L, t, l, l + i - 1, pred, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sort_rec(lua_State* L, Table* t, int l, int u, int limit, SortPredicate pred)
|
||||||
|
{
|
||||||
|
// sort range [l..u] (inclusive, 0-based)
|
||||||
|
while (l < u)
|
||||||
|
{
|
||||||
|
// if the limit has been reached, quick sort is going over the permitted nlogn complexity, so we fall back to heap sort
|
||||||
|
if (FFlag::LuauIntrosort && limit == 0)
|
||||||
|
return sort_heap(L, t, l, u, pred);
|
||||||
|
|
||||||
|
// sort elements a[l], a[(l+u)/2] and a[u]
|
||||||
|
// note: this simultaneously acts as a small sort and a median selector
|
||||||
|
if (sort_less(L, t, u, l, pred)) // a[u] < a[l]?
|
||||||
|
sort_swap(L, t, u, l); // swap a[l] - a[u]
|
||||||
|
if (u - l == 1)
|
||||||
|
break; // only 2 elements
|
||||||
|
int m = l + ((u - l) >> 1); // midpoint
|
||||||
|
if (sort_less(L, t, m, l, pred)) // a[m]<a[l]?
|
||||||
|
sort_swap(L, t, m, l);
|
||||||
|
else if (sort_less(L, t, u, m, pred)) // a[u]<a[m]?
|
||||||
|
sort_swap(L, t, m, u);
|
||||||
|
if (u - l == 2)
|
||||||
|
break; // only 3 elements
|
||||||
|
|
||||||
|
// here l, m, u are ordered; m will become the new pivot
|
||||||
|
int p = u - 1;
|
||||||
|
sort_swap(L, t, m, u - 1); // pivot is now (and always) at u-1
|
||||||
|
|
||||||
|
// a[l] <= P == a[u-1] <= a[u], only need to sort from l+1 to u-2
|
||||||
|
int i = l;
|
||||||
|
int j = u - 1;
|
||||||
|
for (;;)
|
||||||
|
{ // invariant: a[l..i] <= P <= a[j..u]
|
||||||
|
// repeat ++i until a[i] >= P
|
||||||
|
while (sort_less(L, t, ++i, p, pred))
|
||||||
|
{
|
||||||
|
if (i >= u)
|
||||||
|
luaL_error(L, "invalid order function for sorting");
|
||||||
|
}
|
||||||
|
// repeat --j until a[j] <= P
|
||||||
|
while (sort_less(L, t, p, --j, pred))
|
||||||
|
{
|
||||||
|
if (j <= l)
|
||||||
|
luaL_error(L, "invalid order function for sorting");
|
||||||
|
}
|
||||||
|
if (j < i)
|
||||||
|
break;
|
||||||
|
sort_swap(L, t, i, j);
|
||||||
|
}
|
||||||
|
|
||||||
|
// swap pivot a[p] with a[i], which is the new midpoint
|
||||||
|
sort_swap(L, t, p, i);
|
||||||
|
|
||||||
|
if (FFlag::LuauIntrosort)
|
||||||
|
{
|
||||||
|
// adjust limit to allow 1.5 log2N recursive steps
|
||||||
|
limit = (limit >> 1) + (limit >> 2);
|
||||||
|
|
||||||
|
// a[l..i-1] <= a[i] == P <= a[i+1..u]
|
||||||
|
// sort smaller half recursively; the larger half is sorted in the next loop iteration
|
||||||
|
if (i - l < u - i)
|
||||||
|
{
|
||||||
|
sort_rec(L, t, l, i - 1, limit, pred);
|
||||||
|
l = i + 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sort_rec(L, t, i + 1, u, limit, pred);
|
||||||
|
u = i - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// a[l..i-1] <= a[i] == P <= a[i+1..u]
|
||||||
|
// adjust so that smaller half is in [j..i] and larger one in [l..u]
|
||||||
|
if (i - l < u - i)
|
||||||
|
{
|
||||||
|
j = l;
|
||||||
|
i = i - 1;
|
||||||
|
l = i + 2;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
j = i + 1;
|
||||||
|
i = u;
|
||||||
|
u = j - 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// sort smaller half recursively; the larger half is sorted in the next loop iteration
|
||||||
|
sort_rec(L, t, j, i, limit, pred);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int tsort(lua_State* L)
|
||||||
|
{
|
||||||
|
luaL_checktype(L, 1, LUA_TTABLE);
|
||||||
|
Table* t = hvalue(L->base);
|
||||||
|
int n = luaH_getn(t);
|
||||||
|
if (t->readonly)
|
||||||
|
luaG_readonlyerror(L);
|
||||||
|
|
||||||
|
SortPredicate pred = luaV_lessthan;
|
||||||
|
if (!lua_isnoneornil(L, 2)) // is there a 2nd argument?
|
||||||
|
{
|
||||||
|
luaL_checktype(L, 2, LUA_TFUNCTION);
|
||||||
|
pred = sort_func;
|
||||||
|
}
|
||||||
|
lua_settop(L, 2); // make sure there are two arguments
|
||||||
|
|
||||||
|
if (n > 0)
|
||||||
|
sort_rec(L, t, 0, n - 1, n, pred);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int tcreate(lua_State* L)
|
||||||
|
{
|
||||||
|
int size = luaL_checkinteger(L, 1);
|
||||||
|
if (size < 0)
|
||||||
|
luaL_argerror(L, 1, "size out of range");
|
||||||
|
|
||||||
|
if (!lua_isnoneornil(L, 2))
|
||||||
|
{
|
||||||
|
lua_createtable(L, size, 0);
|
||||||
|
Table* t = hvalue(L->top - 1);
|
||||||
|
|
||||||
|
StkId v = L->base + 1;
|
||||||
|
|
||||||
|
for (int i = 0; i < size; ++i)
|
||||||
|
{
|
||||||
|
TValue* e = &t->array[i];
|
||||||
|
setobj2t(L, e, v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lua_createtable(L, size, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int tfind(lua_State* L)
|
||||||
|
{
|
||||||
|
luaL_checktype(L, 1, LUA_TTABLE);
|
||||||
|
luaL_checkany(L, 2);
|
||||||
|
int init = luaL_optinteger(L, 3, 1);
|
||||||
|
if (init < 1)
|
||||||
|
luaL_argerror(L, 3, "index out of range");
|
||||||
|
|
||||||
|
Table* t = hvalue(L->base);
|
||||||
|
StkId v = L->base + 1;
|
||||||
|
|
||||||
|
for (int i = init;; ++i)
|
||||||
|
{
|
||||||
|
const TValue* e = luaH_getnum(t, i);
|
||||||
|
if (ttisnil(e))
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (equalobj(L, v, e))
|
||||||
|
{
|
||||||
|
lua_pushinteger(L, i);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lua_pushnil(L);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int tclear(lua_State* L)
|
||||||
|
{
|
||||||
|
luaL_checktype(L, 1, LUA_TTABLE);
|
||||||
|
|
||||||
|
Table* tt = hvalue(L->base);
|
||||||
|
if (tt->readonly)
|
||||||
|
luaG_readonlyerror(L);
|
||||||
|
|
||||||
|
luaH_clear(tt);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int tfreeze(lua_State* L)
|
||||||
|
{
|
||||||
|
luaL_checktype(L, 1, LUA_TTABLE);
|
||||||
|
luaL_argcheck(L, !lua_getreadonly(L, 1), 1, "table is already frozen");
|
||||||
|
luaL_argcheck(L, !luaL_getmetafield(L, 1, "__metatable"), 1, "table has a protected metatable");
|
||||||
|
|
||||||
|
lua_setreadonly(L, 1, true);
|
||||||
|
|
||||||
|
lua_pushvalue(L, 1);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int tisfrozen(lua_State* L)
|
||||||
|
{
|
||||||
|
luaL_checktype(L, 1, LUA_TTABLE);
|
||||||
|
|
||||||
|
lua_pushboolean(L, lua_getreadonly(L, 1));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int tclone(lua_State* L)
|
||||||
|
{
|
||||||
|
luaL_checktype(L, 1, LUA_TTABLE);
|
||||||
|
luaL_argcheck(L, !luaL_getmetafield(L, 1, "__metatable"), 1, "table has a protected metatable");
|
||||||
|
|
||||||
|
Table* tt = luaH_clone(L, hvalue(L->base));
|
||||||
|
|
||||||
|
TValue v;
|
||||||
|
sethvalue(L, &v, tt);
|
||||||
|
luaA_pushobject(L, &v);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const luaL_Reg tab_funcs[] = {
|
||||||
|
{"concat", tconcat},
|
||||||
|
{"foreach", foreach},
|
||||||
|
{"foreachi", foreachi},
|
||||||
|
{"getn", getn},
|
||||||
|
{"maxn", maxn},
|
||||||
|
{"insert", tinsert},
|
||||||
|
{"remove", tremove},
|
||||||
|
{"sort", tsort},
|
||||||
|
{"pack", tpack},
|
||||||
|
{"unpack", tunpack},
|
||||||
|
{"move", tmove},
|
||||||
|
{"create", tcreate},
|
||||||
|
{"find", tfind},
|
||||||
|
{"clear", tclear},
|
||||||
|
{"freeze", tfreeze},
|
||||||
|
{"isfrozen", tisfrozen},
|
||||||
|
{"clone", tclone},
|
||||||
|
{NULL, NULL},
|
||||||
|
};
|
||||||
|
|
||||||
|
int luaopen_table(lua_State* L)
|
||||||
|
{
|
||||||
|
luaL_register(L, LUA_TABLIBNAME, tab_funcs);
|
||||||
|
|
||||||
|
// Lua 5.1 compat
|
||||||
|
lua_pushcfunction(L, tunpack, "unpack");
|
||||||
|
lua_setglobal(L, "unpack");
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
|
@ -0,0 +1,144 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
||||||
|
#include "ltm.h"
|
||||||
|
|
||||||
|
#include "lstate.h"
|
||||||
|
#include "lstring.h"
|
||||||
|
#include "ludata.h"
|
||||||
|
#include "ltable.h"
|
||||||
|
#include "lgc.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
const char* const luaT_typenames[] = {
|
||||||
|
// ORDER TYPE
|
||||||
|
"nil",
|
||||||
|
"boolean",
|
||||||
|
|
||||||
|
|
||||||
|
"userdata",
|
||||||
|
"number",
|
||||||
|
"vector",
|
||||||
|
|
||||||
|
"string",
|
||||||
|
|
||||||
|
|
||||||
|
"table",
|
||||||
|
"function",
|
||||||
|
"userdata",
|
||||||
|
"thread",
|
||||||
|
};
|
||||||
|
|
||||||
|
const char* const luaT_eventname[] = {
|
||||||
|
// ORDER TM
|
||||||
|
|
||||||
|
"__index",
|
||||||
|
"__newindex",
|
||||||
|
"__mode",
|
||||||
|
"__namecall",
|
||||||
|
"__call",
|
||||||
|
"__iter",
|
||||||
|
"__len",
|
||||||
|
|
||||||
|
"__eq",
|
||||||
|
|
||||||
|
|
||||||
|
"__add",
|
||||||
|
"__sub",
|
||||||
|
"__mul",
|
||||||
|
"__div",
|
||||||
|
"__mod",
|
||||||
|
"__pow",
|
||||||
|
"__unm",
|
||||||
|
|
||||||
|
|
||||||
|
"__lt",
|
||||||
|
"__le",
|
||||||
|
"__concat",
|
||||||
|
"__type",
|
||||||
|
"__metatable",
|
||||||
|
};
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
static_assert(sizeof(luaT_typenames) / sizeof(luaT_typenames[0]) == LUA_T_COUNT, "luaT_typenames size mismatch");
|
||||||
|
static_assert(sizeof(luaT_eventname) / sizeof(luaT_eventname[0]) == TM_N, "luaT_eventname size mismatch");
|
||||||
|
static_assert(TM_EQ < 8, "fasttm optimization stores a bitfield with metamethods in a byte");
|
||||||
|
|
||||||
|
void luaT_init(lua_State* L)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < LUA_T_COUNT; i++)
|
||||||
|
{
|
||||||
|
L->global->ttname[i] = luaS_new(L, luaT_typenames[i]);
|
||||||
|
luaS_fix(L->global->ttname[i]); // never collect these names
|
||||||
|
}
|
||||||
|
for (i = 0; i < TM_N; i++)
|
||||||
|
{
|
||||||
|
L->global->tmname[i] = luaS_new(L, luaT_eventname[i]);
|
||||||
|
luaS_fix(L->global->tmname[i]); // never collect these names
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** function to be used with macro "fasttm": optimized for absence of
|
||||||
|
** tag methods.
|
||||||
|
*/
|
||||||
|
const TValue* luaT_gettm(Table* events, TMS event, TString* ename)
|
||||||
|
{
|
||||||
|
const TValue* tm = luaH_getstr(events, ename);
|
||||||
|
LUAU_ASSERT(event <= TM_EQ);
|
||||||
|
if (ttisnil(tm))
|
||||||
|
{ // no tag method?
|
||||||
|
events->tmcache |= cast_byte(1u << event); // cache this fact
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return tm;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TValue* luaT_gettmbyobj(lua_State* L, const TValue* o, TMS event)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
NB: Tag-methods were replaced by meta-methods in Lua 5.0, but the
|
||||||
|
old names are still around (this function, for example).
|
||||||
|
*/
|
||||||
|
Table* mt;
|
||||||
|
switch (ttype(o))
|
||||||
|
{
|
||||||
|
case LUA_TTABLE:
|
||||||
|
mt = hvalue(o)->metatable;
|
||||||
|
break;
|
||||||
|
case LUA_TUSERDATA:
|
||||||
|
mt = uvalue(o)->metatable;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
mt = L->global->mt[ttype(o)];
|
||||||
|
}
|
||||||
|
return (mt ? luaH_getstr(mt, L->global->tmname[event]) : luaO_nilobject);
|
||||||
|
}
|
||||||
|
|
||||||
|
const TString* luaT_objtypenamestr(lua_State* L, const TValue* o)
|
||||||
|
{
|
||||||
|
if (ttisuserdata(o) && uvalue(o)->tag != UTAG_PROXY && uvalue(o)->metatable)
|
||||||
|
{
|
||||||
|
const TValue* type = luaH_getstr(uvalue(o)->metatable, L->global->tmname[TM_TYPE]);
|
||||||
|
|
||||||
|
if (ttisstring(type))
|
||||||
|
return tsvalue(type);
|
||||||
|
}
|
||||||
|
else if (Table* mt = L->global->mt[ttype(o)])
|
||||||
|
{
|
||||||
|
const TValue* type = luaH_getstr(mt, L->global->tmname[TM_TYPE]);
|
||||||
|
|
||||||
|
if (ttisstring(type))
|
||||||
|
return tsvalue(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
return L->global->ttname[ttype(o)];
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* luaT_objtypename(lua_State* L, const TValue* o)
|
||||||
|
{
|
||||||
|
return getstr(luaT_objtypenamestr(L, o));
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "lobject.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* WARNING: if you change the order of this enumeration,
|
||||||
|
* grep "ORDER TM"
|
||||||
|
*/
|
||||||
|
// clang-format off
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
|
||||||
|
TM_INDEX,
|
||||||
|
TM_NEWINDEX,
|
||||||
|
TM_MODE,
|
||||||
|
TM_NAMECALL,
|
||||||
|
TM_CALL,
|
||||||
|
TM_ITER,
|
||||||
|
TM_LEN,
|
||||||
|
|
||||||
|
TM_EQ, // last tag method with `fast' access
|
||||||
|
|
||||||
|
|
||||||
|
TM_ADD,
|
||||||
|
TM_SUB,
|
||||||
|
TM_MUL,
|
||||||
|
TM_DIV,
|
||||||
|
TM_MOD,
|
||||||
|
TM_POW,
|
||||||
|
TM_UNM,
|
||||||
|
|
||||||
|
|
||||||
|
TM_LT,
|
||||||
|
TM_LE,
|
||||||
|
TM_CONCAT,
|
||||||
|
TM_TYPE,
|
||||||
|
TM_METATABLE,
|
||||||
|
|
||||||
|
TM_N // number of elements in the enum
|
||||||
|
} TMS;
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
#define gfasttm(g, et, e) ((et) == NULL ? NULL : ((et)->tmcache & (1u << (e))) ? NULL : luaT_gettm(et, e, (g)->tmname[e]))
|
||||||
|
|
||||||
|
#define fasttm(l, et, e) gfasttm(l->global, et, e)
|
||||||
|
#define fastnotm(et, e) ((et) == NULL || ((et)->tmcache & (1u << (e))))
|
||||||
|
|
||||||
|
LUAI_DATA const char* const luaT_typenames[];
|
||||||
|
LUAI_DATA const char* const luaT_eventname[];
|
||||||
|
|
||||||
|
LUAI_FUNC const TValue* luaT_gettm(Table* events, TMS event, TString* ename);
|
||||||
|
LUAI_FUNC const TValue* luaT_gettmbyobj(lua_State* L, const TValue* o, TMS event);
|
||||||
|
|
||||||
|
LUAI_FUNC const TString* luaT_objtypenamestr(lua_State* L, const TValue* o);
|
||||||
|
LUAI_FUNC const char* luaT_objtypename(lua_State* L, const TValue* o);
|
||||||
|
|
||||||
|
LUAI_FUNC void luaT_init(lua_State* L);
|
|
@ -0,0 +1,44 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
||||||
|
#include "ludata.h"
|
||||||
|
|
||||||
|
#include "lgc.h"
|
||||||
|
#include "lmem.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
Udata* luaU_newudata(lua_State* L, size_t s, int tag)
|
||||||
|
{
|
||||||
|
if (s > INT_MAX - sizeof(Udata))
|
||||||
|
luaM_toobig(L);
|
||||||
|
Udata* u = luaM_newgco(L, Udata, sizeudata(s), L->activememcat);
|
||||||
|
luaC_init(L, u, LUA_TUSERDATA);
|
||||||
|
u->len = int(s);
|
||||||
|
u->metatable = NULL;
|
||||||
|
LUAU_ASSERT(tag >= 0 && tag <= 255);
|
||||||
|
u->tag = uint8_t(tag);
|
||||||
|
return u;
|
||||||
|
}
|
||||||
|
|
||||||
|
void luaU_freeudata(lua_State* L, Udata* u, lua_Page* page)
|
||||||
|
{
|
||||||
|
if (u->tag < LUA_UTAG_LIMIT)
|
||||||
|
{
|
||||||
|
void (*dtor)(lua_State*, void*) = nullptr;
|
||||||
|
dtor = L->global->udatagc[u->tag];
|
||||||
|
// TODO: access to L here is highly unsafe since this is called during internal GC traversal
|
||||||
|
// certain operations such as lua_getthreaddata are okay, but by and large this risks crashes on improper use
|
||||||
|
if (dtor)
|
||||||
|
dtor(L, u->data);
|
||||||
|
}
|
||||||
|
else if (u->tag == UTAG_IDTOR)
|
||||||
|
{
|
||||||
|
void (*dtor)(void*) = nullptr;
|
||||||
|
memcpy(&dtor, &u->data + u->len - sizeof(dtor), sizeof(dtor));
|
||||||
|
if (dtor)
|
||||||
|
dtor(u->data);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
luaM_freegco(L, u, sizeudata(u->len), u->memcat, page);
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "lobject.h"
|
||||||
|
|
||||||
|
// special tag value is used for user data with inline dtors
|
||||||
|
#define UTAG_IDTOR LUA_UTAG_LIMIT
|
||||||
|
|
||||||
|
// special tag value is used for newproxy-created user data (all other user data objects are host-exposed)
|
||||||
|
#define UTAG_PROXY (LUA_UTAG_LIMIT + 1)
|
||||||
|
|
||||||
|
#define sizeudata(len) (offsetof(Udata, data) + len)
|
||||||
|
|
||||||
|
LUAI_FUNC Udata* luaU_newudata(lua_State* L, size_t s, int tag);
|
||||||
|
LUAI_FUNC void luaU_freeudata(lua_State* L, Udata* u, struct lua_Page* page);
|
|
@ -0,0 +1,294 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
||||||
|
#include "lualib.h"
|
||||||
|
|
||||||
|
#include "lcommon.h"
|
||||||
|
|
||||||
|
#define MAXUNICODE 0x10FFFF
|
||||||
|
|
||||||
|
#define iscont(p) ((*(p)&0xC0) == 0x80)
|
||||||
|
|
||||||
|
// from strlib
|
||||||
|
// translate a relative string position: negative means back from end
|
||||||
|
static int u_posrelat(int pos, size_t len)
|
||||||
|
{
|
||||||
|
if (pos >= 0)
|
||||||
|
return pos;
|
||||||
|
else if (0u - (size_t)pos > len)
|
||||||
|
return 0;
|
||||||
|
else
|
||||||
|
return (int)len + pos + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Decode one UTF-8 sequence, returning NULL if byte sequence is invalid.
|
||||||
|
*/
|
||||||
|
static const char* utf8_decode(const char* o, int* val)
|
||||||
|
{
|
||||||
|
static const unsigned int limits[] = {0xFF, 0x7F, 0x7FF, 0xFFFF};
|
||||||
|
const unsigned char* s = (const unsigned char*)o;
|
||||||
|
unsigned int c = s[0];
|
||||||
|
unsigned int res = 0; // final result
|
||||||
|
if (c < 0x80) // ascii?
|
||||||
|
res = c;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int count = 0; // to count number of continuation bytes
|
||||||
|
while (c & 0x40)
|
||||||
|
{ // still have continuation bytes?
|
||||||
|
int cc = s[++count]; // read next byte
|
||||||
|
if ((cc & 0xC0) != 0x80) // not a continuation byte?
|
||||||
|
return NULL; // invalid byte sequence
|
||||||
|
res = (res << 6) | (cc & 0x3F); // add lower 6 bits from cont. byte
|
||||||
|
c <<= 1; // to test next bit
|
||||||
|
}
|
||||||
|
res |= ((c & 0x7F) << (count * 5)); // add first byte
|
||||||
|
if (count > 3 || res > MAXUNICODE || res <= limits[count])
|
||||||
|
return NULL; // invalid byte sequence
|
||||||
|
s += count; // skip continuation bytes read
|
||||||
|
}
|
||||||
|
if (val)
|
||||||
|
*val = res;
|
||||||
|
return (const char*)s + 1; // +1 to include first byte
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** utf8len(s [, i [, j]]) --> number of characters that start in the
|
||||||
|
** range [i,j], or nil + current position if 's' is not well formed in
|
||||||
|
** that interval
|
||||||
|
*/
|
||||||
|
static int utflen(lua_State* L)
|
||||||
|
{
|
||||||
|
int n = 0;
|
||||||
|
size_t len;
|
||||||
|
const char* s = luaL_checklstring(L, 1, &len);
|
||||||
|
int posi = u_posrelat(luaL_optinteger(L, 2, 1), len);
|
||||||
|
int posj = u_posrelat(luaL_optinteger(L, 3, -1), len);
|
||||||
|
luaL_argcheck(L, 1 <= posi && --posi <= (int)len, 2, "initial position out of string");
|
||||||
|
luaL_argcheck(L, --posj < (int)len, 3, "final position out of string");
|
||||||
|
while (posi <= posj)
|
||||||
|
{
|
||||||
|
const char* s1 = utf8_decode(s + posi, NULL);
|
||||||
|
if (s1 == NULL)
|
||||||
|
{ // conversion error?
|
||||||
|
lua_pushnil(L); // return nil ...
|
||||||
|
lua_pushinteger(L, posi + 1); // ... and current position
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
posi = (int)(s1 - s);
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
lua_pushinteger(L, n);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** codepoint(s, [i, [j]]) -> returns codepoints for all characters
|
||||||
|
** that start in the range [i,j]
|
||||||
|
*/
|
||||||
|
static int codepoint(lua_State* L)
|
||||||
|
{
|
||||||
|
size_t len;
|
||||||
|
const char* s = luaL_checklstring(L, 1, &len);
|
||||||
|
int posi = u_posrelat(luaL_optinteger(L, 2, 1), len);
|
||||||
|
int pose = u_posrelat(luaL_optinteger(L, 3, posi), len);
|
||||||
|
int n;
|
||||||
|
const char* se;
|
||||||
|
luaL_argcheck(L, posi >= 1, 2, "out of range");
|
||||||
|
luaL_argcheck(L, pose <= (int)len, 3, "out of range");
|
||||||
|
if (posi > pose)
|
||||||
|
return 0; // empty interval; return no values
|
||||||
|
if (pose - posi >= INT_MAX) // (int -> int) overflow?
|
||||||
|
luaL_error(L, "string slice too long");
|
||||||
|
n = (int)(pose - posi) + 1;
|
||||||
|
luaL_checkstack(L, n, "string slice too long");
|
||||||
|
n = 0;
|
||||||
|
se = s + pose;
|
||||||
|
for (s += posi - 1; s < se;)
|
||||||
|
{
|
||||||
|
int code;
|
||||||
|
s = utf8_decode(s, &code);
|
||||||
|
if (s == NULL)
|
||||||
|
luaL_error(L, "invalid UTF-8 code");
|
||||||
|
lua_pushinteger(L, code);
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
// from Lua 5.3 lobject.h
|
||||||
|
#define UTF8BUFFSZ 8
|
||||||
|
|
||||||
|
// from Lua 5.3 lobject.c, copied verbatim + static
|
||||||
|
static int luaO_utf8esc(char* buff, unsigned long x)
|
||||||
|
{
|
||||||
|
int n = 1; // number of bytes put in buffer (backwards)
|
||||||
|
LUAU_ASSERT(x <= 0x10FFFF);
|
||||||
|
if (x < 0x80) // ascii?
|
||||||
|
buff[UTF8BUFFSZ - 1] = cast_to(char, x);
|
||||||
|
else
|
||||||
|
{ // need continuation bytes
|
||||||
|
unsigned int mfb = 0x3f; // maximum that fits in first byte
|
||||||
|
do
|
||||||
|
{ // add continuation bytes
|
||||||
|
buff[UTF8BUFFSZ - (n++)] = cast_to(char, 0x80 | (x & 0x3f));
|
||||||
|
x >>= 6; // remove added bits
|
||||||
|
mfb >>= 1; // now there is one less bit available in first byte
|
||||||
|
} while (x > mfb); // still needs continuation byte?
|
||||||
|
buff[UTF8BUFFSZ - n] = cast_to(char, (~mfb << 1) | x); // add first byte
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
// lighter replacement for pushutfchar; doesn't push any string onto the stack
|
||||||
|
static int buffutfchar(lua_State* L, int arg, char* buff, const char** charstr)
|
||||||
|
{
|
||||||
|
int code = luaL_checkinteger(L, arg);
|
||||||
|
luaL_argcheck(L, 0 <= code && code <= MAXUNICODE, arg, "value out of range");
|
||||||
|
int l = luaO_utf8esc(buff, cast_to(long, code));
|
||||||
|
*charstr = buff + UTF8BUFFSZ - l;
|
||||||
|
return l;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** utfchar(n1, n2, ...) -> char(n1)..char(n2)...
|
||||||
|
**
|
||||||
|
** This version avoids the need to make more invasive upgrades elsewhere (like
|
||||||
|
** implementing the %U escape in lua_pushfstring) and avoids pushing string
|
||||||
|
** objects for each codepoint in the multi-argument case. -Jovanni
|
||||||
|
*/
|
||||||
|
static int utfchar(lua_State* L)
|
||||||
|
{
|
||||||
|
char buff[UTF8BUFFSZ];
|
||||||
|
const char* charstr;
|
||||||
|
|
||||||
|
int n = lua_gettop(L); // number of arguments
|
||||||
|
if (n == 1)
|
||||||
|
{ // optimize common case of single char
|
||||||
|
int l = buffutfchar(L, 1, buff, &charstr);
|
||||||
|
lua_pushlstring(L, charstr, l);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
luaL_Buffer b;
|
||||||
|
luaL_buffinit(L, &b);
|
||||||
|
for (int i = 1; i <= n; i++)
|
||||||
|
{
|
||||||
|
int l = buffutfchar(L, i, buff, &charstr);
|
||||||
|
luaL_addlstring(&b, charstr, l, -1);
|
||||||
|
}
|
||||||
|
luaL_pushresult(&b);
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** offset(s, n, [i]) -> index where n-th character counting from
|
||||||
|
** position 'i' starts; 0 means character at 'i'.
|
||||||
|
*/
|
||||||
|
static int byteoffset(lua_State* L)
|
||||||
|
{
|
||||||
|
size_t len;
|
||||||
|
const char* s = luaL_checklstring(L, 1, &len);
|
||||||
|
int n = luaL_checkinteger(L, 2);
|
||||||
|
int posi = (n >= 0) ? 1 : (int)len + 1;
|
||||||
|
posi = u_posrelat(luaL_optinteger(L, 3, posi), len);
|
||||||
|
luaL_argcheck(L, 1 <= posi && --posi <= (int)len, 3, "position out of range");
|
||||||
|
if (n == 0)
|
||||||
|
{
|
||||||
|
// find beginning of current byte sequence
|
||||||
|
while (posi > 0 && iscont(s + posi))
|
||||||
|
posi--;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (iscont(s + posi))
|
||||||
|
luaL_error(L, "initial position is a continuation byte");
|
||||||
|
if (n < 0)
|
||||||
|
{
|
||||||
|
while (n < 0 && posi > 0)
|
||||||
|
{ // move back
|
||||||
|
do
|
||||||
|
{ // find beginning of previous character
|
||||||
|
posi--;
|
||||||
|
} while (posi > 0 && iscont(s + posi));
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
n--; // do not move for 1st character
|
||||||
|
while (n > 0 && posi < (int)len)
|
||||||
|
{
|
||||||
|
do
|
||||||
|
{ // find beginning of next character
|
||||||
|
posi++;
|
||||||
|
} while (iscont(s + posi)); // (cannot pass final '\0')
|
||||||
|
n--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (n == 0) // did it find given character?
|
||||||
|
lua_pushinteger(L, posi + 1);
|
||||||
|
else // no such character
|
||||||
|
lua_pushnil(L);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int iter_aux(lua_State* L)
|
||||||
|
{
|
||||||
|
size_t len;
|
||||||
|
const char* s = luaL_checklstring(L, 1, &len);
|
||||||
|
int n = lua_tointeger(L, 2) - 1;
|
||||||
|
if (n < 0) // first iteration?
|
||||||
|
n = 0; // start from here
|
||||||
|
else if (n < (int)len)
|
||||||
|
{
|
||||||
|
n++; // skip current byte
|
||||||
|
while (iscont(s + n))
|
||||||
|
n++; // and its continuations
|
||||||
|
}
|
||||||
|
if (n >= (int)len)
|
||||||
|
return 0; // no more codepoints
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int code;
|
||||||
|
const char* next = utf8_decode(s + n, &code);
|
||||||
|
if (next == NULL || iscont(next))
|
||||||
|
luaL_error(L, "invalid UTF-8 code");
|
||||||
|
lua_pushinteger(L, n + 1);
|
||||||
|
lua_pushinteger(L, code);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int iter_codes(lua_State* L)
|
||||||
|
{
|
||||||
|
luaL_checkstring(L, 1);
|
||||||
|
lua_pushcfunction(L, iter_aux, NULL);
|
||||||
|
lua_pushvalue(L, 1);
|
||||||
|
lua_pushinteger(L, 0);
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// pattern to match a single UTF-8 character
|
||||||
|
#define UTF8PATT "[\0-\x7F\xC2-\xF4][\x80-\xBF]*"
|
||||||
|
|
||||||
|
static const luaL_Reg funcs[] = {
|
||||||
|
{"offset", byteoffset},
|
||||||
|
{"codepoint", codepoint},
|
||||||
|
{"char", utfchar},
|
||||||
|
{"len", utflen},
|
||||||
|
{"codes", iter_codes},
|
||||||
|
{NULL, NULL},
|
||||||
|
};
|
||||||
|
|
||||||
|
int luaopen_utf8(lua_State* L)
|
||||||
|
{
|
||||||
|
luaL_register(L, LUA_UTF8LIBNAME, funcs);
|
||||||
|
|
||||||
|
lua_pushlstring(L, UTF8PATT, sizeof(UTF8PATT) / sizeof(char) - 1);
|
||||||
|
lua_setfield(L, -2, "charpattern");
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "lobject.h"
|
||||||
|
#include "ltm.h"
|
||||||
|
|
||||||
|
#define tostring(L, o) ((ttype(o) == LUA_TSTRING) || (luaV_tostring(L, o)))
|
||||||
|
|
||||||
|
#define tonumber(o, n) (ttype(o) == LUA_TNUMBER || (((o) = luaV_tonumber(o, n)) != NULL))
|
||||||
|
|
||||||
|
#define equalobj(L, o1, o2) (ttype(o1) == ttype(o2) && luaV_equalval(L, o1, o2))
|
||||||
|
|
||||||
|
LUAI_FUNC int luaV_strcmp(const TString* ls, const TString* rs);
|
||||||
|
LUAI_FUNC int luaV_lessthan(lua_State* L, const TValue* l, const TValue* r);
|
||||||
|
LUAI_FUNC int luaV_lessequal(lua_State* L, const TValue* l, const TValue* r);
|
||||||
|
LUAI_FUNC int luaV_equalval(lua_State* L, const TValue* t1, const TValue* t2);
|
||||||
|
LUAI_FUNC void luaV_doarith(lua_State* L, StkId ra, const TValue* rb, const TValue* rc, TMS op);
|
||||||
|
LUAI_FUNC void luaV_dolen(lua_State* L, StkId ra, const TValue* rb);
|
||||||
|
LUAI_FUNC const TValue* luaV_tonumber(const TValue* obj, TValue* n);
|
||||||
|
LUAI_FUNC const float* luaV_tovector(const TValue* obj);
|
||||||
|
LUAI_FUNC int luaV_tostring(lua_State* L, StkId obj);
|
||||||
|
LUAI_FUNC void luaV_gettable(lua_State* L, const TValue* t, TValue* key, StkId val);
|
||||||
|
LUAI_FUNC void luaV_settable(lua_State* L, const TValue* t, TValue* key, StkId val);
|
||||||
|
LUAI_FUNC void luaV_concat(lua_State* L, int total, int last);
|
||||||
|
LUAI_FUNC void luaV_getimport(lua_State* L, Table* env, TValue* k, uint32_t id, bool propagatenil);
|
||||||
|
LUAI_FUNC void luaV_prepareFORN(lua_State* L, StkId plimit, StkId pstep, StkId pinit);
|
||||||
|
LUAI_FUNC void luaV_callTM(lua_State* L, int nparams, int res);
|
||||||
|
LUAI_FUNC void luaV_tryfuncTM(lua_State* L, StkId func);
|
||||||
|
|
||||||
|
LUAI_FUNC void luau_execute(lua_State* L);
|
||||||
|
LUAI_FUNC int luau_precall(lua_State* L, struct lua_TValue* func, int nresults);
|
||||||
|
LUAI_FUNC void luau_poscall(lua_State* L, StkId first);
|
||||||
|
LUAI_FUNC void luau_callhook(lua_State* L, lua_Hook hook, void* userdata);
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,364 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
||||||
|
#include "lvm.h"
|
||||||
|
|
||||||
|
#include "lstate.h"
|
||||||
|
#include "ltable.h"
|
||||||
|
#include "lfunc.h"
|
||||||
|
#include "lstring.h"
|
||||||
|
#include "lgc.h"
|
||||||
|
#include "lmem.h"
|
||||||
|
#include "lbytecode.h"
|
||||||
|
#include "lapi.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
// TODO: RAII deallocation doesn't work for longjmp builds if a memory error happens
|
||||||
|
template<typename T>
|
||||||
|
struct TempBuffer
|
||||||
|
{
|
||||||
|
lua_State* L;
|
||||||
|
T* data;
|
||||||
|
size_t count;
|
||||||
|
|
||||||
|
TempBuffer(lua_State* L, size_t count)
|
||||||
|
: L(L)
|
||||||
|
, data(luaM_newarray(L, count, T, 0))
|
||||||
|
, count(count)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
~TempBuffer()
|
||||||
|
{
|
||||||
|
luaM_freearray(L, data, count, T, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
T& operator[](size_t index)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(index < count);
|
||||||
|
return data[index];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void luaV_getimport(lua_State* L, Table* env, TValue* k, uint32_t id, bool propagatenil)
|
||||||
|
{
|
||||||
|
int count = id >> 30;
|
||||||
|
int id0 = count > 0 ? int(id >> 20) & 1023 : -1;
|
||||||
|
int id1 = count > 1 ? int(id >> 10) & 1023 : -1;
|
||||||
|
int id2 = count > 2 ? int(id) & 1023 : -1;
|
||||||
|
|
||||||
|
// allocate a stack slot so that we can do table lookups
|
||||||
|
luaD_checkstack(L, 1);
|
||||||
|
setnilvalue(L->top);
|
||||||
|
L->top++;
|
||||||
|
|
||||||
|
// global lookup into L->top-1
|
||||||
|
TValue g;
|
||||||
|
sethvalue(L, &g, env);
|
||||||
|
luaV_gettable(L, &g, &k[id0], L->top - 1);
|
||||||
|
|
||||||
|
// table lookup for id1
|
||||||
|
if (id1 >= 0 && (!propagatenil || !ttisnil(L->top - 1)))
|
||||||
|
luaV_gettable(L, L->top - 1, &k[id1], L->top - 1);
|
||||||
|
|
||||||
|
// table lookup for id2
|
||||||
|
if (id2 >= 0 && (!propagatenil || !ttisnil(L->top - 1)))
|
||||||
|
luaV_gettable(L, L->top - 1, &k[id2], L->top - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
static T read(const char* data, size_t size, size_t& offset)
|
||||||
|
{
|
||||||
|
T result;
|
||||||
|
memcpy(&result, data + offset, sizeof(T));
|
||||||
|
offset += sizeof(T);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned int readVarInt(const char* data, size_t size, size_t& offset)
|
||||||
|
{
|
||||||
|
unsigned int result = 0;
|
||||||
|
unsigned int shift = 0;
|
||||||
|
|
||||||
|
uint8_t byte;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
byte = read<uint8_t>(data, size, offset);
|
||||||
|
result |= (byte & 127) << shift;
|
||||||
|
shift += 7;
|
||||||
|
} while (byte & 128);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static TString* readString(TempBuffer<TString*>& strings, const char* data, size_t size, size_t& offset)
|
||||||
|
{
|
||||||
|
unsigned int id = readVarInt(data, size, offset);
|
||||||
|
|
||||||
|
return id == 0 ? NULL : strings[id - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
static void resolveImportSafe(lua_State* L, Table* env, TValue* k, uint32_t id)
|
||||||
|
{
|
||||||
|
struct ResolveImport
|
||||||
|
{
|
||||||
|
TValue* k;
|
||||||
|
uint32_t id;
|
||||||
|
|
||||||
|
static void run(lua_State* L, void* ud)
|
||||||
|
{
|
||||||
|
ResolveImport* self = static_cast<ResolveImport*>(ud);
|
||||||
|
|
||||||
|
// note: we call getimport with nil propagation which means that accesses to table chains like A.B.C will resolve in nil
|
||||||
|
// this is technically not necessary but it reduces the number of exceptions when loading scripts that rely on getfenv/setfenv for global
|
||||||
|
// injection
|
||||||
|
luaV_getimport(L, L->gt, self->k, self->id, /* propagatenil= */ true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ResolveImport ri = {k, id};
|
||||||
|
if (L->gt->safeenv)
|
||||||
|
{
|
||||||
|
// luaD_pcall will make sure that if any C/Lua calls during import resolution fail, the thread state is restored back
|
||||||
|
int oldTop = lua_gettop(L);
|
||||||
|
int status = luaD_pcall(L, &ResolveImport::run, &ri, savestack(L, L->top), 0);
|
||||||
|
LUAU_ASSERT(oldTop + 1 == lua_gettop(L)); // if an error occurred, luaD_pcall saves it on stack
|
||||||
|
|
||||||
|
if (status != 0)
|
||||||
|
{
|
||||||
|
// replace error object with nil
|
||||||
|
setnilvalue(L->top - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setnilvalue(L->top);
|
||||||
|
L->top++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size, int env)
|
||||||
|
{
|
||||||
|
size_t offset = 0;
|
||||||
|
|
||||||
|
uint8_t version = read<uint8_t>(data, size, offset);
|
||||||
|
|
||||||
|
// 0 means the rest of the bytecode is the error message
|
||||||
|
if (version == 0)
|
||||||
|
{
|
||||||
|
char chunkbuf[LUA_IDSIZE];
|
||||||
|
const char* chunkid = luaO_chunkid(chunkbuf, sizeof(chunkbuf), chunkname, strlen(chunkname));
|
||||||
|
lua_pushfstring(L, "%s%.*s", chunkid, int(size - offset), data + offset);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version < LBC_VERSION_MIN || version > LBC_VERSION_MAX)
|
||||||
|
{
|
||||||
|
char chunkbuf[LUA_IDSIZE];
|
||||||
|
const char* chunkid = luaO_chunkid(chunkbuf, sizeof(chunkbuf), chunkname, strlen(chunkname));
|
||||||
|
lua_pushfstring(L, "%s: bytecode version mismatch (expected [%d..%d], got %d)", chunkid, LBC_VERSION_MIN, LBC_VERSION_MAX, version);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// pause GC for the duration of deserialization - some objects we're creating aren't rooted
|
||||||
|
// TODO: if an allocation error happens mid-load, we do not unpause GC!
|
||||||
|
size_t GCthreshold = L->global->GCthreshold;
|
||||||
|
L->global->GCthreshold = SIZE_MAX;
|
||||||
|
|
||||||
|
// env is 0 for current environment and a stack index otherwise
|
||||||
|
Table* envt = (env == 0) ? L->gt : hvalue(luaA_toobject(L, env));
|
||||||
|
|
||||||
|
TString* source = luaS_new(L, chunkname);
|
||||||
|
|
||||||
|
// string table
|
||||||
|
unsigned int stringCount = readVarInt(data, size, offset);
|
||||||
|
TempBuffer<TString*> strings(L, stringCount);
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < stringCount; ++i)
|
||||||
|
{
|
||||||
|
unsigned int length = readVarInt(data, size, offset);
|
||||||
|
|
||||||
|
strings[i] = luaS_newlstr(L, data + offset, length);
|
||||||
|
offset += length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// proto table
|
||||||
|
unsigned int protoCount = readVarInt(data, size, offset);
|
||||||
|
TempBuffer<Proto*> protos(L, protoCount);
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < protoCount; ++i)
|
||||||
|
{
|
||||||
|
Proto* p = luaF_newproto(L);
|
||||||
|
p->source = source;
|
||||||
|
p->bytecodeid = int(i);
|
||||||
|
|
||||||
|
p->maxstacksize = read<uint8_t>(data, size, offset);
|
||||||
|
p->numparams = read<uint8_t>(data, size, offset);
|
||||||
|
p->nups = read<uint8_t>(data, size, offset);
|
||||||
|
p->is_vararg = read<uint8_t>(data, size, offset);
|
||||||
|
|
||||||
|
p->sizecode = readVarInt(data, size, offset);
|
||||||
|
p->code = luaM_newarray(L, p->sizecode, Instruction, p->memcat);
|
||||||
|
for (int j = 0; j < p->sizecode; ++j)
|
||||||
|
p->code[j] = read<uint32_t>(data, size, offset);
|
||||||
|
|
||||||
|
p->sizek = readVarInt(data, size, offset);
|
||||||
|
p->k = luaM_newarray(L, p->sizek, TValue, p->memcat);
|
||||||
|
|
||||||
|
#ifdef HARDMEMTESTS
|
||||||
|
// this is redundant during normal runs, but resolveImportSafe can trigger GC checks under HARDMEMTESTS
|
||||||
|
// because p->k isn't fully formed at this point, we pre-fill it with nil to make subsequent setup safe
|
||||||
|
for (int j = 0; j < p->sizek; ++j)
|
||||||
|
{
|
||||||
|
setnilvalue(&p->k[j]);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
for (int j = 0; j < p->sizek; ++j)
|
||||||
|
{
|
||||||
|
switch (read<uint8_t>(data, size, offset))
|
||||||
|
{
|
||||||
|
case LBC_CONSTANT_NIL:
|
||||||
|
setnilvalue(&p->k[j]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LBC_CONSTANT_BOOLEAN:
|
||||||
|
{
|
||||||
|
uint8_t v = read<uint8_t>(data, size, offset);
|
||||||
|
setbvalue(&p->k[j], v);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case LBC_CONSTANT_NUMBER:
|
||||||
|
{
|
||||||
|
double v = read<double>(data, size, offset);
|
||||||
|
setnvalue(&p->k[j], v);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case LBC_CONSTANT_STRING:
|
||||||
|
{
|
||||||
|
TString* v = readString(strings, data, size, offset);
|
||||||
|
setsvalue(L, &p->k[j], v);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case LBC_CONSTANT_IMPORT:
|
||||||
|
{
|
||||||
|
uint32_t iid = read<uint32_t>(data, size, offset);
|
||||||
|
resolveImportSafe(L, envt, p->k, iid);
|
||||||
|
setobj(L, &p->k[j], L->top - 1);
|
||||||
|
L->top--;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case LBC_CONSTANT_TABLE:
|
||||||
|
{
|
||||||
|
int keys = readVarInt(data, size, offset);
|
||||||
|
Table* h = luaH_new(L, 0, keys);
|
||||||
|
for (int i = 0; i < keys; ++i)
|
||||||
|
{
|
||||||
|
int key = readVarInt(data, size, offset);
|
||||||
|
TValue* val = luaH_set(L, h, &p->k[key]);
|
||||||
|
setnvalue(val, 0.0);
|
||||||
|
}
|
||||||
|
sethvalue(L, &p->k[j], h);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case LBC_CONSTANT_CLOSURE:
|
||||||
|
{
|
||||||
|
uint32_t fid = readVarInt(data, size, offset);
|
||||||
|
Closure* cl = luaF_newLclosure(L, protos[fid]->nups, envt, protos[fid]);
|
||||||
|
cl->preload = (cl->nupvalues > 0);
|
||||||
|
setclvalue(L, &p->k[j], cl);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
LUAU_ASSERT(!"Unexpected constant kind");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p->sizep = readVarInt(data, size, offset);
|
||||||
|
p->p = luaM_newarray(L, p->sizep, Proto*, p->memcat);
|
||||||
|
for (int j = 0; j < p->sizep; ++j)
|
||||||
|
{
|
||||||
|
uint32_t fid = readVarInt(data, size, offset);
|
||||||
|
p->p[j] = protos[fid];
|
||||||
|
}
|
||||||
|
|
||||||
|
p->linedefined = readVarInt(data, size, offset);
|
||||||
|
p->debugname = readString(strings, data, size, offset);
|
||||||
|
|
||||||
|
uint8_t lineinfo = read<uint8_t>(data, size, offset);
|
||||||
|
|
||||||
|
if (lineinfo)
|
||||||
|
{
|
||||||
|
p->linegaplog2 = read<uint8_t>(data, size, offset);
|
||||||
|
|
||||||
|
int intervals = ((p->sizecode - 1) >> p->linegaplog2) + 1;
|
||||||
|
int absoffset = (p->sizecode + 3) & ~3;
|
||||||
|
|
||||||
|
p->sizelineinfo = absoffset + intervals * sizeof(int);
|
||||||
|
p->lineinfo = luaM_newarray(L, p->sizelineinfo, uint8_t, p->memcat);
|
||||||
|
p->abslineinfo = (int*)(p->lineinfo + absoffset);
|
||||||
|
|
||||||
|
uint8_t lastoffset = 0;
|
||||||
|
for (int j = 0; j < p->sizecode; ++j)
|
||||||
|
{
|
||||||
|
lastoffset += read<uint8_t>(data, size, offset);
|
||||||
|
p->lineinfo[j] = lastoffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
int lastline = 0;
|
||||||
|
for (int j = 0; j < intervals; ++j)
|
||||||
|
{
|
||||||
|
lastline += read<int32_t>(data, size, offset);
|
||||||
|
p->abslineinfo[j] = lastline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t debuginfo = read<uint8_t>(data, size, offset);
|
||||||
|
|
||||||
|
if (debuginfo)
|
||||||
|
{
|
||||||
|
p->sizelocvars = readVarInt(data, size, offset);
|
||||||
|
p->locvars = luaM_newarray(L, p->sizelocvars, LocVar, p->memcat);
|
||||||
|
|
||||||
|
for (int j = 0; j < p->sizelocvars; ++j)
|
||||||
|
{
|
||||||
|
p->locvars[j].varname = readString(strings, data, size, offset);
|
||||||
|
p->locvars[j].startpc = readVarInt(data, size, offset);
|
||||||
|
p->locvars[j].endpc = readVarInt(data, size, offset);
|
||||||
|
p->locvars[j].reg = read<uint8_t>(data, size, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
p->sizeupvalues = readVarInt(data, size, offset);
|
||||||
|
p->upvalues = luaM_newarray(L, p->sizeupvalues, TString*, p->memcat);
|
||||||
|
|
||||||
|
for (int j = 0; j < p->sizeupvalues; ++j)
|
||||||
|
{
|
||||||
|
p->upvalues[j] = readString(strings, data, size, offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protos[i] = p;
|
||||||
|
}
|
||||||
|
|
||||||
|
// "main" proto is pushed to Lua stack
|
||||||
|
uint32_t mainid = readVarInt(data, size, offset);
|
||||||
|
Proto* main = protos[mainid];
|
||||||
|
|
||||||
|
luaC_threadbarrier(L);
|
||||||
|
|
||||||
|
Closure* cl = luaF_newLclosure(L, 0, envt, main);
|
||||||
|
setclvalue(L, L->top, cl);
|
||||||
|
incr_top(L);
|
||||||
|
|
||||||
|
L->global->GCthreshold = GCthreshold;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -0,0 +1,599 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
||||||
|
#include "lvm.h"
|
||||||
|
|
||||||
|
#include "lstate.h"
|
||||||
|
#include "lstring.h"
|
||||||
|
#include "ltable.h"
|
||||||
|
#include "lgc.h"
|
||||||
|
#include "ldo.h"
|
||||||
|
#include "lnumutils.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
// limit for table tag-method chains (to avoid loops)
|
||||||
|
#define MAXTAGLOOP 100
|
||||||
|
|
||||||
|
const TValue* luaV_tonumber(const TValue* obj, TValue* n)
|
||||||
|
{
|
||||||
|
double num;
|
||||||
|
if (ttisnumber(obj))
|
||||||
|
return obj;
|
||||||
|
if (ttisstring(obj) && luaO_str2d(svalue(obj), &num))
|
||||||
|
{
|
||||||
|
setnvalue(n, num);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int luaV_tostring(lua_State* L, StkId obj)
|
||||||
|
{
|
||||||
|
if (!ttisnumber(obj))
|
||||||
|
return 0;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
char s[LUAI_MAXNUM2STR];
|
||||||
|
double n = nvalue(obj);
|
||||||
|
char* e = luai_num2str(s, n);
|
||||||
|
LUAU_ASSERT(e < s + sizeof(s));
|
||||||
|
setsvalue(L, obj, luaS_newlstr(L, s, e - s));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const float* luaV_tovector(const TValue* obj)
|
||||||
|
{
|
||||||
|
if (ttisvector(obj))
|
||||||
|
return obj->value.v;
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static StkId callTMres(lua_State* L, StkId res, const TValue* f, const TValue* p1, const TValue* p2)
|
||||||
|
{
|
||||||
|
ptrdiff_t result = savestack(L, res);
|
||||||
|
// using stack room beyond top is technically safe here, but for very complicated reasons:
|
||||||
|
// * The stack guarantees EXTRA_STACK room beyond stack_last (see luaD_reallocstack) will be allocated
|
||||||
|
// * we cannot move luaD_checkstack above because the arguments are *sometimes* pointers to the lua
|
||||||
|
// stack and checkstack may invalidate those pointers
|
||||||
|
// * we cannot use savestack/restorestack because the arguments are sometimes on the C++ stack
|
||||||
|
// * during stack reallocation all of the allocated stack is copied (even beyond stack_last) so these
|
||||||
|
// values will be preserved even if they go past stack_last
|
||||||
|
LUAU_ASSERT((L->top + 3) < (L->stack + L->stacksize));
|
||||||
|
setobj2s(L, L->top, f); // push function
|
||||||
|
setobj2s(L, L->top + 1, p1); // 1st argument
|
||||||
|
setobj2s(L, L->top + 2, p2); // 2nd argument
|
||||||
|
luaD_checkstack(L, 3);
|
||||||
|
L->top += 3;
|
||||||
|
luaD_call(L, L->top - 3, 1);
|
||||||
|
res = restorestack(L, result);
|
||||||
|
L->top--;
|
||||||
|
setobj2s(L, res, L->top);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void callTM(lua_State* L, const TValue* f, const TValue* p1, const TValue* p2, const TValue* p3)
|
||||||
|
{
|
||||||
|
// using stack room beyond top is technically safe here, but for very complicated reasons:
|
||||||
|
// * The stack guarantees EXTRA_STACK room beyond stack_last (see luaD_reallocstack) will be allocated
|
||||||
|
// * we cannot move luaD_checkstack above because the arguments are *sometimes* pointers to the lua
|
||||||
|
// stack and checkstack may invalidate those pointers
|
||||||
|
// * we cannot use savestack/restorestack because the arguments are sometimes on the C++ stack
|
||||||
|
// * during stack reallocation all of the allocated stack is copied (even beyond stack_last) so these
|
||||||
|
// values will be preserved even if they go past stack_last
|
||||||
|
LUAU_ASSERT((L->top + 4) < (L->stack + L->stacksize));
|
||||||
|
setobj2s(L, L->top, f); // push function
|
||||||
|
setobj2s(L, L->top + 1, p1); // 1st argument
|
||||||
|
setobj2s(L, L->top + 2, p2); // 2nd argument
|
||||||
|
setobj2s(L, L->top + 3, p3); // 3th argument
|
||||||
|
luaD_checkstack(L, 4);
|
||||||
|
L->top += 4;
|
||||||
|
luaD_call(L, L->top - 4, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void luaV_gettable(lua_State* L, const TValue* t, TValue* key, StkId val)
|
||||||
|
{
|
||||||
|
int loop;
|
||||||
|
for (loop = 0; loop < MAXTAGLOOP; loop++)
|
||||||
|
{
|
||||||
|
const TValue* tm;
|
||||||
|
if (ttistable(t))
|
||||||
|
{ // `t' is a table?
|
||||||
|
Table* h = hvalue(t);
|
||||||
|
|
||||||
|
const TValue* res = luaH_get(h, key); // do a primitive get
|
||||||
|
|
||||||
|
if (res != luaO_nilobject)
|
||||||
|
L->cachedslot = gval2slot(h, res); // remember slot to accelerate future lookups
|
||||||
|
|
||||||
|
if (!ttisnil(res) // result is no nil?
|
||||||
|
|| (tm = fasttm(L, h->metatable, TM_INDEX)) == NULL)
|
||||||
|
{ // or no TM?
|
||||||
|
setobj2s(L, val, res);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// t isn't a table, so see if it has an INDEX meta-method to look up the key with
|
||||||
|
}
|
||||||
|
else if (ttisnil(tm = luaT_gettmbyobj(L, t, TM_INDEX)))
|
||||||
|
luaG_indexerror(L, t, key);
|
||||||
|
if (ttisfunction(tm))
|
||||||
|
{
|
||||||
|
callTMres(L, val, tm, t, key);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
t = tm; // else repeat with `tm'
|
||||||
|
}
|
||||||
|
luaG_runerror(L, "'__index' chain too long; possible loop");
|
||||||
|
}
|
||||||
|
|
||||||
|
void luaV_settable(lua_State* L, const TValue* t, TValue* key, StkId val)
|
||||||
|
{
|
||||||
|
int loop;
|
||||||
|
TValue temp;
|
||||||
|
for (loop = 0; loop < MAXTAGLOOP; loop++)
|
||||||
|
{
|
||||||
|
const TValue* tm;
|
||||||
|
if (ttistable(t))
|
||||||
|
{ // `t' is a table?
|
||||||
|
Table* h = hvalue(t);
|
||||||
|
|
||||||
|
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 if (ttisnil(tm = luaT_gettmbyobj(L, t, TM_NEWINDEX)))
|
||||||
|
luaG_indexerror(L, t, key);
|
||||||
|
|
||||||
|
if (ttisfunction(tm))
|
||||||
|
{
|
||||||
|
callTM(L, tm, t, key, val);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// else repeat with `tm'
|
||||||
|
setobj(L, &temp, tm); // avoid pointing inside table (may rehash)
|
||||||
|
t = &temp;
|
||||||
|
}
|
||||||
|
luaG_runerror(L, "'__newindex' chain too long; possible loop");
|
||||||
|
}
|
||||||
|
|
||||||
|
static int call_binTM(lua_State* L, const TValue* p1, const TValue* p2, StkId res, TMS event)
|
||||||
|
{
|
||||||
|
const TValue* tm = luaT_gettmbyobj(L, p1, event); // try first operand
|
||||||
|
if (ttisnil(tm))
|
||||||
|
tm = luaT_gettmbyobj(L, p2, event); // try second operand
|
||||||
|
if (ttisnil(tm))
|
||||||
|
return 0;
|
||||||
|
callTMres(L, res, tm, p1, p2);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const TValue* get_compTM(lua_State* L, Table* mt1, Table* mt2, TMS event)
|
||||||
|
{
|
||||||
|
const TValue* tm1 = fasttm(L, mt1, event);
|
||||||
|
const TValue* tm2;
|
||||||
|
if (tm1 == NULL)
|
||||||
|
return NULL; // no metamethod
|
||||||
|
if (mt1 == mt2)
|
||||||
|
return tm1; // same metatables => same metamethods
|
||||||
|
tm2 = fasttm(L, mt2, event);
|
||||||
|
if (tm2 == NULL)
|
||||||
|
return NULL; // no metamethod
|
||||||
|
if (luaO_rawequalObj(tm1, tm2)) // same metamethods?
|
||||||
|
return tm1;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int call_orderTM(lua_State* L, const TValue* p1, const TValue* p2, TMS event, bool error = false)
|
||||||
|
{
|
||||||
|
const TValue* tm1 = luaT_gettmbyobj(L, p1, event);
|
||||||
|
const TValue* tm2;
|
||||||
|
if (ttisnil(tm1))
|
||||||
|
{
|
||||||
|
if (error)
|
||||||
|
luaG_ordererror(L, p1, p2, event);
|
||||||
|
return -1; // no metamethod?
|
||||||
|
}
|
||||||
|
tm2 = luaT_gettmbyobj(L, p2, event);
|
||||||
|
if (!luaO_rawequalObj(tm1, tm2)) // different metamethods?
|
||||||
|
{
|
||||||
|
if (error)
|
||||||
|
luaG_ordererror(L, p1, p2, event);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
callTMres(L, L->top, tm1, p1, p2);
|
||||||
|
return !l_isfalse(L->top);
|
||||||
|
}
|
||||||
|
|
||||||
|
int luaV_strcmp(const TString* ls, const TString* rs)
|
||||||
|
{
|
||||||
|
if (ls == rs)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
const char* l = getstr(ls);
|
||||||
|
const char* r = getstr(rs);
|
||||||
|
|
||||||
|
// always safe to read one character because even empty strings are nul terminated
|
||||||
|
if (*l != *r)
|
||||||
|
return uint8_t(*l) - uint8_t(*r);
|
||||||
|
|
||||||
|
size_t ll = ls->len;
|
||||||
|
size_t lr = rs->len;
|
||||||
|
size_t lmin = ll < lr ? ll : lr;
|
||||||
|
|
||||||
|
int res = memcmp(l, r, lmin);
|
||||||
|
if (res != 0)
|
||||||
|
return res;
|
||||||
|
|
||||||
|
return ll == lr ? 0 : ll < lr ? -1 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int luaV_lessthan(lua_State* L, const TValue* l, const TValue* r)
|
||||||
|
{
|
||||||
|
if (LUAU_UNLIKELY(ttype(l) != ttype(r)))
|
||||||
|
luaG_ordererror(L, l, r, TM_LT);
|
||||||
|
else if (LUAU_LIKELY(ttisnumber(l)))
|
||||||
|
return luai_numlt(nvalue(l), nvalue(r));
|
||||||
|
else if (ttisstring(l))
|
||||||
|
return luaV_strcmp(tsvalue(l), tsvalue(r)) < 0;
|
||||||
|
else
|
||||||
|
return call_orderTM(L, l, r, TM_LT, /* error= */ true);
|
||||||
|
}
|
||||||
|
|
||||||
|
int luaV_lessequal(lua_State* L, const TValue* l, const TValue* r)
|
||||||
|
{
|
||||||
|
int res;
|
||||||
|
if (ttype(l) != ttype(r))
|
||||||
|
luaG_ordererror(L, l, r, TM_LE);
|
||||||
|
else if (ttisnumber(l))
|
||||||
|
return luai_numle(nvalue(l), nvalue(r));
|
||||||
|
else if (ttisstring(l))
|
||||||
|
return luaV_strcmp(tsvalue(l), tsvalue(r)) <= 0;
|
||||||
|
else if ((res = call_orderTM(L, l, r, TM_LE)) != -1) // first try `le'
|
||||||
|
return res;
|
||||||
|
else if ((res = call_orderTM(L, r, l, TM_LT)) == -1) // error if not `lt'
|
||||||
|
luaG_ordererror(L, l, r, TM_LE);
|
||||||
|
return !res;
|
||||||
|
}
|
||||||
|
|
||||||
|
int luaV_equalval(lua_State* L, const TValue* t1, const TValue* t2)
|
||||||
|
{
|
||||||
|
const TValue* tm;
|
||||||
|
LUAU_ASSERT(ttype(t1) == ttype(t2));
|
||||||
|
switch (ttype(t1))
|
||||||
|
{
|
||||||
|
case LUA_TNIL:
|
||||||
|
return 1;
|
||||||
|
case LUA_TNUMBER:
|
||||||
|
return luai_numeq(nvalue(t1), nvalue(t2));
|
||||||
|
case LUA_TVECTOR:
|
||||||
|
return luai_veceq(vvalue(t1), vvalue(t2));
|
||||||
|
case LUA_TBOOLEAN:
|
||||||
|
return bvalue(t1) == bvalue(t2); // true must be 1 !!
|
||||||
|
case LUA_TLIGHTUSERDATA:
|
||||||
|
return pvalue(t1) == pvalue(t2);
|
||||||
|
case LUA_TUSERDATA:
|
||||||
|
{
|
||||||
|
tm = get_compTM(L, uvalue(t1)->metatable, uvalue(t2)->metatable, TM_EQ);
|
||||||
|
if (!tm)
|
||||||
|
return uvalue(t1) == uvalue(t2);
|
||||||
|
break; // will try TM
|
||||||
|
}
|
||||||
|
case LUA_TTABLE:
|
||||||
|
{
|
||||||
|
tm = get_compTM(L, hvalue(t1)->metatable, hvalue(t2)->metatable, TM_EQ);
|
||||||
|
if (!tm)
|
||||||
|
return hvalue(t1) == hvalue(t2);
|
||||||
|
break; // will try TM
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return gcvalue(t1) == gcvalue(t2);
|
||||||
|
}
|
||||||
|
callTMres(L, L->top, tm, t1, t2); // call TM
|
||||||
|
return !l_isfalse(L->top);
|
||||||
|
}
|
||||||
|
|
||||||
|
void luaV_concat(lua_State* L, int total, int last)
|
||||||
|
{
|
||||||
|
do
|
||||||
|
{
|
||||||
|
StkId top = L->base + last + 1;
|
||||||
|
int n = 2; // number of elements handled in this pass (at least 2)
|
||||||
|
if (!(ttisstring(top - 2) || ttisnumber(top - 2)) || !tostring(L, top - 1))
|
||||||
|
{
|
||||||
|
if (!call_binTM(L, top - 2, top - 1, top - 2, TM_CONCAT))
|
||||||
|
luaG_concaterror(L, top - 2, top - 1);
|
||||||
|
}
|
||||||
|
else if (tsvalue(top - 1)->len == 0) // second op is empty?
|
||||||
|
(void)tostring(L, top - 2); // result is first op (as string)
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// at least two string values; get as many as possible
|
||||||
|
size_t tl = tsvalue(top - 1)->len;
|
||||||
|
char* buffer;
|
||||||
|
int i;
|
||||||
|
// collect total length
|
||||||
|
for (n = 1; n < total && tostring(L, top - n - 1); n++)
|
||||||
|
{
|
||||||
|
size_t l = tsvalue(top - n - 1)->len;
|
||||||
|
if (l > MAXSSIZE - tl)
|
||||||
|
luaG_runerror(L, "string length overflow");
|
||||||
|
tl += l;
|
||||||
|
}
|
||||||
|
|
||||||
|
char buf[LUA_BUFFERSIZE];
|
||||||
|
TString* ts = nullptr;
|
||||||
|
|
||||||
|
if (tl < LUA_BUFFERSIZE)
|
||||||
|
{
|
||||||
|
buffer = buf;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ts = luaS_bufstart(L, tl);
|
||||||
|
buffer = ts->data;
|
||||||
|
}
|
||||||
|
|
||||||
|
tl = 0;
|
||||||
|
for (i = n; i > 0; i--)
|
||||||
|
{ // concat all strings
|
||||||
|
size_t l = tsvalue(top - i)->len;
|
||||||
|
memcpy(buffer + tl, svalue(top - i), l);
|
||||||
|
tl += l;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tl < LUA_BUFFERSIZE)
|
||||||
|
{
|
||||||
|
setsvalue(L, top - n, luaS_newlstr(L, buffer, tl));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setsvalue(L, top - n, luaS_buffinish(L, ts));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
total -= n - 1; // got `n' strings to create 1 new
|
||||||
|
last -= n - 1;
|
||||||
|
} while (total > 1); // repeat until only 1 result left
|
||||||
|
}
|
||||||
|
|
||||||
|
void luaV_doarith(lua_State* L, StkId ra, const TValue* rb, const TValue* rc, TMS op)
|
||||||
|
{
|
||||||
|
TValue tempb, tempc;
|
||||||
|
const TValue *b, *c;
|
||||||
|
if ((b = luaV_tonumber(rb, &tempb)) != NULL && (c = luaV_tonumber(rc, &tempc)) != NULL)
|
||||||
|
{
|
||||||
|
double nb = nvalue(b), nc = nvalue(c);
|
||||||
|
switch (op)
|
||||||
|
{
|
||||||
|
case TM_ADD:
|
||||||
|
setnvalue(ra, luai_numadd(nb, nc));
|
||||||
|
break;
|
||||||
|
case TM_SUB:
|
||||||
|
setnvalue(ra, luai_numsub(nb, nc));
|
||||||
|
break;
|
||||||
|
case TM_MUL:
|
||||||
|
setnvalue(ra, luai_nummul(nb, nc));
|
||||||
|
break;
|
||||||
|
case TM_DIV:
|
||||||
|
setnvalue(ra, luai_numdiv(nb, nc));
|
||||||
|
break;
|
||||||
|
case TM_MOD:
|
||||||
|
setnvalue(ra, luai_nummod(nb, nc));
|
||||||
|
break;
|
||||||
|
case TM_POW:
|
||||||
|
setnvalue(ra, luai_numpow(nb, nc));
|
||||||
|
break;
|
||||||
|
case TM_UNM:
|
||||||
|
setnvalue(ra, luai_numunm(nb));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LUAU_ASSERT(0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// vector operations that we support: v + v, v - v, v * v, s * v, v * s, v / v, s / v, v / s, -v
|
||||||
|
const float* vb = luaV_tovector(rb);
|
||||||
|
const float* vc = luaV_tovector(rc);
|
||||||
|
|
||||||
|
if (vb && vc)
|
||||||
|
{
|
||||||
|
switch (op)
|
||||||
|
{
|
||||||
|
case TM_ADD:
|
||||||
|
setvvalue(ra, vb[0] + vc[0], vb[1] + vc[1], vb[2] + vc[2], vb[3] + vc[3]);
|
||||||
|
return;
|
||||||
|
case TM_SUB:
|
||||||
|
setvvalue(ra, vb[0] - vc[0], vb[1] - vc[1], vb[2] - vc[2], vb[3] - vc[3]);
|
||||||
|
return;
|
||||||
|
case TM_MUL:
|
||||||
|
setvvalue(ra, vb[0] * vc[0], vb[1] * vc[1], vb[2] * vc[2], vb[3] * vc[3]);
|
||||||
|
return;
|
||||||
|
case TM_DIV:
|
||||||
|
setvvalue(ra, vb[0] / vc[0], vb[1] / vc[1], vb[2] / vc[2], vb[3] / vc[3]);
|
||||||
|
return;
|
||||||
|
case TM_UNM:
|
||||||
|
setvvalue(ra, -vb[0], -vb[1], -vb[2], -vb[3]);
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (vb)
|
||||||
|
{
|
||||||
|
c = luaV_tonumber(rc, &tempc);
|
||||||
|
|
||||||
|
if (c)
|
||||||
|
{
|
||||||
|
float nc = cast_to(float, nvalue(c));
|
||||||
|
|
||||||
|
switch (op)
|
||||||
|
{
|
||||||
|
case TM_MUL:
|
||||||
|
setvvalue(ra, vb[0] * nc, vb[1] * nc, vb[2] * nc, vb[3] * nc);
|
||||||
|
return;
|
||||||
|
case TM_DIV:
|
||||||
|
setvvalue(ra, vb[0] / nc, vb[1] / nc, vb[2] / nc, vb[3] / nc);
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (vc)
|
||||||
|
{
|
||||||
|
b = luaV_tonumber(rb, &tempb);
|
||||||
|
|
||||||
|
if (b)
|
||||||
|
{
|
||||||
|
float nb = cast_to(float, nvalue(b));
|
||||||
|
|
||||||
|
switch (op)
|
||||||
|
{
|
||||||
|
case TM_MUL:
|
||||||
|
setvvalue(ra, nb * vc[0], nb * vc[1], nb * vc[2], nb * vc[3]);
|
||||||
|
return;
|
||||||
|
case TM_DIV:
|
||||||
|
setvvalue(ra, nb / vc[0], nb / vc[1], nb / vc[2], nb / vc[3]);
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!call_binTM(L, rb, rc, ra, op))
|
||||||
|
{
|
||||||
|
luaG_aritherror(L, rb, rc, op);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void luaV_dolen(lua_State* L, StkId ra, const TValue* rb)
|
||||||
|
{
|
||||||
|
const TValue* tm = NULL;
|
||||||
|
switch (ttype(rb))
|
||||||
|
{
|
||||||
|
case LUA_TTABLE:
|
||||||
|
{
|
||||||
|
Table* h = hvalue(rb);
|
||||||
|
if ((tm = fasttm(L, h->metatable, TM_LEN)) == NULL)
|
||||||
|
{
|
||||||
|
setnvalue(ra, cast_num(luaH_getn(h)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case LUA_TSTRING:
|
||||||
|
{
|
||||||
|
TString* ts = tsvalue(rb);
|
||||||
|
setnvalue(ra, cast_num(ts->len));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
tm = luaT_gettmbyobj(L, rb, TM_LEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ttisnil(tm))
|
||||||
|
luaG_typeerror(L, rb, "get length of");
|
||||||
|
|
||||||
|
StkId res = callTMres(L, ra, tm, rb, luaO_nilobject);
|
||||||
|
if (!ttisnumber(res))
|
||||||
|
luaG_runerror(L, "'__len' must return a number"); // note, we can't access rb since stack may have been reallocated
|
||||||
|
}
|
||||||
|
|
||||||
|
LUAU_NOINLINE void luaV_prepareFORN(lua_State* L, StkId plimit, StkId pstep, StkId pinit)
|
||||||
|
{
|
||||||
|
if (!ttisnumber(pinit) && !luaV_tonumber(pinit, pinit))
|
||||||
|
luaG_forerror(L, pinit, "initial value");
|
||||||
|
if (!ttisnumber(plimit) && !luaV_tonumber(plimit, plimit))
|
||||||
|
luaG_forerror(L, plimit, "limit");
|
||||||
|
if (!ttisnumber(pstep) && !luaV_tonumber(pstep, pstep))
|
||||||
|
luaG_forerror(L, pstep, "step");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 void luaV_callTM(lua_State* L, int nparams, int res)
|
||||||
|
{
|
||||||
|
++L->nCcalls;
|
||||||
|
|
||||||
|
if (L->nCcalls >= LUAI_MAXCCALLS)
|
||||||
|
luaD_checkCstack(L);
|
||||||
|
|
||||||
|
luaD_checkstack(L, LUA_MINSTACK);
|
||||||
|
|
||||||
|
StkId top = L->top;
|
||||||
|
StkId fun = top - nparams - 1;
|
||||||
|
|
||||||
|
CallInfo* ci = incr_ci(L);
|
||||||
|
ci->func = fun;
|
||||||
|
ci->base = fun + 1;
|
||||||
|
ci->top = top + LUA_MINSTACK;
|
||||||
|
ci->savedpc = NULL;
|
||||||
|
ci->flags = 0;
|
||||||
|
ci->nresults = (res >= 0);
|
||||||
|
LUAU_ASSERT(ci->top <= L->stack_last);
|
||||||
|
|
||||||
|
LUAU_ASSERT(ttisfunction(ci->func));
|
||||||
|
LUAU_ASSERT(clvalue(ci->func)->isC);
|
||||||
|
|
||||||
|
L->base = fun + 1;
|
||||||
|
LUAU_ASSERT(L->top == L->base + nparams);
|
||||||
|
|
||||||
|
lua_CFunction func = clvalue(fun)->c.f;
|
||||||
|
int n = func(L);
|
||||||
|
LUAU_ASSERT(n >= 0); // yields should have been blocked by nCcalls
|
||||||
|
|
||||||
|
// ci is our callinfo, cip is our parent
|
||||||
|
// note that we read L->ci again since it may have been reallocated by the call
|
||||||
|
CallInfo* cip = L->ci - 1;
|
||||||
|
|
||||||
|
// copy return value into parent stack
|
||||||
|
if (res >= 0)
|
||||||
|
{
|
||||||
|
if (n > 0)
|
||||||
|
{
|
||||||
|
setobj2s(L, &cip->base[res], L->top - n);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setnilvalue(&cip->base[res]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
L->ci = cip;
|
||||||
|
L->base = cip->base;
|
||||||
|
L->top = cip->top;
|
||||||
|
|
||||||
|
--L->nCcalls;
|
||||||
|
}
|
||||||
|
|
||||||
|
LUAU_NOINLINE void luaV_tryfuncTM(lua_State* L, StkId func)
|
||||||
|
{
|
||||||
|
const TValue* tm = luaT_gettmbyobj(L, func, TM_CALL);
|
||||||
|
if (!ttisfunction(tm))
|
||||||
|
luaG_typeerror(L, func, "call");
|
||||||
|
for (StkId p = L->top; p > func; p--) // open space for metamethod
|
||||||
|
setobj2s(L, p, p - 1);
|
||||||
|
L->top++; // stack space pre-allocated by the caller
|
||||||
|
setobj2s(L, func, tm); // tag method is the new function to be called
|
||||||
|
}
|
117
src/lib.rs
117
src/lib.rs
|
@ -1,21 +1,11 @@
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::process::Command;
|
|
||||||
|
|
||||||
pub struct Build {
|
pub struct Build {
|
||||||
out_dir: Option<PathBuf>,
|
out_dir: Option<PathBuf>,
|
||||||
target: Option<String>,
|
target: Option<String>,
|
||||||
host: Option<String>,
|
host: Option<String>,
|
||||||
// Max number of Lua stack slots that a C function can use
|
|
||||||
max_cstack_size: usize,
|
|
||||||
// Use longjmp instead of C++ exceptions
|
|
||||||
use_longjmp: bool,
|
|
||||||
// Enable code generator (jit)
|
|
||||||
enable_codegen: bool,
|
|
||||||
// Vector size, must be 3 (default) or 4
|
|
||||||
vector_size: usize,
|
|
||||||
luau_src: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Artifacts {
|
pub struct Artifacts {
|
||||||
|
@ -32,11 +22,6 @@ impl Build {
|
||||||
out_dir: env::var_os("OUT_DIR").map(|s| PathBuf::from(s).join("luau-build")),
|
out_dir: env::var_os("OUT_DIR").map(|s| PathBuf::from(s).join("luau-build")),
|
||||||
target: env::var("TARGET").ok(),
|
target: env::var("TARGET").ok(),
|
||||||
host: env::var("HOST").ok(),
|
host: env::var("HOST").ok(),
|
||||||
max_cstack_size: 100000,
|
|
||||||
use_longjmp: false,
|
|
||||||
enable_codegen: false,
|
|
||||||
vector_size: 3,
|
|
||||||
luau_src: "https://git.pfaff.dev/michael/luau.git".into(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,32 +40,6 @@ impl Build {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_max_cstack_size(&mut self, size: usize) -> &mut Build {
|
|
||||||
self.max_cstack_size = size;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn use_longjmp(&mut self, r#use: bool) -> &mut Build {
|
|
||||||
self.use_longjmp = r#use;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn enable_codegen(&mut self, enable: bool) -> &mut Build {
|
|
||||||
self.enable_codegen = enable;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_vector_size(&mut self, size: usize) -> &mut Build {
|
|
||||||
assert!(size == 3 || size == 4);
|
|
||||||
self.vector_size = size;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn luau_src(&mut self, git_url: impl Into<String>) -> &mut Build {
|
|
||||||
self.luau_src = git_url.into();
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn build(&mut self) -> Artifacts {
|
pub fn build(&mut self) -> Artifacts {
|
||||||
let target = &self.target.as_ref().expect("TARGET not set")[..];
|
let target = &self.target.as_ref().expect("TARGET not set")[..];
|
||||||
let host = &self.host.as_ref().expect("HOST not set")[..];
|
let host = &self.host.as_ref().expect("HOST not set")[..];
|
||||||
|
@ -88,36 +47,17 @@ impl Build {
|
||||||
let lib_dir = out_dir.join("lib");
|
let lib_dir = out_dir.join("lib");
|
||||||
let include_dir = out_dir.join("include");
|
let include_dir = out_dir.join("include");
|
||||||
|
|
||||||
let source_dir_base = out_dir.join("luau");
|
let source_dir_base = Path::new(env!("CARGO_MANIFEST_DIR"));
|
||||||
|
let common_include_dir = source_dir_base.join("luau").join("Common").join("include");
|
||||||
if !out_dir.exists() {
|
let ast_source_dir = source_dir_base.join("luau").join("Ast").join("src");
|
||||||
fs::create_dir(&out_dir).unwrap();
|
let ast_include_dir = source_dir_base.join("luau").join("Ast").join("include");
|
||||||
}
|
let compiler_source_dir = source_dir_base.join("luau").join("Compiler").join("src");
|
||||||
|
|
||||||
// Clone the sources
|
|
||||||
if source_dir_base.exists() {
|
|
||||||
fs::remove_dir_all(&source_dir_base).unwrap();
|
|
||||||
}
|
|
||||||
let status = Command::new("git")
|
|
||||||
.args(["clone", "--depth=1", &self.luau_src])
|
|
||||||
.current_dir(out_dir)
|
|
||||||
.status()
|
|
||||||
.unwrap();
|
|
||||||
if !status.success() {
|
|
||||||
panic!("Failed to clone sources");
|
|
||||||
}
|
|
||||||
|
|
||||||
let common_include_dir = source_dir_base.join("Common").join("include");
|
|
||||||
let ast_source_dir = source_dir_base.join("Ast").join("src");
|
|
||||||
let ast_include_dir = source_dir_base.join("Ast").join("include");
|
|
||||||
let codegen_source_dir = source_dir_base.join("CodeGen").join("src");
|
|
||||||
let codegen_include_dir = source_dir_base.join("CodeGen").join("include");
|
|
||||||
let compiler_source_dir = source_dir_base.join("Compiler").join("src");
|
|
||||||
let compiler_include_dir = source_dir_base
|
let compiler_include_dir = source_dir_base
|
||||||
|
.join("luau")
|
||||||
.join("Compiler")
|
.join("Compiler")
|
||||||
.join("include");
|
.join("include");
|
||||||
let vm_source_dir = source_dir_base.join("VM").join("src");
|
let vm_source_dir = source_dir_base.join("luau").join("VM").join("src");
|
||||||
let vm_include_dir = source_dir_base.join("VM").join("include");
|
let vm_include_dir = source_dir_base.join("luau").join("VM").join("include");
|
||||||
|
|
||||||
// Cleanup
|
// Cleanup
|
||||||
if lib_dir.exists() {
|
if lib_dir.exists() {
|
||||||
|
@ -141,23 +81,9 @@ impl Build {
|
||||||
.flag_if_supported("/std:c++17") // MSVC
|
.flag_if_supported("/std:c++17") // MSVC
|
||||||
.cpp(true);
|
.cpp(true);
|
||||||
|
|
||||||
// Common defines
|
|
||||||
config.define("LUAI_MAXCSTACK", &*self.max_cstack_size.to_string());
|
|
||||||
config.define("LUA_VECTOR_SIZE", &*self.vector_size.to_string());
|
|
||||||
|
|
||||||
if self.use_longjmp {
|
|
||||||
config.define("LUA_USE_LONGJMP", "1");
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.enable_codegen {
|
|
||||||
config.define("LUA_CUSTOM_EXECUTION", None);
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg!(not(debug_assertions)) {
|
if cfg!(not(debug_assertions)) {
|
||||||
config.define("NDEBUG", None);
|
config.define("NDEBUG", None);
|
||||||
config.opt_level(2);
|
config.opt_level(2);
|
||||||
// this flag allows compiler to lower sqrt() into a single CPU instruction
|
|
||||||
config.flag_if_supported("-fno-math-errno");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build Ast
|
// Build Ast
|
||||||
|
@ -170,23 +96,6 @@ impl Build {
|
||||||
.out_dir(&lib_dir)
|
.out_dir(&lib_dir)
|
||||||
.compile(ast_lib_name);
|
.compile(ast_lib_name);
|
||||||
|
|
||||||
// Build CogeGen
|
|
||||||
let codegen_lib_name = "luaucodegen";
|
|
||||||
if self.enable_codegen {
|
|
||||||
config
|
|
||||||
.clone()
|
|
||||||
.include(&codegen_include_dir)
|
|
||||||
.include(&common_include_dir)
|
|
||||||
.include(&vm_include_dir)
|
|
||||||
.include(&vm_source_dir)
|
|
||||||
.define("LUACODEGEN_API", "extern \"C\"")
|
|
||||||
// Code generator uses lua VM internals, so we need to provide the same defines used to build VM
|
|
||||||
.define("LUA_API", "extern \"C\"")
|
|
||||||
.add_files_by_ext(&codegen_source_dir, "cpp")
|
|
||||||
.out_dir(&lib_dir)
|
|
||||||
.compile(codegen_lib_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build Compiler
|
// Build Compiler
|
||||||
let compiler_lib_name = "luaucompiler";
|
let compiler_lib_name = "luaucompiler";
|
||||||
config
|
config
|
||||||
|
@ -206,6 +115,8 @@ impl Build {
|
||||||
.include(&vm_include_dir)
|
.include(&vm_include_dir)
|
||||||
.include(&common_include_dir)
|
.include(&common_include_dir)
|
||||||
.define("LUA_API", "extern \"C\"")
|
.define("LUA_API", "extern \"C\"")
|
||||||
|
.define("LUAI_MAXCSTACK", "100000")
|
||||||
|
// .define("LUA_USE_LONGJMP", "1")
|
||||||
.add_files_by_ext(&vm_source_dir, "cpp")
|
.add_files_by_ext(&vm_source_dir, "cpp")
|
||||||
.out_dir(&lib_dir)
|
.out_dir(&lib_dir)
|
||||||
.compile(vm_lib_name);
|
.compile(vm_lib_name);
|
||||||
|
@ -217,7 +128,7 @@ impl Build {
|
||||||
fs::copy(compiler_include_dir.join(f), include_dir.join(f)).unwrap();
|
fs::copy(compiler_include_dir.join(f), include_dir.join(f)).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut artifacts = Artifacts {
|
Artifacts {
|
||||||
lib_dir,
|
lib_dir,
|
||||||
include_dir,
|
include_dir,
|
||||||
libs: vec![
|
libs: vec![
|
||||||
|
@ -226,13 +137,7 @@ impl Build {
|
||||||
vm_lib_name.to_string(),
|
vm_lib_name.to_string(),
|
||||||
],
|
],
|
||||||
cpp_stdlib: Self::get_cpp_link_stdlib(target),
|
cpp_stdlib: Self::get_cpp_link_stdlib(target),
|
||||||
};
|
|
||||||
|
|
||||||
if self.enable_codegen {
|
|
||||||
artifacts.libs.push(codegen_lib_name.to_string());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
artifacts
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_cpp_link_stdlib(target: &str) -> Option<String> {
|
fn get_cpp_link_stdlib(target: &str) -> Option<String> {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
fn main() {
|
fn main() {
|
||||||
println!("cargo:rerun-if-changed=build.rs");
|
println!("cargo:rerun-if-changed=build.rs");
|
||||||
let artifacts = luau0_src::Build::new().enable_codegen(true).build();
|
let artifacts = luau0_src::Build::new().build();
|
||||||
artifacts.print_cargo_metadata();
|
artifacts.print_cargo_metadata();
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,14 +15,9 @@ extern "C" {
|
||||||
pub fn free(ptr: *mut c_void);
|
pub fn free(ptr: *mut c_void);
|
||||||
|
|
||||||
pub fn luaL_newstate() -> *mut c_void;
|
pub fn luaL_newstate() -> *mut c_void;
|
||||||
pub fn lua_close(state: *mut c_void);
|
|
||||||
pub fn luaL_openlibs(state: *mut c_void);
|
pub fn luaL_openlibs(state: *mut c_void);
|
||||||
pub fn lua_getfield(state: *mut c_void, index: c_int, k: *const c_char) -> c_int;
|
pub fn lua_getfield(state: *mut c_void, index: c_int, k: *const c_char) -> c_int;
|
||||||
pub fn lua_tolstring(state: *mut c_void, index: c_int, len: *mut c_long) -> *const c_char;
|
pub fn lua_tolstring(state: *mut c_void, index: c_int, len: *mut c_long) -> *const c_char;
|
||||||
pub fn lua_call(state: *mut c_void, nargs: c_int, nresults: c_int);
|
|
||||||
|
|
||||||
pub fn lua_pushinteger(state: *mut c_void, n: c_int);
|
|
||||||
pub fn lua_tointegerx(state: *mut c_void, index: c_int, isnum: *mut c_int) -> c_int;
|
|
||||||
|
|
||||||
pub fn luau_compile(
|
pub fn luau_compile(
|
||||||
source: *const c_char,
|
source: *const c_char,
|
||||||
|
@ -37,10 +32,6 @@ extern "C" {
|
||||||
size: usize,
|
size: usize,
|
||||||
env: c_int,
|
env: c_int,
|
||||||
) -> c_int;
|
) -> c_int;
|
||||||
|
|
||||||
pub fn luau_codegen_supported() -> c_int;
|
|
||||||
pub fn luau_codegen_create(state: *mut c_void);
|
|
||||||
pub fn luau_codegen_compile(state: *mut c_void, idx: c_int);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub unsafe fn lua_getglobal(state: *mut c_void, k: *const c_char) {
|
pub unsafe fn lua_getglobal(state: *mut c_void, k: *const c_char) {
|
||||||
|
@ -54,11 +45,6 @@ fn luau_works() {
|
||||||
let state = luaL_newstate();
|
let state = luaL_newstate();
|
||||||
assert!(state != ptr::null_mut());
|
assert!(state != ptr::null_mut());
|
||||||
|
|
||||||
// Enable JIT if supported
|
|
||||||
if luau_codegen_supported() != 0 {
|
|
||||||
luau_codegen_create(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
luaL_openlibs(state);
|
luaL_openlibs(state);
|
||||||
|
|
||||||
let version = {
|
let version = {
|
||||||
|
@ -70,7 +56,7 @@ fn luau_works() {
|
||||||
|
|
||||||
assert_eq!(version, "Luau".as_bytes());
|
assert_eq!(version, "Luau".as_bytes());
|
||||||
|
|
||||||
let code = "local a, b = ... return a + b\0";
|
let code = "function sum(a, b) return a + b end\0";
|
||||||
let mut bytecode_size = 0;
|
let mut bytecode_size = 0;
|
||||||
let bytecode = luau_compile(
|
let bytecode = luau_compile(
|
||||||
code.as_ptr().cast(),
|
code.as_ptr().cast(),
|
||||||
|
@ -78,21 +64,8 @@ fn luau_works() {
|
||||||
ptr::null_mut(),
|
ptr::null_mut(),
|
||||||
&mut bytecode_size,
|
&mut bytecode_size,
|
||||||
);
|
);
|
||||||
let result = luau_load(state, "sum\0".as_ptr().cast(), bytecode, bytecode_size, 0);
|
let result = luau_load(state, "test\0".as_ptr().cast(), bytecode, bytecode_size, 0);
|
||||||
assert_eq!(result, 0);
|
assert_eq!(result, 0);
|
||||||
free(bytecode.cast());
|
free(bytecode.cast());
|
||||||
|
|
||||||
// Compile the function (JIT, if supported)
|
|
||||||
if luau_codegen_supported() != 0 {
|
|
||||||
luau_codegen_compile(state, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call the loaded function
|
|
||||||
lua_pushinteger(state, 123);
|
|
||||||
lua_pushinteger(state, 321);
|
|
||||||
lua_call(state, 2, 1);
|
|
||||||
assert_eq!(lua_tointegerx(state, -1, ptr::null_mut()), 444);
|
|
||||||
|
|
||||||
lua_close(state);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue