Initial commit

This commit is contained in:
Alex Orlenko 2022-02-23 12:12:51 +00:00
commit 2b08eba69c
No known key found for this signature in database
GPG Key ID: 4C150C250863B96D
92 changed files with 36646 additions and 0 deletions

89
.github/workflows/main.yml vendored Normal file
View File

@ -0,0 +1,89 @@
name: CI
on: [push, pull_request]
jobs:
build:
name: Build
runs-on: ${{ matrix.os }}
strategy:
matrix:
target:
- x86_64-unknown-linux-gnu
- i686-unknown-linux-gnu
- aarch64-unknown-linux-gnu
- x86_64-apple-darwin
- x86_64-pc-windows-msvc
rust: [stable]
include:
- target: x86_64-unknown-linux-gnu
os: ubuntu-20.04
- target: i686-unknown-linux-gnu
os: ubuntu-20.04
- target: aarch64-unknown-linux-gnu
os: ubuntu-20.04
- target: x86_64-apple-darwin
os: macos-latest
- target: x86_64-pc-windows-msvc
os: windows-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
override: false
- name: Install GCC (i686-unknown-linux-gnu)
if: ${{ matrix.target == 'i686-unknown-linux-gnu' }}
run: |
sudo apt-get update -y
sudo apt-get install -y --no-install-recommends gcc-multilib g++-multilib
shell: bash
- name: Install GCC (aarch64-unknown-linux-gnu)
if: ${{ matrix.target == 'aarch64-unknown-linux-gnu' }}
run: |
sudo apt-get update -y
sudo apt-get install -y --no-install-recommends gcc-aarch64-linux-gnu g++-aarch64-linux-gnu libc6-dev-arm64-cross
shell: bash
- name: Build
run: |
cargo build --manifest-path testcrate/Cargo.toml --target ${{ matrix.target }} --release
shell: bash
test:
name: Test
runs-on: ${{ matrix.os }}
needs: build
strategy:
matrix:
os: [ubuntu-20.04, macos-latest, windows-latest]
rust: [stable]
include:
- os: ubuntu-20.04
target: x86_64-unknown-linux-gnu
- os: macos-latest
target: x86_64-apple-darwin
- os: windows-latest
target: x86_64-pc-windows-msvc
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
override: true
- name: Run tests
run: |
cargo test --manifest-path testcrate/Cargo.toml --release
shell: bash
rustfmt:
name: Rustfmt
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
components: rustfmt
override: true
- run: cargo fmt -- --check

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
.vscode/
target/
**/*.rs.bk
Cargo.lock

18
Cargo.toml Normal file
View File

@ -0,0 +1,18 @@
[package]
name = "luau-src"
version = "1.0.0+luau515"
authors = ["Aleksandr Orlenko <zxteam@protonmail.com>"]
edition = "2018"
repository = "https://github.com/khvzak/luau-src-rs"
keywords = ["lua", "luau", "roblox"]
readme = "README.md"
license = "MIT"
description = """
Minimal sources of Roblox Luau and logic to build them.
"""
[workspace]
members = ["testcrate"]
[dependencies]
cc = "1.0.72"

46
LICENSE Normal file
View File

@ -0,0 +1,46 @@
MIT License
Copyright (c) 2022 Aleksandr Orlenko
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.
### Luau license ###
MIT License
Copyright (c) 2019-2022 Roblox Corporation
Copyright (c) 19942019 Lua.org, PUC-Rio.
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.

15
README.md Normal file
View File

@ -0,0 +1,15 @@
# luau-src
[![Build Status]][github-actions]
[Build Status]: https://github.com/khvzak/luau-src-rs/workflows/CI/badge.svg
[github-actions]: https://github.com/khvzak/luau-src-rs/actions
This crate contains the sources of Roblox [Luau] and logic to build them.
Intended to be consumed by the [mlua] crate.
[Luau]: https://github.com/Roblox/luau
[mlua]: https://crates.io/crates/mlua
# License
This project is licensed under [MIT license](http://opensource.org/licenses/MIT)

1268
luau/Ast/include/Luau/Ast.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,133 @@
// 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
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;
}
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, void (*reg)(const char*, T*, bool) = nullptr)
: value(def)
, dynamic(dynamic)
, name(name)
, next(list)
{
list = this;
if (reg)
reg(name, &value, dynamic);
}
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, nullptr); \
}
#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, nullptr); \
}
#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, nullptr); \
}
#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, nullptr); \
}

View File

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

View File

@ -0,0 +1,410 @@
// 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 <vector>
#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;
DenseHashTable(const Key& empty_key, size_t buckets = 0)
: count(0)
, empty_key(empty_key)
{
// buckets has to be power-of-two or zero
LUAU_ASSERT((buckets & (buckets - 1)) == 0);
// don't move this to initializer list! this works around an MSVC codegen issue on AMD CPUs:
// https://developercommunity.visualstudio.com/t/stdvector-constructor-from-size-t-is-25-times-slow/1546547
if (buckets)
data.resize(buckets, ItemInterface::create(empty_key));
}
void clear()
{
data.clear();
count = 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 = data.size() - 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 (data.empty())
return 0;
if (eq(key, empty_key))
return 0;
size_t hashmod = data.size() - 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 = data.empty() ? 16 : data.size() * 2;
if (data.empty() && data.capacity() >= newsize)
{
LUAU_ASSERT(count == 0);
data.resize(newsize, ItemInterface::create(empty_key));
return;
}
DenseHashTable newtable(empty_key, newsize);
for (size_t i = 0; i < data.size(); ++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);
data.swap(newtable.data);
}
void rehash_if_full()
{
if (count >= data.size() * 3 / 4)
{
rehash();
}
}
const_iterator begin() const
{
size_t start = 0;
while (start < data.size() && eq(ItemInterface::getKey(data[start]), empty_key))
start++;
return const_iterator(this, start);
}
const_iterator end() const
{
return const_iterator(this, data.size());
}
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->data.size();
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;
};
private:
std::vector<Item> data;
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 Key create(const Key& key)
{
return 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 std::pair<Key, Value> create(const Key& key)
{
return std::pair<Key, Value>(key, 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;
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();
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();
}
};
// 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;
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();
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();
}
};
} // namespace Luau

View File

@ -0,0 +1,241 @@
// 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"
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,
AddAssign,
SubAssign,
MulAssign,
DivAssign,
ModAssign,
PowAssign,
ConcatAssign,
RawString,
QuotedString,
Number,
Name,
Comment,
BlockComment,
BrokenString,
BrokenComment,
BrokenUnicode,
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);
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();
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;
};
inline bool isSpace(char ch)
{
return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == '\v' || ch == '\f';
}
} // namespace Luau

View File

@ -0,0 +1,109 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include <string>
namespace Luau
{
struct Position
{
unsigned int line, column;
Position(unsigned int line, unsigned int column)
: line(line)
, column(column)
{
}
bool operator==(const Position& rhs) const
{
return this->column == rhs.column && this->line == rhs.line;
}
bool operator!=(const Position& rhs) const
{
return !(*this == rhs);
}
bool operator<(const Position& rhs) const
{
if (line == rhs.line)
return column < rhs.column;
else
return line < rhs.line;
}
bool operator>(const Position& rhs) const
{
if (line == rhs.line)
return column > rhs.column;
else
return line > rhs.line;
}
bool operator<=(const Position& rhs) const
{
return *this == rhs || *this < rhs;
}
bool operator>=(const Position& rhs) const
{
return *this == rhs || *this > rhs;
}
};
struct Location
{
Position begin, end;
Location()
: begin(0, 0)
, end(0, 0)
{
}
Location(const Position& begin, const Position& end)
: begin(begin)
, end(end)
{
}
Location(const Position& begin, unsigned int length)
: begin(begin)
, end(begin.line, begin.column + length)
{
}
Location(const Location& begin, const Location& end)
: begin(begin.begin)
, end(end.end)
{
}
bool operator==(const Location& rhs) const
{
return this->begin == rhs.begin && this->end == rhs.end;
}
bool operator!=(const Location& rhs) const
{
return !(*this == rhs);
}
bool encloses(const Location& l) const
{
return begin <= l.begin && end >= l.end;
}
bool contains(const Position& p) const
{
return begin <= p && p < end;
}
bool containsClosed(const Position& p) const
{
return begin <= p && p <= end;
}
};
std::string toString(const Position& position);
std::string toString(const Location& location);
} // namespace Luau

View File

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

View File

@ -0,0 +1,69 @@
// 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;
std::vector<HotComment> hotcomments;
std::vector<ParseError> errors;
std::vector<Comment> commentLocations;
};
static constexpr const char* kParseNameError = "%error-id%";
} // namespace Luau

View File

@ -0,0 +1,383 @@
// 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>
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();
// function funcname funcbody |
// funcname ::= Name {`.' Name} [`:' Name]
AstStat* parseFunctionStat();
// local function Name funcbody |
// local namelist [`=' explist]
AstStat* parseLocal();
// return [explist]
AstStat* parseReturn();
// type Name `=' typeannotation
AstStat* parseTypeAlias(const Location& start, bool exported);
AstDeclaredClassProp parseDeclaredClassMethod();
// `declare global' Name: typeannotation |
// `declare function' Name`(' [parlist] `)' [`:` TypeAnnotation]
AstStat* parseDeclaration(const Location& start);
// varlist `=' explist
AstStat* parseAssignment(AstExpr* initial);
// var [`+=' | `-=' | `*=' | `/=' | `%=' | `^=' | `..='] exp
AstStat* parseCompoundAssignment(AstExpr* initial, AstExprBinary::Op op);
// funcbody ::= `(' [parlist] `)' block end
// parlist ::= namelist [`,' `...'] | `...'
std::pair<AstExprFunction*, AstLocal*> parseFunctionBody(
bool hasself, const Lexeme& matchFunction, const AstName& debugname, std::optional<Name> localName);
// explist ::= {exp `,'} exp
void parseExprList(TempVector<AstExpr*>& result);
// binding ::= Name [`:` TypeAnnotation]
Binding parseBinding();
// bindinglist ::= (binding | `...') {`,' bindinglist}
// Returns the location of the vararg ..., or std::nullopt if the function is not vararg.
std::pair<std::optional<Location>, AstTypePack*> parseBindingList(TempVector<Binding>& result, bool allowDot3 = false);
AstType* parseOptionalTypeAnnotation();
// TypeList ::= TypeAnnotation [`,' TypeList]
// ReturnType ::= TypeAnnotation | `(' TypeList `)'
// TableProp ::= Name `:' TypeAnnotation
// TableIndexer ::= `[' TypeAnnotation `]' `:' TypeAnnotation
// PropList ::= (TableProp | TableIndexer) [`,' PropList]
// TypeAnnotation
// ::= 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> parseOptionalReturnTypeAnnotation();
std::pair<Location, AstTypeList> parseReturnTypeAnnotation();
AstTableIndexer* parseTableIndexerAnnotation();
AstTypeOrPack parseFunctionTypeAnnotation(bool allowPack);
AstType* parseFunctionTypeAnnotationTail(const Lexeme& begin, AstArray<AstGenericType> generics, AstArray<AstGenericTypePack> genericPacks,
AstArray<AstType*>& params, AstArray<std::optional<AstArgumentName>>& paramNames, AstTypePack* varargAnnotation);
AstType* parseTableTypeAnnotation();
AstTypeOrPack parseSimpleTypeAnnotation(bool allowPack);
AstTypeOrPack parseTypeOrPackAnnotation();
AstType* parseTypeAnnotation(TempVector<AstType*>& parts, const Location& begin);
AstType* parseTypeAnnotation();
AstTypePack* parseTypePackAnnotation();
AstTypePack* parseVariadicArgumentAnnotation();
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 [`::' typeAnnotation]
AstExpr* parseAssertionExpr();
// simpleexp -> NUMBER | STRING | NIL | true | false | ... | constructor | FUNCTION body | primaryexp
AstExpr* parseSimpleExpr();
// args ::= `(' [explist] `)' | tableconstructor | String
AstExpr* parseFunctionArgs(AstExpr* func, bool self, const Location& selfLocation);
// tableconstructor ::= `{' [fieldlist] `}'
// fieldlist ::= field {fieldsep field} [fieldsep]
// field ::= `[' exp `]' `=' exp | Name `=' exp | exp
// fieldsep ::= `,' | `;'
AstExpr* parseTableConstructor();
// TODO: Add grammar rules here?
AstExpr* parseIfElseExpr();
// 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);
// `<' typeAnnotation[, ...] `>'
AstArray<AstTypeOrPack> parseTypeParams();
std::optional<AstArray<char>> parseCharArray();
AstExpr* parseString();
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);
bool expectMatchAndConsume(char value, const Lexeme& begin, bool searchForMissing = false);
void expectMatchAndConsumeFail(Lexeme::Type type, const Lexeme& begin, const char* extra = nullptr);
bool expectMatchEndAndConsume(Lexeme::Type type, const Lexeme& begin);
void expectMatchEndAndConsumeFail(Lexeme::Type type, const Lexeme& 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* reportTypeAnnotationError(const Location& location, const AstArray<AstType*>& types, bool isMissing, const char* format, ...)
LUAU_PRINTF_ATTR(5, 6);
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;
Lexeme 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<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*> scratchAnnotation;
std::vector<AstTypeOrPack> scratchTypeOrPackAnnotation;
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

View File

@ -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 <vector>
#include <string>
#include <stdarg.h>
#if defined(__GNUC__)
#define LUAU_PRINTF_ATTR(fmt, arg) __attribute__((format(printf, fmt, arg)))
#else
#define LUAU_PRINTF_ATTR(fmt, arg)
#endif
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);
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 isIdentifier(std::string_view s);
} // namespace Luau

View File

@ -0,0 +1,223 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "Common.h"
#include <vector>
#include <stdint.h>
LUAU_FASTFLAG(DebugLuauTimeTracing)
#if defined(LUAU_ENABLE_TIME_TRACE)
namespace Luau
{
namespace TimeTrace
{
uint32_t getClockMicroseconds();
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(ThreadContext& context, uint16_t token)
: context(context)
{
if (!FFlag::DebugLuauTimeTracing)
return;
context.eventEnter(token);
}
~Scope()
{
if (!FFlag::DebugLuauTimeTracing)
return;
context.eventLeave();
}
ThreadContext& context;
};
struct OptionalTailScope
{
explicit OptionalTailScope(ThreadContext& context, uint16_t token, uint32_t threshold)
: context(context)
, 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 std::pair<uint16_t, Luau::TimeTrace::ThreadContext&> createScopeData(const char* name, const char* category);
} // namespace TimeTrace
} // namespace Luau
// Regular scope
#define LUAU_TIMETRACE_SCOPE(name, category) \
static auto lttScopeStatic = Luau::TimeTrace::createScopeData(name, category); \
Luau::TimeTrace::Scope lttScope(lttScopeStatic.second, lttScopeStatic.first)
// 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 auto lttScopeStaticOptTail = Luau::TimeTrace::createScopeData(name, category); \
Luau::TimeTrace::OptionalTailScope lttScope(lttScopeStaticOptTail.second, lttScopeStaticOptTail.first, microsec)
// Extra key/value data can be added to regular scopes
#define LUAU_TIMETRACE_ARGUMENT(name, value) \
do \
{ \
if (FFlag::DebugLuauTimeTracing) \
lttScopeStatic.second.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

938
luau/Ast/src/Ast.cpp Normal file
View File

@ -0,0 +1,938 @@
// 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)
: AstExpr(ClassIndex(), location)
, value(value)
{
}
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, std::optional<Location> vararg, AstStatBlock* body, size_t functionDepth,
const AstName& debugname, std::optional<AstTypeList> returnAnnotation, AstTypePack* varargAnnotation, bool hasEnd,
std::optional<Location> argLocation)
: AstExpr(ClassIndex(), location)
, generics(generics)
, genericPacks(genericPacks)
, self(self)
, args(args)
, returnAnnotation(returnAnnotation)
, vararg(vararg.has_value())
, varargLocation(vararg.value_or(Location()))
, 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)
{
}
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 AstArray<AstGenericType>& generics,
const AstArray<AstGenericTypePack>& genericPacks, AstType* type, bool exported)
: AstStat(ClassIndex(), location)
, name(name)
, 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();
}
} // namespace Luau

1818
luau/Ast/src/Confusables.cpp Normal file

File diff suppressed because it is too large Load Diff

1139
luau/Ast/src/Lexer.cpp Normal file

File diff suppressed because it is too large Load Diff

17
luau/Ast/src/Location.cpp Normal file
View File

@ -0,0 +1,17 @@
// 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
{
std::string toString(const Position& position)
{
return "{ line = " + std::to_string(position.line) + ", col = " + std::to_string(position.column) + " }";
}
std::string toString(const Location& location)
{
return "Location { " + toString(location.begin) + ", " + toString(location.end) + " }";
}
} // namespace Luau

2888
luau/Ast/src/Parser.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,286 @@
// 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
{
static 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)
{
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 != '\"')
r += c;
else
{
r += '\\';
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

257
luau/Ast/src/TimeTrace.cpp Normal file
View File

@ -0,0 +1,257 @@
// 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)
#if defined(LUAU_ENABLE_TIME_TRACE)
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
}
uint32_t getClockMicroseconds()
{
static double period = getClockPeriod() * 1e6;
static double start = getClockTimestamp();
return uint32_t((getClockTimestamp() - start) * period);
}
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;
}
std::pair<uint16_t, Luau::TimeTrace::ThreadContext&> createScopeData(const char* name, const char* category)
{
uint16_t token = createToken(Luau::TimeTrace::getGlobalContext(), name, category);
return {token, Luau::TimeTrace::getThreadContext()};
}
} // namespace TimeTrace
} // namespace Luau
#endif

View File

@ -0,0 +1,486 @@
// 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-254. 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.
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 (0..255)
LOP_GETUPVAL,
// SETUPVAL: store value into the upvalue table for the current function
// A: target register
// B: upvalue index (0..255)
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; 0 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)
// 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/FORGLOOP_INEXT: FORGLOOP with 2 output variables (no AUX encoding), assuming generator is luaB_inext
// FORGPREP_INEXT prepares the index variable and jumps to FORGLOOP_INEXT
// FORGLOOP_INEXT has identical encoding and semantics to FORGLOOP (except for AUX encoding)
LOP_FORGPREP_INEXT,
LOP_FORGLOOP_INEXT,
// FORGPREP_NEXT/FORGLOOP_NEXT: FORGLOOP with 2 output variables (no AUX encoding), assuming generator is luaB_next
// FORGPREP_NEXT prepares the index variable and jumps to FORGLOOP_NEXT
// FORGLOOP_NEXT has identical encoding and semantics to FORGLOOP (except for AUX encoding)
LOP_FORGPREP_NEXT,
LOP_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,
// JUMPIFEQK, JUMPIFNOTEQK: jumps to target offset if the comparison with constant is true (or false, for NOT variants)
// A: source register 1
// D: jump offset (-32768..32767; 0 means "next instruction" aka "don't jump")
// AUX: constant table index
LOP_JUMPIFEQK,
LOP_JUMPIFNOTEQK,
// 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,
// 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
LBC_VERSION = 1,
LBC_VERSION_FUTURE = 2, // TODO: This will be removed in favor of LBC_VERSION with LuauBytecodeV2Force
// 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,
};
// Capture type, used in LOP_CAPTURE
enum LuauCaptureType
{
LCT_VAL = 0,
LCT_REF,
LCT_UPVAL,
};

View File

@ -0,0 +1,252 @@
// 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 <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 finalize();
enum DumpFlags
{
Dump_Code = 1 << 0,
Dump_Lines = 1 << 1,
Dump_Source = 1 << 2,
Dump_Locals = 1 << 3,
};
void setDumpFlags(uint32_t flags)
{
dumpFlags = flags;
dumpFunctionPtr = &BytecodeBuilder::dumpCurrentFunction;
}
void setDumpSource(const std::string& source);
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;
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 uint32_t getStringHash(StringRef key);
static std::string getError(const std::string& message);
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;
};
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;
int debugLine = 0;
std::vector<DebugLocal> debugLocals;
std::vector<DebugUpval> debugUpvals;
DenseHashMap<StringRef, unsigned int, StringRefHash> stringTable;
BytecodeEncoder* encoder = nullptr;
std::string bytecode;
uint32_t dumpFlags = 0;
std::vector<std::string> dumpSource;
std::string (BytecodeBuilder::*dumpFunctionPtr)() const = nullptr;
void validate() const;
std::string dumpCurrentFunction() const;
const uint32_t* dumpInstruction(const uint32_t* opcode, std::string& output) 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

View File

@ -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 AstStatBlock;
class AstNameTable;
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, AstStatBlock* root, 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

View File

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

View File

@ -0,0 +1,202 @@
// 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"
LUAU_FASTFLAGVARIABLE(LuauCompileSelectBuiltin2, false)
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();
}
}
int getBuiltinFunctionId(const Builtin& builtin, const CompileOptions& options)
{
if (builtin.empty())
return -1;
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("unpack"))
return LBF_TABLE_UNPACK;
if (FFlag::LuauCompileSelectBuiltin2 && builtin.isGlobal("select"))
return LBF_SELECT_VARARG;
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;
}
} // namespace Compile
} // namespace Luau

View File

@ -0,0 +1,41 @@
// 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);
int getBuiltinFunctionId(const Builtin& builtin, const CompileOptions& options);
} // namespace Compile
} // namespace Luau

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,394 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "ConstantFolding.h"
#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;
ConstantVisitor(DenseHashMap<AstExpr*, Constant>& constants, DenseHashMap<AstLocal*, Variable>& variables)
: constants(constants)
, variables(variables)
, locals(nullptr)
{
}
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);
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);
if (la.type != Constant::Type_Unknown && ra.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
{
LUAU_ASSERT(!"Unknown expression type");
}
if (result.type != Constant::Type_Unknown)
constants[node] = result;
return result;
}
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]);
if (arg.type != Constant::Type_Unknown)
{
// note: we rely on trackValues to have been run before us
Variable* v = variables.find(node->vars.data[i]);
LUAU_ASSERT(v);
if (!v->written)
{
locals[node->vars.data[i]] = arg;
v->constant = true;
}
}
}
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)
{
// note: we rely on trackValues to have been run before us
Variable* v = variables.find(node->vars.data[i]);
LUAU_ASSERT(v);
if (!v->written)
{
locals[node->vars.data[i]].type = Constant::Type_Nil;
v->constant = true;
}
}
}
}
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, AstNode* root)
{
ConstantVisitor visitor{constants, variables};
root->visit(&visitor);
}
} // namespace Compile
} // namespace Luau

View File

@ -0,0 +1,48 @@
// 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;
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<char> getString() const
{
LUAU_ASSERT(type == Type_String);
return {valueString, stringLength};
}
};
void foldConstants(DenseHashMap<AstExpr*, Constant>& constants, DenseHashMap<AstLocal*, Variable>& variables, AstNode* root);
} // namespace Compile
} // namespace Luau

View File

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

View File

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

View File

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

View File

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

View File

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

406
luau/VM/include/lua.h Normal file
View File

@ -0,0 +1,406 @@
// 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 (-10000)
#define LUA_ENVIRONINDEX (-10001)
#define LUA_GLOBALSINDEX (-10002)
#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,
LUA_ERRMEM,
LUA_ERRERR,
LUA_BREAK, /* yielded for a debug breakpoint */
};
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)(lua_State* L, 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_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 void lua_pushlightuserdata(lua_State* L, void* p);
LUA_API int lua_pushthread(lua_State* L);
/*
** get functions (Lua -> stack)
*/
LUA_API void lua_gettable(lua_State* L, int idx);
LUA_API void lua_getfield(lua_State* L, int idx, const char* k);
LUA_API void lua_rawgetfield(lua_State* L, int idx, const char* k);
LUA_API void lua_rawget(lua_State* L, int idx);
LUA_API void 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 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*));
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_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);
/*
** garbage-collection function and options
*/
enum lua_GCOp
{
LUA_GCSTOP,
LUA_GCRESTART,
LUA_GCCOLLECT,
LUA_GCCOUNT,
LUA_GCCOUNTB,
LUA_GCISRUNNING,
// 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,
LUA_GCSETGOAL,
LUA_GCSETSTEPMUL,
LUA_GCSETSTEPSIZE,
};
LUA_API int lua_gc(lua_State* L, int what, int data);
/*
** miscellaneous functions
*/
LUA_API l_noret lua_error(lua_State* L);
LUA_API int lua_next(lua_State* L, int idx);
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_setuserdatadtor(lua_State* L, int tag, void (*dtor)(void*));
/*
** 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_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 void 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) */
int linedefined; /* (s) */
int currentline; /* (l) */
unsigned char nupvals; /* (u) number of upvalues */
unsigned char nparams; /* (a) number of parameters */
char isvararg; /* (a) */
char short_src[LUA_IDSIZE]; /* (s) */
void* userdata; /* only valid in luau_callhook */
};
/* }====================================================================== */
/* 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.
******************************************************************************/

158
luau/VM/include/luaconf.h Normal file
View File

@ -0,0 +1,158 @@
// 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
// 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
/*
@@ LUAI_GCGOAL defines the desired top heap size in relation to the live heap
@* size at the end of the GC cycle
** CHANGE it if you want the GC to run faster or slower (higher values
** mean larger GC pauses which mean slower collection.) You can also change
** this value dynamically.
*/
#ifndef LUAI_GCGOAL
#define LUAI_GCGOAL 200 /* 200% (allow heap to double compared to live heap size) */
#endif
/*
@@ LUAI_GCSTEPMUL / LUAI_GCSTEPSIZE define the default speed of garbage collection
@* relative to memory allocation.
** Every LUAI_GCSTEPSIZE KB allocated, incremental collector collects LUAI_GCSTEPSIZE
** times LUAI_GCSTEPMUL% bytes.
** CHANGE it if you want to change the granularity of the garbage
** collection.
*/
#ifndef LUAI_GCSTEPMUL
#define LUAI_GCSTEPMUL 200 /* GC runs 'twice the speed' of memory allocation */
#endif
#ifndef LUAI_GCSTEPSIZE
#define LUAI_GCSTEPSIZE 1 /* GC runs every KB of memory allocation */
#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
/* }================================================================== */
/*
@@ 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

137
luau/VM/include/lualib.h Normal file
View File

@ -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);
/*
** ===============================================================
** 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_typename(L, i) lua_typename(L, lua_type(L, (i)))
#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))
LUALIB_API void luaL_buffinit(lua_State* L, luaL_Buffer* B);
LUALIB_API char* luaL_buffinitsize(lua_State* L, luaL_Buffer* B, size_t size);
LUALIB_API char* luaL_extendbuffer(luaL_Buffer* B, size_t additionalsize, int boxloc);
LUALIB_API void luaL_reservebuffer(luaL_Buffer* B, size_t size, int boxloc);
LUALIB_API void luaL_addlstring(luaL_Buffer* B, const char* s, size_t l);
LUALIB_API void luaL_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);

1305
luau/VM/src/lapi.cpp Normal file

File diff suppressed because it is too large Load Diff

8
luau/VM/src/lapi.h Normal file
View File

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

542
luau/VM/src/laux.cpp Normal file
View File

@ -0,0 +1,542 @@
// 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>
LUAU_FASTFLAG(LuauSchubfach)
/* 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;
}
/*
** {======================================================
** 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);
}
setsvalue2s(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)
{
if (size_t(B->end - B->p) < len)
luaL_extendbuffer(B, len - (B->end - B->p), -1);
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)
{
setsvalue2s(L, L->top - 1, luaS_buffinish(L, storage));
}
else
{
setsvalue2s(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:
if (FFlag::LuauSchubfach)
{
double n = lua_tonumber(L, idx);
char s[LUAI_MAXNUM2STR];
char* e = luai_num2str(s, n);
lua_pushlstring(L, s, e - s);
}
else
{
lua_pushstring(L, lua_tostring(L, idx));
}
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);
if (FFlag::LuauSchubfach)
{
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);
}
else
{
#if LUA_VECTOR_SIZE == 4
lua_pushfstring(L, LUA_NUMBER_FMT ", " LUA_NUMBER_FMT ", " LUA_NUMBER_FMT ", " LUA_NUMBER_FMT, v[0], v[1], v[2], v[3]);
#else
lua_pushfstring(L, LUA_NUMBER_FMT ", " LUA_NUMBER_FMT ", " LUA_NUMBER_FMT, v[0], v[1], v[2]);
#endif
}
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);
}

468
luau/VM/src/lbaselib.cpp Normal file
View File

@ -0,0 +1,468 @@
// 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 <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_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);
lua_pushstring(L, luaL_typename(L, 1));
return 1;
}
static int luaB_typeof(lua_State* L)
{
luaL_checkany(L, 1);
const TValue* obj = luaA_toobject(L, 1);
lua_pushstring(L, luaT_objtypename(L, obj));
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_newuserdata(L, 0);
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},
{"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;
}

236
luau/VM/src/lbitlib.cpp Normal file
View File

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

1189
luau/VM/src/lbuiltins.cpp Normal file

File diff suppressed because it is too large Load Diff

9
luau/VM/src/lbuiltins.h Normal file
View File

@ -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 luau_FastFunction luauF_table[256];

9
luau/VM/src/lbytecode.h Normal file
View File

@ -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
// This is a forwarding header for Luau bytecode definition
// Luau consists of several components, including compiler (Ast, Compiler) and VM (virtual machine)
// These components are fully independent, but they both need the bytecode format defined in this header
// so it needs to be shared.
#include "../../Compiler/include/Luau/Bytecode.h"

52
luau/VM/src/lcommon.h Normal file
View File

@ -0,0 +1,52 @@
// 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"
// This is a forwarding header for Luau common definition (assertions, flags)
// Luau consists of several components, including compiler (Ast, Compiler) and VM (virtual machine)
// These components are fully independent, but they need a common set of utilities defined in this header
// so it needs to be shared.
#include "../../Ast/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

278
luau/VM/src/lcorolib.cpp Normal file
View File

@ -0,0 +1,278 @@
// 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_RUN 0 /* running */
#define CO_SUS 1 /* suspended */
#define CO_NOR 2 /* 'normal' (it resumed another coroutine) */
#define CO_DEAD 3
#define CO_STATUS_ERROR -1
#define CO_STATUS_BREAK -2
static const char* const statnames[] = {"running", "suspended", "normal", "dead"};
static int auxstatus(lua_State* L, lua_State* co)
{
if (co == L)
return CO_RUN;
if (co->status == LUA_YIELD)
return CO_SUS;
if (co->status == LUA_BREAK)
return CO_NOR;
if (co->status != 0) /* some error occurred */
return CO_DEAD;
if (co->ci != co->base_ci) /* does it have frames? */
return CO_NOR;
if (co->top == co->base)
return CO_DEAD;
return CO_SUS; /* initial state */
}
static int costatus(lua_State* L)
{
lua_State* co = lua_tothread(L, 1);
luaL_argexpected(L, co, 1, "thread");
lua_pushstring(L, statnames[auxstatus(L, co)]);
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 = auxstatus(L, co);
if (status != CO_SUS)
{
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 = auxstatus(L, co);
if (status != CO_DEAD && status != CO_SUS)
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;
}

167
luau/VM/src/ldblib.cpp Normal file
View File

@ -0,0 +1,167 @@
// 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];
#ifdef _MSC_VER
_itoa(ar.currentline, line, 10); // 5x faster than sprintf
#else
sprintf(line, "%d", ar.currentline);
#endif
luaL_addchar(&buf, ':');
luaL_addstring(&buf, line);
}
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;
}

503
luau/VM/src/ldebug.cpp Normal file
View File

@ -0,0 +1,503 @@
// 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>
LUAU_FASTFLAG(LuauBytecodeV2Force)
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_checkthreadsleep(L);
luaA_pushobject(L, ci->base + (n - 1));
res = 1;
}
else if (fp->is_vararg && n < ci->base - ci->func)
{
luaC_checkthreadsleep(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_checkthreadsleep(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)
setobjs2s(L, ci->base + var->reg, L->top - 1);
L->top--; /* pop value */
const char* name = var ? getstr(var->varname) : NULL;
return name;
}
static int getlinedefined(Proto* p)
{
if (FFlag::LuauBytecodeV2Force)
return p->linedefined;
else if (p->linedefined >= 0)
return p->linedefined;
else
return luaG_getline(p, 0);
}
static int auxgetinfo(lua_State* L, const char* what, lua_Debug* ar, Closure* f, CallInfo* ci)
{
int status = 1;
for (; *what; what++)
{
switch (*what)
{
case 's':
{
if (f->isC)
{
ar->source = "=[C]";
ar->what = "C";
ar->linedefined = -1;
}
else
{
ar->source = getstr(f->l.p->source);
ar->what = "Lua";
ar->linedefined = getlinedefined(f->l.p);
}
luaO_chunkid(ar->short_src, ar->source, LUA_IDSIZE);
break;
}
case 'l':
{
if (ci)
{
ar->currentline = isLua(ci) ? currentline(L, ci) : -1;
}
else
{
ar->currentline = f->isC ? -1 : getlinedefined(f->l.p);
}
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;
}
default:;
}
}
return status;
}
int lua_getinfo(lua_State* L, int level, const char* what, lua_Debug* ar)
{
int status = 0;
Closure* f = NULL;
CallInfo* ci = NULL;
if (level < 0)
{
StkId func = L->top + 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)
{
status = auxgetinfo(L, what, ar, f, ci);
if (strchr(what, 'f'))
{
luaC_checkthreadsleep(L);
setclvalue(L, L->top, f);
incr_top(L);
}
}
return status;
}
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);
}
static void pusherror(lua_State* L, const char* msg)
{
CallInfo* ci = L->ci;
if (isLua(ci))
{
char buff[LUA_IDSIZE]; /* add file:line information */
luaO_chunkid(buff, getstr(getluaproto(ci)->source), LUA_IDSIZE);
int line = currentline(L, ci);
luaO_pushfstring(L, "%s:%d: %s", buff, 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);
// 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);
}
void 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);
luaG_breakpoint(L, clvalue(func)->l.p, line, 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;
}
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 = getlinedefined(p);
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];
sprintf(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];
sprintf(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;
}

28
luau/VM/src/ldebug.h Normal file
View File

@ -0,0 +1,28 @@
// 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 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);

568
luau/VM/src/ldo.cpp Normal file
View File

@ -0,0 +1,568 @@
// 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_FASTFLAG(LuauReduceStackReallocs)
/*
** {======================================================
** Error-recovery functions
** =======================================================
*/
#if LUA_USE_LONGJMP
struct lua_jmpbuf
{
lua_jmpbuf* volatile prev;
volatile int status;
jmp_buf buf;
};
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 (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;
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/LUA_ERRSYNTAX pass an object on the stack which is intended to describe the error.
if (status == LUA_ERRRUN || status == LUA_ERRSYNTAX)
{
// Conversion to a string could still fail. For example if a user passes a non-string/non-number argument to `error()`.
if (const char* str = lua_tostring(L, -1))
{
return str;
}
}
switch (status)
{
case LUA_ERRRUN:
return "lua_exception: LUA_ERRRUN (no string/number provided as description)";
case LUA_ERRSYNTAX:
return "lua_exception: LUA_ERRSYNTAX (no string/number provided as description)";
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;
// TODO (FFlagLuauGcPagedSweep): 'next' type will change after removal of the flag and the cast will not be required
for (UpVal* up = L->openupval; up != NULL; up = (UpVal*)up->next)
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 + (FFlag::LuauReduceStackReallocs ? EXTRA_STACK : 1 + EXTRA_STACK);
LUAU_ASSERT(L->stack_last - L->stack == L->stacksize - (FFlag::LuauReduceStackReallocs ? EXTRA_STACK : 1 + 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)
{
if (L->size_ci > LUAI_MAXCALLS) /* overflow while handling overflow? */
luaD_throw(L, LUA_ERRERR);
else
{
luaD_reallocCI(L, 2 * L->size_ci);
if (L->size_ci > LUAI_MAXCALLS)
luaG_runerror(L, "stack overflow");
}
return ++L->ci;
}
/*
** 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)
{
if (L->nCcalls == LUAI_MAXCCALLS)
luaG_runerror(L, "C stack overflow");
else if (L->nCcalls >= (LUAI_MAXCCALLS + (LUAI_MAXCCALLS >> 3)))
luaD_throw(L, LUA_ERRERR); /* error while handing stack error */
}
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 */
int oldactive = luaC_threadactive(L);
l_setbit(L->stackstate, THREAD_ACTIVEBIT);
luaC_checkthreadsleep(L);
luau_execute(L); /* call it */
if (!oldactive)
resetbit(L->stackstate, THREAD_ACTIVEBIT);
}
L->nCcalls--;
luaC_checkGC(L);
}
static void seterrorobj(lua_State* L, int errcode, StkId oldtop)
{
switch (errcode)
{
case LUA_ERRMEM:
{
setsvalue2s(L, oldtop, luaS_newliteral(L, LUA_MEMERRMSG)); /* can not fail because string is pinned in luaopen */
break;
}
case LUA_ERRERR:
{
setsvalue2s(L, oldtop, luaS_newliteral(L, LUA_ERRERRMSG)); /* can not fail because string is pinned in luaopen */
break;
}
case LUA_ERRSYNTAX:
case LUA_ERRRUN:
{
setobjs2s(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;
setsvalue2s(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;
resetbit(L->stackstate, THREAD_ACTIVEBIT);
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_setbit(L->stackstate, THREAD_ACTIVEBIT);
luaC_checkthreadsleep(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_setbit(L->stackstate, THREAD_ACTIVEBIT);
luaC_checkthreadsleep(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);
setobjs2s(L, L->top, L->top - 1);
setobjs2s(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 - (FFlag::LuauReduceStackReallocs ? EXTRA_STACK : 1 + 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);
int oldactive = luaC_threadactive(L);
int status = luaD_rawrunprotected(L, func, u);
if (status != 0)
{
// call user-defined error function (used in xpcall)
if (ef)
{
// 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)
resetbit(L->stackstate, THREAD_ACTIVEBIT);
// 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, status, oldtop);
L->ci = restoreci(L, old_ci);
L->base = L->ci->base;
restore_stack_limit(L);
}
return status;
}

54
luau/VM/src/ldo.h Normal file
View File

@ -0,0 +1,54 @@
// 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 - (FFlag::LuauReduceStackReallocs ? EXTRA_STACK : 1 + 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 l_noret luaD_throw(lua_State* L, int errcode);
LUAI_FUNC int luaD_rawrunprotected(lua_State* L, Pfunc f, void* ud);

213
luau/VM/src/lfunc.cpp Normal file
View File

@ -0,0 +1,213 @@
// 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"
LUAU_FASTFLAGVARIABLE(LuauNoDirectUpvalRemoval, false)
LUAU_FASTFLAG(LuauGcPagedSweep)
Proto* luaF_newproto(lua_State* L)
{
Proto* f = luaM_newgco(L, Proto, sizeof(Proto), L->activememcat);
luaC_link(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;
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_link(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_link(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(p->v != &p->u.value);
if (p->v == level)
{ /* found a corresponding upvalue? */
if (isdead(g, obj2gco(p))) /* is it dead? */
changewhite(obj2gco(p)); /* resurrect it */
return p;
}
// TODO (FFlagLuauGcPagedSweep): 'next' type will change after removal of the flag and the cast will not be required
pp = (UpVal**)&p->next;
}
UpVal* uv = luaM_newgco(L, UpVal, sizeof(UpVal), L->activememcat); /* not found: create a new one */
uv->tt = LUA_TUPVAL;
uv->marked = luaC_white(g);
uv->memcat = L->activememcat;
uv->v = level; /* current value lives in the stack */
// chain the upvalue in the threads open upvalue list at the proper position
UpVal* next = *pp;
// TODO (FFlagLuauGcPagedSweep): 'next' type will change after removal of the flag and the cast will not be required
uv->next = (GCObject*)next;
if (FFlag::LuauGcPagedSweep)
{
uv->u.l.threadprev = pp;
if (next)
{
// TODO (FFlagLuauGcPagedSweep): 'next' type will change after removal of the flag and the cast will not be required
next->u.l.threadprev = (UpVal**)&uv->next;
}
}
*pp = uv;
// double link the upvalue in the global open upvalue list
uv->u.l.prev = &g->uvhead;
uv->u.l.next = g->uvhead.u.l.next;
uv->u.l.next->u.l.prev = uv;
g->uvhead.u.l.next = uv;
LUAU_ASSERT(uv->u.l.next->u.l.prev == uv && uv->u.l.prev->u.l.next == uv);
return uv;
}
void luaF_unlinkupval(UpVal* uv)
{
// unlink upvalue from the global open upvalue list
LUAU_ASSERT(uv->u.l.next->u.l.prev == uv && uv->u.l.prev->u.l.next == uv);
uv->u.l.next->u.l.prev = uv->u.l.prev;
uv->u.l.prev->u.l.next = uv->u.l.next;
if (FFlag::LuauGcPagedSweep)
{
// unlink upvalue from the thread open upvalue list
// TODO (FFlagLuauGcPagedSweep): 'next' type will change after removal of the flag and this and the following cast will not be required
*uv->u.l.threadprev = (UpVal*)uv->next;
if (UpVal* next = (UpVal*)uv->next)
next->u.l.threadprev = uv->u.l.threadprev;
}
}
void luaF_freeupval(lua_State* L, UpVal* uv, lua_Page* page)
{
if (uv->v != &uv->u.value) /* is it open? */
luaF_unlinkupval(uv); /* remove from open list */
luaM_freegco(L, uv, sizeof(UpVal), uv->memcat, page); /* free upvalue */
}
void luaF_close(lua_State* L, StkId level)
{
global_State* g = L->global; // TODO: remove with FFlagLuauNoDirectUpvalRemoval
UpVal* uv;
while (L->openupval != NULL && (uv = L->openupval)->v >= level)
{
GCObject* o = obj2gco(uv);
LUAU_ASSERT(!isblack(o) && uv->v != &uv->u.value);
if (!FFlag::LuauGcPagedSweep)
L->openupval = (UpVal*)uv->next; /* remove from `open' list */
if (FFlag::LuauGcPagedSweep && isdead(g, o))
{
// by removing the upvalue from global/thread open upvalue lists, L->openupval will be pointing to the next upvalue
luaF_unlinkupval(uv);
// close the upvalue without copying the dead data so that luaF_freeupval will not unlink again
uv->v = &uv->u.value;
}
else if (!FFlag::LuauNoDirectUpvalRemoval && isdead(g, o))
{
luaF_freeupval(L, uv, NULL); /* free upvalue */
}
else
{
// by removing the upvalue from global/thread open upvalue lists, L->openupval will be pointing to the next upvalue
luaF_unlinkupval(uv);
setobj(L, &uv->u.value, uv->v);
uv->v = &uv->u.value; /* now current value lives here */
luaC_linkupval(L, uv); /* link upvalue into `gcroot' list */
}
}
}
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);
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)
{
int i;
for (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 */
}

19
luau/VM/src/lfunc.h Normal file
View File

@ -0,0 +1,19 @@
// 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_freeproto(lua_State* L, Proto* f, struct lua_Page* page);
LUAI_FUNC void luaF_freeclosure(lua_State* L, Closure* c, struct lua_Page* page);
void luaF_unlinkupval(UpVal* uv);
LUAI_FUNC void luaF_freeupval(lua_State* L, UpVal* uv, struct lua_Page* page);
LUAI_FUNC const LocVar* luaF_getlocal(const Proto* func, int local_number, int pc);

1233
luau/VM/src/lgc.cpp Normal file

File diff suppressed because it is too large Load Diff

150
luau/VM/src/lgc.h Normal file
View File

@ -0,0 +1,150 @@
// 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"
/*
** Possible states of the Garbage Collector
*/
#define GCSpause 0
#define GCSpropagate 1
#define GCSpropagateagain 2
#define GCSatomic 3
// TODO: remove with FFlagLuauGcPagedSweep
#define GCSsweepstring 4
#define GCSsweep 5
/*
** 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)
// Thread stack states
#define THREAD_ACTIVEBIT 0 // thread is currently active
#define THREAD_SLEEPINGBIT 1 // thread is not executing and stack should not be modified
#define luaC_threadactive(L) (testbit((L)->stackstate, THREAD_ACTIVEBIT))
#define luaC_threadsleeping(L) (testbit((L)->stackstate, THREAD_SLEEPINGBIT))
#define luaC_checkGC(L) \
{ \
condhardstacktests(luaD_reallocstack(L, L->stacksize - (FFlag::LuauReduceStackReallocs ? EXTRA_STACK : 1 + 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, t); \
}
#define luaC_objbarrier(L, p, o) \
{ \
if (isblack(obj2gco(p)) && iswhite(obj2gco(o))) \
luaC_barrierf(L, obj2gco(p), obj2gco(o)); \
}
// TODO: remove with FFlagLuauGcForwardMetatableBarrier
#define luaC_objbarriert(L, t, o) \
{ \
if (isblack(obj2gco(t)) && iswhite(obj2gco(o))) \
luaC_barriertable(L, t, obj2gco(o)); \
}
#define luaC_upvalbarrier(L, uv, tv) \
{ \
if (iscollectable(tv) && iswhite(gcvalue(tv)) && (!(uv) || ((UpVal*)uv)->v != &((UpVal*)uv)->u.value)) \
luaC_barrierupval(L, gcvalue(tv)); \
}
#define luaC_checkthreadsleep(L) \
{ \
if (luaC_threadsleeping(L)) \
luaC_wakethread(L); \
}
#define luaC_link(L, o, tt) luaC_linkobj(L, cast_to(GCObject*, (o)), tt)
LUAI_FUNC void luaC_freeall(lua_State* L);
LUAI_FUNC void luaC_step(lua_State* L, bool assist);
LUAI_FUNC void luaC_fullgc(lua_State* L);
LUAI_FUNC void luaC_linkobj(lua_State* L, GCObject* o, uint8_t tt);
LUAI_FUNC void luaC_linkupval(lua_State* L, UpVal* uv);
LUAI_FUNC void luaC_barrierupval(lua_State* L, GCObject* v);
LUAI_FUNC void luaC_barrierf(lua_State* L, GCObject* o, GCObject* v);
LUAI_FUNC void luaC_barriertable(lua_State* L, Table* t, GCObject* v);
LUAI_FUNC void luaC_barrierback(lua_State* L, Table* t);
LUAI_FUNC void luaC_validate(lua_State* L);
LUAI_FUNC void luaC_dump(lua_State* L, void* file, const char* (*categoryName)(lua_State* L, uint8_t memcat));
LUAI_FUNC int64_t luaC_allocationrate(lua_State* L);
LUAI_FUNC void luaC_wakethread(lua_State* L);
LUAI_FUNC const char* luaC_statename(int state);

605
luau/VM/src/lgcdebug.cpp Normal file
View File

@ -0,0 +1,605 @@
// 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>
LUAU_FASTFLAG(LuauGcPagedSweep)
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));
// TODO (FFlagLuauGcPagedSweep): 'next' type will change after removal of the flag and the cast will not be required
for (UpVal* uv = l->openupval; uv; uv = (UpVal*)uv->next)
{
LUAU_ASSERT(uv->tt == LUA_TUPVAL);
LUAU_ASSERT(uv->v != &uv->u.value);
}
}
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 == GCSsweepstring || 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 validatelist(global_State* g, GCObject* o)
{
LUAU_ASSERT(!FFlag::LuauGcPagedSweep);
while (o)
{
validateobj(g, o);
o = o->gch.next;
}
}
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)
{
LUAU_ASSERT(FFlag::LuauGcPagedSweep);
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);
if (FFlag::LuauGcPagedSweep)
{
validategco(L, NULL, obj2gco(g->mainthread));
luaM_visitgco(L, L, validategco);
}
else
{
for (int i = 0; i < g->strt.size; ++i)
validatelist(g, (GCObject*)(g->strt.hash[i]));
validatelist(g, g->rootgc);
validatelist(g, (GCObject*)(g->strbufgc));
}
for (UpVal* uv = g->uvhead.u.l.next; uv != &g->uvhead; uv = uv->u.l.next)
{
LUAU_ASSERT(uv->tt == LUA_TUPVAL);
LUAU_ASSERT(uv->v != &uv->u.value);
LUAU_ASSERT(uv->u.l.next->u.l.prev == uv && uv->u.l.prev->u.l.next == uv);
}
}
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->nupvalues)
{
fprintf(f, ",\"upvalues\":[");
dumprefs(f, cl->c.upvals, cl->nupvalues);
fprintf(f, "]");
}
}
else
{
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->abslineinfo ? p->abslineinfo[0] : 0);
}
if (th->top > th->stack)
{
fprintf(f, ",\"stack\":[");
dumprefs(f, th->stack, th->top - th->stack);
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", uv->memcat, int(sizeof(UpVal)));
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 void dumplist(FILE* f, GCObject* o)
{
LUAU_ASSERT(!FFlag::LuauGcPagedSweep);
while (o)
{
dumpref(f, o);
fputc(':', f);
dumpobj(f, o);
fputc(',', f);
fputc('\n', f);
// thread has additional list containing collectable objects that are not present in rootgc
if (o->gch.tt == LUA_TTHREAD)
dumplist(f, (GCObject*)gco2th(o)->openupval);
o = o->gch.next;
}
}
static bool dumpgco(void* context, lua_Page* page, GCObject* gco)
{
LUAU_ASSERT(FFlag::LuauGcPagedSweep);
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");
if (FFlag::LuauGcPagedSweep)
{
dumpgco(f, NULL, obj2gco(g->mainthread));
luaM_visitgco(L, f, dumpgco);
}
else
{
dumplist(f, g->rootgc);
dumplist(f, (GCObject*)(g->strbufgc));
for (int i = 0; i < g->strt.size; ++i)
dumplist(f, (GCObject*)(g->strt.hash[i]));
}
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");
}

87
luau/VM/src/linit.cpp Normal file
View File

@ -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(lua_State* L, 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);
}

445
luau/VM/src/lmathlib.cpp Normal file
View File

@ -0,0 +1,445 @@
// 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 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 lerp(w,
lerp(v, lerp(u, grad(p[aa], xf, yf, zf), grad(p[ba], xf - 1, yf, zf)), lerp(u, grad(p[ab], xf, yf - 1, zf), grad(p[bb], xf - 1, yf - 1, zf))),
lerp(v, lerp(u, grad(p[aa + 1], xf, yf, zf - 1), grad(p[ba + 1], xf - 1, yf, zf - 1)),
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;
}

745
luau/VM/src/lmem.cpp Normal file
View File

@ -0,0 +1,745 @@
// 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.
*/
LUAU_FASTFLAG(LuauGcPagedSweep)
#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__) && !defined(__MACH__)
#define ABISWITCH(x64, ms32, gcc32) (sizeof(void*) == 8 ? x64 : 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");
// TODO (FFlagLuauGcPagedSweep): this will become ABISWITCH(16, 16, 16)
static_assert(offsetof(Udata, data) == ABISWITCH(24, 16, 16), "size mismatch for userdata header");
// TODO (FFlagLuauGcPagedSweep): this will become ABISWITCH(48, 32, 32)
static_assert(sizeof(Table) == ABISWITCH(56, 36, 36), "size mismatch for table header");
// TODO (FFlagLuauGcPagedSweep): new code with old 'next' pointer requires that GCObject start at the same point as TString/UpVal
static_assert(offsetof(GCObject, uv) == 0, "UpVal data must be located at the start of the GCObject");
static_assert(offsetof(GCObject, ts) == 0, "TString data must be located at the start of the GCObject");
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* newpageold(lua_State* L, uint8_t sizeClass)
{
LUAU_ASSERT(!FFlag::LuauGcPagedSweep);
global_State* g = L->global;
lua_Page* page = (lua_Page*)(*g->frealloc)(L, g->ud, NULL, 0, kPageSize);
if (!page)
luaD_throw(L, LUA_ERRMEM);
int blockSize = kSizeClassConfig.sizeOfClass[sizeClass] + kBlockHeader;
int blockCount = (kPageSize - offsetof(lua_Page, data)) / blockSize;
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 = kPageSize;
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;
// prepend a page to page freelist (which is empty because we only ever allocate a new page when it is!)
LUAU_ASSERT(!g->freepages[sizeClass]);
g->freepages[sizeClass] = page;
return page;
}
static lua_Page* newpage(lua_State* L, lua_Page** gcopageset, int pageSize, int blockSize, int blockCount)
{
LUAU_ASSERT(FFlag::LuauGcPagedSweep);
global_State* g = L->global;
LUAU_ASSERT(pageSize - int(offsetof(lua_Page, data)) >= blockSize * blockCount);
lua_Page* page = (lua_Page*)(*g->frealloc)(L, 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)
{
LUAU_ASSERT(FFlag::LuauGcPagedSweep);
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 freepageold(lua_State* L, lua_Page* page, uint8_t sizeClass)
{
LUAU_ASSERT(!FFlag::LuauGcPagedSweep);
global_State* g = L->global;
// remove page from freelist
if (page->next)
page->next->prev = page->prev;
if (page->prev)
page->prev->next = page->next;
else if (g->freepages[sizeClass] == page)
g->freepages[sizeClass] = page->next;
// so long
(*g->frealloc)(L, g->ud, page, kPageSize, 0);
}
static void freepage(lua_State* L, lua_Page** gcopageset, lua_Page* page)
{
LUAU_ASSERT(FFlag::LuauGcPagedSweep);
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)(L, g->ud, page, page->pageSize, 0);
}
static void freeclasspage(lua_State* L, lua_Page** freepageset, lua_Page** gcopageset, lua_Page* page, uint8_t sizeClass)
{
LUAU_ASSERT(FFlag::LuauGcPagedSweep);
// 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)
{
if (FFlag::LuauGcPagedSweep)
page = newclasspage(L, g->freepages, NULL, sizeClass, true);
else
page = newpageold(L, sizeClass);
}
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)
{
LUAU_ASSERT(FFlag::LuauGcPagedSweep);
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)
{
if (FFlag::LuauGcPagedSweep)
freeclasspage(L, g->freepages, NULL, page, sizeClass);
else
freepageold(L, page, sizeClass);
}
}
static void freegcoblock(lua_State* L, int sizeClass, void* block, lua_Page* page)
{
LUAU_ASSERT(FFlag::LuauGcPagedSweep);
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)(L, 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)
{
if (!FFlag::LuauGcPagedSweep)
return (GCObject*)luaM_new_(L, nsize, 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)(L, 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)
{
if (!FFlag::LuauGcPagedSweep)
{
luaM_free_(L, block, osize, memcat);
return;
}
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)(L, 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)(L, g->ud, block, osize, 0);
}
else
{
result = (*g->frealloc)(L, 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)
{
LUAU_ASSERT(FFlag::LuauGcPagedSweep);
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))
{
LUAU_ASSERT(FFlag::LuauGcPagedSweep);
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))
{
LUAU_ASSERT(FFlag::LuauGcPagedSweep);
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;
}
}

36
luau/VM/src/lmem.h Normal file
View File

@ -0,0 +1,36 @@
// 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;
// TODO: remove with FFlagLuauGcPagedSweep and rename luaM_newgco to luaM_new
#define luaM_new(L, t, size, memcat) cast_to(t*, luaM_new_(L, size, memcat))
#define luaM_newgco(L, t, size, memcat) cast_to(t*, luaM_newgco_(L, size, memcat))
// TODO: remove with FFlagLuauGcPagedSweep and rename luaM_freegco to luaM_free
#define luaM_free(L, p, size, memcat) luaM_free_(L, (p), size, memcat)
#define luaM_freegco(L, p, size, memcat, page) luaM_freegco_(L, obj2gco(p), size, memcat, page)
#define luaM_arraysize_(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_(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_(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));

375
luau/VM/src/lnumprint.cpp Normal file
View File

@ -0,0 +1,375 @@
// 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>
#include <stdio.h> // TODO: Remove with LuauSchubfach
#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.
LUAU_FASTFLAGVARIABLE(LuauSchubfach, false)
// 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)
{
if (!FFlag::LuauSchubfach)
{
snprintf(buf, LUAI_MAXNUM2STR, LUA_NUMBER_FMT, n);
return buf + strlen(buf);
}
// 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);
}
}

63
luau/VM/src/lnumutils.h Normal file
View File

@ -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
#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 LUA_NUMBER_FMT "%.14g" /* TODO: Remove with LuauSchubfach */
#define LUAI_MAXNUM2STR 48
LUAI_FUNC char* luai_num2str(char* buf, double n);
#define luai_str2num(s, p) strtod((s), (p))

160
luau/VM/src/lobject.cpp Normal file
View File

@ -0,0 +1,160 @@
// 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);
setsvalue2s(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;
}
void luaO_chunkid(char* out, const char* source, size_t bufflen)
{
if (*source == '=')
{
source++; /* skip the `=' */
size_t srclen = strlen(source);
size_t dstlen = srclen < bufflen ? srclen : bufflen - 1;
memcpy(out, source, dstlen);
out[dstlen] = '\0';
}
else if (*source == '@')
{
size_t l;
source++; /* skip the `@' */
bufflen -= sizeof("...");
l = strlen(source);
strcpy(out, "");
if (l > bufflen)
{
source += (l - bufflen); /* get last part of file name */
strcat(out, "...");
}
strcat(out, source);
}
else
{ /* out = [string "string"] */
size_t len = strcspn(source, "\n\r"); /* stop at first newline */
bufflen -= sizeof("[string \"...\"]");
if (len > bufflen)
len = bufflen;
strcpy(out, "[string \"");
if (source[len] != '\0')
{ /* must truncate? */
strncat(out, source, len);
strcat(out, "...");
}
else
strcat(out, source);
strcat(out, "\"]");
}
}

461
luau/VM/src/lobject.h Normal file
View File

@ -0,0 +1,461 @@
// 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 \
GCObject* next; /* TODO: remove with FFlagLuauGcPagedSweep */ \
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
*/
/* from stack to (same) stack */
#define setobjs2s setobj
/* to stack (not from same stack) */
#define setobj2s setobj
#define setsvalue2s setsvalue
#define sethvalue2s sethvalue
#define setptvalue2s setptvalue
/* from table to same table */
#define setobjt2t setobj
/* to table */
#define setobj2t setobj
/* to new object */
#define setobj2n setobj
#define setsvalue2n setsvalue
#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
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
GCObject* gclist;
int sizecode;
int sizep;
int sizelocvars;
int sizeupvalues;
int sizek;
int sizelineinfo;
int linegaplog2;
int linedefined;
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;
// 1 (x86) or 5 (x64) byte padding
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 double linked list (when open) */
// TODO: when FFlagLuauGcPagedSweep is removed, old outer 'next' value will be placed here
/* note: this is the location of a pointer to this upvalue in the previous element that can be either an UpVal or a lua_State */
struct UpVal** threadprev;
} l;
} u;
} UpVal;
/*
** 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 flags; /* 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 void luaO_chunkid(char* out, const char* source, size_t len);

193
luau/VM/src/loslib.cpp Normal file
View File

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

61
luau/VM/src/lperf.cpp Normal file
View File

@ -0,0 +1,61 @@
// 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;
}

267
luau/VM/src/lstate.cpp Normal file
View File

@ -0,0 +1,267 @@
// 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"
LUAU_FASTFLAG(LuauGcPagedSweep)
LUAU_FASTFLAGVARIABLE(LuauReduceStackReallocs, false)
/*
** 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 - (FFlag::LuauReduceStackReallocs ? EXTRA_STACK : 1 + 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->stackstate = 0;
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 */
if (!FFlag::LuauGcPagedSweep)
LUAU_ASSERT(g->rootgc == obj2gco(L));
LUAU_ASSERT(g->strbufgc == NULL);
LUAU_ASSERT(g->strt.nuse == 0);
luaM_freearray(L, L->global->strt.hash, L->global->strt.size, TString*, 0);
freestack(L, L);
for (int i = 0; i < LUA_SIZECLASSES; i++)
{
LUAU_ASSERT(g->freepages[i] == NULL);
if (FFlag::LuauGcPagedSweep)
LUAU_ASSERT(g->freegcopages[i] == NULL);
}
if (FFlag::LuauGcPagedSweep)
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);
(*g->frealloc)(L, 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_link(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)
{
luaF_close(L1, L1->stack); /* close all upvalues for this thread */
LUAU_ASSERT(L1->openupval == NULL);
global_State* g = L->global;
if (g->cb.userthread)
g->cb.userthread(NULL, L1);
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 (FFlag::LuauReduceStackReallocs)
{
if (L->size_ci != BASIC_CI_SIZE)
luaD_reallocCI(L, BASIC_CI_SIZE);
}
else
{
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 (FFlag::LuauReduceStackReallocs)
{
if (L->stacksize != BASIC_STACK_SIZE + EXTRA_STACK)
luaD_reallocstack(L, BASIC_STACK_SIZE);
}
else
{
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)(NULL, ud, NULL, 0, sizeof(LG));
if (l == NULL)
return NULL;
L = (lua_State*)l;
g = &((LG*)L)->g;
if (!FFlag::LuauGcPagedSweep)
L->next = NULL;
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.l.prev = &g->uvhead;
g->uvhead.u.l.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;
if (!FFlag::LuauGcPagedSweep)
g->rootgc = obj2gco(L);
g->sweepstrgc = 0;
if (!FFlag::LuauGcPagedSweep)
g->sweepgc = &g->rootgc;
g->gray = NULL;
g->grayagain = NULL;
g->weak = NULL;
g->strbufgc = 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;
if (FFlag::LuauGcPagedSweep)
g->freegcopages[i] = NULL;
}
if (FFlag::LuauGcPagedSweep)
{
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();
g->gcstats = GCStats();
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);
}

271
luau/VM/src/lstate.h Normal file
View File

@ -0,0 +1,271 @@
// 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 GCCycleStats
{
size_t heapgoalsizebytes = 0;
size_t heaptriggersizebytes = 0;
double waittime = 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 atomicstarttimestamp = 0.0;
size_t atomicstarttotalsizebytes = 0;
double atomictime = 0.0;
double sweeptime = 0.0;
size_t assistwork = 0;
size_t explicitwork = 0;
size_t endtotalsizebytes = 0;
};
// data for proportional-integral controller of heap trigger value
struct GCHeapTriggerStats
{
static const unsigned termcount = 32;
int32_t terms[termcount] = {0};
uint32_t termpos = 0;
int32_t integral = 0;
};
struct GCStats
{
double stepexplicittimeacc = 0.0;
double stepassisttimeacc = 0.0;
// when cycle is completed, last cycle values are updated
uint64_t completedcycles = 0;
GCCycleStats lastcycle;
GCCycleStats currcycle;
// only step count and their time is accumulated
GCCycleStats cyclestatsacc;
GCHeapTriggerStats triggerstats;
};
/*
** `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 */
int sweepstrgc; /* position of sweep in `strt' */
// TODO: remove with FFlagLuauGcPagedSweep
GCObject* rootgc; /* list of all collectable objects */
// TODO: remove with FFlagLuauGcPagedSweep
GCObject** sweepgc; /* position of sweep in `rootgc' */
GCObject* gray; /* list of gray objects */
GCObject* grayagain; /* list of objects to be traversed atomically */
GCObject* weak; /* list of weak tables (to be cleared) */
TString* strbufgc; // list of all string buffer objects
size_t GCthreshold; // when totalbytes > GCthreshold; run GC step
size_t totalbytes; // number of bytes currently allocated
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 */
void (*udatagc[LUA_UTAG_LIMIT])(void*); /* for each userdata tag, a gc callback to be called immediately before freeing memory */
lua_Callbacks cb;
GCStats gcstats;
} 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 */
uint8_t stackstate;
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);

262
luau/VM/src/lstring.cpp Normal file
View File

@ -0,0 +1,262 @@
// 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>
LUAU_FASTFLAG(LuauGcPagedSweep)
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)
{
if (L->global->gcstate == GCSsweepstring)
return; /* cannot resize during GC traverse */
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 */
// TODO (FFlagLuauGcPagedSweep): 'next' type will change after removal of the flag and the cast will not be required
TString* next = (TString*)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));
// TODO (FFlagLuauGcPagedSweep): 'next' type will change after removal of the flag and the cast will not be required
p->next = (GCObject*)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)
{
TString* ts;
stringtable* tb;
if (l > MAXSSIZE)
luaM_toobig(L);
ts = luaM_newgco(L, TString, sizestring(l), L->activememcat);
ts->len = unsigned(l);
ts->hash = h;
ts->marked = luaC_white(L->global);
ts->tt = LUA_TSTRING;
ts->memcat = L->activememcat;
memcpy(ts->data, str, l);
ts->data[l] = '\0'; /* ending 0 */
ts->atom = L->global->cb.useratom ? L->global->cb.useratom(ts->data, l) : -1;
tb = &L->global->strt;
h = lmod(h, tb->size);
// TODO (FFlagLuauGcPagedSweep): 'next' type will change after removal of the flag and the case will not be required
ts->next = (GCObject*)tb->hash[h]; /* chain new entry */
tb->hash[h] = ts;
tb->nuse++;
if (tb->nuse > cast_to(uint32_t, tb->size) && tb->size <= INT_MAX / 2)
luaS_resize(L, tb->size * 2); /* too crowded */
return ts;
}
static void linkstrbuf(lua_State* L, TString* ts)
{
global_State* g = L->global;
if (FFlag::LuauGcPagedSweep)
{
// TODO (FFlagLuauGcPagedSweep): 'next' type will change after removal of the flag and the cast will not be required
ts->next = (GCObject*)g->strbufgc;
g->strbufgc = ts;
ts->marked = luaC_white(g);
}
else
{
GCObject* o = obj2gco(ts);
o->gch.next = (GCObject*)g->strbufgc;
g->strbufgc = gco2ts(o);
o->gch.marked = luaC_white(g);
}
}
static void unlinkstrbuf(lua_State* L, TString* ts)
{
global_State* g = L->global;
TString** p = &g->strbufgc;
while (TString* curr = *p)
{
if (curr == ts)
{
// TODO (FFlagLuauGcPagedSweep): 'next' type will change after removal of the flag and the cast will not be required
*p = (TString*)curr->next;
return;
}
else
{
// TODO (FFlagLuauGcPagedSweep): 'next' type will change after removal of the flag and the cast will not be required
p = (TString**)&curr->next;
}
}
LUAU_ASSERT(!"failed to find string buffer");
}
TString* luaS_bufstart(lua_State* L, size_t size)
{
if (size > MAXSSIZE)
luaM_toobig(L);
TString* ts = luaM_newgco(L, TString, sizestring(size), L->activememcat);
ts->tt = LUA_TSTRING;
ts->memcat = L->activememcat;
linkstrbuf(L, ts);
ts->len = unsigned(size);
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
// TODO (FFlagLuauGcPagedSweep): 'next' type will change after removal of the flag and the cast will not be required
for (TString* el = tb->hash[bucket]; el != NULL; el = (TString*)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;
}
}
unlinkstrbuf(L, ts);
ts->hash = h;
ts->data[ts->len] = '\0'; // ending 0
// Complete string object
ts->atom = L->global->cb.useratom ? L->global->cb.useratom(ts->data, ts->len) : -1;
// TODO (FFlagLuauGcPagedSweep): 'next' type will change after removal of the flag and the cast will not be required
ts->next = (GCObject*)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);
// TODO (FFlagLuauGcPagedSweep): 'next' type will change after removal of the flag and the cast will not be required
for (TString* el = L->global->strt.hash[lmod(h, L->global->strt.size)]; el != NULL; el = (TString*)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)
{
LUAU_ASSERT(FFlag::LuauGcPagedSweep);
global_State* g = L->global;
TString** p = &g->strt.hash[lmod(ts->hash, g->strt.size)];
while (TString* curr = *p)
{
if (curr == ts)
{
// TODO (FFlagLuauGcPagedSweep): 'next' type will change after removal of the flag and the cast will not be required
*p = (TString*)curr->next;
return true;
}
else
{
// TODO (FFlagLuauGcPagedSweep): 'next' type will change after removal of the flag and the cast will not be required
p = (TString**)&curr->next;
}
}
return false;
}
void luaS_free(lua_State* L, TString* ts, lua_Page* page)
{
if (FFlag::LuauGcPagedSweep)
{
// Unchain from the string table
if (!unlinkstr(L, ts))
unlinkstrbuf(L, ts); // An unlikely scenario when we have a string buffer on our hands
else
L->global->strt.nuse--;
luaM_freegco(L, ts, sizestring(ts->len), ts->memcat, page);
}
else
{
L->global->strt.nuse--;
luaM_free(L, ts, sizestring(ts->len), ts->memcat);
}
}

26
luau/VM/src/lstring.h Normal file
View File

@ -0,0 +1,26 @@
// 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)
#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);

1654
luau/VM/src/lstrlib.cpp Normal file

File diff suppressed because it is too large Load Diff

805
luau/VM/src/ltable.cpp Normal file
View File

@ -0,0 +1,805 @@
// 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 its elements in two parts: an array part and a hash part.
** Non-negative integer keys 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.
*/
#include "ltable.h"
#include "lstate.h"
#include "ldebug.h"
#include "lgc.h"
#include "lmem.h"
#include "lnumutils.h"
#include <string.h>
// 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");
// reset cache of absent metamethods, cache is updated in luaT_gettm
#define invalidateTMcache(t) t->flags = 0
// 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] == 0x8000000) ? 0 : i[0];
i[1] = (i[1] == 0x8000000) ? 0 : i[1];
i[2] = (i[2] == 0x8000000) ? 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] == 0x8000000) ? 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 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);
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]))
setobjt2t(L, luaH_setnum(L, t, i + 1), &t->array[i]);
}
/* shrink array */
luaM_reallocarray(L, t->array, oldasize, nasize, TValue, t->memcat);
}
/* 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, luaH_set(L, t, &ok), gval(old));
}
}
if (nold != dummynode)
luaM_freearray(L, nold, twoto(oldhsize), LuaNode, t->memcat); /* free old array */
}
void luaH_resizearray(lua_State* L, Table* t, int nasize)
{
int nsize = (t->node == dummynode) ? 0 : sizenode(t);
resize(L, t, nasize, 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);
/* resize the table to new computed sizes */
resize(L, t, nasize, totaluse - na);
}
/*
** }=============================================================
*/
Table* luaH_new(lua_State* L, int narray, int nhash)
{
Table* t = luaM_newgco(L, Table, sizeof(Table), L->activememcat);
luaC_link(L, t, LUA_TTABLE);
t->metatable = NULL;
t->flags = 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);
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)
{
LuaNode* mp = mainposition(t, key);
if (!ttisnil(gval(mp)) || mp == dummynode)
{
LuaNode* othern;
LuaNode* n = getfreepos(t); /* get a free place */
if (n == NULL)
{ /* cannot find a free place? */
rehash(L, t, key); /* grow table */
return luaH_set(L, t, key); /* re-insert key into grown table */
}
LUAU_ASSERT(n != dummynode);
TValue mk;
getnodekey(L, &mk, mp);
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
{
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 LUAU_NOINLINE int unbound_search(Table* t, unsigned int j)
{
unsigned int i = j; /* i is zero or a present index */
j++;
/* find `i' and `j' such that i is present and j is not */
while (!ttisnil(luaH_getnum(t, j)))
{
i = j;
j *= 2;
if (j > cast_to(unsigned int, INT_MAX))
{ /* overflow? */
/* table was built with bad purposes: resort to linear search */
i = 1;
while (!ttisnil(luaH_getnum(t, i)))
i++;
return i - 1;
}
}
/* now do a binary search between them */
while (j - i > 1)
{
unsigned int m = (i + j) / 2;
if (ttisnil(luaH_getnum(t, m)))
j = m;
else
i = m;
}
return i;
}
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 must find a boundary in hash part */
else if (t->node == dummynode) /* hash part is empty? */
return j; /* that is easy... */
else
return unbound_search(t, j);
}
Table* luaH_clone(lua_State* L, Table* tt)
{
Table* t = luaM_newgco(L, Table, sizeof(Table), L->activememcat);
luaC_link(L, t, LUA_TTABLE);
t->metatable = tt->metatable;
t->flags = tt->flags;
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->flags = cast_byte(~0);
}

29
luau/VM/src/ltable.h Normal file
View File

@ -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"
#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)
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 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);
extern const LuaNode luaH_dummynode;

539
luau/VM/src/ltablib.cpp Normal file
View File

@ -0,0 +1,539 @@
// 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 "ltable.h"
#include "lstring.h"
#include "lgc.h"
#include "ldebug.h"
#include "lvm.h"
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_runerror(L, "Attempt to modify a readonly table");
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_runerror(L, "Attempt to modify a readonly table");
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);
}
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;
}
/*
** {======================================================
** Quicksort
** (based on `Algorithms in MODULA-3', Robert Sedgewick;
** Addison-Wesley, 1993.)
*/
static void set2(lua_State* L, int i, int j)
{
lua_rawseti(L, 1, i);
lua_rawseti(L, 1, j);
}
static int sort_comp(lua_State* L, int a, int b)
{
if (!lua_isnil(L, 2))
{ /* function? */
int res;
lua_pushvalue(L, 2);
lua_pushvalue(L, a - 1); /* -1 to compensate function */
lua_pushvalue(L, b - 2); /* -2 to compensate function and `a' */
lua_call(L, 2, 1);
res = lua_toboolean(L, -1);
lua_pop(L, 1);
return res;
}
else /* a < b? */
return lua_lessthan(L, a, b);
}
static void auxsort(lua_State* L, int l, int u)
{
while (l < u)
{ /* for tail recursion */
int i, j;
/* sort elements a[l], a[(l+u)/2] and a[u] */
lua_rawgeti(L, 1, l);
lua_rawgeti(L, 1, u);
if (sort_comp(L, -1, -2)) /* a[u] < a[l]? */
set2(L, l, u); /* swap a[l] - a[u] */
else
lua_pop(L, 2);
if (u - l == 1)
break; /* only 2 elements */
i = (l + u) / 2;
lua_rawgeti(L, 1, i);
lua_rawgeti(L, 1, l);
if (sort_comp(L, -2, -1)) /* a[i]<a[l]? */
set2(L, i, l);
else
{
lua_pop(L, 1); /* remove a[l] */
lua_rawgeti(L, 1, u);
if (sort_comp(L, -1, -2)) /* a[u]<a[i]? */
set2(L, i, u);
else
lua_pop(L, 2);
}
if (u - l == 2)
break; /* only 3 elements */
lua_rawgeti(L, 1, i); /* Pivot */
lua_pushvalue(L, -1);
lua_rawgeti(L, 1, u - 1);
set2(L, i, u - 1);
/* a[l] <= P == a[u-1] <= a[u], only need to sort from l+1 to u-2 */
i = l;
j = u - 1;
for (;;)
{ /* invariant: a[l..i] <= P <= a[j..u] */
/* repeat ++i until a[i] >= P */
while (lua_rawgeti(L, 1, ++i), sort_comp(L, -1, -2))
{
if (i >= u)
luaL_error(L, "invalid order function for sorting");
lua_pop(L, 1); /* remove a[i] */
}
/* repeat --j until a[j] <= P */
while (lua_rawgeti(L, 1, --j), sort_comp(L, -3, -1))
{
if (j <= l)
luaL_error(L, "invalid order function for sorting");
lua_pop(L, 1); /* remove a[j] */
}
if (j < i)
{
lua_pop(L, 3); /* pop pivot, a[i], a[j] */
break;
}
set2(L, i, j);
}
lua_rawgeti(L, 1, u - 1);
lua_rawgeti(L, 1, i);
set2(L, u - 1, i); /* swap pivot (a[u-1]) with a[i] */
/* 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;
}
auxsort(L, j, i); /* call recursively the smaller one */
} /* repeat the routine for the larger one */
}
static int sort(lua_State* L)
{
luaL_checktype(L, 1, LUA_TTABLE);
int n = lua_objlen(L, 1);
luaL_checkstack(L, 40, ""); /* assume array is smaller than 2^40 */
if (!lua_isnoneornil(L, 2)) /* is there a 2nd argument? */
luaL_checktype(L, 2, LUA_TFUNCTION);
lua_settop(L, 2); /* make sure there is two arguments */
auxsort(L, 1, n);
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_runerror(L, "Attempt to modify a readonly table");
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 const luaL_Reg tab_funcs[] = {
{"concat", tconcat},
{"foreach", foreach},
{"foreachi", foreachi},
{"getn", getn},
{"maxn", maxn},
{"insert", tinsert},
{"remove", tremove},
{"sort", sort},
{"pack", tpack},
{"unpack", tunpack},
{"move", tmove},
{"create", tcreate},
{"find", tfind},
{"clear", tclear},
{"freeze", tfreeze},
{"isfrozen", tisfrozen},
{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;
}

140
luau/VM/src/ltm.cpp Normal file
View File

@ -0,0 +1,140 @@
// 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 "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",
"__eq",
"__add",
"__sub",
"__mul",
"__div",
"__mod",
"__pow",
"__unm",
"__len",
"__lt",
"__le",
"__concat",
"__call",
"__type",
};
// 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");
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->flags |= 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 && 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));
}

57
luau/VM/src/ltm.h Normal file
View File

@ -0,0 +1,57 @@
// 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_EQ, /* last tag method with `fast' access */
TM_ADD,
TM_SUB,
TM_MUL,
TM_DIV,
TM_MOD,
TM_POW,
TM_UNM,
TM_LEN,
TM_LT,
TM_LE,
TM_CONCAT,
TM_CALL,
TM_TYPE,
TM_N /* number of elements in the enum */
} TMS;
// clang-format on
#define gfasttm(g, et, e) ((et) == NULL ? NULL : ((et)->flags & (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)->flags & (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);

37
luau/VM/src/ludata.cpp Normal file
View File

@ -0,0 +1,37 @@
// 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_link(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)
{
LUAU_ASSERT(u->tag < LUA_UTAG_LIMIT || u->tag == UTAG_IDTOR);
void (*dtor)(void*) = nullptr;
if (u->tag == UTAG_IDTOR)
memcpy(&dtor, &u->data + u->len - sizeof(dtor), sizeof(dtor));
else if (u->tag)
dtor = L->global->udatagc[u->tag];
if (dtor)
dtor(u->data);
luaM_freegco(L, u, sizeudata(u->len), u->memcat, page);
}

13
luau/VM/src/ludata.h Normal file
View File

@ -0,0 +1,13 @@
// 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
#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);

294
luau/VM/src/lutf8lib.cpp Normal file
View File

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

31
luau/VM/src/lvm.h Normal file
View File

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

2951
luau/VM/src/lvmexecute.cpp Normal file

File diff suppressed because it is too large Load Diff

370
luau/VM/src/lvmload.cpp Normal file
View File

@ -0,0 +1,370 @@
// 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>
LUAU_FASTFLAGVARIABLE(LuauBytecodeV2Force, false)
// 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 chunkid[LUA_IDSIZE];
luaO_chunkid(chunkid, chunkname, LUA_IDSIZE);
lua_pushfstring(L, "%s%.*s", chunkid, int(size - offset), data + offset);
return 1;
}
if (FFlag::LuauBytecodeV2Force ? (version != LBC_VERSION_FUTURE) : (version != LBC_VERSION && version != LBC_VERSION_FUTURE))
{
char chunkid[LUA_IDSIZE];
luaO_chunkid(chunkid, chunkname, LUA_IDSIZE);
lua_pushfstring(L, "%s: bytecode version mismatch (expected %d, got %d)", chunkid,
FFlag::LuauBytecodeV2Force ? LBC_VERSION_FUTURE : LBC_VERSION, 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->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);
setsvalue2n(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];
}
if (FFlag::LuauBytecodeV2Force || version == LBC_VERSION_FUTURE)
p->linedefined = readVarInt(data, size, offset);
else
p->linedefined = -1;
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_checkthreadsleep(L);
Closure* cl = luaF_newLclosure(L, 0, envt, main);
setclvalue(L, L->top, cl);
incr_top(L);
L->global->GCthreshold = GCthreshold;
return 0;
}

493
luau/VM/src/lvmutils.cpp Normal file
View File

@ -0,0 +1,493 @@
// 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));
setsvalue2s(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 void 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--;
setobjs2s(L, res, L->top);
}
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, "loop in gettable");
}
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);
if (h->readonly)
luaG_runerror(L, "Attempt to modify a readonly table");
TValue* oldval = luaH_set(L, h, key); /* do a primitive set */
L->cachedslot = gval2slot(h, oldval); /* remember slot to accelerate future lookups */
if (!ttisnil(oldval) || /* result is no nil? */
(tm = fasttm(L, h->metatable, TM_NEWINDEX)) == NULL)
{ /* or no TM? */
setobj2t(L, oldval, val);
luaC_barriert(L, h, val);
return;
}
/* else will try the tag method */
}
else if (ttisnil(tm = luaT_gettmbyobj(L, t, TM_NEWINDEX)))
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, "loop in settable");
}
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)
{
const TValue* tm1 = luaT_gettmbyobj(L, p1, event);
const TValue* tm2;
if (ttisnil(tm1))
return -1; /* no metamethod? */
tm2 = luaT_gettmbyobj(L, p2, event);
if (!luaO_rawequalObj(tm1, tm2)) /* different metamethods? */
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);
size_t ll = ls->len;
const char* r = getstr(rs);
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)
{
int res;
if (ttype(l) != ttype(r))
luaG_ordererror(L, l, r, TM_LT);
else if (ttisnumber(l))
return luai_numlt(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_LT)) == -1)
luaG_ordererror(L, l, r, TM_LT);
return res;
}
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)
{
setsvalue2s(L, top - n, luaS_newlstr(L, buffer, tl));
}
else
{
setsvalue2s(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)
{
switch (ttype(rb))
{
case LUA_TTABLE:
{
setnvalue(ra, cast_num(luaH_getn(hvalue(rb))));
break;
}
case LUA_TSTRING:
{
setnvalue(ra, cast_num(tsvalue(rb)->len));
break;
}
default:
{ /* try metamethod */
if (!call_binTM(L, rb, luaO_nilobject, ra, TM_LEN))
luaG_typeerror(L, rb, "get length of");
}
}
}

195
src/lib.rs Normal file
View File

@ -0,0 +1,195 @@
use std::env;
use std::fs;
use std::path::{Path, PathBuf};
pub struct Build {
out_dir: Option<PathBuf>,
target: Option<String>,
host: Option<String>,
}
pub struct Artifacts {
include_dir: PathBuf,
lib_dir: PathBuf,
libs: Vec<String>,
cpp_stdlib: Option<String>,
}
impl Build {
#[allow(clippy::new_without_default)]
pub fn new() -> Build {
Build {
out_dir: env::var_os("OUT_DIR").map(|s| PathBuf::from(s).join("luau-build")),
target: env::var("TARGET").ok(),
host: env::var("HOST").ok(),
}
}
pub fn out_dir<P: AsRef<Path>>(&mut self, path: P) -> &mut Build {
self.out_dir = Some(path.as_ref().to_path_buf());
self
}
pub fn target(&mut self, target: &str) -> &mut Build {
self.target = Some(target.to_string());
self
}
pub fn host(&mut self, host: &str) -> &mut Build {
self.host = Some(host.to_string());
self
}
pub fn build(&mut self) -> Artifacts {
let target = &self.target.as_ref().expect("TARGET not set")[..];
let host = &self.host.as_ref().expect("HOST not set")[..];
let out_dir = self.out_dir.as_ref().expect("OUT_DIR not set");
let lib_dir = out_dir.join("lib");
let include_dir = out_dir.join("include");
let source_dir_base = Path::new(env!("CARGO_MANIFEST_DIR"));
let ast_source_dir = source_dir_base.join("luau").join("Ast").join("src");
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");
let compiler_include_dir = source_dir_base
.join("luau")
.join("Compiler")
.join("include");
let vm_source_dir = source_dir_base.join("luau").join("VM").join("src");
let vm_include_dir = source_dir_base.join("luau").join("VM").join("include");
// Cleanup
if lib_dir.exists() {
fs::remove_dir_all(&lib_dir).unwrap();
}
fs::create_dir_all(&lib_dir).unwrap();
if include_dir.exists() {
fs::remove_dir_all(&include_dir).unwrap();
}
fs::create_dir_all(&include_dir).unwrap();
// Configure C++
let mut config = cc::Build::new();
config
.target(target)
.host(host)
.warnings(false)
.opt_level(2)
.cargo_metadata(false)
.flag_if_supported("-std=c++17")
.flag_if_supported("/std:c++17") // MSVC
.cpp(true);
if cfg!(not(debug_assertions)) {
config.define("NDEBUG", None);
}
// Build Ast
let ast_lib_name = "luauast";
config
.clone()
.include(&ast_include_dir)
.add_files_by_ext(&ast_source_dir, "cpp")
.out_dir(&lib_dir)
.compile(ast_lib_name);
// Build Compiler
let compiler_lib_name = "luaucompiler";
config
.clone()
.include(&compiler_include_dir)
.include(&ast_include_dir)
.define("LUACODE_API", "extern \"C\"")
.add_files_by_ext(&compiler_source_dir, "cpp")
.out_dir(&lib_dir)
.compile(compiler_lib_name);
// Build VM
let vm_lib_name = "luauvm";
config
.clone()
.include(&vm_include_dir)
.define("LUA_API", "extern \"C\"")
.define("LUA_USE_LONGJMP", "1")
.add_files_by_ext(&vm_source_dir, "cpp")
.out_dir(&lib_dir)
.compile(vm_lib_name);
for f in &["lua.h", "luaconf.h", "lualib.h"] {
fs::copy(vm_include_dir.join(f), include_dir.join(f)).unwrap();
}
for f in &["luacode.h"] {
fs::copy(compiler_include_dir.join(f), include_dir.join(f)).unwrap();
}
Artifacts {
lib_dir,
include_dir,
libs: vec![
ast_lib_name.to_string(),
compiler_lib_name.to_string(),
vm_lib_name.to_string(),
],
cpp_stdlib: Self::get_cpp_link_stdlib(target),
}
}
fn get_cpp_link_stdlib(target: &str) -> Option<String> {
// Copied from the `cc` crate
if target.contains("msvc") {
None
} else if target.contains("apple") {
Some("c++".to_string())
} else if target.contains("freebsd") {
Some("c++".to_string())
} else if target.contains("openbsd") {
Some("c++".to_string())
} else if target.contains("android") {
Some("c++_shared".to_string())
} else {
Some("stdc++".to_string())
}
}
}
impl Artifacts {
pub fn include_dir(&self) -> &Path {
&self.include_dir
}
pub fn lib_dir(&self) -> &Path {
&self.lib_dir
}
pub fn libs(&self) -> &[String] {
&self.libs
}
pub fn print_cargo_metadata(&self) {
println!("cargo:rustc-link-search=native={}", self.lib_dir.display());
for lib in self.libs.iter() {
println!("cargo:rustc-link-lib=static={}", lib);
}
if let Some(ref cpp_stdlib) = self.cpp_stdlib {
println!("cargo:rustc-link-lib={}", cpp_stdlib);
}
}
}
trait AddFilesByExt {
fn add_files_by_ext(&mut self, dir: &Path, ext: &str) -> &mut Self;
}
impl AddFilesByExt for cc::Build {
fn add_files_by_ext(&mut self, dir: &Path, ext: &str) -> &mut Self {
for entry in fs::read_dir(dir)
.unwrap()
.filter_map(|e| e.ok())
.filter(|e| e.path().extension() == Some(ext.as_ref()))
{
self.file(entry.path());
}
self
}
}

7
testcrate/Cargo.toml Normal file
View File

@ -0,0 +1,7 @@
[package]
name = "testcrate"
version = "0.1.0"
authors = ["Aleksandr Orlenko <zxteam@protonmail.com>"]
[build-dependencies.luau-src]
path = ".."

5
testcrate/build.rs Normal file
View File

@ -0,0 +1,5 @@
fn main() {
println!("cargo:rerun-if-changed=build.rs");
let artifacts = luau_src::Build::new().build();
artifacts.print_cargo_metadata();
}

71
testcrate/src/lib.rs Normal file
View File

@ -0,0 +1,71 @@
use std::os::raw::{c_char, c_int, c_long, c_void};
#[repr(C)]
#[allow(non_snake_case)]
pub struct lua_CompileOptions {
optimizationLevel: c_int,
debugLevel: c_int,
coverageLevel: c_int,
vectorLib: *const c_char,
vectorCtor: *const c_char,
mutableGlobals: *const *const c_char,
}
extern "C" {
pub fn free(ptr: *mut c_void);
pub fn luaL_newstate() -> *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);
pub fn lua_tolstring(state: *mut c_void, index: c_int, len: *mut c_long) -> *const c_char;
pub fn luau_compile(
source: *const c_char,
size: usize,
options: *mut lua_CompileOptions,
outsize: *mut usize,
) -> *mut c_char;
pub fn luau_load(
state: *mut c_void,
chunkname: *const c_char,
data: *const c_char,
size: usize,
env: c_int,
) -> c_int;
}
pub unsafe fn lua_getglobal(state: *mut c_void, k: *const c_char) {
lua_getfield(state, -10002 /* LUA_GLOBALSINDEX */, k);
}
#[test]
fn luau_works() {
use std::{ptr, slice};
unsafe {
let state = luaL_newstate();
assert!(state != ptr::null_mut());
luaL_openlibs(state);
let version = {
lua_getglobal(state, "_VERSION\0".as_ptr().cast());
let mut len: c_long = 0;
let version_ptr = lua_tolstring(state, -1, &mut len);
slice::from_raw_parts(version_ptr as *const u8, len as usize)
};
assert_eq!(version, "Luau".as_bytes());
let code = "function sum(a, b) return a + b end\0";
let mut bytecode_size = 0;
let bytecode = luau_compile(
code.as_ptr().cast(),
code.len() - 1,
ptr::null_mut(),
&mut bytecode_size,
);
let result = luau_load(state, "test\0".as_ptr().cast(), bytecode, bytecode_size, 0);
assert_eq!(result, 0);
free(bytecode.cast());
}
}