Initial commit

This commit is contained in:
2026-08-23 11:11:36 +03:00
commit 0548f8de26
97 changed files with 20841 additions and 0 deletions
+23
View File
@@ -0,0 +1,23 @@
#include <bridge.h>
#include "Luau/AstJsonEncoder.h"
#define ZIG_LUAU_ANALYSIS(name) ZIG_FN(Luau_Analysis_##name)
ZIG_EXPORT const char* ZIG_LUAU_ANALYSIS(AstJsonEncoder_toJson)(Luau::AstNode* node, size_t* len)
{
std::string res = Luau::toJson(node);
char* copy = static_cast<char*>(malloc(res.size()));
if (!copy)
return nullptr;
memcpy(copy, res.data(), res.size());
*len = res.size();
return copy;
}
ZIG_EXPORT void ZIG_LUAU_ANALYSIS(AstJsonEncoder_free)(const char* json)
{
free((void*)json);
}
+46
View File
@@ -0,0 +1,46 @@
const std = @import("std");
const Ast = @import("../Ast/Ast.zig");
extern "c" fn zig_Luau_Analysis_AstJsonEncoder_toJson(*Ast.Node, *usize) [*c]const u8;
extern "c" fn zig_Luau_Analysis_AstJsonEncoder_free([*c]const u8) void;
pub fn toJson(allocator: std.mem.Allocator, node: *Ast.Node) ![]const u8 {
var size: usize = 0;
const json = zig_Luau_Analysis_AstJsonEncoder_toJson(node, &size);
defer zig_Luau_Analysis_AstJsonEncoder_free(json);
if (json == null)
return error.OutOfMemory;
const result = try allocator.dupe(u8, json[0..size]);
return result;
}
test toJson {
const Lexer = @import("../Ast/Lexer.zig");
const Parser = @import("../Ast/Parser.zig");
const Allocator = @import("../Ast/Allocator.zig");
{
const allocator = Allocator.init();
defer allocator.deinit();
const table = Lexer.AstNameTable.init(allocator);
defer table.deinit();
const source =
\\local x = 1
\\
;
var parse_result = Parser.parse(source, table, allocator, .{});
defer parse_result.deinit();
const root = parse_result.root;
const data = try toJson(std.testing.allocator, @ptrCast(@alignCast(root)));
defer std.testing.allocator.free(data);
try std.testing.expectEqualStrings(
\\{"type":"AstStatBlock","location":"0,0 - 1,0","hasEnd":true,"body":[{"type":"AstStatLocal","location":"0,0 - 0,11","vars":[{"luauType":null,"name":"x","isConst":false,"type":"AstLocal","location":"0,6 - 0,7"}],"values":[{"type":"AstExprConstantNumber","location":"0,10 - 0,11","value":1}]}]}
, data);
}
}
+113
View File
@@ -0,0 +1,113 @@
#include <bridge.h>
#include "Luau/Ast.h"
#include "Luau/Frontend.h"
#include "Luau/ModuleResolver.h"
#define ZIG_LUAU_ANALYSIS(name) ZIG_FN(Luau_Analysis_##name)
ZIG_EXPORT using FileResolver_readSource = const char* (*)(void* ctx, const char* name, size_t len, size_t* outLen, unsigned char* outType);
ZIG_EXPORT using FileResolver_resolveModule = const char* (*)(void* ctx, const char* name, size_t len, const char* node, size_t nodeLen, size_t* outLen);
ZIG_EXPORT using FileResolver_getHumanReadableModuleName = const char* (*)(void* ctx, const char* name, size_t len, size_t* outLen);
ZIG_EXPORT using FileResolver_freeString = void (*)(void* ctx, const char* str, size_t len);
struct zig_FileResolver : Luau::FileResolver
{
void* ctx = nullptr;
FileResolver_readSource c_readSource;
FileResolver_resolveModule c_resolveModule;
FileResolver_getHumanReadableModuleName c_getHumanReadableModuleName;
FileResolver_freeString c_freeString;
zig_FileResolver(
void* ctx,
FileResolver_readSource fn_readSource,
FileResolver_resolveModule fn_resolveModule,
FileResolver_getHumanReadableModuleName fn_getHumanReadableModuleName,
FileResolver_freeString fn_freeString
)
: ctx(ctx),
c_readSource(fn_readSource),
c_resolveModule(fn_resolveModule),
c_getHumanReadableModuleName(fn_getHumanReadableModuleName),
c_freeString(fn_freeString)
{
}
std::optional<Luau::SourceCode> readSource(const Luau::ModuleName& name) override
{
size_t len = 0;
unsigned char type = 0;
const char* source = c_readSource(ctx, name.data(), name.size(), &len, &type);
if (!source)
return std::nullopt;
Luau::SourceCode::Type sourceType;
if (type == 0)
{
sourceType = Luau::SourceCode::Script;
}
else if (type == 1)
{
sourceType = Luau::SourceCode::Module;
}
else
{
sourceType = Luau::SourceCode::None;
}
std::string sourceStr(source, len);
c_freeString(ctx, source, len);
return Luau::SourceCode{sourceStr, sourceType};
}
std::optional<Luau::ModuleInfo> resolveModule(const Luau::ModuleInfo* context, Luau::AstExpr* node, const Luau::TypeCheckLimits& limits) override
{
if (Luau::AstExprConstantString* expr = node->as<Luau::AstExprConstantString>())
{
std::string path{expr->value.data, expr->value.size};
size_t len = 0;
const char* result = c_resolveModule(ctx, context->name.c_str(), context->name.size(), path.c_str(), path.size(), &len);
if (result)
{
std::string resolvedPath(result, len);
c_freeString(ctx, result, len);
return {{resolvedPath}};
}
}
return std::nullopt;
}
std::string getHumanReadableModuleName(const Luau::ModuleName& name) const override
{
size_t len = 0;
const char* nameStr = c_getHumanReadableModuleName(ctx, name.data(), name.size(), &len);
std::string result(nameStr, len);
c_freeString(ctx, nameStr, len);
return result;
}
};
ZIG_EXPORT zig_FileResolver* ZIG_LUAU_ANALYSIS(FileResolver_init)(
void* ctx,
FileResolver_readSource fn_readSource,
FileResolver_resolveModule fn_resolveModule,
FileResolver_getHumanReadableModuleName fn_getHumanReadableModuleName,
FileResolver_freeString fn_freeString
)
{
return new zig_FileResolver(
ctx,
fn_readSource,
fn_resolveModule,
fn_getHumanReadableModuleName,
fn_freeString
);
}
ZIG_EXPORT void* ZIG_LUAU_ANALYSIS(FileResolver_dtor)(zig_FileResolver* resolver)
{
void* ctx = resolver->ctx;
delete resolver;
return ctx;
}
+92
View File
@@ -0,0 +1,92 @@
const std = @import("std");
const FileResolver_readSource = fn (ud: *anyopaque, name: [*c]const u8, len: usize, outLen: *usize, outType: *u8) callconv(.c) ?[*]const u8;
const FileResolver_resolveModule = fn (ud: *anyopaque, name: [*c]const u8, len: usize, node: [*c]const u8, nodeLen: usize, outLen: *usize) callconv(.c) ?[*]const u8;
const FileResolver_getHumanReadableModuleName = fn (ud: *anyopaque, name: [*c]const u8, len: usize, outLen: *usize) callconv(.c) [*]const u8;
const FileResolver_freeString = fn (ud: *anyopaque, str: [*c]const u8, len: usize) callconv(.c) void;
extern "c" fn zig_Luau_Analysis_FileResolver_init(
*anyopaque,
*const FileResolver_readSource,
*const FileResolver_resolveModule,
*const FileResolver_getHumanReadableModuleName,
*const FileResolver_freeString,
) *anyopaque;
extern "c" fn zig_Luau_Analysis_FileResolver_dtor(*anyopaque) *anyopaque;
pub const SourceCodeType = enum {
Script,
Module,
None,
};
pub fn FileResolver(comptime T: type) type {
return opaque {
const Self = @This();
pub fn init(ctx: *T) *Self {
return @ptrCast(zig_Luau_Analysis_FileResolver_init(
@ptrCast(@alignCast(ctx)),
struct {
fn inner(ud: *anyopaque, name: [*c]const u8, len: usize, outLen: *usize, outType: *u8) callconv(.c) ?[*]const u8 {
const res: struct { []const u8, SourceCodeType } = @call(.always_inline, T.readSource, .{ @as(*T, @ptrCast(@alignCast(ud))), name[0..len] }) orelse return null;
const buf, const t = res;
outLen.* = buf.len;
outType.* = @intFromEnum(t);
return buf.ptr;
}
}.inner,
struct {
fn inner(ud: *anyopaque, name: [*c]const u8, len: usize, node: [*c]const u8, nodeLen: usize, outLen: *usize) callconv(.c) ?[*]const u8 {
const res: []const u8 = @call(.always_inline, T.resolveModule, .{ @as(*T, @ptrCast(@alignCast(ud))), name[0..len], node[0..nodeLen] }) orelse return null;
outLen.* = res.len;
return res.ptr;
}
}.inner,
struct {
fn inner(ud: *anyopaque, name: [*c]const u8, len: usize, outLen: *usize) callconv(.c) [*]const u8 {
const res: []const u8 = @call(.always_inline, T.getHumanReadableModuleName, .{ @as(*T, @ptrCast(@alignCast(ud))), name[0..len] });
outLen.* = res.len;
return res.ptr;
}
}.inner,
struct {
fn inner(ud: *anyopaque, str: [*c]const u8, len: usize) callconv(.c) void {
@call(.always_inline, T.freeString, .{ @as(*T, @ptrCast(@alignCast(ud))), str[0..len] });
}
}.inner,
));
}
pub fn deinit(self: *Self) void {
const ctx = zig_Luau_Analysis_FileResolver_dtor(self);
if (@hasDecl(T, "deinit")) {
@call(.always_inline, T.deinit, .{@as(*T, @ptrCast(@alignCast(ctx)))});
}
}
};
}
test "FileResolver" {
const Sample = struct {
const Self = @This();
pub fn readSource(_: *Self, _: []const u8) ?struct { []const u8, SourceCodeType } {
return null;
}
pub fn resolveModule(_: *Self, _: []const u8, _: []const u8) ?[]const u8 {
return null;
}
pub fn getHumanReadableModuleName(_: *Self, name: []const u8) []const u8 {
return name;
}
pub fn freeString(_: *Self, _: []const u8) void {}
};
const SampleResolver = FileResolver(Sample);
var sample: Sample = .{};
const resolver = SampleResolver.init(&sample);
defer resolver.deinit();
}
+466
View File
@@ -0,0 +1,466 @@
// This code is based on https://github.com/luau-lang/luau/blob/68cdcc4a3a5f3ed23186c4f7f6b8a5aacf835bee/CLI/src/FileUtils.cpp
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "./FileUtils.h"
#include "Luau/Common.h"
#ifdef _WIN32
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <direct.h>
#include <windows.h>
#else
#include <dirent.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#endif
#include <string.h>
#include <string_view>
#ifdef _WIN32
static std::wstring fromUtf8(const std::string& path)
{
size_t result = MultiByteToWideChar(CP_UTF8, 0, path.data(), int(path.size()), nullptr, 0);
LUAU_ASSERT(result);
std::wstring buf(result, L'\0');
MultiByteToWideChar(CP_UTF8, 0, path.data(), int(path.size()), &buf[0], int(buf.size()));
return buf;
}
static std::string toUtf8(const std::wstring& path)
{
size_t result = WideCharToMultiByte(CP_UTF8, 0, path.data(), int(path.size()), nullptr, 0, nullptr, nullptr);
LUAU_ASSERT(result);
std::string buf(result, '\0');
WideCharToMultiByte(CP_UTF8, 0, path.data(), int(path.size()), &buf[0], int(buf.size()), nullptr, nullptr);
return buf;
}
#endif
bool isAbsolutePath(std::string_view path)
{
#ifdef _WIN32
// Must either begin with "X:/", "X:\", "/", or "\", where X is a drive letter
return (path.size() >= 3 && isalpha(path[0]) && path[1] == ':' && (path[2] == '/' || path[2] == '\\')) ||
(path.size() >= 1 && (path[0] == '/' || path[0] == '\\'));
#else
// Must begin with '/'
return path.size() >= 1 && path[0] == '/';
#endif
}
std::optional<std::string> getCurrentWorkingDirectory()
{
// 2^17 - derived from the Windows path length limit
constexpr size_t maxPathLength = 131072;
constexpr size_t initialPathLength = 260;
std::string directory(initialPathLength, '\0');
char* cstr = nullptr;
while (!cstr && directory.size() <= maxPathLength)
{
#ifdef _WIN32
cstr = _getcwd(directory.data(), static_cast<int>(directory.size()));
#else
cstr = getcwd(directory.data(), directory.size());
#endif
if (cstr)
{
directory.resize(strlen(cstr));
return directory;
}
else if (errno != ERANGE || directory.size() * 2 > maxPathLength)
{
return std::nullopt;
}
else
{
directory.resize(directory.size() * 2);
}
}
return std::nullopt;
}
std::string normalizePath(std::string_view path)
{
const std::vector<std::string_view> components = splitPath(path);
std::vector<std::string_view> normalizedComponents;
const bool isAbsolute = isAbsolutePath(path);
// 1. Normalize path components
const size_t startIndex = isAbsolute ? 1 : 0;
for (size_t i = startIndex; i < components.size(); i++)
{
std::string_view component = components[i];
if (component == "..")
{
if (normalizedComponents.empty())
{
if (!isAbsolute)
{
normalizedComponents.emplace_back("..");
}
}
else if (normalizedComponents.back() == "..")
{
normalizedComponents.emplace_back("..");
}
else
{
normalizedComponents.pop_back();
}
}
else if (!component.empty() && component != ".")
{
normalizedComponents.emplace_back(component);
}
}
std::string normalizedPath;
// 2. Add correct prefix to formatted path
if (isAbsolute)
{
normalizedPath += components[0];
normalizedPath += "/";
}
else if (normalizedComponents.empty() || normalizedComponents[0] != "..")
{
normalizedPath += "./";
}
// 3. Join path components to form the normalized path
for (auto iter = normalizedComponents.begin(); iter != normalizedComponents.end(); ++iter)
{
if (iter != normalizedComponents.begin())
normalizedPath += "/";
normalizedPath += *iter;
}
if (normalizedPath.size() >= 2 && normalizedPath[normalizedPath.size() - 1] == '.' && normalizedPath[normalizedPath.size() - 2] == '.')
normalizedPath += "/";
return normalizedPath;
}
std::optional<std::string> resolvePath(std::string_view path, std::string_view baseFilePath)
{
std::optional<std::string> baseFilePathParent = getParentPath(baseFilePath);
if (!baseFilePathParent)
return std::nullopt;
return normalizePath(joinPaths(*baseFilePathParent, path));
}
bool hasFileExtension(std::string_view name, const std::vector<std::string>& extensions)
{
for (const std::string& extension : extensions)
{
if (name.size() >= extension.size() && name.substr(name.size() - extension.size()) == extension)
return true;
}
return false;
}
std::optional<std::string> readFile(const std::string& name)
{
#ifdef _WIN32
FILE* file = _wfopen(fromUtf8(name).c_str(), L"rb");
#else
FILE* file = fopen(name.c_str(), "rb");
#endif
if (!file)
return std::nullopt;
fseek(file, 0, SEEK_END);
long length = ftell(file);
if (length < 0)
{
fclose(file);
return std::nullopt;
}
fseek(file, 0, SEEK_SET);
std::string result(length, 0);
size_t read = fread(result.data(), 1, length, file);
fclose(file);
if (read != size_t(length))
return std::nullopt;
// Skip first line if it's a shebang
if (length > 2 && result[0] == '#' && result[1] == '!')
result.erase(0, result.find('\n'));
return result;
}
std::optional<std::string> readStdin()
{
std::string result;
char buffer[4096] = {};
while (fgets(buffer, sizeof(buffer), stdin) != nullptr)
result.append(buffer);
// If eof was not reached for stdin, then a read error occurred
if (!feof(stdin))
return std::nullopt;
return result;
}
template<typename Ch>
static void joinPaths(std::basic_string<Ch>& str, const Ch* lhs, const Ch* rhs)
{
str = lhs;
if (!str.empty() && str.back() != '/' && str.back() != '\\' && *rhs != '/' && *rhs != '\\')
str += '/';
str += rhs;
}
#ifdef _WIN32
static bool traverseDirectoryRec(const std::wstring& path, const std::function<void(const std::string& name)>& callback)
{
std::wstring query = path + std::wstring(L"/*");
WIN32_FIND_DATAW data;
HANDLE h = FindFirstFileW(query.c_str(), &data);
if (h == INVALID_HANDLE_VALUE)
return false;
std::wstring buf;
do
{
if (wcscmp(data.cFileName, L".") != 0 && wcscmp(data.cFileName, L"..") != 0)
{
joinPaths(buf, path.c_str(), data.cFileName);
if (data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
{
// Skip reparse points to avoid handling cycles
}
else if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
traverseDirectoryRec(buf, callback);
}
else
{
callback(toUtf8(buf));
}
}
} while (FindNextFileW(h, &data));
FindClose(h);
return true;
}
bool traverseDirectory(const std::string& path, const std::function<void(const std::string& name)>& callback)
{
return traverseDirectoryRec(fromUtf8(path), callback);
}
#else
static bool traverseDirectoryRec(const std::string& path, const std::function<void(const std::string& name)>& callback)
{
int fd = open(path.c_str(), O_DIRECTORY);
DIR* dir = fdopendir(fd);
if (!dir)
return false;
std::string buf;
while (dirent* entry = readdir(dir))
{
const dirent& data = *entry;
if (strcmp(data.d_name, ".") != 0 && strcmp(data.d_name, "..") != 0)
{
joinPaths(buf, path.c_str(), data.d_name);
#if defined(DTTOIF)
mode_t mode = DTTOIF(data.d_type);
#else
mode_t mode = 0;
#endif
// we need to stat an UNKNOWN to be able to tell the type
if ((mode & S_IFMT) == 0)
{
struct stat st = {};
#ifdef _ATFILE_SOURCE
fstatat(fd, data.d_name, &st, 0);
#else
lstat(buf.c_str(), &st);
#endif
mode = st.st_mode;
}
if (mode == S_IFDIR)
{
traverseDirectoryRec(buf, callback);
}
else if (mode == S_IFREG)
{
callback(buf);
}
else if (mode == S_IFLNK)
{
// Skip symbolic links to avoid handling cycles
}
}
}
closedir(dir);
return true;
}
bool traverseDirectory(const std::string& path, const std::function<void(const std::string& name)>& callback)
{
return traverseDirectoryRec(path, callback);
}
#endif
bool isFile(const std::string& path)
{
#ifdef _WIN32
DWORD fileAttributes = GetFileAttributesW(fromUtf8(path).c_str());
if (fileAttributes == INVALID_FILE_ATTRIBUTES)
return false;
return (fileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0;
#else
struct stat st = {};
lstat(path.c_str(), &st);
return (st.st_mode & S_IFMT) == S_IFREG;
#endif
}
bool isDirectory(const std::string& path)
{
#ifdef _WIN32
DWORD fileAttributes = GetFileAttributesW(fromUtf8(path).c_str());
if (fileAttributes == INVALID_FILE_ATTRIBUTES)
return false;
return (fileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
#else
struct stat st = {};
lstat(path.c_str(), &st);
return (st.st_mode & S_IFMT) == S_IFDIR;
#endif
}
std::vector<std::string_view> splitPath(std::string_view path)
{
std::vector<std::string_view> components;
size_t pos = 0;
size_t nextPos = path.find_first_of("\\/", pos);
while (nextPos != std::string::npos)
{
components.push_back(path.substr(pos, nextPos - pos));
pos = nextPos + 1;
nextPos = path.find_first_of("\\/", pos);
}
components.push_back(path.substr(pos));
return components;
}
std::string joinPaths(std::string_view lhs, std::string_view rhs)
{
std::string result = std::string(lhs);
if (!result.empty() && result.back() != '/' && result.back() != '\\')
result += '/';
result += rhs;
return result;
}
std::optional<std::string> getParentPath(std::string_view path)
{
if (path == "" || path == "." || path == "/")
return std::nullopt;
#ifdef _WIN32
if (path.size() == 2 && path.back() == ':')
return std::nullopt;
#endif
size_t slash = path.find_last_of("\\/", path.size() - 1);
if (slash == 0)
return "/";
if (slash != std::string::npos)
return std::string(path.substr(0, slash));
return "";
}
static std::string getExtension(const std::string& path)
{
size_t dot = path.find_last_of(".\\/");
if (dot == std::string::npos || path[dot] != '.')
return "";
return path.substr(dot);
}
std::vector<std::string> getSourceFiles(int argc, char** argv)
{
std::vector<std::string> files;
for (int i = 1; i < argc; ++i)
{
// Early out once we reach --program-args,-a since the remaining args are passed to lua
if (strcmp(argv[i], "--program-args") == 0 || strcmp(argv[i], "-a") == 0)
return files;
// Treat '-' as a special file whose source is read from stdin
// All other arguments that start with '-' are skipped
if (argv[i][0] == '-' && argv[i][1] != '\0')
continue;
std::string normalized = normalizePath(argv[i]);
if (isDirectory(normalized))
{
traverseDirectory(
normalized,
[&](const std::string& name)
{
std::string ext = getExtension(name);
if (ext == ".lua" || ext == ".luau")
files.push_back(name);
}
);
}
else
{
files.push_back(normalized);
}
}
return files;
}
+30
View File
@@ -0,0 +1,30 @@
// This code is based on https://github.com/luau-lang/luau/blob/68cdcc4a3a5f3ed23186c4f7f6b8a5aacf835bee/CLI/include/Luau/FileUtils.h
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include <optional>
#include <string>
#include <string_view>
#include <functional>
#include <vector>
std::optional<std::string> getCurrentWorkingDirectory();
std::string normalizePath(std::string_view path);
std::optional<std::string> resolvePath(std::string_view relativePath, std::string_view baseFilePath);
std::optional<std::string> readFile(const std::string& name);
std::optional<std::string> readStdin();
bool hasFileExtension(std::string_view name, const std::vector<std::string>& extensions);
bool isAbsolutePath(std::string_view path);
bool isFile(const std::string& path);
bool isDirectory(const std::string& path);
bool traverseDirectory(const std::string& path, const std::function<void(const std::string& name)>& callback);
std::vector<std::string_view> splitPath(std::string_view path);
std::string joinPaths(std::string_view lhs, std::string_view rhs);
std::optional<std::string> getParentPath(std::string_view path);
std::vector<std::string> getSourceFiles(int argc, char** argv);
+182
View File
@@ -0,0 +1,182 @@
#include <bridge.h>
#include "Luau/Frontend.h"
#include "Luau/BuiltinDefinitions.h"
#define ZIG_LUAU_ANALYSIS(name) ZIG_FN(Luau_Analysis_##name)
ZIG_EXPORT struct luau_FrontendOptions
{
// When true, we retain full type information about every term in the AST.
// Setting this to false cuts back on RAM and is a good idea for batch
// jobs where the type graph is not deeply inspected after typechecking
// is complete.
bool retainFullTypeGraphs = false;
// Run typechecking only in mode required for autocomplete (strict mode in
// order to get more precise type information)
bool forAutocomplete = false;
bool runLintChecks = false;
// When true, some internal complexity limits will be scaled down for modules that miss the limit set by moduleTimeLimitSec
bool applyInternalLimitScaling = false;
};
ZIG_EXPORT Luau::Frontend* ZIG_LUAU_ANALYSIS(Frontend_init)(Luau::FileResolver* fileResolver, Luau::ConfigResolver* configResolver, luau_FrontendOptions options)
{
Luau::FrontendOptions frontendOptions;
frontendOptions.retainFullTypeGraphs = options.retainFullTypeGraphs;
frontendOptions.runLintChecks = options.runLintChecks;
frontendOptions.forAutocomplete = options.forAutocomplete;
frontendOptions.applyInternalLimitScaling = options.applyInternalLimitScaling;
return new Luau::Frontend(fileResolver, configResolver, frontendOptions);
}
ZIG_EXPORT using Frontend_loadDefinitionError = bool (*)(void* ctx, const char* str, size_t len, Luau::Location location);
ZIG_EXPORT bool ZIG_LUAU_ANALYSIS(Frontend_loadDefinitionFile)(
Luau::Frontend* frontend,
const char* src,
size_t srcLen,
const char* packagename,
bool captureComments,
bool typeCheckForAutocomplete,
void* ctx,
Frontend_loadDefinitionError fn_loadDefinitionError
)
{
std::string source(src, srcLen);
std::string packageName(packagename);
Luau::LoadDefinitionFileResult result = frontend->loadDefinitionFile(frontend->globals, frontend->globals.globalScope, source, packageName, captureComments, typeCheckForAutocomplete);
if (!result.success)
{
if (fn_loadDefinitionError)
{
if (result.parseResult.errors.size() > 0){
Luau::ParseError error = result.parseResult.errors.front();
std::string msg = error.getMessage();
Luau::Location location = error.getLocation();
fn_loadDefinitionError(ctx, msg.c_str(), msg.size(), location);
} else if (result.module->errors.size() > 0) {
Luau::TypeError error = result.module->errors.front();
std::string msg = "<type error>";
Luau::Location location = error.location;
fn_loadDefinitionError(ctx, msg.c_str(), msg.size(), location);
}
}
}
return result.success;
}
ZIG_EXPORT void ZIG_LUAU_ANALYSIS(Frontend_registerBuiltinGlobals)(Luau::Frontend& frontend)
{
Luau::registerBuiltinGlobals(frontend, frontend.globals);
}
ZIG_EXPORT void ZIG_LUAU_ANALYSIS(Frontend_freeze)(Luau::Frontend* frontend)
{
Luau::freeze(frontend->globals.globalTypes);
}
ZIG_EXPORT void ZIG_LUAU_ANALYSIS(Frontend_queueModuleCheck)(Luau::Frontend* frontend, const char* path, size_t pathLen)
{
std::string modulePath(path, pathLen);
frontend->queueModuleCheck(modulePath);
}
ZIG_EXPORT using Frontend_checkedModule = bool (*)(void* ctx, const char* str, size_t len);
ZIG_EXPORT using Frontend_checkedModuleError = void (*)(
void* ctx,
const char* moduleName,
size_t moduleNameLen,
const char* errorMessage,
size_t errorMessageLen,
Luau::Location location
);
ZIG_EXPORT bool ZIG_LUAU_ANALYSIS(Frontend_checkQueuedModules)(
Luau::Frontend* frontend,
void* ctx,
Frontend_checkedModule fn_checkedModule,
Frontend_checkedModuleError fn_checkedModuleError
)
{
std::vector<Luau::ModuleName> checkedModules;
try
{
checkedModules = frontend->checkQueuedModules(std::nullopt);
}
catch (const Luau::InternalCompilerError& ice)
{
Luau::Location location = ice.location ? *ice.location : Luau::Location();
std::string moduleName = ice.moduleName ? *ice.moduleName : "<unknown module>";
std::string readableName = frontend->fileResolver->getHumanReadableModuleName(moduleName);
if (fn_checkedModuleError)
fn_checkedModuleError(ctx, readableName.c_str(), readableName.size(), ice.message.c_str(), ice.message.size(), location);
return false;
}
for (const auto& module : checkedModules)
{
if (!fn_checkedModule(ctx, module.c_str(), module.size()))
return false;
}
return true;
}
ZIG_EXPORT using Frontend_checkedResult = void (*)(
void* ctx,
unsigned char kind,
const char* moduleName,
size_t moduleNameLen,
const char* errorMessage,
size_t errorMessageLen,
const char* typeName,
Luau::Location location
);
ZIG_EXPORT unsigned char ZIG_LUAU_ANALYSIS(Frontend_getCheckResult)(
Luau::Frontend* frontend,
const char* moduleName,
size_t moduleNameLen,
bool accumulateNested,
bool forAutocomplete,
void* ctx,
Frontend_checkedResult fn_checkedResult
)
{
std::string name(moduleName, moduleNameLen);
std::optional<Luau::CheckResult> cr = frontend->getCheckResult(name, false);
if (!cr)
{
return 0;
}
for (auto& error : cr->errors)
{
std::string readableName = frontend->fileResolver->getHumanReadableModuleName(error.moduleName);
if (const Luau::SyntaxError* syntaxError = Luau::get_if<Luau::SyntaxError>(&error.data))
fn_checkedResult(ctx, 0, readableName.c_str(), readableName.size(), syntaxError->message.c_str(), syntaxError->message.size(), "SyntaxError", error.location);
else
{
std::string msg = Luau::toString(error, Luau::TypeErrorToStringOptions{frontend->fileResolver});
fn_checkedResult(ctx, 0, readableName.c_str(), readableName.size(), msg.c_str(), msg.size(), "TypeError", error.location);
}
}
std::string readableName = frontend->fileResolver->getHumanReadableModuleName(name);
for (auto& error : cr->lintResult.errors)
fn_checkedResult(ctx, 1, readableName.c_str(), readableName.size(), error.text.c_str(), error.text.size(), Luau::LintWarning::getName(error.code), error.location);
for (auto& warning : cr->lintResult.warnings)
fn_checkedResult(ctx, 2, readableName.c_str(), readableName.size(), warning.text.c_str(), warning.text.size(), Luau::LintWarning::getName(warning.code), warning.location);
return cr->errors.empty() && cr->lintResult.errors.empty() ? 1 : 2;
}
ZIG_EXPORT void ZIG_LUAU_ANALYSIS(Frontend_dtor)(Luau::Frontend* frontend)
{
delete frontend;
}
+383
View File
@@ -0,0 +1,383 @@
const std = @import("std");
const Location = @import("../Ast/Location.zig").Location;
const GenericConfigResolver = @import("GenericConfigResolver.zig");
pub const LoadDefinitionResult = extern struct {
success: bool,
};
pub const Options = extern struct {
/// When true, we retain full type information about every term in the AST.
/// Setting this to false cuts back on RAM and is a good idea for batch
/// jobs where the type graph is not deeply inspected after typechecking
/// is complete.
retainFullTypeGraphs: bool = false,
/// Run typechecking only in mode required for autocomplete (strict mode in
/// order to get more precise type information)
forAutocomplete: bool = false,
runLintChecks: bool = false,
/// When true, some internal complexity limits will be scaled down for modules that miss the limit set by moduleTimeLimitSec
applyInternalLimitScaling: bool = false,
};
pub const CheckResultStatus = enum(u8) {
None,
Success,
Error,
};
pub const CheckResultErrorKind = enum(u8) {
Error,
LintError,
LintWarning,
};
const loadDefinitionFileErrorFn = fn (?*anyopaque, [*c]const u8, usize, Location) callconv(.c) void;
const CheckedModuleFn = fn (?*anyopaque, [*c]const u8, usize) callconv(.c) bool;
const CheckedModuleErrorFn = fn (?*anyopaque, [*c]const u8, usize, [*c]const u8, usize, Location) callconv(.c) void;
const CheckedResultFn = fn (?*anyopaque, u8, [*c]const u8, usize, [*c]const u8, usize, [*c]const u8, Location) callconv(.c) void;
extern "c" fn zig_luau_free(ptr: *anyopaque) void;
extern "c" fn zig_Luau_Analysis_Frontend_init(*anyopaque, *GenericConfigResolver.GenericConfigResolver, Options) *Frontend;
extern "c" fn zig_Luau_Analysis_Frontend_registerBuiltinGlobals(*Frontend) void;
extern "c" fn zig_Luau_Analysis_Frontend_freeze(*Frontend) void;
extern "c" fn zig_Luau_Analysis_Frontend_loadDefinitionFile(*Frontend, [*c]const u8, usize, [*c]const u8, bool, bool, ?*anyopaque, ?*const loadDefinitionFileErrorFn) bool;
extern "c" fn zig_Luau_Analysis_Frontend_queueModuleCheck(*Frontend, [*c]const u8, usize) void;
extern "c" fn zig_Luau_Analysis_Frontend_checkQueuedModules(*Frontend, ?*anyopaque, *const CheckedModuleFn, ?*const CheckedModuleErrorFn) bool;
extern "c" fn zig_Luau_Analysis_Frontend_getCheckResult(*Frontend, [*c]const u8, usize, bool, bool, ?*anyopaque, *const CheckedResultFn) u8;
extern "c" fn zig_Luau_Analysis_Frontend_dtor(*Frontend) void;
pub const Frontend = opaque {
pub fn registerBuiltinGlobals(self: *Frontend) void {
zig_Luau_Analysis_Frontend_registerBuiltinGlobals(self);
}
pub fn freeze(self: *Frontend) void {
zig_Luau_Analysis_Frontend_freeze(self);
}
pub fn queueModuleCheck(self: *Frontend, path: []const u8) void {
return zig_Luau_Analysis_Frontend_queueModuleCheck(self, path.ptr, path.len);
}
pub fn checkQueuedModules(
self: *Frontend,
context: anytype,
comptime checkedModule: *const fn (@TypeOf(context), [:0]const u8) bool,
comptime checkedModuleError: *const fn (@TypeOf(context), moduleName: [:0]const u8, errMsg: [:0]const u8, Location) void,
) bool {
const T = @TypeOf(context);
if (@typeInfo(T) != .pointer and T != void)
@compileError("context must be a pointer type or void");
if (T != void and @typeInfo(T).pointer.is_const)
@compileError("context must be a mutable pointer type or void");
return zig_Luau_Analysis_Frontend_checkQueuedModules(
self,
if (T == void) null else @ptrCast(@alignCast(context)),
struct {
fn inner(
ud: ?*anyopaque,
name: [*c]const u8,
len: usize,
) callconv(.c) bool {
return @call(.always_inline, checkedModule, .{
if (T == void) undefined else @as(T, @ptrCast(@alignCast(ud.?))),
name[0..len :0],
});
}
}.inner,
struct {
fn inner(
ud: ?*anyopaque,
name: [*c]const u8,
len: usize,
msg: [*c]const u8,
msgLen: usize,
loc: Location,
) callconv(.c) void {
@call(.always_inline, checkedModuleError, .{
if (T == void) undefined else @as(T, @ptrCast(@alignCast(ud.?))),
name[0..len :0],
msg[0..msgLen :0],
loc,
});
}
}.inner,
);
}
pub fn getCheckResult(
self: *Frontend,
moduleName: []const u8,
captureComments: bool,
typeCheckForAutocomplete: bool,
context: anytype,
comptime checkFn: *const fn (@TypeOf(context), CheckResultErrorKind, [:0]const u8, [:0]const u8, [:0]const u8, Location) void,
) CheckResultStatus {
const T = @TypeOf(context);
if (@typeInfo(T) != .pointer and T != void)
@compileError("context must be a pointer type or void");
if (T != void and @typeInfo(T).pointer.is_const)
@compileError("context must be a mutable pointer type or void");
const result = zig_Luau_Analysis_Frontend_getCheckResult(
self,
moduleName.ptr,
moduleName.len,
captureComments,
typeCheckForAutocomplete,
if (T == void) null else @as(*anyopaque, @ptrCast(@alignCast(context))),
struct {
fn inner(
ud: ?*anyopaque,
kind: u8,
readableModuleName: [*c]const u8,
readableModuleNameLen: usize,
errorMessage: [*c]const u8,
errorMessageLen: usize,
contextName: [*c]const u8,
loc: Location,
) callconv(.c) void {
@call(.always_inline, checkFn, .{
if (T == void) undefined else @as(T, @ptrCast(@alignCast(ud.?))),
@as(CheckResultErrorKind, @enumFromInt(kind)),
readableModuleName[0..readableModuleNameLen :0],
errorMessage[0..errorMessageLen :0],
std.mem.span(contextName),
loc,
});
}
}.inner,
);
return @enumFromInt(result);
}
pub fn loadDefinitionFile(
self: *Frontend,
src: []const u8,
packageName: [:0]const u8,
captureComments: bool,
typeCheckForAutocomplete: ?bool,
) bool {
return zig_Luau_Analysis_Frontend_loadDefinitionFile(self, src.ptr, src.len, packageName, captureComments, typeCheckForAutocomplete orelse false, null, null);
}
const LoadDefintionResult = struct {
message: []const u8,
location: Location,
allocator: std.mem.Allocator,
pub fn deinit(self: *LoadDefintionResult) void {
self.allocator.free(self.message);
}
};
pub fn loadDefinitionFileWithAlloc(
self: *Frontend,
allocator: std.mem.Allocator,
src: []const u8,
packageName: [:0]const u8,
captureComments: bool,
typeCheckForAutocomplete: ?bool,
) !?LoadDefintionResult {
var result: struct { anyerror, LoadDefintionResult } = .{ error.None, .{
.allocator = allocator,
.message = undefined,
.location = undefined,
} };
const success = zig_Luau_Analysis_Frontend_loadDefinitionFile(self, src.ptr, src.len, packageName, captureComments, typeCheckForAutocomplete orelse false, &result, struct {
fn inner(
ud: ?*anyopaque,
msg: [*c]const u8,
len: usize,
loc: Location,
) callconv(.c) void {
const res: *struct { anyerror, LoadDefintionResult } = @ptrCast(@alignCast(ud.?));
res.@"1".location = loc;
res.@"1".message = res.@"1".allocator.dupe(u8, msg[0..len]) catch |err| {
res.@"0" = err;
return;
};
}
}.inner);
if (success) {
return null;
}
if (result.@"0" != error.None) {
return result.@"0";
}
return result.@"1";
}
pub fn deinit(self: *Frontend) void {
zig_Luau_Analysis_Frontend_dtor(self);
}
};
pub fn init(fileResolver: anytype, configResolver: *GenericConfigResolver.GenericConfigResolver, opts: Options) *Frontend {
return zig_Luau_Analysis_Frontend_init(@ptrCast(@alignCast(fileResolver)), configResolver, opts);
}
test Frontend {
const FileResolver = @import("FileResolver.zig");
{
const FileImpl = struct {
const Self = @This();
pub fn readSource(_: *Self, _: []const u8) ?struct { []const u8, FileResolver.SourceCodeType } {
return null;
}
pub fn resolveModule(_: *Self, _: []const u8, _: []const u8) ?[]const u8 {
return null;
}
pub fn getHumanReadableModuleName(_: *Self, name: []const u8) []const u8 {
return name;
}
pub fn freeString(_: *Self, _: []const u8) void {}
};
const FileImplResolver = FileResolver.FileResolver(FileImpl);
var file_impl: FileImpl = .{};
const file_resolver = FileImplResolver.init(&file_impl);
defer file_resolver.deinit();
const config_resolver = GenericConfigResolver.init(.Strict);
defer config_resolver.deinit();
const frontend = init(file_resolver, config_resolver, .{});
defer frontend.deinit();
frontend.registerBuiltinGlobals();
var load_result = try frontend.loadDefinitionFileWithAlloc(
std.testing.allocator,
\\ - This is a test
,
"@test",
false,
null,
) orelse @panic("no fail");
defer load_result.deinit();
try std.testing.expectEqualStrings("Expected identifier when parsing expression, got '-'", load_result.message);
try std.testing.expectEqual(0, load_result.location.begin.line);
try std.testing.expectEqual(1, load_result.location.begin.column);
try std.testing.expectEqual(0, load_result.location.end.line);
try std.testing.expectEqual(2, load_result.location.end.column);
}
{
const StaticFileTree = std.StaticStringMap([]const u8).initComptime(.{
.{
"./main.luau",
\\local module = require("./module.luau")
,
},
.{
"./module.luau",
\\print("module");
\\return {};
,
},
.{
"./sub/test.luau",
\\local test = global.foo;
\\local test2 = g.foo;
\\
,
},
});
const FileImpl = struct {
const Self = @This();
pub fn readSource(_: *Self, path: []const u8) ?struct { []const u8, FileResolver.SourceCodeType } {
const source = StaticFileTree.get(path) orelse @panic("failed to find source");
return .{ source, .Module };
}
pub fn resolveModule(_: *Self, _: []const u8, to: []const u8) ?[]const u8 {
return to;
}
pub fn getHumanReadableModuleName(_: *Self, name: []const u8) []const u8 {
return name;
}
pub fn freeString(_: *Self, _: []const u8) void {}
};
const FileImplResolver = FileResolver.FileResolver(FileImpl);
var file_impl: FileImpl = .{};
const file_resolver = FileImplResolver.init(&file_impl);
defer file_resolver.deinit();
const config_resolver = GenericConfigResolver.init(.Strict);
defer config_resolver.deinit();
const frontend = init(file_resolver, config_resolver, .{});
defer frontend.deinit();
frontend.registerBuiltinGlobals();
try std.testing.expectEqual(null, try frontend.loadDefinitionFileWithAlloc(
std.testing.allocator,
\\declare global: {
\\ foo: string,
\\}
,
"@main",
false,
null,
));
frontend.queueModuleCheck("./main.luau");
frontend.queueModuleCheck("./sub/test.luau");
const success = frontend.checkQueuedModules(
frontend,
struct {
fn checkedModule(f: *Frontend, name: [:0]const u8) bool {
switch (f.getCheckResult(name, false, false, @as(void, undefined), struct {
fn inner(_: void, kind: CheckResultErrorKind, readableModuleName: [:0]const u8, errorMessage: [:0]const u8, typeName: [:0]const u8, loc: Location) void {
if (!std.mem.eql(u8, readableModuleName, "./sub/test.luau"))
@panic("Expected no errors in main module");
std.testing.expectEqual(.Error, kind) catch @panic("failed");
std.testing.expectEqualStrings("Unknown global 'g'; consider assigning to it first", errorMessage) catch @panic("failed");
std.testing.expectEqualStrings("TypeError", typeName) catch @panic("failed");
std.testing.expectEqual(1, loc.begin.line) catch @panic("failed");
std.testing.expectEqual(14, loc.begin.column) catch @panic("failed");
std.testing.expectEqual(1, loc.end.line) catch @panic("failed");
std.testing.expectEqual(15, loc.end.column) catch @panic("failed");
}
}.inner)) {
.None => unreachable,
.Success => {},
.Error => if (!std.mem.eql(u8, name, "./sub/test.luau")) @panic("Expected no errors in main module"),
}
return true;
}
}.checkedModule,
struct {
fn checkedModuleError(_: *Frontend, name: [:0]const u8, errMsg: [:0]const u8, loc: Location) void {
std.debug.print("Error in module {s}: {s} at {d}:{d}-{d}:{d}\n", .{
name,
errMsg,
loc.begin.line,
loc.begin.column,
loc.end.line,
loc.end.column,
});
@panic("Module check failed");
}
}.checkedModuleError,
);
try std.testing.expect(success);
}
}
+84
View File
@@ -0,0 +1,84 @@
#include <bridge.h>
#include "Luau/ConfigResolver.h"
#include "./FileUtils.h"
#define ZIG_LUAU_ANALYSIS(name) ZIG_FN(Luau_Analysis_##name)
// This code is based on https://github.com/luau-lang/luau/blob/68cdcc4a3a5f3ed23186c4f7f6b8a5aacf835bee/CLI/src/Analyze.cpp#L202
struct GenericConfigResolver : Luau::ConfigResolver
{
mutable std::vector<std::pair<std::string, std::string>> configErrors;
mutable std::unordered_map<std::string, Luau::Config> configCache;
Luau::Config defaultConfig;
GenericConfigResolver(Luau::Mode mode)
{
defaultConfig.mode = mode;
}
const Luau::Config& getConfig(const Luau::ModuleName& name, const Luau::TypeCheckLimits& limits) const override
{
std::optional<std::string> path = getParentPath(name);
if (!path)
return defaultConfig;
return readConfigRec(*path, limits);
}
const Luau::Config& readConfigRec(const std::string& path, const Luau::TypeCheckLimits& limits) const
{
auto it = configCache.find(path);
if (it != configCache.end())
return it->second;
std::optional<std::string> parent = getParentPath(path);
Luau::Config result = parent ? readConfigRec(*parent, limits) : defaultConfig;
std::string configPath = joinPaths(path, Luau::kConfigName);
if (std::optional<std::string> contents = readFile(configPath))
{
Luau::ConfigOptions::AliasOptions aliasOpts;
aliasOpts.configLocation = configPath;
aliasOpts.overwriteAliases = true;
Luau::ConfigOptions opts;
opts.aliasOptions = std::move(aliasOpts);
std::optional<std::string> error = Luau::parseConfig(*contents, result, opts);
if (error)
configErrors.push_back({configPath, *error});
}
return configCache[path] = result;
}
};
ZIG_EXPORT GenericConfigResolver* ZIG_LUAU_ANALYSIS(GenericConfigResolver_init)(unsigned char mode)
{
Luau::Mode luauMode = Luau::Mode::NoCheck;
if (mode == 0)
luauMode = Luau::Mode::NoCheck;
else if (mode == 1)
luauMode = Luau::Mode::Nonstrict;
else if (mode == 2)
luauMode = Luau::Mode::Strict;
else if (mode == 3)
luauMode = Luau::Mode::Definition;
return new GenericConfigResolver(luauMode);
}
ZIG_EXPORT struct ErrorGroup
{
const char **paths;
const char **messages;
size_t size;
};
ZIG_EXPORT void ZIG_LUAU_ANALYSIS(GenericConfigResolver_dtor)(GenericConfigResolver* resolver)
{
delete resolver;
}
+59
View File
@@ -0,0 +1,59 @@
const std = @import("std");
const cpp_std = @import("../cpp_std.zig");
const ErrorGroup = extern struct {
paths: [*][*c]const u8,
messages: [*][*c]const u8,
size: usize,
};
const Mode = enum(u8) {
NoCheck = 0,
Nonstrict = 1,
Strict = 2,
Definition = 3,
};
const ConfigErrors = cpp_std.Vector(cpp_std.Pair(cpp_std.String, cpp_std.String));
const ResolverInterface = extern struct {
vtable: *const anyopaque,
errors: ConfigErrors,
};
extern "c" fn zig_Luau_Analysis_GenericConfigResolver_init(u8) *GenericConfigResolver;
extern "c" fn zig_Luau_Analysis_GenericConfigResolver_dtor(*GenericConfigResolver) void;
pub const GenericConfigResolver = opaque {
pub const AnyErrorGroup = struct {
group: ErrorGroup,
pub fn paths(self: AnyErrorGroup) []const [*c]const u8 {
return self.group.paths[0..self.group.size];
}
pub fn messages(self: AnyErrorGroup) []const [*c]const u8 {
return self.group.messages[0..self.group.size];
}
};
pub fn getErrors(self: *GenericConfigResolver) ConfigErrors {
return @as(*ResolverInterface, @ptrCast(@alignCast(self))).errors;
}
pub fn deinit(self: *GenericConfigResolver) void {
zig_Luau_Analysis_GenericConfigResolver_dtor(self);
}
};
pub fn init(mode: Mode) *GenericConfigResolver {
return zig_Luau_Analysis_GenericConfigResolver_init(@intFromEnum(mode));
}
test GenericConfigResolver {
const resolver = init(.Strict);
defer resolver.deinit();
const errors = resolver.getErrors();
try std.testing.expect(errors.size() == 0);
}
+15
View File
@@ -0,0 +1,15 @@
#include <bridge.h>
#include "Luau/Allocator.h"
#define ZIG_LUAU_AST(name) ZIG_FN(Luau_Ast_##name)
ZIG_EXPORT Luau::Allocator* ZIG_LUAU_AST(Allocator_init)()
{
return new Luau::Allocator();
}
ZIG_EXPORT void ZIG_LUAU_AST(Allocator_dtor)(Luau::Allocator* allocator)
{
delete allocator;
}
+46
View File
@@ -0,0 +1,46 @@
const std = @import("std");
// extern fn zig_delete_any(*Page) callconv(.c) void;
extern "c" fn zig_Luau_Ast_Allocator_init() *This;
extern "c" fn zig_Luau_Ast_Allocator_dtor(*This) void;
const This = @This();
root: [*c]Page,
offset: usize = 0,
pub const Page = extern struct {
next: [*c]Page = null,
data: [8192]u8 align(8),
};
// /// cleans up the luau allocator
// /// frees all pages created by C++
// pub fn destroy(self: This) void {
// var page = self.root;
// while (page != null) {
// const next = page.*.next;
// // pages are C++ allocated, so we need to use the C++ deallocator
// zig_delete_any(page);
// std.debug.print("clean page\n", .{});
// page = next;
// }
// }
pub fn init() *This {
return zig_Luau_Ast_Allocator_init();
}
pub fn deinit(self: *This) void {
zig_Luau_Ast_Allocator_dtor(self);
}
test This {
const allocator = This.init();
defer allocator.deinit();
}
// sources:
// https://github.com/luau-lang/luau/blob/a2303a6ae68c53035eccf230c4450b9f068536af/Ast/include/Luau/Allocator.h
// https://github.com/luau-lang/luau/blob/a2303a6ae68c53035eccf230c4450b9f068536af/Ast/src/Allocator.cpp
+2454
View File
File diff suppressed because it is too large Load Diff
+211
View File
@@ -0,0 +1,211 @@
#include <bridge.h>
#include "Luau/Ast.h"
#include "Luau/Cst.h"
using namespace Luau;
// AST
ZIG_EXPORT const unsigned char AstAttrIndex = AstAttr::ClassIndex();
ZIG_EXPORT const unsigned char AstGenericTypeIndex = AstGenericType::ClassIndex();
ZIG_EXPORT const unsigned char AstGenericTypePackIndex = AstGenericTypePack::ClassIndex();
ZIG_EXPORT const unsigned char AstExprGroupIndex = AstExprGroup::ClassIndex();
ZIG_EXPORT const unsigned char AstExprConstantNilIndex = AstExprConstantNil::ClassIndex();
ZIG_EXPORT const unsigned char AstExprConstantBoolIndex = AstExprConstantBool::ClassIndex();
ZIG_EXPORT const unsigned char AstExprConstantNumberIndex = AstExprConstantNumber::ClassIndex();
ZIG_EXPORT const unsigned char AstExprConstantIntegerIndex = AstExprConstantInteger::ClassIndex();
ZIG_EXPORT const unsigned char AstExprConstantStringIndex = AstExprConstantString::ClassIndex();
ZIG_EXPORT const unsigned char AstExprLocalIndex = AstExprLocal::ClassIndex();
ZIG_EXPORT const unsigned char AstExprGlobalIndex = AstExprGlobal::ClassIndex();
ZIG_EXPORT const unsigned char AstExprVarargsIndex = AstExprVarargs::ClassIndex();
ZIG_EXPORT const unsigned char AstExprCallIndex = AstExprCall::ClassIndex();
ZIG_EXPORT const unsigned char AstExprIndexNameIndex = AstExprIndexName::ClassIndex();
ZIG_EXPORT const unsigned char AstExprIndexExprIndex = AstExprIndexExpr::ClassIndex();
ZIG_EXPORT const unsigned char AstExprFunctionIndex = AstExprFunction::ClassIndex();
ZIG_EXPORT const unsigned char AstExprTableIndex = AstExprTable::ClassIndex();
ZIG_EXPORT const unsigned char AstExprUnaryIndex = AstExprUnary::ClassIndex();
ZIG_EXPORT const unsigned char AstExprBinaryIndex = AstExprBinary::ClassIndex();
ZIG_EXPORT const unsigned char AstExprTypeAssertionIndex = AstExprTypeAssertion::ClassIndex();
ZIG_EXPORT const unsigned char AstExprIfElseIndex = AstExprIfElse::ClassIndex();
ZIG_EXPORT const unsigned char AstExprInterpStringIndex = AstExprInterpString::ClassIndex();
ZIG_EXPORT const unsigned char AstExprInstantiateIndex = AstExprInstantiate::ClassIndex();
ZIG_EXPORT const unsigned char AstStatBlockIndex = AstStatBlock::ClassIndex();
ZIG_EXPORT const unsigned char AstStatIfIndex = AstStatIf::ClassIndex();
ZIG_EXPORT const unsigned char AstStatWhileIndex = AstStatWhile::ClassIndex();
ZIG_EXPORT const unsigned char AstStatRepeatIndex = AstStatRepeat::ClassIndex();
ZIG_EXPORT const unsigned char AstStatBreakIndex = AstStatBreak::ClassIndex();
ZIG_EXPORT const unsigned char AstStatContinueIndex = AstStatContinue::ClassIndex();
ZIG_EXPORT const unsigned char AstStatReturnIndex = AstStatReturn::ClassIndex();
ZIG_EXPORT const unsigned char AstStatExprIndex = AstStatExpr::ClassIndex();
ZIG_EXPORT const unsigned char AstStatLocalIndex = AstStatLocal::ClassIndex();
ZIG_EXPORT const unsigned char AstStatForIndex = AstStatFor::ClassIndex();
ZIG_EXPORT const unsigned char AstStatForInIndex = AstStatForIn::ClassIndex();
ZIG_EXPORT const unsigned char AstStatAssignIndex = AstStatAssign::ClassIndex();
ZIG_EXPORT const unsigned char AstStatCompoundAssignIndex = AstStatCompoundAssign::ClassIndex();
ZIG_EXPORT const unsigned char AstStatFunctionIndex = AstStatFunction::ClassIndex();
ZIG_EXPORT const unsigned char AstStatLocalFunctionIndex = AstStatLocalFunction::ClassIndex();
ZIG_EXPORT const unsigned char AstStatTypeAliasIndex = AstStatTypeAlias::ClassIndex();
ZIG_EXPORT const unsigned char AstStatTypeFunctionIndex = AstStatTypeFunction::ClassIndex();
ZIG_EXPORT const unsigned char AstStatDeclareFunctionIndex = AstStatDeclareFunction::ClassIndex();
ZIG_EXPORT const unsigned char AstStatDeclareGlobalIndex = AstStatDeclareGlobal::ClassIndex();
ZIG_EXPORT const unsigned char AstStatClassIndex = AstStatClass::ClassIndex();
ZIG_EXPORT const unsigned char AstStatDeclareExternTypeIndex = AstStatDeclareExternType::ClassIndex();
ZIG_EXPORT const unsigned char AstTypeReferenceIndex = AstTypeReference::ClassIndex();
ZIG_EXPORT const unsigned char AstTypeTableIndex = AstTypeTable::ClassIndex();
ZIG_EXPORT const unsigned char AstTypeFunctionIndex = AstTypeFunction::ClassIndex();
ZIG_EXPORT const unsigned char AstTypeTypeofIndex = AstTypeTypeof::ClassIndex();
ZIG_EXPORT const unsigned char AstTypeOptionalIndex = AstTypeOptional::ClassIndex();
ZIG_EXPORT const unsigned char AstTypeUnionIndex = AstTypeUnion::ClassIndex();
ZIG_EXPORT const unsigned char AstTypeIntersectionIndex = AstTypeIntersection::ClassIndex();
ZIG_EXPORT const unsigned char AstExprErrorIndex = AstExprError::ClassIndex();
ZIG_EXPORT const unsigned char AstStatErrorIndex = AstStatError::ClassIndex();
ZIG_EXPORT const unsigned char AstTypeErrorIndex = AstTypeError::ClassIndex();
ZIG_EXPORT const unsigned char AstTypeSingletonBoolIndex = AstTypeSingletonBool::ClassIndex();
ZIG_EXPORT const unsigned char AstTypeSingletonStringIndex = AstTypeSingletonString::ClassIndex();
ZIG_EXPORT const unsigned char AstTypeGroupIndex = AstTypeGroup::ClassIndex();
ZIG_EXPORT const unsigned char AstTypePackExplicitIndex = AstTypePackExplicit::ClassIndex();
ZIG_EXPORT const unsigned char AstTypePackVariadicIndex = AstTypePackVariadic::ClassIndex();
ZIG_EXPORT const unsigned char AstTypePackGenericIndex = AstTypePackGeneric::ClassIndex();
ZIG_EXPORT const unsigned long AstAttrSize = sizeof(AstAttr);
ZIG_EXPORT const unsigned long AstGenericTypeSize = sizeof(AstGenericType);
ZIG_EXPORT const unsigned long AstGenericTypePackSize = sizeof(AstGenericTypePack);
ZIG_EXPORT const unsigned long AstExprGroupSize = sizeof(AstExprGroup);
ZIG_EXPORT const unsigned long AstExprConstantNilSize = sizeof(AstExprConstantNil);
ZIG_EXPORT const unsigned long AstExprConstantBoolSize = sizeof(AstExprConstantBool);
ZIG_EXPORT const unsigned long AstExprConstantNumberSize = sizeof(AstExprConstantNumber);
ZIG_EXPORT const unsigned long AstExprConstantIntegerSize = sizeof(AstExprConstantInteger);
ZIG_EXPORT const unsigned long AstExprConstantStringSize = sizeof(AstExprConstantString);
ZIG_EXPORT const unsigned long AstExprLocalSize = sizeof(AstExprLocal);
ZIG_EXPORT const unsigned long AstExprGlobalSize = sizeof(AstExprGlobal);
ZIG_EXPORT const unsigned long AstExprVarargsSize = sizeof(AstExprVarargs);
ZIG_EXPORT const unsigned long AstExprCallSize = sizeof(AstExprCall);
ZIG_EXPORT const unsigned long AstExprIndexNameSize = sizeof(AstExprIndexName);
ZIG_EXPORT const unsigned long AstExprIndexExprSize = sizeof(AstExprIndexExpr);
ZIG_EXPORT const unsigned long AstExprFunctionSize = sizeof(AstExprFunction);
ZIG_EXPORT const unsigned long AstExprTableSize = sizeof(AstExprTable);
ZIG_EXPORT const unsigned long AstExprUnarySize = sizeof(AstExprUnary);
ZIG_EXPORT const unsigned long AstExprBinarySize = sizeof(AstExprBinary);
ZIG_EXPORT const unsigned long AstExprTypeAssertionSize = sizeof(AstExprTypeAssertion);
ZIG_EXPORT const unsigned long AstExprIfElseSize = sizeof(AstExprIfElse);
ZIG_EXPORT const unsigned long AstExprInterpStringSize = sizeof(AstExprInterpString);
ZIG_EXPORT const unsigned long AstExprInstantiateSize = sizeof(AstExprInstantiate);
ZIG_EXPORT const unsigned long AstStatBlockSize = sizeof(AstStatBlock);
ZIG_EXPORT const unsigned long AstStatIfSize = sizeof(AstStatIf);
ZIG_EXPORT const unsigned long AstStatWhileSize = sizeof(AstStatWhile);
ZIG_EXPORT const unsigned long AstStatRepeatSize = sizeof(AstStatRepeat);
ZIG_EXPORT const unsigned long AstStatBreakSize = sizeof(AstStatBreak);
ZIG_EXPORT const unsigned long AstStatContinueSize = sizeof(AstStatContinue);
ZIG_EXPORT const unsigned long AstStatReturnSize = sizeof(AstStatReturn);
ZIG_EXPORT const unsigned long AstStatExprSize = sizeof(AstStatExpr);
ZIG_EXPORT const unsigned long AstStatLocalSize = sizeof(AstStatLocal);
ZIG_EXPORT const unsigned long AstStatForSize = sizeof(AstStatFor);
ZIG_EXPORT const unsigned long AstStatForInSize = sizeof(AstStatForIn);
ZIG_EXPORT const unsigned long AstStatAssignSize = sizeof(AstStatAssign);
ZIG_EXPORT const unsigned long AstStatCompoundAssignSize = sizeof(AstStatCompoundAssign);
ZIG_EXPORT const unsigned long AstStatFunctionSize = sizeof(AstStatFunction);
ZIG_EXPORT const unsigned long AstStatLocalFunctionSize = sizeof(AstStatLocalFunction);
ZIG_EXPORT const unsigned long AstStatTypeAliasSize = sizeof(AstStatTypeAlias);
ZIG_EXPORT const unsigned long AstStatTypeFunctionSize = sizeof(AstStatTypeFunction);
ZIG_EXPORT const unsigned long AstStatDeclareFunctionSize = sizeof(AstStatDeclareFunction);
ZIG_EXPORT const unsigned long AstStatDeclareGlobalSize = sizeof(AstStatDeclareGlobal);
ZIG_EXPORT const unsigned long AstStatClassSize = sizeof(AstStatClass);
ZIG_EXPORT const unsigned long AstStatDeclareExternTypeSize = sizeof(AstStatDeclareExternType);
ZIG_EXPORT const unsigned long AstTypeReferenceSize = sizeof(AstTypeReference);
ZIG_EXPORT const unsigned long AstTypeTableSize = sizeof(AstTypeTable);
ZIG_EXPORT const unsigned long AstTypeFunctionSize = sizeof(AstTypeFunction);
ZIG_EXPORT const unsigned long AstTypeTypeofSize = sizeof(AstTypeTypeof);
ZIG_EXPORT const unsigned long AstTypeOptionalSize = sizeof(AstTypeOptional);
ZIG_EXPORT const unsigned long AstTypeUnionSize = sizeof(AstTypeUnion);
ZIG_EXPORT const unsigned long AstTypeIntersectionSize = sizeof(AstTypeIntersection);
ZIG_EXPORT const unsigned long AstExprErrorSize = sizeof(AstExprError);
ZIG_EXPORT const unsigned long AstStatErrorSize = sizeof(AstStatError);
ZIG_EXPORT const unsigned long AstTypeErrorSize = sizeof(AstTypeError);
ZIG_EXPORT const unsigned long AstTypeSingletonBoolSize = sizeof(AstTypeSingletonBool);
ZIG_EXPORT const unsigned long AstTypeSingletonStringSize = sizeof(AstTypeSingletonString);
ZIG_EXPORT const unsigned long AstTypeGroupSize = sizeof(AstTypeGroup);
ZIG_EXPORT const unsigned long AstTypePackExplicitSize = sizeof(AstTypePackExplicit);
ZIG_EXPORT const unsigned long AstTypePackVariadicSize = sizeof(AstTypePackVariadic);
ZIG_EXPORT const unsigned long AstTypePackGenericSize = sizeof(AstTypePackGeneric);
// CST
ZIG_EXPORT const unsigned char CstAttrIndex = CstAttr::CstClassIndex();
ZIG_EXPORT const unsigned char CstParametrizedAttrIndex = CstParametrizedAttr::CstClassIndex();
ZIG_EXPORT const unsigned char CstExprGroupIndex = CstExprGroup::CstClassIndex();
ZIG_EXPORT const unsigned char CstExprConstantNumberIndex = CstExprConstantNumber::CstClassIndex();
ZIG_EXPORT const unsigned char CstExprConstantIntegerIndex = CstExprConstantInteger::CstClassIndex();
ZIG_EXPORT const unsigned char CstExprConstantStringIndex = CstExprConstantString::CstClassIndex();
ZIG_EXPORT const unsigned char CstExprCallIndex = CstExprCall::CstClassIndex();
ZIG_EXPORT const unsigned char CstExprIndexExprIndex = CstExprIndexExpr::CstClassIndex();
ZIG_EXPORT const unsigned char CstExprFunctionIndex = CstExprFunction::CstClassIndex();
ZIG_EXPORT const unsigned char CstExprTableIndex = CstExprTable::CstClassIndex();
ZIG_EXPORT const unsigned char CstExprOpIndex = CstExprOp::CstClassIndex();
ZIG_EXPORT const unsigned char CstExprTypeAssertionIndex = CstExprTypeAssertion::CstClassIndex();
ZIG_EXPORT const unsigned char CstExprIfElseIndex = CstExprIfElse::CstClassIndex();
ZIG_EXPORT const unsigned char CstExprInterpStringIndex = CstExprInterpString::CstClassIndex();
ZIG_EXPORT const unsigned char CstExprExplicitTypeInstantiationIndex = CstExprExplicitTypeInstantiation::CstClassIndex();
ZIG_EXPORT const unsigned char CstStatDoIndex = CstStatDo::CstClassIndex();
ZIG_EXPORT const unsigned char CstStatRepeatIndex = CstStatRepeat::CstClassIndex();
ZIG_EXPORT const unsigned char CstStatReturnIndex = CstStatReturn::CstClassIndex();
ZIG_EXPORT const unsigned char CstStatLocalIndex = CstStatLocal::CstClassIndex();
ZIG_EXPORT const unsigned char CstStatForIndex = CstStatFor::CstClassIndex();
ZIG_EXPORT const unsigned char CstStatForInIndex = CstStatForIn::CstClassIndex();
ZIG_EXPORT const unsigned char CstStatAssignIndex = CstStatAssign::CstClassIndex();
ZIG_EXPORT const unsigned char CstStatCompoundAssignIndex = CstStatCompoundAssign::CstClassIndex();
ZIG_EXPORT const unsigned char CstStatFunctionIndex = CstStatFunction::CstClassIndex();
ZIG_EXPORT const unsigned char CstStatLocalFunctionIndex = CstStatLocalFunction::CstClassIndex();
ZIG_EXPORT const unsigned char CstGenericTypeIndex = CstGenericType::CstClassIndex();
ZIG_EXPORT const unsigned char CstGenericTypePackIndex = CstGenericTypePack::CstClassIndex();
ZIG_EXPORT const unsigned char CstStatTypeAliasIndex = CstStatTypeAlias::CstClassIndex();
ZIG_EXPORT const unsigned char CstStatTypeFunctionIndex = CstStatTypeFunction::CstClassIndex();
ZIG_EXPORT const unsigned char CstTypeReferenceIndex = CstTypeReference::CstClassIndex();
ZIG_EXPORT const unsigned char CstTypeTableIndex = CstTypeTable::CstClassIndex();
ZIG_EXPORT const int CstTypeTableItemKindIndexer = static_cast<int>(CstTypeTable::Item::Kind::Indexer);
ZIG_EXPORT const int CstTypeTableItemKindProperty = static_cast<int>(CstTypeTable::Item::Kind::Property);
ZIG_EXPORT const int CstTypeTableItemKindStringProperty = static_cast<int>(CstTypeTable::Item::Kind::StringProperty);
ZIG_EXPORT const unsigned char CstTypeFunctionIndex = CstTypeFunction::CstClassIndex();
ZIG_EXPORT const unsigned char CstTypeTypeofIndex = CstTypeTypeof::CstClassIndex();
ZIG_EXPORT const unsigned char CstTypeUnionIndex = CstTypeUnion::CstClassIndex();
ZIG_EXPORT const unsigned char CstTypeIntersectionIndex = CstTypeIntersection::CstClassIndex();
ZIG_EXPORT const unsigned char CstTypeSingletonStringIndex = CstTypeSingletonString::CstClassIndex();
ZIG_EXPORT const unsigned char CstTypeGroupIndex = CstTypeGroup::CstClassIndex();
ZIG_EXPORT const unsigned char CstTypePackExplicitIndex = CstTypePackExplicit::CstClassIndex();
ZIG_EXPORT const unsigned char CstTypePackGenericIndex = CstTypePackGeneric::CstClassIndex();
ZIG_EXPORT const unsigned long CstExprGroupSize = sizeof(CstExprGroup);
ZIG_EXPORT const unsigned long CstExprConstantNumberSize = sizeof(CstExprConstantNumber);
ZIG_EXPORT const unsigned long CstExprConstantIntegerSize = sizeof(CstExprConstantInteger);
ZIG_EXPORT const unsigned long CstExprConstantStringSize = sizeof(CstExprConstantString);
ZIG_EXPORT const unsigned long CstExprCallSize = sizeof(CstExprCall);
ZIG_EXPORT const unsigned long CstExprIndexExprSize = sizeof(CstExprIndexExpr);
ZIG_EXPORT const unsigned long CstExprFunctionSize = sizeof(CstExprFunction);
ZIG_EXPORT const unsigned long CstExprTableSize = sizeof(CstExprTable);
ZIG_EXPORT const unsigned long CstExprOpSize = sizeof(CstExprOp);
ZIG_EXPORT const unsigned long CstExprTypeAssertionSize = sizeof(CstExprTypeAssertion);
ZIG_EXPORT const unsigned long CstExprIfElseSize = sizeof(CstExprIfElse);
ZIG_EXPORT const unsigned long CstExprInterpStringSize = sizeof(CstExprInterpString);
ZIG_EXPORT const unsigned long CstExprExplicitTypeInstantiationSize = sizeof(CstExprExplicitTypeInstantiation);
ZIG_EXPORT const unsigned long CstStatDoSize = sizeof(CstStatDo);
ZIG_EXPORT const unsigned long CstStatRepeatSize = sizeof(CstStatRepeat);
ZIG_EXPORT const unsigned long CstStatReturnSize = sizeof(CstStatReturn);
ZIG_EXPORT const unsigned long CstStatLocalSize = sizeof(CstStatLocal);
ZIG_EXPORT const unsigned long CstStatForSize = sizeof(CstStatFor);
ZIG_EXPORT const unsigned long CstStatForInSize = sizeof(CstStatForIn);
ZIG_EXPORT const unsigned long CstStatAssignSize = sizeof(CstStatAssign);
ZIG_EXPORT const unsigned long CstStatCompoundAssignSize = sizeof(CstStatCompoundAssign);
ZIG_EXPORT const unsigned long CstStatFunctionSize = sizeof(CstStatFunction);
ZIG_EXPORT const unsigned long CstStatLocalFunctionSize = sizeof(CstStatLocalFunction);
ZIG_EXPORT const unsigned long CstGenericTypeSize = sizeof(CstGenericType);
ZIG_EXPORT const unsigned long CstGenericTypePackSize = sizeof(CstGenericTypePack);
ZIG_EXPORT const unsigned long CstStatTypeAliasSize = sizeof(CstStatTypeAlias);
ZIG_EXPORT const unsigned long CstStatTypeFunctionSize = sizeof(CstStatTypeFunction);
ZIG_EXPORT const unsigned long CstTypeReferenceSize = sizeof(CstTypeReference);
ZIG_EXPORT const unsigned long CstTypeTableSize = sizeof(CstTypeTable);
ZIG_EXPORT const unsigned long CstTypeFunctionSize = sizeof(CstTypeFunction);
ZIG_EXPORT const unsigned long CstTypeTypeofSize = sizeof(CstTypeTypeof);
ZIG_EXPORT const unsigned long CstTypeUnionSize = sizeof(CstTypeUnion);
ZIG_EXPORT const unsigned long CstTypeIntersectionSize = sizeof(CstTypeIntersection);
ZIG_EXPORT const unsigned long CstTypeSingletonStringSize = sizeof(CstTypeSingletonString);
ZIG_EXPORT const unsigned long CstTypeGroupSize = sizeof(CstTypeGroup);
ZIG_EXPORT const unsigned long CstTypePackExplicitSize = sizeof(CstTypePackExplicit);
ZIG_EXPORT const unsigned long CstTypePackGenericSize = sizeof(CstTypePackGeneric);
+648
View File
@@ -0,0 +1,648 @@
const std = @import("std");
const Ast = @import("Ast.zig");
const Location = @import("Location.zig").Location;
const cpp_std = @import("../cpp_std.zig");
const Cst = @This();
pub const Node = extern struct {
classIndex: Kind,
pub const Kind = enum(i32) {
unknown,
attr,
parametrized_attr,
expr_group,
expr_constant_number,
expr_constant_integer,
expr_constant_string,
expr_call,
expr_index_expr,
expr_function,
expr_table,
expr_op,
expr_type_assertion,
expr_if_else,
expr_interp_string,
expr_explicit_type_instantiation,
stat_do,
stat_repeat,
stat_return,
stat_local,
stat_for,
stat_for_in,
stat_assign,
stat_compound_assign,
stat_function,
stat_local_function,
generic_type,
generic_type_pack,
stat_type_alias,
stat_type_function,
type_reference,
type_table,
type_function,
type_typeof,
type_union,
type_intersection,
type_singleton_string,
type_group,
type_pack_explicit,
type_pack_generic,
pub fn Type(comptime self: Kind) type {
return switch (self) {
.unknown => Node,
.attr => Attr,
.parametrized_attr => ParametrizedAttr,
.expr_group => ExprGroup,
.expr_constant_number => ExprConstantNumber,
.expr_constant_integer => ExprConstantInteger,
.expr_constant_string => ExprConstantString,
.expr_call => ExprCall,
.expr_index_expr => ExprIndexExpr,
.expr_function => ExprFunction,
.expr_table => ExprTable,
.expr_op => ExprOp,
.expr_type_assertion => ExprTypeAssertion,
.expr_if_else => ExprIfElse,
.expr_interp_string => ExprInterpString,
.expr_explicit_type_instantiation => ExprExplicitTypeInstantiation,
.stat_do => StatDo,
.stat_repeat => StatRepeat,
.stat_return => StatReturn,
.stat_local => StatLocal,
.stat_for => StatFor,
.stat_for_in => StatForIn,
.stat_assign => StatAssign,
.stat_compound_assign => StatCompoundAssign,
.stat_function => StatFunction,
.stat_local_function => StatLocalFunction,
.generic_type => GenericType,
.generic_type_pack => GenericTypePack,
.stat_type_alias => StatTypeAlias,
.stat_type_function => StatTypeFunction,
.type_reference => TypeReference,
.type_table => TypeTable,
.type_function => TypeFunction,
.type_typeof => TypeTypeof,
.type_union => TypeUnion,
.type_intersection => TypeIntersection,
.type_singleton_string => TypeSingletonString,
.type_group => TypeGroup,
.type_pack_explicit => TypePackExplicit,
.type_pack_generic => TypePackGeneric,
};
}
};
pub const is = IsFn;
pub const as = AsCastFn;
};
pub fn IsFn(base: anytype, comptime to: Node.Kind) bool {
return base.classIndex == to;
}
pub fn AsCastFn(base: anytype, comptime to: Node.Kind) ?*to.Type() {
return if (base.classIndex == to) @ptrCast(@alignCast(base)) else null;
}
pub const Attr = extern struct {
classIndex: Node.Kind = .attr,
/// false when inside an attribute list, ie @[native checked]
hasAt: bool,
};
pub const ParametrizedAttr = extern struct {
classIndex: Node.Kind = .parametrized_attr,
/// for `@x(args)` form
openParenPosition: Location.Position,
closeParenPosition: Location.Position,
/// Commas inside the `(a, b, c)` arg list
argsCommaPositions: Ast.Array(Location.Position),
};
pub const AttrList = extern struct {
atBracketPosition: Location.Position,
closeBracketPosition: Location.Position,
commaPositions: Ast.Array(Location.Position),
};
pub const ExprGroup = extern struct {
classIndex: Node.Kind = .expr_group,
closePosition: Location.Position,
};
pub const ExprConstantNumber = extern struct {
classIndex: Node.Kind = .expr_constant_number,
value: Ast.Array(u8),
};
pub const ExprConstantInteger = extern struct {
classIndex: Node.Kind = .expr_constant_integer,
value: Ast.Array(u8),
};
pub const ExprConstantString = extern struct {
classIndex: Node.Kind = .expr_constant_string,
sourceString: Ast.Array(u8),
quoteStyle: QuoteStyle,
blockDepth: u32,
pub const QuoteStyle = enum(u32) {
quoted_single,
quoted_double,
quoted_raw,
quoted_interp,
};
};
pub const TypeInstantiation = extern struct {
leftArrow1Position: Location.Position = .missing,
leftArrow2Position: Location.Position = .missing,
commaPositions: Ast.Array(Location.Position),
rightArrow1Position: Location.Position = .missing,
rightArrow2Position: Location.Position = .missing,
};
pub const ExprCall = extern struct {
classIndex: Node.Kind = .expr_call,
openParens: Location.Position,
closeParens: Location.Position,
commaPositions: Ast.Array(Location.Position),
explicitTypes: ?*TypeInstantiation = null,
};
pub const ExprIndexExpr = extern struct {
classIndex: Node.Kind = .expr_index_expr,
openBracketPosition: Location.Position,
closeBracketPosition: Location.Position,
};
pub const ExprFunction = extern struct {
classIndex: Node.Kind = .expr_function,
attrLists: Ast.Array(*AttrList) = .{},
functionKeywordPosition: Location.Position = .missing,
openGenericsPosition: Location.Position = .missing,
genericsCommaPositions: Ast.Array(Location.Position),
closeGenericsPosition: Location.Position = .missing,
argsAnnotationColonPositions: Ast.Array(Location.Position),
argsCommaPositions: Ast.Array(Location.Position),
varargAnnotationColonPosition: Location.Position = .missing,
returnSpecifierPosition: Location.Position = .missing,
};
pub const ExprTable = extern struct {
classIndex: Node.Kind = .expr_table,
items: Ast.Array(Item),
pub const Separator = enum(u32) {
comma,
semicolon,
missing,
};
pub const Item = extern struct {
/// '[', only if Kind == General
indexerOpenPosition: Location.Position,
/// ']', only if Kind == General
indexerClosePosition: Location.Position,
/// only if Kind != List
equalsPosition: Location.Position,
/// may be missing for last Item
separator: Separator,
/// may be missing for last Item
separatorPosition: Location.Position,
};
};
pub const ExprOp = extern struct {
classIndex: Node.Kind = .expr_op,
opPosition: Location.Position,
};
pub const ExprTypeAssertion = extern struct {
classIndex: Node.Kind = .expr_type_assertion,
opPosition: Location.Position,
};
pub const ExprIfElse = extern struct {
classIndex: Node.Kind = .expr_if_else,
thenPosition: Location.Position,
elsePosition: Location.Position,
isElseIf: bool,
};
pub const ExprInterpString = extern struct {
classIndex: Node.Kind = .expr_interp_string,
sourceStrings: Ast.Array(Ast.Array(u8)),
stringPositions: Ast.Array(Location.Position),
};
pub const ExprExplicitTypeInstantiation = extern struct {
classIndex: Node.Kind = .expr_explicit_type_instantiation,
instantiation: TypeInstantiation,
};
pub const StatDo = extern struct {
classIndex: Node.Kind = .stat_do,
statsStartPosition: Location.Position,
endPosition: Location.Position,
};
pub const StatRepeat = extern struct {
classIndex: Node.Kind = .stat_repeat,
untilPosition: Location.Position,
};
pub const StatReturn = extern struct {
classIndex: Node.Kind = .stat_return,
commaPositions: Ast.Array(Location.Position),
};
pub const StatLocal = extern struct {
classIndex: Node.Kind = .stat_local,
varsAnnotationColonPositions: Ast.Array(Location.Position),
varsCommaPositions: Ast.Array(Location.Position),
valuesCommaPositions: Ast.Array(Location.Position),
};
pub const StatFor = extern struct {
classIndex: Node.Kind = .stat_for,
annotationColonPosition: Location.Position,
equalsPosition: Location.Position,
endCommaPosition: Location.Position,
stepCommaPosition: Location.Position,
};
pub const StatForIn = extern struct {
classIndex: Node.Kind = .stat_for_in,
varsAnnotationColonPositions: Ast.Array(Location.Position),
varsCommaPositions: Ast.Array(Location.Position),
valuesCommaPositions: Ast.Array(Location.Position),
};
pub const StatAssign = extern struct {
classIndex: Node.Kind = .stat_assign,
varsCommaPositions: Ast.Array(Location.Position),
equalsPosition: Location.Position,
valuesCommaPositions: Ast.Array(Location.Position),
};
pub const StatCompoundAssign = extern struct {
classIndex: Node.Kind = .stat_compound_assign,
opPosition: Location.Position,
};
pub const StatFunction = extern struct {
classIndex: Node.Kind = .stat_function,
attrLists: Ast.Array(*AttrList),
functionKeywordPosition: Location.Position,
};
pub const StatLocalFunction = extern struct {
classIndex: Node.Kind = .stat_local_function,
attrLists: Ast.Array(*AttrList),
localKeywordPosition: Location.Position,
functionKeywordPosition: Location.Position,
};
pub const GenericType = extern struct {
classIndex: Node.Kind = .generic_type,
defaultEqualsPosition: Location.Position,
};
pub const GenericTypePack = extern struct {
classIndex: Node.Kind = .generic_type_pack,
ellipsisPosition: Location.Position,
defaultEqualsPosition: Location.Position,
};
pub const StatTypeAlias = extern struct {
classIndex: Node.Kind = .stat_type_alias,
typeKeywordPosition: Location.Position,
genericsOpenPosition: Location.Position,
genericsCommaPositions: Ast.Array(Location.Position),
genericsClosePosition: Location.Position,
equalsPosition: Location.Position,
};
pub const StatTypeFunction = extern struct {
classIndex: Node.Kind = .stat_type_function,
typeKeywordPosition: Location.Position,
functionKeywordPosition: Location.Position,
};
pub const TypeReference = extern struct {
classIndex: Node.Kind = .type_reference,
prefixPointPosition: Location.Position,
openParametersPosition: Location.Position,
parametersCommaPositions: Ast.Array(Location.Position),
closeParametersPosition: Location.Position,
};
pub const TypeTable = extern struct {
classIndex: Node.Kind = .type_table,
items: Ast.Array(Item),
isArray: bool,
pub const Item = extern struct {
kind: Kind,
indexerOpenPosition: Location.Position, // '[', only if Kind != Property
indexerClosePosition: Location.Position, // ']' only if Kind != Property
colonPosition: Location.Position,
separator: ExprTable.Separator, // may be missing for last Item
separatorPosition: Location.Position,
stringInfo: ?*ExprConstantString, // only if Kind == StringProperty
stringPosition: Location.Position, // only if Kind == StringProperty
pub const Kind = enum(u32) {
indexer,
property,
string_property,
};
};
};
pub const TypeFunction = extern struct {
classIndex: Node.Kind = .type_function,
openGenericsPosition: Location.Position,
genericsCommaPositions: Ast.Array(Location.Position),
closeGenericsPosition: Location.Position,
openArgsPosition: Location.Position,
argumentNameColonPositions: Ast.Array(Location.Position),
argumentsCommaPositions: Ast.Array(Location.Position),
closeArgsPosition: Location.Position,
returnArrowPosition: Location.Position,
};
pub const TypeTypeof = extern struct {
classIndex: Node.Kind = .type_typeof,
openPosition: Location.Position,
closePosition: Location.Position,
};
pub const TypeUnion = extern struct {
classIndex: Node.Kind = .type_union,
leadingPosition: Location.Position,
separatorPositions: Ast.Array(Location.Position),
};
pub const TypeIntersection = extern struct {
classIndex: Node.Kind = .type_intersection,
leadingPosition: Location.Position,
separatorPositions: Ast.Array(Location.Position),
};
pub const TypeSingletonString = extern struct {
classIndex: Node.Kind = .type_singleton_string,
sourceString: Ast.Array(u8),
quoteStyle: ExprConstantString.QuoteStyle,
blockDepth: u32,
};
pub const TypeGroup = extern struct {
classIndex: Node.Kind = .type_group,
closePosition: Location.Position,
};
pub const TypePackExplicit = extern struct {
classIndex: Node.Kind = .type_pack_explicit,
openParenthesesPosition: Location.Position,
closeParenthesesPosition: Location.Position,
commaPositions: Ast.Array(Location.Position),
};
pub const TypePackGeneric = extern struct {
classIndex: Node.Kind = .type_pack_generic,
ellipsisPosition: Location.Position,
};
test Node {
const Lexer = @import("Lexer.zig");
const Parser = @import("Parser.zig");
const Allocator = @import("Allocator.zig");
{
const allocator = Allocator.init();
defer allocator.deinit();
const table = Lexer.AstNameTable.init(allocator);
defer table.deinit();
const source =
\\local x: number = 1;
\\local x = 2
\\local x = 3
\\
;
var parse_result = Parser.parse(source, table, allocator, .{
.storeCstData = true,
});
defer parse_result.deinit();
const root = parse_result.root;
try std.testing.expectEqual(Ast.Node.Kind.stat_block, root.classIndex);
const stats = root.body.slice();
try std.testing.expectEqual(3, stats.len);
try std.testing.expectEqual(7, parse_result.cstNodeMap.count);
try std.testing.expect(parse_result.cstNodeMap.find(@ptrCast(@alignCast(root))) == null);
for (stats, 1..) |node, order| {
switch (node.classIndex) {
.stat_local => {
const local: *Ast.StatLocal = node.as(.stat_local).?;
const cst_node = (parse_result.cstNodeMap.find(@ptrCast(@alignCast(node))) orelse @panic("Not found")).second;
const cst_local: *StatLocal = cst_node.as(.stat_local).?;
try std.testing.expect(cst_local.varsCommaPositions.size == 0);
try std.testing.expect(cst_local.valuesCommaPositions.size == 0);
try std.testing.expect(cst_local.varsAnnotationColonPositions.size == 1);
try std.testing.expectEqualStrings("x", std.mem.span(local.vars.slice()[0].name.value));
try std.testing.expect(@as(f64, @floatFromInt(order)) == local.values.slice()[0].as(.expr_constant_number).?.value);
},
else => {},
}
}
}
}
test "CstValuesCheck" {
if (@import("builtin").cpu.arch.isWasm() or @import("builtin").os.tag == .windows)
return error.SkipZigTest;
const CstValues = struct {
pub extern "c" const CstAttrIndex: u8;
pub extern "c" const CstParametrizedAttrIndex: u8;
pub extern "c" const CstExprGroupIndex: u8;
pub extern "c" const CstExprConstantNumberIndex: u8;
pub extern "c" const CstExprConstantIntegerIndex: u8;
pub extern "c" const CstExprConstantStringIndex: u8;
pub extern "c" const CstExprCallIndex: u8;
pub extern "c" const CstExprIndexExprIndex: u8;
pub extern "c" const CstExprFunctionIndex: u8;
pub extern "c" const CstExprTableIndex: u8;
pub extern "c" const CstExprOpIndex: u8;
pub extern "c" const CstExprTypeAssertionIndex: u8;
pub extern "c" const CstExprIfElseIndex: u8;
pub extern "c" const CstExprInterpStringIndex: u8;
pub extern "c" const CstExprExplicitTypeInstantiationIndex: u8;
pub extern "c" const CstStatDoIndex: u8;
pub extern "c" const CstStatRepeatIndex: u8;
pub extern "c" const CstStatReturnIndex: u8;
pub extern "c" const CstStatLocalIndex: u8;
pub extern "c" const CstStatForIndex: u8;
pub extern "c" const CstStatForInIndex: u8;
pub extern "c" const CstStatAssignIndex: u8;
pub extern "c" const CstStatCompoundAssignIndex: u8;
pub extern "c" const CstStatFunctionIndex: u8;
pub extern "c" const CstStatLocalFunctionIndex: u8;
pub extern "c" const CstGenericTypeIndex: u8;
pub extern "c" const CstGenericTypePackIndex: u8;
pub extern "c" const CstStatTypeAliasIndex: u8;
pub extern "c" const CstStatTypeFunctionIndex: u8;
pub extern "c" const CstTypeReferenceIndex: u8;
pub extern "c" const CstTypeTableIndex: u8;
pub extern "c" const CstTypeTableItemKindIndexer: u8;
pub extern "c" const CstTypeTableItemKindProperty: u8;
pub extern "c" const CstTypeTableItemKindStringProperty: u8;
pub extern "c" const CstTypeFunctionIndex: u8;
pub extern "c" const CstTypeTypeofIndex: u8;
pub extern "c" const CstTypeUnionIndex: u8;
pub extern "c" const CstTypeIntersectionIndex: u8;
pub extern "c" const CstTypeSingletonStringIndex: u8;
pub extern "c" const CstTypeGroupIndex: u8;
pub extern "c" const CstTypePackExplicitIndex: u8;
pub extern "c" const CstTypePackGenericIndex: u8;
pub extern "c" const CstExprGroupSize: usize;
pub extern "c" const CstExprConstantNumberSize: usize;
pub extern "c" const CstExprConstantIntegerSize: usize;
pub extern "c" const CstExprConstantStringSize: usize;
pub extern "c" const CstExprCallSize: usize;
pub extern "c" const CstExprIndexExprSize: usize;
pub extern "c" const CstExprFunctionSize: usize;
pub extern "c" const CstExprTableSize: usize;
pub extern "c" const CstExprOpSize: usize;
pub extern "c" const CstExprTypeAssertionSize: usize;
pub extern "c" const CstExprIfElseSize: usize;
pub extern "c" const CstExprInterpStringSize: usize;
pub extern "c" const CstExprExplicitTypeInstantiationSize: usize;
pub extern "c" const CstStatDoSize: usize;
pub extern "c" const CstStatRepeatSize: usize;
pub extern "c" const CstStatReturnSize: usize;
pub extern "c" const CstStatLocalSize: usize;
pub extern "c" const CstStatForSize: usize;
pub extern "c" const CstStatForInSize: usize;
pub extern "c" const CstStatAssignSize: usize;
pub extern "c" const CstStatCompoundAssignSize: usize;
pub extern "c" const CstStatFunctionSize: usize;
pub extern "c" const CstStatLocalFunctionSize: usize;
pub extern "c" const CstGenericTypeSize: usize;
pub extern "c" const CstGenericTypePackSize: usize;
pub extern "c" const CstStatTypeAliasSize: usize;
pub extern "c" const CstStatTypeFunctionSize: usize;
pub extern "c" const CstTypeReferenceSize: usize;
pub extern "c" const CstTypeTableSize: usize;
pub extern "c" const CstTypeFunctionSize: usize;
pub extern "c" const CstTypeTypeofSize: usize;
pub extern "c" const CstTypeUnionSize: usize;
pub extern "c" const CstTypeIntersectionSize: usize;
pub extern "c" const CstTypeSingletonStringSize: usize;
pub extern "c" const CstTypeGroupSize: usize;
pub extern "c" const CstTypePackExplicitSize: usize;
pub extern "c" const CstTypePackGenericSize: usize;
};
try std.testing.expect(CstValues.CstTypeTableItemKindIndexer == @intFromEnum(TypeTable.Item.Kind.indexer));
try std.testing.expect(CstValues.CstTypeTableItemKindProperty == @intFromEnum(TypeTable.Item.Kind.property));
try std.testing.expect(CstValues.CstTypeTableItemKindStringProperty == @intFromEnum(TypeTable.Item.Kind.string_property));
@setEvalBranchQuota(2000);
inline for (@typeInfo(CstValues).@"struct".decls) |decl| {
if (comptime std.mem.endsWith(u8, decl.name, "Index")) {
const name = decl.name[3 .. decl.name.len - 5];
const cst_node_type = @field(Cst, name);
const info = @typeInfo(cst_node_type).@"struct";
comptime var field: ?std.builtin.Type.StructField = null;
inline for (info.fields) |f| {
if (comptime std.mem.eql(u8, f.name, "classIndex")) {
field = f;
break;
}
}
if (field == null)
@compileError("classIndex field not found");
const default_value_ptr = field.?.default_value_ptr orelse @compileError("classIndex field does not have a default value");
const enum_value = @as(*const Cst.Node.Kind, @ptrCast(@alignCast(default_value_ptr))).*;
std.testing.expectEqual(@field(CstValues, decl.name), @intFromEnum(enum_value)) catch |err| {
std.debug.print("index error for {s}\n", .{name});
return err;
};
} else if (comptime std.mem.endsWith(u8, decl.name, "Size")) {
const name = decl.name[3 .. decl.name.len - 4];
const cst_node_type = @field(Cst, name);
std.testing.expectEqual(@field(CstValues, decl.name), @sizeOf(cst_node_type)) catch |err| {
std.debug.print("size error for {s}\n", .{name});
return err;
};
}
}
}
// sources:
// https://github.com/luau-lang/luau/blob/40d4815888f63362a6cb79b3e74c4aafa0b2cbf4/Ast/include/Luau/Cst.h
// https://github.com/luau-lang/luau/blob/40d4815888f63362a6cb79b3e74c4aafa0b2cbf4/Ast/src/Cst.cpp
+15
View File
@@ -0,0 +1,15 @@
#include <bridge.h>
#include "Luau/Lexer.h"
#define ZIG_LUAU_AST(name) ZIG_FN(Luau_Ast_##name)
ZIG_EXPORT Luau::AstNameTable* ZIG_LUAU_AST(Lexer_AstNameTable_init)(Luau::Allocator* allocator)
{
return new Luau::AstNameTable(*allocator);
}
ZIG_EXPORT void ZIG_LUAU_AST(Lexer_AstNameTable_dtor)(Luau::AstNameTable* names)
{
delete names;
}
+130
View File
@@ -0,0 +1,130 @@
const std = @import("std");
const Ast = @import("Ast.zig");
const Allocator = @import("Allocator.zig");
const DenseHash = @import("../Common/DenseHash.zig");
extern "c" fn zig_Luau_Ast_Lexer_AstNameTable_init(*Allocator) *AstNameTable;
extern "c" fn zig_Luau_Ast_Lexer_AstNameTable_dtor(*AstNameTable) void;
pub const Lexeme = struct {
pub const Type = enum(c_int) {
Eof = 0,
// 1..255 means actual character values
Char_END = 256,
Equal,
LessEqual,
GreaterEqual,
NotEqual,
Dot2,
Dot3,
SkinnyArrow,
DoubleColon,
FloorDiv,
InterpStringBegin,
InterpStringMid,
InterpStringEnd,
// An interpolated string with no expressions (like `x`)
InterpStringSimple,
AddAssign,
SubAssign,
MulAssign,
DivAssign,
FloorDivAssign,
ModAssign,
PowAssign,
ConcatAssign,
RawString,
QuotedString,
Number,
Name,
Comment,
BlockComment,
Attribute,
AttributeOpen,
BrokenString,
BrokenComment,
BrokenUnicode,
BrokenInterpDoubleBrace,
Error,
// Reserved_BEGIN,
ReservedAnd,
ReservedBreak,
ReservedDo,
ReservedElse,
ReservedElseif,
ReservedEnd,
ReservedFalse,
ReservedFor,
ReservedFunction,
ReservedIf,
ReservedIn,
ReservedLocal,
ReservedNil,
ReservedNot,
ReservedOr,
ReservedRepeat,
ReservedReturn,
ReservedThen,
ReservedTrue,
ReservedUntil,
ReservedWhile,
Reserved_END,
pub const Reserved_BEGIN = Type.ReservedAnd;
};
};
pub const AstNameTable = extern struct {
data: DenseHash.DenseHashSet(Entry, EntryHash),
allocator: *Allocator,
const Entry = extern struct {
value: Ast.Name,
length: u32,
type: Lexeme.Type,
};
const EntryHash = extern struct {
pub fn hash(e: *const Entry) usize {
var h: u32 = 2166136261;
for (0..e.length) |i| {
h ^= @as(u8, e.value[i]);
h *= 16777619;
}
return h;
}
pub fn eq(_: *const Entry, _: *const Entry) bool {
@compileError("not implemented");
}
};
pub fn init(allocator: *Allocator) *AstNameTable {
return zig_Luau_Ast_Lexer_AstNameTable_init(allocator);
}
pub fn deinit(self: *AstNameTable) void {
zig_Luau_Ast_Lexer_AstNameTable_dtor(self);
}
};
test AstNameTable {
const allocator = Allocator.init();
defer allocator.deinit();
const astNameTable = AstNameTable.init(allocator);
defer astNameTable.deinit();
}
// sources:
// https://github.com/luau-lang/luau/blob/a2303a6ae68c53035eccf230c4450b9f068536af/Ast/include/Luau/Lexer.h
// https://github.com/luau-lang/luau/blob/a2303a6ae68c53035eccf230c4450b9f068536af/Ast/src/Lexer.cpp
+60
View File
@@ -0,0 +1,60 @@
const std = @import("std");
pub const Location = extern struct {
begin: Position = .zeros,
end: Position = .zeros,
pub fn eq(self: Location, other: Location) bool {
return self.begin.eq(other.begin) and self.end.eq(other.end);
}
pub fn encloses(self: Location, other: Location) bool {
return self.begin.lessThanOrEq(other.begin) and self.end.greaterThanOrEq(other.end);
}
pub fn overlaps(self: Location, other: Location) bool {
return (self.begin.lessThanOrEq(other.begin) and self.end.greaterThanOrEq(other.begin)) or (self.begin.lessThanOrEq(other.end) and self.end.greaterThanOrEq(other.end)) or (other.begin.greaterThanOrEq(self.begin) and other.end.lessThanOrEq(self.end));
}
pub fn contains(self: Location, position: Position) bool {
return self.begin.lessThanOrEq(position) and position.lessThan(self.end);
}
pub fn containsClosed(self: Location, position: Position) bool {
return self.begin.lessThanOrEq(position) and position.lessThanOrEq(self.end);
}
pub const Position = extern struct {
line: c_uint,
column: c_uint,
pub const missing: Position = .{ .line = std.math.maxInt(u32), .column = std.math.maxInt(u32) };
pub const zeros: Position = .{ .line = 0, .column = 0 };
pub fn eq(self: Position, other: Position) bool {
return self.line == other.line and self.column == other.column;
}
pub fn lessThan(self: Position, other: Position) bool {
if (self.line == other.line)
return self.column < other.column;
return self.line < other.line;
}
pub inline fn lessThanOrEq(self: Position, other: Position) bool {
return self.eq(other) or self.lessThan(other);
}
pub inline fn greaterThan(self: Position, other: Position) bool {
return !self.lessThanOrEq(other);
}
pub inline fn greaterThanOrEq(self: Position, other: Position) bool {
return !self.lessThan(other);
}
pub fn hasValue(self: Position) bool {
return self.line != std.math.maxInt(u32) and self.column != std.math.maxInt(u32);
}
};
};
// sources:
// https://github.com/luau-lang/luau/blob/a2303a6ae68c53035eccf230c4450b9f068536af/Ast/include/Luau/Location.h
// https://github.com/luau-lang/luau/blob/a2303a6ae68c53035eccf230c4450b9f068536af/Ast/src/Location.cpp
+78
View File
@@ -0,0 +1,78 @@
#include <bridge.h>
#include "Luau/Ast.h"
#include "Luau/Parser.h"
#define ZIG_LUAU_AST(name) ZIG_FN(Luau_Ast_##name)
ZIG_EXPORT struct luau_ParseOptions
{
unsigned char data[sizeof(Luau::ParseOptions)];
};
ZIG_EXPORT Luau::ParseResult* ZIG_LUAU_AST(Parser_parse)(
const char* source, size_t sourceLen,
Luau::AstNameTable* names,
Luau::Allocator* allocator,
const luau_ParseOptions* options
)
{
Luau::ParseOptions parseOptions;
if (options)
{
static_assert(sizeof(luau_ParseOptions) == sizeof(Luau::ParseOptions), "C and C++ interface must match");
memcpy(static_cast<void*>(&parseOptions), options, sizeof(parseOptions));
}
Luau::ParseResult result = Luau::Parser::parse(source, sourceLen, *names, *allocator, parseOptions);
return new Luau::ParseResult(std::move(result));
}
ZIG_EXPORT void ZIG_LUAU_AST(ParseResult_dtor)(Luau::ParseResult* result)
{
delete result;
}
ZIG_EXPORT Luau::ParseNodeResult<Luau::AstExpr>* ZIG_LUAU_AST(Parser_parseExpr)(
const char* source, size_t sourceLen,
Luau::AstNameTable* names,
Luau::Allocator* allocator,
const luau_ParseOptions* options
)
{
Luau::ParseOptions parseOptions;
if (options)
{
static_assert(sizeof(luau_ParseOptions) == sizeof(Luau::ParseOptions), "C and C++ interface must match");
memcpy(static_cast<void*>(&parseOptions), options, sizeof(parseOptions));
}
Luau::ParseNodeResult<Luau::AstExpr> result = Luau::Parser::parseExpr(source, sourceLen, *names, *allocator, parseOptions);
return new Luau::ParseNodeResult<Luau::AstExpr>(std::move(result));
}
ZIG_EXPORT void ZIG_LUAU_AST(ParseNodeResult_AstExpr_dtor)(Luau::ParseNodeResult<Luau::AstExpr>* result)
{
delete result;
}
ZIG_EXPORT Luau::ParseNodeResult<Luau::AstType>* ZIG_LUAU_AST(Parser_parseType)(
const char* source, size_t sourceLen,
Luau::AstNameTable* names,
Luau::Allocator* allocator,
const luau_ParseOptions* options
)
{
Luau::ParseOptions parseOptions;
if (options)
{
static_assert(sizeof(luau_ParseOptions) == sizeof(Luau::ParseOptions), "C and C++ interface must match");
memcpy(static_cast<void*>(&parseOptions), options, sizeof(parseOptions));
}
Luau::ParseNodeResult<Luau::AstType> result = Luau::Parser::parseType(source, sourceLen, *names, *allocator, parseOptions);
return new Luau::ParseNodeResult<Luau::AstType>(std::move(result));
}
ZIG_EXPORT void ZIG_LUAU_AST(ParseNodeResult_AstType_dtor)(Luau::ParseNodeResult<Luau::AstType>* result)
{
delete result;
}
+156
View File
@@ -0,0 +1,156 @@
const std = @import("std");
const cpp_std = @import("../cpp_std.zig");
const Ast = @import("Ast.zig");
const Cst = @import("Cst.zig");
const Lexer = @import("Lexer.zig");
const Location = @import("Location.zig").Location;
const Allocator = @import("Allocator.zig");
const DenseHash = @import("../Common/DenseHash.zig");
pub const ParseError = cpp_std.Exception(extern struct {
location: Location,
message: cpp_std.String,
});
pub const ParseErrors = cpp_std.Exception(extern struct {
errors: cpp_std.Vector(ParseError),
message: cpp_std.String,
});
pub const HotComment = extern struct {
header: bool,
location: Location,
content: cpp_std.String,
};
pub const Comment = extern struct {
type: Lexer.Lexeme.Type, // Comment, BlockComment, or BrokenComment
location: Location,
};
pub const ParseOptions = extern struct {
allowDeclarationSyntax: bool = false,
captureComments: bool = false,
parseFragment: cpp_std.Optional(FragmentParseResumeSettings) = .nullopt,
storeCstData: bool = false,
noErrorLimit: bool = false,
pub const FragmentParseResumeSettings = extern struct {
localMap: DenseHash.DenseHashMap(Ast.Name, *Ast.Local, struct {}) = .init(.{ .value = "" }, 0),
localStack: cpp_std.Vector(*Ast.Local) = undefined,
resumePosition: Location.Position,
};
};
extern "c" fn zig_Luau_Ast_Parser_parse([*]const u8, usize, *Lexer.AstNameTable, *Allocator, *const ParseOptions) *ParseResult;
extern "c" fn zig_Luau_Ast_Parser_parseExpr([*]const u8, usize, *Lexer.AstNameTable, *Allocator, *const ParseOptions) *ParseNodeResult(Ast.Expr);
extern "c" fn zig_Luau_Ast_Parser_parseType([*]const u8, usize, *Lexer.AstNameTable, *Allocator, *const ParseOptions) *ParseNodeResult(Ast.Type);
extern "c" fn zig_Luau_Ast_ParseResult_dtor(*ParseResult) void;
extern "c" fn zig_Luau_Ast_ParseNodeResult_AstExpr_dtor(*ParseNodeResult(Ast.Expr)) void;
extern "c" fn zig_Luau_Ast_ParseNodeResult_AstType_dtor(*ParseNodeResult(Ast.Type)) void;
pub fn parse(source: []const u8, nameTable: *Lexer.AstNameTable, allocator: *Allocator, options: ParseOptions) *ParseResult {
return zig_Luau_Ast_Parser_parse(source.ptr, source.len, nameTable, allocator, &options);
}
pub fn parseExpr(source: []const u8, nameTable: *Lexer.AstNameTable, allocator: *Allocator, options: ParseOptions) *ParseNodeResult(Ast.Expr) {
return zig_Luau_Ast_Parser_parseExpr(source.ptr, source.len, nameTable, allocator, &options);
}
pub fn parseType(source: []const u8, nameTable: *Lexer.AstNameTable, allocator: *Allocator, options: ParseOptions) *ParseNodeResult(Ast.Type) {
return zig_Luau_Ast_Parser_parseType(source.ptr, source.len, nameTable, allocator, &options);
}
pub const CstNodeMap = DenseHash.DenseHashMap(*Ast.Node, *Cst.Node, struct {});
pub const ParseResult = extern struct {
root: *Ast.StatBlock,
lines: usize = 0,
hotcomments: cpp_std.Vector(HotComment),
errors: cpp_std.Vector(ParseError),
commentLocations: cpp_std.Vector(Comment),
cstNodeMap: CstNodeMap,
pub inline fn deinit(self: *ParseResult) void {
zig_Luau_Ast_ParseResult_dtor(self);
}
};
pub fn ParseNodeResult(comptime T: type) type {
return extern struct {
expr: *T,
lines: usize = 0,
hotcomments: cpp_std.Vector(HotComment),
errors: cpp_std.Vector(ParseError),
commentLocations: cpp_std.Vector(Comment),
cstNodeMap: CstNodeMap,
pub const Self = @This();
pub inline fn deinit(self: *Self) void {
comptime std.debug.assert(T == Ast.Type or T == Ast.Expr);
if (T == Ast.Type) {
zig_Luau_Ast_ParseNodeResult_AstType_dtor(self);
} else {
zig_Luau_Ast_ParseNodeResult_AstExpr_dtor(self);
}
}
};
}
test ParseResult {
{
const allocator = Allocator.init();
defer allocator.deinit();
const astNameTable = Lexer.AstNameTable.init(allocator);
defer astNameTable.deinit();
const source =
\\--!test
\\-- This is a test comment
\\local x =
\\
;
var parseResult = parse(source, astNameTable, allocator, .{});
defer parseResult.deinit();
{
var iter = parseResult.hotcomments.iterator();
var count: usize = 0;
while (iter.next()) |comment| : (count += 1) {
const string = comment.content.slice();
try std.testing.expectEqualStrings("test", string);
try std.testing.expectEqual(true, comment.header);
try std.testing.expectEqual(0, comment.location.begin.line);
try std.testing.expectEqual(0, comment.location.begin.column);
try std.testing.expectEqual(0, comment.location.end.line);
try std.testing.expectEqual(7, comment.location.end.column);
}
try std.testing.expectEqual(1, count);
}
{
try std.testing.expectEqual(1, parseResult.errors.size());
const first = parseResult.errors.at(0).value;
try std.testing.expectEqualStrings("Expected identifier when parsing expression, got <eof>", first.message.slice());
try std.testing.expectEqual(3, first.location.begin.line);
try std.testing.expectEqual(0, first.location.begin.column);
try std.testing.expectEqual(3, first.location.end.line);
try std.testing.expectEqual(0, first.location.end.column);
}
}
}
// sources:
// https://github.com/luau-lang/luau/blob/a2303a6ae68c53035eccf230c4450b9f068536af/Ast/include/Luau/Parser.h
// https://github.com/luau-lang/luau/blob/a2303a6ae68c53035eccf230c4450b9f068536af/Ast/src/Parser.cpp
+15
View File
@@ -0,0 +1,15 @@
const c = @import("c");
const lua = @import("../VM/lua.zig");
pub inline fn supported() bool {
return c.luau_codegen_supported() != 0;
}
pub inline fn create(L: *lua.State) void {
c.luau_codegen_create(@ptrCast(L));
}
pub inline fn compile(L: *lua.State, idx: i32) void {
c.luau_codegen_compile(@ptrCast(L), idx);
}
+731
View File
@@ -0,0 +1,731 @@
// This file contains the bytecode definition for Luau interpreter
// Creating the bytecode is outside the scope of this file and is handled by bytecode builder (BytecodeBuilder.h) and bytecode compiler (Compiler.h)
// Note that ALL enums declared in this file are order-sensitive since the values are baked into bytecode that needs to be processed by legacy clients.
// # Bytecode definitions
// Bytecode instructions are using "word code" - each instruction is one or many 32-bit words.
// The first word in the instruction is always the instruction header, and *must* contain the opcode (enum below) in the least significant byte.
//
// Instruction word can be encoded using one of the following encodings:
// ABC - least-significant byte for the opcode, followed by three bytes, A, B and C; each byte declares a register index, small index into some other table or an unsigned integral value
// AD - least-significant byte for the opcode, followed by A byte, followed by D half-word (16-bit integer). D is a signed integer that commonly specifies constant table index or jump offset
// E - least-significant byte for the opcode, followed by E (24-bit integer). E is a signed integer that commonly specifies a jump offset
//
// Instruction word is sometimes followed by one extra word, indicated as AUX - this is just a 32-bit word and is decoded according to the specification for each opcode.
// For each opcode the encoding is *static* - that is, based on the opcode you know a-priory how large the instruction is, with the exception of NEWCLOSURE
// # Bytecode indices
// Bytecode instructions commonly refer to integer values that define offsets or indices for various entities. For each type, there's a maximum encodable value.
// Note that in some cases, the compiler will set a lower limit than the maximum encodable value is to prevent fragile code into bumping against the limits whenever we change the compilation details.
// Additionally, in some specific instructions such as ANDK, the limit on the encoded value is smaller; this means that if a value is larger, a different instruction must be selected.
//
// Registers: 0-254. Registers refer to the values on the function's stack frame, including arguments.
// Upvalues: 0-199. Upvalues refer to the values stored in the closure object.
// Constants: 0-2^23-1. Constants are stored in a table allocated with each proto; to allow for future bytecode tweaks the encodable value is limited to 23 bits.
// Closures: 0-2^15-1. Closures are created from child protos via a child index; the limit is for the number of closures immediately referenced in each function.
// Jumps: -2^23..2^23. Jump offsets are specified in word increments, so jumping over an instruction may sometimes require an offset of 2 or more. Note that for jump instructions with AUX, the AUX word is included as part of the jump offset.
// # Bytecode versions
// Bytecode serialized format embeds a version number, that dictates both the serialized form as well as the allowed instructions. As long as the bytecode version falls into supported
// range (indicated by BYTECODE_MIN / BYTECODE_MAX) and was produced by Luau compiler, it should load and execute correctly.
//
// Note that Luau runtime doesn't provide indefinite bytecode compatibility: support for older versions gets removed over time. As such, bytecode isn't a durable storage format and it's expected
// that Luau users can recompile bytecode from source on Luau version upgrades if necessary.
// # Bytecode version history
//
// Note: due to limitations of the versioning scheme, some bytecode blobs that carry version 2 are using features from version 3. Starting from version 3, version should be sufficient to indicate bytecode compatibility.
//
// Version 1: Baseline version for the open-source release. Supported until 0.521.
// Version 2: Adds Proto::linedefined. Supported until 0.544.
// Version 3: Adds FORGPREP/JUMPXEQK* and enhances AUX encoding for FORGLOOP. Removes FORGLOOP_NEXT/INEXT and JUMPIFEQK/JUMPIFNOTEQK. Currently supported.
// Version 4: Adds Proto::flags, typeinfo, and floor division opcodes IDIV/IDIVK. Currently supported.
// Version 5: Adds SUBRK/DIVRK and vector constants. Currently supported.
// Version 6: Adds FASTCALL3. Currently supported.
// # Bytecode type information history
// Version 1: (from bytecode version 4) Type information for function signature. Currently supported.
// Version 2: (from bytecode version 4) Type information for arguments, upvalues, locals and some temporaries. Currently supported.
// Bytecode opcode, part of the instruction header
pub const Opcode = enum(u32) {
// NOP: noop
NOP,
// BREAK: debugger break
BREAK,
// LOADNIL: sets register to nil
// A: target register
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
LOADB,
// LOADN: sets register to a number literal
// A: target register
// D: value (-32768..32767)
LOADN,
// LOADK: sets register to an entry from the constant table from the proto (number/vector/string)
// A: target register
// D: constant table index (0..32767)
LOADK,
// MOVE: move (copy) value from one register to another
// A: target register
// B: source register
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
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
SETGLOBAL,
// GETUPVAL: load upvalue from the upvalue table for the current function
// A: target register
// B: upvalue index
GETUPVAL,
// SETUPVAL: store value into the upvalue table for the current function
// A: target register
// B: upvalue index
SETUPVAL,
// CLOSEUPVALS: close (migrate to heap) all upvalues that were captured for registers >= target
// A: target register
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)
GETIMPORT,
// GETTABLE: load value from table into target register using key from register
// A: target register
// B: table register
// C: index register
GETTABLE,
// SETTABLE: store source register into table using key from register
// A: source register
// B: table register
// C: index register
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
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
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)
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)
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)
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
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)
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)
RETURN,
// JUMP: jumps to target offset
// D: jump offset (-32768..32767; 0 means "next instruction" aka "don't jump")
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")
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")
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")
JUMPIFNOT,
// JUMPIFEQ, JUMPIFLE, JUMPIFLT, JUMPIFNOTEQ, JUMPIFNOTLE, JUMPIFNOTLT: jumps to target offset if the comparison is true (or false, for NOT variants)
// A: source register 1
// D: jump offset (-32768..32767; 1 means "next instruction" aka "don't jump")
// AUX: source register 2
JUMPIFEQ,
JUMPIFLE,
JUMPIFLT,
JUMPIFNOTEQ,
JUMPIFNOTLE,
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
ADD,
SUB,
MUL,
DIV,
MOD,
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); must refer to a number
ADDK,
SUBK,
MULK,
DIVK,
MODK,
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
AND,
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)
ANDK,
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
CONCAT,
// NOT, MINUS, LENGTH: compute unary operation for source register and put the result into target register
// A: target register
// B: source register
NOT,
MINUS,
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
NEWTABLE,
// DUPTABLE: duplicate table using the constant table template to target register
// A: target register
// D: constant table index (0..32767)
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
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
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)
FORNLOOP,
// FORGLOOP: adjust loop variables for one iteration of a generic for loop, jump back to the loop header if loop needs to continue
// A: target register; generic for loops assume a register layout [generator, state, index, variables...]
// D: jump offset (-32768..32767)
// AUX: variable count (1..255) in the low 8 bits, high bit indicates whether to use ipairs-style traversal in the fast path
// loop variables are adjusted by calling generator(state, index) and expecting it to return a tuple that's copied to the user variables
// the first variable is then copied into index; generator/state are immutable, index isn't visible to user code
FORGLOOP,
// FORGPREP_INEXT: prepare FORGLOOP with 2 output variables (no AUX encoding), assuming generator is luaB_inext, and jump to FORGLOOP
// A: target register (see FORGLOOP for register layout)
FORGPREP_INEXT,
// FASTCALL3: perform a fast call of a built-in function using 3 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
// AUX: source register 3 in second least-significant byte
FASTCALL3,
// FORGPREP_NEXT: prepare FORGLOOP with 2 output variables (no AUX encoding), assuming generator is luaB_next, and jump to FORGLOOP
// A: target register (see FORGLOOP for register layout)
FORGPREP_NEXT,
// NATIVECALL: start executing new function in native code
// this is a pseudo-instruction that is never emitted by bytecode compiler, but can be constructed at runtime to accelerate native code dispatch
NATIVECALL,
// 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)
GETVARARGS,
// DUPCLOSURE: create closure from a pre-created function object (reusing it unless environments diverge)
// A: target register
// D: constant table index (0..32767)
DUPCLOSURE,
// PREPVARARGS: prepare stack for variadic functions so that GETVARARGS works correctly
// A: number of fixed arguments
PREPVARARGS,
// LOADKX: sets register to an entry from the constant table from the proto (number/string)
// A: target register
// AUX: constant table index
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")
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
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
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)
CAPTURE,
// SUBRK, DIVRK: compute arithmetic operation between the constant and a source register and put the result into target register
// A: target register
// B: constant table index (0..255); must refer to a number
// C: source register
SUBRK,
DIVRK,
// 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
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
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
FASTCALL2K,
// FORGPREP: prepare loop variables for a generic for loop, jump to the loop backedge unconditionally
// A: target register; generic for loops assume a register layout [generator, state, index, variables...]
// D: jump offset (-32768..32767)
FORGPREP,
// JUMPXEQKNIL, JUMPXEQKB: jumps to target offset if the comparison with constant is true (or false, see AUX)
// A: source register 1
// D: jump offset (-32768..32767; 1 means "next instruction" aka "don't jump")
// AUX: constant value (for boolean) in low bit, NOT flag (that flips comparison result) in high bit
JUMPXEQKNIL,
JUMPXEQKB,
// JUMPXEQKN, JUMPXEQKS: jumps to target offset if the comparison with constant is true (or false, see AUX)
// A: source register 1
// D: jump offset (-32768..32767; 1 means "next instruction" aka "don't jump")
// AUX: constant table index in low 24 bits, NOT flag (that flips comparison result) in high bit
JUMPXEQKN,
JUMPXEQKS,
// IDIV: compute floor division between two source registers and put the result into target register
// A: target register
// B: source register 1
// C: source register 2
IDIV,
// IDIVK compute floor division 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)
IDIVK,
// Atom-based userdata field access acceleration
// These are equivalent to their GETTABLEKS/SETTABLEKS/NAMECALL counterparts, except tailored towards userdata field accesses
// If the user has registered metamethods for a userdata tag, callbacks will be called by these instructions
GETUDATAKS,
SETUDATAKS,
NAMECALLUDATA,
// NEWCLASSMEMBER: register this method on a class object.
// A: target register of class
// B: reserved
// C: initial value of this member. currently must be a function.
// AUX: The name of this member as a constant string
NEWCLASSMEMBER,
// CALLFB: call specified function with collecting runtime stats in a feedback slot
// 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)
// AUX: feedback slot id. 0xFFFFFFFF - sealed
CALLFB,
// CMPPROTO: check if a register contains a closure with a specified Luau function proto id
// A: closure register
// D: jump offset if proto doesn't match
// AUX: proto id
CMPPROTO,
// Enum entry for number of opcodes, not a valid opcode by itself!
_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
pub inline fn INSN_OP(insn: u32) u8 {
return (insn) & 0xff;
}
// ABC encoding: three 8-bit values, containing registers or small numbers
pub inline fn INSN_A(insn: u32) u8 {
return (((insn) >> 8) & 0xff);
}
pub inline fn INSN_B(insn: u32) u8 {
return (((insn) >> 16) & 0xff);
}
pub inline fn INSN_C(insn: u32) u8 {
return (((insn) >> 24) & 0xff);
}
// AD encoding: one 8-bit value, one signed 16-bit value
pub inline fn INSN_D(insn: u32) i16 {
return (@as(i32, @bitCast(insn)) >> 16);
}
// E encoding: one signed 24-bit value
pub inline fn INSN_E(insn: u32) i32 {
return (@as(i32, @bitCast(insn)) >> 8);
}
// Bytecode tags, used internally for bytecode encoded as a string
pub const BytecodeTag = enum(u32) {
CONSTANT_NIL = 0,
CONSTANT_BOOLEAN,
CONSTANT_NUMBER,
CONSTANT_STRING,
CONSTANT_IMPORT,
CONSTANT_TABLE,
CONSTANT_CLOSURE,
CONSTANT_VECTOR,
CONSTANT_TABLE_WITH_CONSTANTS,
CONSTANT_INTEGER,
CONSTANT_CLASS_SHAPE,
// WARNING: This must always be last.
CONSTANT__COUNT,
// Bytecode version; runtime supports [MIN, MAX], compiler emits TARGET by default but may emit a higher version when flags are enabled
// Type encoding version
// Types of constant table entries
pub const VERSION_MIN = 3;
pub const VERSION_MAX = 6;
pub const VERSION_TARGET = 6;
pub const TYPE_VERSION_MIN = 1;
pub const TYPE_VERSION_MAX = 3;
pub const TYPE_VERSION_TARGET = 3;
};
// Type table tags
pub const BytecodeType = enum(u32) {
TYPE_NIL = 0,
TYPE_BOOLEAN,
TYPE_NUMBER,
TYPE_STRING,
TYPE_TABLE,
TYPE_FUNCTION,
TYPE_THREAD,
TYPE_USERDATA,
TYPE_VECTOR,
TYPE_BUFFER,
TYPE_ANY = 15,
TYPE_TAGGED_USERDATA_BASE = 64,
TYPE_TAGGED_USERDATA_END = 64 + 32,
TYPE_OPTIONAL_BIT = 1 << 7,
TYPE_INVALID = 256,
};
// Builtin function ids, used in FASTCALL
pub const BuiltinFunction = enum(u32) {
LBF_NONE = 0,
// assert()
LBF_ASSERT,
// math.
LBF_MATH_ABS,
LBF_MATH_ACOS,
LBF_MATH_ASIN,
LBF_MATH_ATAN2,
LBF_MATH_ATAN,
LBF_MATH_CEIL,
LBF_MATH_COSH,
LBF_MATH_COS,
LBF_MATH_DEG,
LBF_MATH_EXP,
LBF_MATH_FLOOR,
LBF_MATH_FMOD,
LBF_MATH_FREXP,
LBF_MATH_LDEXP,
LBF_MATH_LOG10,
LBF_MATH_LOG,
LBF_MATH_MAX,
LBF_MATH_MIN,
LBF_MATH_MODF,
LBF_MATH_POW,
LBF_MATH_RAD,
LBF_MATH_SINH,
LBF_MATH_SIN,
LBF_MATH_SQRT,
LBF_MATH_TANH,
LBF_MATH_TAN,
// bit32.
LBF_BIT32_ARSHIFT,
LBF_BIT32_BAND,
LBF_BIT32_BNOT,
LBF_BIT32_BOR,
LBF_BIT32_BXOR,
LBF_BIT32_BTEST,
LBF_BIT32_EXTRACT,
LBF_BIT32_LROTATE,
LBF_BIT32_LSHIFT,
LBF_BIT32_REPLACE,
LBF_BIT32_RROTATE,
LBF_BIT32_RSHIFT,
// type()
LBF_TYPE,
// string.
LBF_STRING_BYTE,
LBF_STRING_CHAR,
LBF_STRING_LEN,
// typeof()
LBF_TYPEOF,
// string.
LBF_STRING_SUB,
// math.
LBF_MATH_CLAMP,
LBF_MATH_SIGN,
LBF_MATH_ROUND,
// raw*
LBF_RAWSET,
LBF_RAWGET,
LBF_RAWEQUAL,
// table.
LBF_TABLE_INSERT,
LBF_TABLE_UNPACK,
// vector ctor
LBF_VECTOR,
// bit32.count
LBF_BIT32_COUNTLZ,
LBF_BIT32_COUNTRZ,
// select(_, ...)
LBF_SELECT_VARARG,
// rawlen
LBF_RAWLEN,
// bit32.extract(_, k, k)
LBF_BIT32_EXTRACTK,
// get/setmetatable
LBF_GETMETATABLE,
LBF_SETMETATABLE,
// tonumber/tostring
LBF_TONUMBER,
LBF_TOSTRING,
// bit32.byteswap(n)
LBF_BIT32_BYTESWAP,
// buffer.
LBF_BUFFER_READI8,
LBF_BUFFER_READU8,
LBF_BUFFER_WRITEU8,
LBF_BUFFER_READI16,
LBF_BUFFER_READU16,
LBF_BUFFER_WRITEU16,
LBF_BUFFER_READI32,
LBF_BUFFER_READU32,
LBF_BUFFER_WRITEU32,
LBF_BUFFER_READF32,
LBF_BUFFER_WRITEF32,
LBF_BUFFER_READF64,
LBF_BUFFER_WRITEF64,
// vector.
LBF_VECTOR_MAGNITUDE,
LBF_VECTOR_NORMALIZE,
LBF_VECTOR_CROSS,
LBF_VECTOR_DOT,
LBF_VECTOR_FLOOR,
LBF_VECTOR_CEIL,
LBF_VECTOR_ABS,
LBF_VECTOR_SIGN,
LBF_VECTOR_CLAMP,
LBF_VECTOR_MIN,
LBF_VECTOR_MAX,
// math.lerp
LBF_MATH_LERP,
// vector.lerp
LBF_VECTOR_LERP,
// math.
LBF_MATH_ISNAN,
LBF_MATH_ISINF,
LBF_MATH_ISFINITE,
// integer
LBF_INTEGER_CREATE,
LBF_INTEGER_TONUMBER,
LBF_INTEGER_NEG,
LBF_INTEGER_ADD,
LBF_INTEGER_SUB,
LBF_INTEGER_MUL,
LBF_INTEGER_DIV,
LBF_INTEGER_MIN,
LBF_INTEGER_MAX,
LBF_INTEGER_REM,
LBF_INTEGER_IDIV,
LBF_INTEGER_UDIV,
LBF_INTEGER_UREM,
LBF_INTEGER_MOD,
LBF_INTEGER_CLAMP,
LBF_INTEGER_BAND,
LBF_INTEGER_BOR,
LBF_INTEGER_BNOT,
LBF_INTEGER_BXOR,
LBF_INTEGER_LT,
LBF_INTEGER_LE,
LBF_INTEGER_ULT,
LBF_INTEGER_ULE,
LBF_INTEGER_GT,
LBF_INTEGER_GE,
LBF_INTEGER_UGT,
LBF_INTEGER_UGE,
LBF_INTEGER_LSHIFT,
LBF_INTEGER_RSHIFT,
LBF_INTEGER_ARSHIFT,
LBF_INTEGER_LROTATE,
LBF_INTEGER_RROTATE,
LBF_INTEGER_EXTRACT,
LBF_INTEGER_BTEST,
LBF_INTEGER_COUNTRZ,
LBF_INTEGER_COUNTLZ,
LBF_INTEGER_BSWAP,
// buffer.readinteger / buffer.writeinteger (int64_t)
LBF_BUFFER_READINTEGER,
LBF_BUFFER_WRITEINTEGER,
};
// Capture type, used in CAPTURE
pub const CaptureType = enum(u32) {
LCT_VAL = 0,
LCT_REF,
LCT_UPVAL,
};
// Proto flag bitmask, stored in Proto::flags
pub const ProtoFlag = enum(u32) {
/// used to tag main proto for modules with --!native
LPF_NATIVE_MODULE = 1 << 0,
/// used to tag individual protos as not profitable to compile natively
LPF_NATIVE_COLD = 1 << 1,
/// used to tag main proto for modules that have at least one function with native attribute
LPF_NATIVE_FUNCTION = 1 << 2,
/// function can be inlined
LPF_INLINABLE = 1 << 3,
};
pub const LuauFeedbackType = enum(u32) { LFT_CALLTARGET = 0 };
// sources:
// https://github.com/luau-lang/luau/blob/a2303a6ae68c53035eccf230c4450b9f068536af/Common/include/Luau/Bytecode.h
+40
View File
@@ -0,0 +1,40 @@
const Bytecode = @import("Bytecode.zig");
pub inline fn getOpLength(op: Bytecode.Opcode) usize {
return switch (op) {
.GETGLOBAL,
.SETGLOBAL,
.GETIMPORT,
.GETTABLEKS,
.SETTABLEKS,
.NAMECALL,
.JUMPIFEQ,
.JUMPIFLE,
.JUMPIFLT,
.JUMPIFNOTEQ,
.JUMPIFNOTLE,
.JUMPIFNOTLT,
.NEWTABLE,
.SETLIST,
.FORGLOOP,
.LOADKX,
.FASTCALL2,
.FASTCALL2K,
.FASTCALL3,
.JUMPXEQKNIL,
.JUMPXEQKB,
.JUMPXEQKN,
.JUMPXEQKS,
.GETUDATAKS,
.SETUDATAKS,
.NAMECALLUDATA,
.NEWCLASSMEMBER,
.CALLFB,
.CMPPROTO,
=> 2,
else => 1,
};
}
// sources:
// https://github.com/luau-lang/luau/blob/32d52d1b2ceef46fc25d87094a2d7f201c3ea5b8/Common/include/Luau/BytecodeUtils.h
+191
View File
@@ -0,0 +1,191 @@
const std = @import("std");
const cpp_std = @import("../cpp_std.zig");
extern fn zig_new_any(size: usize) callconv(.c) *anyopaque;
extern fn zig_delete_any(*anyopaque) callconv(.c) void;
pub fn DenseHashPointer(key: *const anyopaque) usize {
// return (@intFromPtr(key) >> 4) ^ (@intFromPtr(key) >> 9);
// The idea to use this hash function was suggested here originally: https://maskray.me/blog/2026-06-07-recent-llvm-hash-table-improvements
// Hash function implementation is detailed here: https://github.com/MaskRay/llvm-project/blob/main/llvm/include/llvm/ADT/DenseMapInfo.h
// This hash produces better scattering for arena allocated types, because the pointers usually share the higher order bits.
// When inserting lots of keys, quadratic probing is not enough to save DenseHash, although it usually takes many more elements,
// before it becomes a problem
var u: u64 = @intFromPtr(key);
u *%= 0xbf58476d1ce4e5b9;
u ^= u >> 31;
// On 32-bit platforms uint64_t to size_t is a narrowing, so we need
// to static cast here.
return @truncate(u);
}
pub const detail = struct {
pub fn DenseHashTable(
comptime Key: type,
comptime Item: type,
comptime MutableItem: type,
comptime ItemInterface: type,
comptime Hasher: type,
) type {
const hash = if (@hasDecl(Hasher, "hash")) Hasher.hash else struct {
pub fn hash(e: Key) usize {
if (comptime @typeInfo(Key) == .pointer) {
return DenseHashPointer(@ptrCast(@alignCast(e)));
} else {
@compileError("Hasher must implement 'hash' function");
}
}
}.hash;
const eq = if (@hasDecl(ItemInterface, "eq")) ItemInterface.eq else struct {
pub fn eq(a: Key, b: Key) bool {
return a == b;
}
}.eq;
_ = MutableItem;
return extern struct {
data: ?[*]Item = null,
capacity: usize = 0,
count: usize = 0,
empty_key: Key,
hasher: u8 = 0,
eq: u8 = 0,
const This = @This();
pub fn init(empty_key: Key, buckets: usize) This {
var data: ?[*]Item = null;
var capacity: usize = 0;
if (buckets > 0) {
data = @ptrCast(@alignCast(zig_new_any(@sizeOf(Item) * buckets)));
capacity = buckets;
ItemInterface.fill(data.?, buckets, empty_key);
}
return .{
.data = data,
.capacity = capacity,
.count = 0,
.empty_key = empty_key,
};
}
pub fn find(self: *This, key: Key) ?*const Item {
if (self.count == 0)
return null;
if (eq(key, self.empty_key))
return null;
const hashmod = self.capacity - 1;
var bucket = hash(key) & hashmod;
for (0..hashmod) |probe| {
const probe_item = &self.data.?[bucket];
// Element exists
if (eq(ItemInterface.getKey(probe_item), key))
return probe_item;
// Element does not exist
if (eq(ItemInterface.getKey(probe_item), self.empty_key))
return null;
// Hash collision, quadratic probing
bucket = (bucket + probe + 1) & hashmod;
}
// Hash table is full - this should not happen
std.debug.assert(false);
return null;
}
pub fn size(self: This) usize {
return self.count;
}
pub fn deinit(self: *This) void {
if (self.data) |data| {
ItemInterface.destroy(data, self.capacity);
zig_delete_any(@ptrCast(@alignCast(data)));
self.data = null;
self.capacity = 0;
}
}
};
}
};
pub fn ItemInterfaceSet(comptime Key: type) type {
return struct {
pub fn getKey(item: *const Key) Key {
return item.*;
}
pub fn setKey(item: *Key, key: Key) void {
item.* = key;
}
pub fn fill(data: [*]Key, count: usize, key: Key) void {
for (0..count) |i|
data[i] = key;
}
pub fn destroy(data: [*]Key, count: usize) void {
if (@hasDecl(Key, "deinit"))
for (0..count) |i| {
Key.deinit(data[i]);
};
}
};
}
pub fn ItemInterfaceMap(comptime Key: type, comptime Value: type) type {
return struct {
pub fn getKey(item: *const cpp_std.Pair(Key, Value)) Key {
return item.first;
}
pub fn setKey(item: *cpp_std.Pair(Key, Value), key: Key) void {
item.first = key;
}
pub fn fill(data: [*]cpp_std.Pair(Key, Value), count: usize, key: Key) void {
for (0..count) |i| {
data[i].first = key;
data[i].second = .{};
}
}
pub fn destroy(data: [*]cpp_std.Pair(Key, Value), count: usize) void {
for (0..count) |i| {
const ptr = data[i];
if (@hasDecl(Key, "deinit"))
Key.deinit(&ptr.first);
if (@hasDecl(Value, "deinit"))
Value.deinit(&ptr.second);
}
}
};
}
pub fn DenseHashSet(
comptime Key: type,
comptime Hasher: type,
) type {
return detail.DenseHashTable(Key, Key, Key, ItemInterfaceSet(Key), Hasher);
}
pub fn DenseHashMap(
comptime Key: type,
comptime Value: type,
comptime Hasher: type,
) type {
return detail.DenseHashTable(Key, cpp_std.Pair(Key, Value), cpp_std.Pair(Key, Value), ItemInterfaceMap(Key, Value), Hasher);
}
// sources:
// https://github.com/luau-lang/luau/blob/a2303a6ae68c53035eccf230c4450b9f068536af/Common/include/Luau/DenseHash.h
+28
View File
@@ -0,0 +1,28 @@
const std = @import("std");
pub inline fn isAnalysisFlagExperimental(flag: []const u8) bool {
// Flags in this list are disabled by default in various command-line tools. They may have behavior that is not fully final,
// or critical bugs that are found after the code has been submitted. This list is intended _only_ for flags that affect
// Luau's type checking. Flags that may change runtime behavior (e.g.: parser or VM flags) are not appropriate for this list.
const kList = [_][]const u8{
"LuauInstantiateInSubtyping", // requires some fixes to lua-apps code
"LuauFixIndexerSubtypingOrdering", // requires some small fixes to lua-apps code since this fixes a false negative
"StudioReportLuauAny2", // takes telemetry data for usage of any types
"LuauTableCloneClonesType3", // requires fixes in lua-apps code, terrifyingly
"LuauSolverV2",
"UseNewLuauTypeSolverDefaultEnabled", // This can change the default solver used in cli applications, so it also needs to be disabled. Will require fixes in lua-apps code
};
for (comptime kList) |item|
if (std.mem.eql(u8, flag, item))
return true;
return false;
}
test {
std.testing.refAllDecls(@This());
}
// sources:
// https://github.com/luau-lang/luau/blob/a2303a6ae68c53035eccf230c4450b9f068536af/Common/include/Luau/ExperimentalFlags.h
+66
View File
@@ -0,0 +1,66 @@
const std = @import("std");
pub fn Variant(comptime Ts: []const type) type {
comptime {
if (Ts.len == 0) @compileError("variant must have at least 1 type");
}
const storage_size = comptime blk: {
var max: usize = 0;
for (Ts) |T|
max = @max(max, @sizeOf(T));
break :blk max;
};
const storage_align = comptime blk: {
var max: usize = 1;
for (Ts) |T|
max = @max(max, @alignOf(T));
break :blk max;
};
const TaggedUnion = blk: {
var names: [Ts.len][]const u8 = undefined;
var field_types: [Ts.len]type = undefined;
var field_attributes: [Ts.len]std.builtin.Type.UnionField.Attributes = undefined;
inline for (Ts, 0..) |T, i| {
names[i] = std.fmt.comptimePrint("{d}", .{i}); // becomes @"0", @"1", etc.
field_types[i] = T;
field_attributes[i] = .{
.@"align" = @alignOf(T),
};
}
break :blk @Union(
.auto,
null,
&names,
&field_types,
&field_attributes,
);
};
return extern struct {
typeId: c_int,
storage: [storage_size]u8 align(storage_align),
pub const Union = TaggedUnion;
pub fn @"union"(self: *const @This()) Union {
inline for (Ts, 0..) |T, i| {
if (self.typeId == @as(c_int, @intCast(i))) {
const active_ptr: *const T = @ptrCast(@alignCast(&self.storage));
return @unionInit(
Union,
std.fmt.comptimePrint("{d}", .{i}),
active_ptr.*,
);
}
}
unreachable;
}
pub fn index(self: *const @This()) c_int {
return self.typeId;
}
};
}
+133
View File
@@ -0,0 +1,133 @@
#include <bridge.h>
#include "luacode.h"
#include "Luau/Common.h"
#include "Luau/Parser.h"
#include "Luau/BytecodeBuilder.h"
#include "Luau/Compiler.h"
#include "Luau/TimeTrace.h"
#define ZIG_LUAU_COMPILER(name) ZIG_FN(Luau_Compiler_##name)
const char* outputBytes(const std::string& result, size_t* len)
{
char* copy = static_cast<char*>(malloc(result.size()));
if (!copy)
return nullptr;
memcpy(copy, result.data(), result.size());
*len = result.size();
return copy;
}
ZIG_EXPORT const char* ZIG_LUAU_COMPILER(compile_ParseResult)(
const Luau::ParseResult* result,
Luau::AstNameTable* names,
size_t* len,
lua_CompileOptions* options,
Luau::BytecodeEncoder* encoder = nullptr
) {
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));
}
LUAU_TIMETRACE_SCOPE("compile", "Compiler");
if (!result->errors.empty())
{
// Users of this function expect only a single error message
const Luau::ParseError& parseError = result->errors.front();
std::string error = Luau::format(":%d: %s", parseError.getLocation().begin.line + 1, parseError.what());
return outputBytes(Luau::BytecodeBuilder::getError(error), len);
}
try
{
Luau::BytecodeBuilder bcb(encoder);
Luau::compileOrThrow(bcb, *result, *names, opts);
return outputBytes(bcb.getBytecode(), len);
}
catch (Luau::CompileError& e)
{
std::string error = Luau::format(":%d: %s", e.getLocation().begin.line + 1, e.what());
return outputBytes(Luau::BytecodeBuilder::getError(error), len);
}
}
ZIG_EXPORT int ZIG_LUAU_COMPILER(compileLoad_ParseResult)(
const Luau::ParseResult* result,
Luau::AstNameTable* names,
lua_State* L,
const char* moduleName,
const lua_CompileOptions* options,
int env,
Luau::BytecodeEncoder* encoder = nullptr
) {
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));
}
LUAU_TIMETRACE_SCOPE("compile", "Compiler");
if (!result->errors.empty())
{
// Users of this function expect only a single error message
const Luau::ParseError& parseError = result->errors.front();
std::string error = Luau::format(":%d: %s", parseError.getLocation().begin.line + 1, parseError.what());
std::string bytecode = Luau::BytecodeBuilder::getError(error);
return luau_load(L, moduleName, bytecode.data(), bytecode.size(), env);
}
try
{
Luau::BytecodeBuilder bcb(encoder);
Luau::compileOrThrow(bcb, *result, *names, opts);
std::string bytecode = bcb.getBytecode();
return luau_load(L, moduleName, bytecode.data(), bytecode.size(), env);
}
catch (Luau::CompileError& e)
{
std::string error = Luau::format(":%d: %s", e.getLocation().begin.line + 1, e.what());
std::string bytecode = Luau::BytecodeBuilder::getError(error);
return luau_load(L, moduleName, bytecode.data(), bytecode.size(), env);
}
}
ZIG_EXPORT int ZIG_LUAU_COMPILER(compileLoad)(
lua_State* L,
const char* moduleName,
const char* contents,
size_t len,
const lua_CompileOptions* options,
int env
) {
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 bytecode = Luau::compile(std::string(contents, len), opts);
return luau_load(L, moduleName, bytecode.data(), bytecode.size(), env);
}
ZIG_EXPORT void ZIG_LUAU_COMPILER(compile_free)(void *ptr)
{
free(ptr);
}
+157
View File
@@ -0,0 +1,157 @@
const std = @import("std");
const lua = @import("../VM/lua.zig");
const Parser = @import("../Ast/Parser.zig");
const Lexer = @import("../Ast/Lexer.zig");
const Location = @import("../Ast/Location.zig").Location;
const cpp_std = @import("../cpp_std.zig");
pub const CompileConstant = *anyopaque;
/// return a type identifier for a global library member
/// values are defined by 'enum LuauBytecodeType' in Bytecode.h
pub const LibraryMemberTypeCallback = *const fn (library: [*c]const u8, member: [*c]const u8) callconv(.c) c_int;
/// setup a value of a constant for a global library member
/// use setCompileConstant*** set of functions for values
pub const LibraryMemberConstantCallback = *const fn (library: [*c]const u8, member: [*c]const u8, constant: *CompileConstant) callconv(.c) c_int;
pub const CompileOptions = extern struct {
/// 0 - no optimization
/// 1 - baseline optimization level that doesn't prevent debuggability
/// 2 - includes optimizations that harm debuggability such as inlining
optimizationLevel: c_int = 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
debugLevel: c_int = 1,
/// type information is used to guide native code generation decisions
/// information includes testable typeArguments for function arguments, locals, upvalues and some temporaries
/// 0 - generate for native modules
/// 1 - generate for all modules
typeInfoLevel: c_int = 0,
/// 0 - no code coverage support
/// 1 - statement coverage
/// 2 - statement and expression coverage (verbose)
coverageLevel: c_int = 0,
/// alternative global builtin to construct vectors, in addition to default builtin 'vector.create'
vectorLib: [*c]const u8 = null,
vectorCtor: [*c]const u8 = null,
/// alternative vector type name for type tables, in addition to default type 'vector'
vectorType: [*c]const u8 = null,
/// null-terminated array of globals that are mutable; disables the import optimization for fields accessed through these
mutableGlobals: [*c]const [*c]const u8 = null,
/// null-terminated array of userdata typeArguments that will be included in the type information
userdataTypes: [*c]const [*c]const u8 = null,
/// null-terminated array of globals which act as libraries and have members with known type and/or constant value
/// when an import of one of these libraries is accessed, callbacks below will be called to receive that information
librariesWithKnownMembers: [*c]const [*c]const u8 = null,
libraryMemberTypeCb: ?LibraryMemberTypeCallback = null,
libraryMemberConstantCb: ?LibraryMemberConstantCallback = null,
// null-terminated array of library functions that should not be compiled into a built-in fastcall ("name" "lib.name")
disabledBuiltins: [*c]const [*c]const u8 = null,
};
pub const CompilerError = cpp_std.Exception(extern struct {
location: Location,
message: cpp_std.String,
});
extern "c" fn zig_Luau_Compiler_compile_ParseResult(
*const Parser.ParseResult,
*const Lexer.AstNameTable,
*usize,
?*const CompileOptions,
?*anyopaque,
) ?[*]const u8;
extern "c" fn zig_Luau_Compiler_compileLoad_ParseResult(
*const Parser.ParseResult,
*const Lexer.AstNameTable,
*lua.State,
[*c]const u8,
?*const CompileOptions,
c_int,
?*anyopaque,
) c_int;
extern "c" fn zig_Luau_Compiler_compileLoad(
*lua.State,
[*c]const u8,
[*c]const u8,
usize,
?*const CompileOptions,
c_int,
) c_int;
extern "c" fn zig_Luau_Compiler_compile_free(*anyopaque) void;
pub fn compileParseResult(
allocator: std.mem.Allocator,
parseResult: *Parser.ParseResult,
namesTable: *Lexer.AstNameTable,
options: ?CompileOptions,
) error{OutOfMemory}![]const u8 {
var size: usize = 0;
const bytes = zig_Luau_Compiler_compile_ParseResult(parseResult, namesTable, &size, if (options) |*o| o else null, null) orelse return error.OutOfMemory;
defer zig_Luau_Compiler_compile_free(@ptrCast(@constCast(bytes)));
return try allocator.dupe(u8, bytes[0..size]);
}
pub fn compileLoadParseResult(
L: *lua.State,
moduleName: [:0]const u8,
parseResult: *Parser.ParseResult,
namesTable: *Lexer.AstNameTable,
options: ?CompileOptions,
env: i32,
) error{Fail}!void {
if (zig_Luau_Compiler_compileLoad_ParseResult(parseResult, namesTable, L, moduleName, if (options) |*o| o else null, env, null) != 0)
return error.Fail;
}
pub fn compileLoad(
L: *lua.State,
moduleName: [:0]const u8,
source: []const u8,
options: ?CompileOptions,
env: i32,
) error{Fail}!void {
if (zig_Luau_Compiler_compileLoad(L, moduleName, source.ptr, source.len, if (options) |*o| o else null, env) != 0)
return error.Fail;
}
test compileParseResult {
const Allocator = @import("../Ast/Allocator.zig");
const allocator = Allocator.init();
defer allocator.deinit();
const astNameTable = Lexer.AstNameTable.init(allocator);
defer astNameTable.deinit();
const source =
\\--!test
\\-- This is a test comment
\\local x =
\\
;
const parseResult = Parser.parse(source, astNameTable, allocator, .{});
defer parseResult.deinit();
const zig_allocator = std.testing.allocator;
const bytes = try compileParseResult(zig_allocator, parseResult, astNameTable, null);
defer zig_allocator.free(bytes);
try std.testing.expect(bytes[0] == 0);
try std.testing.expectEqualStrings(":4: Expected identifier when parsing expression, got <eof>", bytes[1..]);
}
// sources:
// https://github.com/luau-lang/luau/blob/8fe64db609ccbffb0abb7507c7ecef8c88327ef3/Compiler/include/Luau/Compiler.h
// https://github.com/luau-lang/luau/blob/8fe64db609ccbffb0abb7507c7ecef8c88327ef3/Compiler/src/Compiler.cpp
+49
View File
@@ -0,0 +1,49 @@
const c = @import("c");
const std = @import("std");
const Compiler = @import("Compiler.zig");
extern "c" fn zig_luau_free(ptr: *anyopaque) void;
extern "c" fn luau_compile(source: [*c]const u8, size: usize, options: ?*const Compiler.CompileOptions, outsize: [*c]usize) [*c]u8;
extern "c" fn luau_set_compile_constant_nil(constant: *Compiler.CompileConstant) void;
extern "c" fn luau_set_compile_constant_boolean(constant: *Compiler.CompileConstant, b: c_int) void;
extern "c" fn luau_set_compile_constant_number(constant: *Compiler.CompileConstant, n: f64) void;
extern "c" fn luau_set_compile_constant_vector(constant: *Compiler.CompileConstant, x: f32, y: f32, z: f32, w: f32) void;
extern "c" fn luau_set_compile_constant_string(constant: *Compiler.CompileConstant, s: [*c]const u8, l: usize) void;
/// Compile luau source into bytecode, return callee owned buffer allocated through the given allocator.
pub fn compile(allocator: std.mem.Allocator, source: []const u8, options: ?Compiler.CompileOptions) ![]const u8 {
var size: usize = 0;
const bytecode = luau_compile(source.ptr, source.len, if (options) |*o| o else null, &size);
if (bytecode == null)
return error.OutOfMemory;
defer zig_luau_free(bytecode);
return try allocator.dupe(u8, bytecode[0..size]);
}
pub fn set_compile_constant_nil(constant: *Compiler.CompileConstant) void {
luau_set_compile_constant_nil(constant);
}
pub fn set_compile_constant_boolean(constant: *Compiler.CompileConstant, b: bool) void {
luau_set_compile_constant_boolean(constant, if (b) 1 else 0);
}
pub fn set_compile_constant_number(constant: *Compiler.CompileConstant, n: f64) void {
luau_set_compile_constant_number(constant, n);
}
pub fn set_compile_constant_vector(constant: *Compiler.CompileConstant, x: f32, y: f32, z: f32, w: f32) void {
luau_set_compile_constant_vector(constant, x, y, z, w);
}
pub fn set_compile_constant_string(constant: *Compiler.CompileConstant, s: []const u8) void {
luau_set_compile_constant_string(constant, s.ptr, s.len);
}
// sources:
// https://github.com/luau-lang/luau/blob/8fe64db609ccbffb0abb7507c7ecef8c88327ef3/Compiler/include/luacode.h
// https://github.com/luau-lang/luau/blob/8fe64db609ccbffb0abb7507c7ecef8c88327ef3/Compiler/src/lcode.cpp
+12
View File
@@ -0,0 +1,12 @@
const lua = @import("../VM/lua.zig");
extern "c" fn luau_enable_jit_inliner(lua_State: *lua.State) void;
extern "c" fn luau_disable_jit_inliner(lua_State: *lua.State) void;
pub fn enable(L: *lua.State) void {
luau_enable_jit_inliner(L);
}
pub fn disable(L: *lua.State) void {
luau_disable_jit_inliner(L);
}
+25
View File
@@ -0,0 +1,25 @@
#include <bridge.h>
#include "lobject.h"
#include "lstate.h"
ZIG_EXPORT const unsigned char GCObject_size = sizeof(GCObject);
ZIG_EXPORT const unsigned char GCheader_size = sizeof(GCheader);
ZIG_EXPORT const unsigned char Value_size = sizeof(Value);
ZIG_EXPORT const unsigned char TValue_size = sizeof(TValue);
ZIG_EXPORT const unsigned char TString_size = sizeof(TString);
ZIG_EXPORT const unsigned char Udata_size = sizeof(Udata);
ZIG_EXPORT const unsigned char LuauBuffer_size = sizeof(LuauBuffer);
ZIG_EXPORT const unsigned char Proto_size = sizeof(Proto);
ZIG_EXPORT const unsigned char LocVar_size = sizeof(LocVar);
ZIG_EXPORT const unsigned char UpVal_size = sizeof(UpVal);
ZIG_EXPORT const unsigned char Closure_size = sizeof(Closure);
ZIG_EXPORT const unsigned char TKey_size = sizeof(TKey);
ZIG_EXPORT const unsigned char LuaNode_size = sizeof(LuaNode);
ZIG_EXPORT const unsigned char LuaTable_size = sizeof(LuaTable);
ZIG_EXPORT const unsigned char LuauClass_size = sizeof(LuauClass);
ZIG_EXPORT const unsigned char LuauObject_size = sizeof(LuauObject);
ZIG_EXPORT const unsigned char TString_data_offset = offsetof(TString, data);
ZIG_EXPORT const unsigned char Udata_data_offset = offsetof(Udata, data);
ZIG_EXPORT const unsigned char LuauBuffer_data_offset = offsetof(LuauBuffer, data);
+11
View File
@@ -0,0 +1,11 @@
pub const Memory = error{ BlockTooBig, OutOfMemory };
pub const Table = Memory || error{
@"table overflow",
@"attempt to modify a readonly table",
@"table index is nil",
@"table index is nan",
@"table index contains nan",
};
pub const TableReadonly = Table.@"attempt to modify a readonly table";
+1650
View File
File diff suppressed because it is too large Load Diff
+278
View File
@@ -0,0 +1,278 @@
const c = @import("c");
const std = @import("std");
const lua = @import("lua.zig");
const ltm = @import("ltm.zig");
const lapi = @import("lapi.zig");
const lobject = @import("lobject.zig");
const Errorset = @import("errorset.zig");
pub const Reg = struct {
name: [:0]const u8,
func: ?lua.CFunction,
};
pub fn OptionalValue(comptime T: type, L: *lua.State, check: anytype, narg: i32, d: T) T {
if (L.isnoneornil(narg))
return d
else
return check(L, narg);
}
pub fn currfuncname(L: *lua.State) ?[:0]const u8 {
const cl: ?*lobject.Closure = if (@intFromPtr(L.ci) > @intFromPtr(L.base_ci))
L.curr_func()
else
null;
const debugname: ?[:0]const u8 = if (cl != null and cl.?.isC != 0)
std.mem.span(cl.?.d.c.debugname)
else
null;
if (debugname != null and std.mem.eql(u8, debugname.?, "__namecall")) {
return if (L.namecall) |namecall|
std.mem.span(namecall.getstr())
else
null;
} else return debugname;
}
pub inline fn LargerrorL(L: *lua.State, narg: i32, extramsg: [:0]const u8) Errorset.Table!noreturn {
const fname = currfuncname(L);
if (fname) |name|
try LerrorL(L, "invalid argument #{d} to '{s}' ({s})", .{ narg, name, extramsg })
else
try LerrorL(L, "invalid argument #{d} ({s})", .{ narg, extramsg });
}
pub inline fn Largerror(L: *lua.State, narg: i32, extramsg: [:0]const u8) Errorset.Table!noreturn {
try LargerrorL(L, narg, extramsg);
}
pub inline fn Largcheck(L: *lua.State, cond: bool, narg: i32, extramsg: [:0]const u8) Errorset.Table!noreturn {
if (!cond) try LargerrorL(L, narg, extramsg);
}
pub inline fn LtypeerrorL(L: *lua.State, narg: i32, tname: [:0]const u8) noreturn {
c.luaL_typeerror(@as(*c.lua_State, @ptrCast(L)), narg, tname);
}
inline fn tag_error(L: *lua.State, narg: i32, tag: lua.Type) void {
LtypeerrorL(L, narg, lapi.typename(tag));
}
pub fn Lwhere(L: *lua.State, level: i32) Errorset.Table!void {
var info: lua.Debug = .{ .ssbuf = undefined };
if (L.getinfo(level, "sl", &info)) {
if (info.currentline) |line| {
try L.pushfstring("{s}:{d}: ", .{ info.short_src.?, line });
return;
}
}
try L.rawcheckstack(1);
try L.pushstring("");
}
pub fn LerrorL(L: *lua.State, comptime fmt: []const u8, args: anytype) Errorset.Table!noreturn {
try Lwhere(L, 1);
try L.pushvfstring(fmt, args);
try L.concat(2);
L.raiseerror();
}
pub inline fn Lcheckoption(L: *lua.State, comptime T: type, narg: i32, def: ?T) T {
const name = blk: {
if (def) |d|
break :blk Loptstring(narg, @tagName(d))
else
break :blk L.checkstring(narg);
};
inline for (std.meta.fields(T)) |field| {
if (std.mem.eql(u8, field.name, name))
return @enumFromInt(field.value);
}
var buf: [128]u8 = undefined;
return LargerrorL(L, narg, std.fmt.bufPrintZ(&buf, "invalid option '{s}'", .{name}) catch "");
}
/// Returns true if metatable was created, false if it already exists.
pub fn Lnewmetatable(L: *lua.State, tname: [:0]const u8) !bool {
if (try L.getfield(lua.REGISTRYINDEX, tname) != .Nil) // get registry.name, name already in use?
return false; // leave previous value on top, but return false
L.pop(1);
try L.newtable(); // create metatable
L.pushvalue(-1);
try L.setfield(lua.REGISTRYINDEX, tname); // registry.name = metatable
return true;
}
pub inline fn Lgetmetatable(L: *lua.State, tname: [:0]const u8) !lua.Type {
return L.getfield(lua.REGISTRYINDEX, tname);
}
pub inline fn Lcheckudata(L: *lua.State, comptime T: type, ud: i32, tname: [:0]const u8) ?*T {
return @ptrCast(c.luaL_checkudata(@ptrCast(L), ud, tname).?);
}
pub fn Lcheckbuffer(L: *lua.State, idx: i32) []u8 {
if (L.tobuffer(idx)) |b|
return b
else
return tag_error(L, idx, .Buffer);
}
pub fn Lcheckstack(L: *lua.State, space: usize, msg: ?[]const u8) Errorset.Table!void {
if (!try L.checkstack(space))
if (msg) |m|
try LerrorL(L, "stack overflow ({s})", .{m})
else
try LerrorL(L, "stack overflow", .{});
}
pub fn Lchecktype(L: *lua.State, narg: i32, t: lua.Type) void {
if (L.typeOf(narg) != t)
tag_error(L, narg, t);
}
pub fn Lcheckany(L: *lua.State, narg: i32) !void {
if (L.typeOf(narg) == .None)
try LerrorL(L, "missing argument #{d}", .{narg});
}
pub fn Lchecklstring(L: *lua.State, narg: i32) []const u8 {
if (L.tolstring(narg)) |s|
return s
else
tag_error(L, narg, .String);
}
pub fn Lcheckstring(L: *lua.State, narg: i32) [:0]const u8 {
if (L.tolstring(narg)) |s|
return s
else
tag_error(L, narg, .String);
}
pub fn Loptlstring(L: *lua.State, narg: i32, d: []const u8) []const u8 {
return OptionalValue([]const u8, L, Lchecklstring, narg, d);
}
pub fn Loptstring(L: *lua.State, narg: i32, d: [:0]const u8) [:0]const u8 {
return OptionalValue([:0]const u8, L, Lcheckstring, narg, d);
}
pub fn Lchecknumber(L: *lua.State, narg: i32) f64 {
return L.tonumberx(narg) orelse tag_error(L, narg, .Number);
}
pub fn Loptnumber(L: *lua.State, narg: i32, d: f64) f64 {
return OptionalValue(f64, L, Lchecknumber, narg, d);
}
pub fn Lcheckboolean(L: *lua.State, narg: i32) bool {
if (L.isboolean(narg))
return L.toboolean(narg)
else
tag_error(L, narg, .Boolean);
}
pub fn Loptboolean(L: *lua.State, narg: i32, d: bool) bool {
return OptionalValue(bool, L, Lcheckboolean, narg, d);
}
pub fn Lcheckinteger(L: *lua.State, narg: i32) i32 {
return L.tointegerx(narg) orelse tag_error(L, narg, .Number);
}
pub fn Lcheckinteger64(L: *lua.State, narg: i32) i64 {
return L.tointeger64(narg) orelse tag_error(L, narg, .Number);
}
pub fn Loptinteger(L: *lua.State, narg: i32, d: i32) i32 {
return OptionalValue(i32, L, Lcheckinteger, narg, d);
}
pub fn Loptinteger64(L: *lua.State, narg: i32, d: i64) i64 {
return OptionalValue(i64, L, Lcheckinteger64, narg, d);
}
pub fn Lcheckunsigned(L: *lua.State, narg: i32) u32 {
return L.tounsignedx(narg) orelse tag_error(L, narg, .Number);
}
pub fn Loptunsigned(L: *lua.State, narg: i32, d: u32) u32 {
return OptionalValue(u32, L, Lcheckunsigned, narg, d);
}
pub fn Lcheckvector(L: *lua.State, narg: i32) []const f32 {
return L.tovector(narg) orelse tag_error(L, narg, .Vector);
}
pub fn Loptvector(L: *lua.State, narg: i32, d: []const f32) []const f32 {
return OptionalValue([]const f32, L, Lcheckvector, narg, d);
}
pub fn Lgetmetafield(L: *lua.State, obj: i32, event: [:0]const u8) Errorset.Table!bool {
if (!L.getmetatable(obj)) // no metatable?
return false;
try L.pushstring(event);
_ = L.rawget(-2);
if (L.isnil(-1)) {
L.pop(2); // remove metatable and metafield
return false;
}
L.remove(-2); // remove only metatable
return true;
}
/// `unsafe` throws exceptions. Use `Zcallmeta`.
pub fn Lcallmeta(L: *lua.State, obj: i32, event: [:0]const u8) bool {
return c.luaL_callmeta(@ptrCast(L), obj, event) != 0;
}
pub fn Lregister(L: *lua.State, libname: ?[:0]const u8, funcs: []const Reg) Errorset.Table!void {
if (libname) |name| {
_ = try Lfindtable(L, lua.REGISTRYINDEX, "_LOADED", 1);
_ = try L.getfield(-1, name);
if (!L.istable(-1)) {
L.pop(1);
if (try Lfindtable(L, lua.GLOBALSINDEX, name, funcs.len)) |_|
try LerrorL(L, "name conflict for module '{s}'", .{name});
L.pushvalue(-1);
try L.setfield(-3, name);
}
L.remove(-2);
L.insert(-1);
}
for (funcs) |f| {
if (f.func) |func| {
try L.pushcfunction(func, f.name);
try L.setfield(-2, f.name);
}
}
}
pub inline fn Lfindtable(L: *lua.State, idx: i32, fname: [:0]const u8, szhint: usize) !?[]const u8 {
const p = c.luaL_findtable(@ptrCast(L), idx, fname, @truncate(@as(isize, @intCast(szhint))));
if (p != null)
return std.mem.span(p)
else
return null;
}
pub inline fn Ltypename(L: *lua.State, idx: i32) [:0]const u8 {
return std.mem.span(c.luaL_typename(@ptrCast(L), idx));
}
pub inline fn Lcallyieldable(L: *lua.State, nargs: i32, nresults: i32) i32 {
return c.luaL_callyieldable(@ptrCast(L), nargs, nresults);
}
/// `unsafe` throws exceptions. Use `Ztolstring`.
pub inline fn Ltolstring(L: *lua.State, idx: i32) [:0]const u8 {
var len: usize = undefined;
if (c.luaL_tolstring(@ptrCast(L), idx, &len)) |str|
return str[0..len :0]
else
unreachable;
}
+7
View File
@@ -0,0 +1,7 @@
const c = @import("c");
const lua = @import("lua.zig");
pub inline fn open(L: *lua.State) void {
_ = c.luaopen_base(@ptrCast(L));
}
+7
View File
@@ -0,0 +1,7 @@
const c = @import("c");
const lua = @import("lua.zig");
pub inline fn open(L: *lua.State) void {
_ = c.luaopen_bit32(@ptrCast(L));
}
+33
View File
@@ -0,0 +1,33 @@
const std = @import("std");
const lua = @import("lua.zig");
const lobject = @import("lobject.zig");
const lgc = @import("lgc.zig");
const lmem = @import("lmem.zig");
const Errorset = @import("errorset.zig");
// buffer size limit
pub const MAX_BUFFER_SIZE = 1 << 30;
// GCObject size has to be at least 16 bytes, so a minimum of 8 bytes is always reserved
pub inline fn sizebuffer(len: usize) usize {
return @offsetOf(lobject.Buffer, "data") + (if (len < 8) 8 else len);
}
pub fn Bnewbuffer(L: *lua.State, s: usize) Errorset.Memory!*lobject.Buffer {
if (s > MAX_BUFFER_SIZE)
return error.BlockTooBig;
const b = try lmem.Mnewgco(L, lobject.Buffer, sizebuffer(s), L.activememcat);
lgc.Cinit(L, @ptrCast(@alignCast(b)), @intFromEnum(lua.Type.Buffer));
b.len = @intCast(s);
@memset(@as([*]u8, @ptrCast(@alignCast(&b.data)))[0..s], 0);
return b;
}
pub fn Bfreebuffer(L: *lua.State, b: *lobject.Buffer, page: *lmem.lua_Page) void {
lmem.Mfreegco(L, b.obj2gco(), sizebuffer(b.len), b.header.memcat, page);
}
+7
View File
@@ -0,0 +1,7 @@
const c = @import("c");
const lua = @import("lua.zig");
pub inline fn open(L: *lua.State) void {
_ = c.luaopen_buffer(@ptrCast(L));
}
+90
View File
@@ -0,0 +1,90 @@
const std = @import("std");
const lua = @import("lua.zig");
const lobject = @import("lobject.zig");
const lgc = @import("lgc.zig");
const ltm = @import("ltm.zig");
const lmem = @import("lmem.zig");
const ltable = @import("ltable.zig");
const lstring = @import("lstring.zig");
const lfunc = @import("lfunc.zig");
const Errorset = @import("errorset.zig");
pub fn Rnewclass(
L: *lua.State,
name: *lobject.TString,
memberstooffset: *lobject.LuaTable,
offsettomember: [*]*lobject.TString,
numberofinstancemembers: u32,
numberofstaticmembers: u32,
) !*lobject.LuauClass {
std.debug.assert(L.global.GCthreshold == std.math.maxInt(usize)); // GC must be paused
const classobject = try lmem.Mnewgco(L, lobject.LuauClass, @sizeOf(lobject.LuauClass), L.activememcat);
lgc.Cinit(L, @ptrCast(@alignCast(classobject)), @intFromEnum(lua.Type.Class));
classobject.name = name;
classobject.staticmembers = try lmem.Mnewarray(L, lobject.TValue, numberofstaticmembers, classobject.header.memcat);
for (0..numberofstaticmembers) |i|
classobject.staticmembers[i].setnilvalue();
classobject.memberstooffset = memberstooffset;
classobject.offsettomember = offsettomember;
classobject.metatable = try ltable.Hnew(L, 0, 1);
const constructor = try lfunc.FnewCclosure(L, 0, L.gt.?);
constructor.d.c.f = zig_luaR_createobject;
constructor.d.c.debugname = "luaR_createobject";
constructor.d.c.cont = null;
const dest = try ltable.Hsetstr(L, classobject.metatable, L.global.tmname[@intFromEnum(ltm.TMS.TM_CALL)]);
std.debug.assert(dest.ttisnil());
dest.setclvalue(L, constructor);
classobject.metatable.readonly = 1;
classobject.instancemetatable = null;
classobject.numberofinstancemembers = numberofinstancemembers;
classobject.numberofallmembers = numberofinstancemembers + numberofstaticmembers;
return classobject;
}
pub fn Raddclassmember(L: *lua.State, classobject: *lobject.LuauClass, name: *lobject.TString, value: *const lobject.TValue) !void {
std.debug.assert(@as(?*anyopaque, @ptrCast(@alignCast(classobject.staticmembers))) != null);
const offset = ltable.Hgetstr(classobject.memberstooffset, name);
const offsetint: u32 = @intFromFloat(offset.nvalue());
std.debug.assert(offsetint >= classobject.numberofinstancemembers and offsetint < classobject.numberofallmembers);
std.debug.assert(value.ttisfunction() and value.value.gc.?.gch.ttype() == @intFromEnum(lua.Type.Function));
classobject.staticmembers[offsetint - classobject.numberofinstancemembers].setobj(L, value);
lgc.Cbarrier(L, @ptrCast(@alignCast(classobject)), value);
var isMetamethod: bool = name == lstring.Sassumelstr(L, "__tostring");
var i: u32 = 0;
while (!isMetamethod and i < ltm.N) : (i += 1)
isMetamethod = name == L.global.tmname[i];
if (isMetamethod) {
if (classobject.instancemetatable == null) {
classobject.instancemetatable = try ltable.Hnew(L, 0, 1);
lgc.Cobjbarrier(L, @ptrCast(@alignCast(classobject)), @ptrCast(@alignCast(classobject.instancemetatable.?)));
}
const dest = try ltable.Hsetstr(L, classobject.instancemetatable.?, name);
dest.setobj(L, value);
lgc.Cbarrier(L, @ptrCast(@alignCast(classobject.instancemetatable.?)), value);
}
}
extern "c" fn zig_luaR_createobject(L: *lua.State) c_int;
pub fn Rfreeclass(L: *lua.State, classobject: *lobject.LuauClass, page: *lmem.lua_Page) void {
lmem.Mfreearray(L, lobject.TValue, classobject.staticmembers, classobject.numberofallmembers - classobject.numberofinstancemembers, classobject.header.memcat);
lmem.Mfreearray(L, *lobject.TString, classobject.offsettomember, classobject.numberofallmembers, classobject.header.memcat);
lmem.Mfreegco(L, @ptrCast(@alignCast(classobject)), @sizeOf(lobject.LuauClass), classobject.header.memcat, page);
}
pub fn Rfreeobject(L: *lua.State, classinstance: *lobject.LuauObject, page: *lmem.lua_Page) void {
lmem.Mfreearray(L, lobject.TValue, classinstance.members, classinstance.numberofmembers, classinstance.header.memcat);
lmem.Mfreegco(L, @ptrCast(@alignCast(classinstance)), @sizeOf(lobject.LuauObject), classinstance.header.memcat, page);
}
+7
View File
@@ -0,0 +1,7 @@
const config = @import("luaconf.zig");
///
/// type for virtual-machine instructions
/// must be an unsigned with (at least) 4 bytes (see details in lopcodes.h)
///
pub const Instruction = u32;
+7
View File
@@ -0,0 +1,7 @@
const c = @import("c");
const lua = @import("lua.zig");
pub inline fn open(L: *lua.State) void {
_ = c.luaopen_coroutine(@ptrCast(L));
}
+7
View File
@@ -0,0 +1,7 @@
const c = @import("c");
const lua = @import("lua.zig");
pub inline fn open(L: *lua.State) void {
_ = c.luaopen_debug(@ptrCast(L));
}
+146
View File
@@ -0,0 +1,146 @@
const c = @import("c");
const std = @import("std");
const build_config = @import("config");
const lua = @import("lua.zig");
const ltm = @import("ltm.zig");
const lstate = @import("lstate.zig");
const lobject = @import("lobject.zig");
const Errorset = @import("errorset.zig");
pub const MEMERRMSG = "not enough memory";
pub const ERRERRMSG = "error in error handling";
pub fn currentpc(ci: *lstate.CallInfo) usize {
if (ci.savedpc.inst) |pc| {
return (@divExact(@intFromPtr(pc) - @intFromPtr(ci.ci_func().d.l.p.code), @sizeOf(u32))) - 1;
} else return 0;
}
pub fn currentline(ci: *lstate.CallInfo) i32 {
std.debug.assert(ci.isLua());
return Ggetline(ci.ci_func().d.l.p, currentpc(ci));
}
pub fn getluaproto(ci: *lstate.CallInfo) ?*lobject.Proto {
return if (ci.isLua())
ci.ci_func().d.l.p
else
null;
}
pub inline fn getargument(L: *lua.State, level: i32, n: i32) bool {
return c.lua_getargument(@ptrCast(L), level, n) != 0;
}
pub inline fn getlocal(L: *lua.State, level: i32, n: i32) ?[:0]const u8 {
const name = c.lua_getlocal(@ptrCast(L), level, n);
if (name != null)
return std.mem.span(name);
return null;
}
pub inline fn setlocal(L: *lua.State, level: i32, n: i32) ?[:0]const u8 {
const name = c.lua_setlocal(@ptrCast(L), level, n);
if (name != null)
return std.mem.span(name);
return null;
}
pub inline fn stackdepth(L: *lua.State) usize {
return L.ci.? - L.base_ci.?;
}
pub fn getinfo(L: *lua.State, level: i32, what: [:0]const u8, ar: *lua.Debug) bool {
var info: c.lua_Debug = undefined;
if (c.lua_getinfo(@ptrCast(L), level, what.ptr, &info) == 0)
return false;
ar.fromLua(info, what);
return true;
}
fn pusherror(L: *lua.State, msg: [:0]const u8) Errorset.Memory!void {
const ci = L.ci.?;
if (ci.isLua()) {
const source = getluaproto(ci).?.source;
// var chunkbuf: [lua.config.IDSIZE]u8 = undefined;
const line = currentline(ci);
if (source) |src| {
try L.pushfstring("{s}:{d}: {s}", .{ std.mem.span(src.getstr()), line, msg });
} else {
try L.pushfstring(":{d}: {s}", .{ line, msg });
}
} else {
try L.pushstring(msg);
}
}
pub fn GrunerrorL(L: *lua.State, comptime fmt: []const u8, args: anytype) Errorset.Table!noreturn {
try L.pushvfstring(fmt, args);
try L.rawcheckstack(1);
L.raiseerror();
}
pub fn Ggetline(p: *lobject.Proto, pc: usize) i32 {
std.debug.assert(pc >= 0 and pc < p.sizecode);
if (p.lineinfo) |lineinfo| {
return p.abslineinfo.?[pc >> @intCast(p.linegaplog2)] + lineinfo[pc];
} else return 0;
}
pub fn Gisnative(L: *lua.State, level: usize) bool {
if (level >= L.ci.?[0].sub(@ptrCast(L.base_ci.?)))
return false;
const ci = L.ci.? - level;
return (ci[0].flags & lstate.CALLINFO_NATIVE) != 0;
}
pub fn singlestep(L: *lua.State, enabled: bool) void {
if (comptime !build_config.use_zig_backend) {
return c.lua_singlestep(@ptrCast(L), if (enabled) 1 else 0);
}
L.singlestep_on = enabled;
}
pub inline fn breakpoint(L: *lua.State, funcindex: i32, line: i32, enabled: bool) i32 {
return c.lua_breakpoint(@ptrCast(L), funcindex, line, if (enabled) 1 else 0);
}
pub fn getcoverage(
L: *lua.State,
comptime T: type,
context: *T,
funcindex: i32,
comptime callback: *const fn (
ctx: *T,
func: ?[:0]const u8,
line: i32,
depth: i32,
hits: []const i32,
) void,
) void {
c.lua_getcoverage(@ptrCast(L), funcindex, context, struct {
fn inner(
ctx: ?*anyopaque,
func: [*c]const u8,
line: c_int,
depth: c_int,
hits: [*c]const c_int,
size: usize,
) callconv(.c) void {
@call(.always_inline, callback, .{
@as(*T, @ptrCast(@alignCast(ctx.?))),
if (func != null) std.mem.span(func) else null,
line,
depth,
hits[0..size],
});
}
}.inner);
}
pub inline fn debugtrace(L: *lua.State) [:0]const u8 {
return std.mem.span(c.lua_debugtrace(@ptrCast(L)));
}
+138
View File
@@ -0,0 +1,138 @@
const c = @import("c");
const std = @import("std");
const lmem = @import("lmem.zig");
const lstate = @import("lstate.zig");
const lua = @import("lua.zig");
const ldebug = @import("ldebug.zig");
const lobject = @import("lobject.zig");
const Errorset = @import("errorset.zig");
extern "c" fn zig_luau_luaD_throw(L: *lua.State, errcode: i32) noreturn;
pub const MAX_STACK_SIZE = (1024 / @sizeOf(lobject.TValue)) * 1024 * 1024;
pub inline fn throw(L: *lua.State, errcode: lua.Status) noreturn {
zig_luau_luaD_throw(L, @intFromEnum(errcode));
}
pub inline fn getgrownstacksize(L: *lua.State, n: usize) usize {
return if (n <= L.stacksize) 2 * @as(u32, @intCast(L.stacksize)) else @as(u32, @intCast(L.stacksize)) + n;
}
pub inline fn stacklimitreached(L: *lua.State, n: usize) bool {
return @intFromPtr(L.stack_last) - @intFromPtr(L.top) <= n * @sizeOf(lobject.TValue);
}
pub inline fn Dcheckstackfornewci(L: *lua.State, n: usize) Errorset.Memory!void {
if (@intFromPtr(L.stack_last) - @intFromPtr(L.top) < n * @sizeOf(lobject.TValue))
try Dreallocstack(L, getgrownstacksize(L, n), true)
else
try Dreallocstack(L, @as(u32, @intCast(L.stacksize - lstate.EXTRA_STACK)), true);
}
pub inline fn Dcheckstack(L: *lua.State, n: usize) Errorset.Memory!void {
if (@intFromPtr(L.stack_last) - @intFromPtr(L.top) < n * @sizeOf(lobject.TValue))
try Dgrowstack(L, n)
else
try Dreallocstack(L, @as(u32, @intCast(L.stacksize - lstate.EXTRA_STACK)), false);
}
pub inline fn incr_top(L: *lua.State) Errorset.Memory!void {
try Dcheckstack(L, 1);
L.top += 1;
}
pub inline fn expandstacklimit(L: *lua.State, p: *lobject.TValue) void {
std.debug.assert(@intFromPtr(p) <= @intFromPtr(L.stack_last));
if (@intFromPtr(L.ci.?[0].top) < @intFromPtr(p))
L.ci.?[0].top = @ptrCast(p);
}
fn correctstack(L: *lua.State, oldstack: [*]lobject.TValue) void {
L.top = L.stack + (L.top - oldstack);
var up: ?*lobject.UpVal = L.openupval;
while (up) |uv| : (up = uv.u.open.threadnext)
uv.v = @ptrCast(L.stack + (@as([*]lobject.TValue, @ptrCast(uv.v)) - oldstack));
var ci = L.base_ci.?;
const top_bound = @intFromPtr(L.ci);
while (@intFromPtr(ci) <= top_bound) : (ci += 1) {
ci[0].top = L.stack + (ci[0].top - oldstack);
ci[0].base = L.stack + (ci[0].base - oldstack);
ci[0].func = L.stack + (ci[0].func - oldstack);
}
L.base = L.stack + (L.base - oldstack);
}
pub fn Dreallocstack(L: *lua.State, newsize: usize, fornewci: bool) Errorset.Memory!void {
// throw 'out of memory' error because space for a custom error message cannot be guaranteed here
if (newsize > MAX_STACK_SIZE) {
// reallocation was performed to setup a new CallInfo frame, which we have to remove
if (fornewci) {
const cip = L.ci.? - 1;
L.ci = cip;
L.base = cip[0].base;
L.top = cip[0].top;
}
return error.OutOfMemory;
}
const realsize = newsize + lstate.EXTRA_STACK;
if (L.stacksize == realsize) {
// fast path: skip reallocation
return;
}
const oldstack = L.stack;
std.debug.assert(L.stack_last - L.stack == L.stacksize - lstate.EXTRA_STACK);
L.stack = try lmem.Mreallocarray(L, lobject.TValue, L.stack, @intCast(L.stacksize), realsize, L.header.memcat) orelse unreachable;
const newstack = L.stack;
var i: usize = @intCast(L.stacksize);
while (i < realsize) : (i += 1)
newstack[i].setnilvalue();
L.stacksize = @intCast(realsize);
L.stack_last = newstack + newsize;
correctstack(L, oldstack);
}
pub fn DreallocCI(L: *lua.State, newsize: usize) Errorset.Memory!void {
const oldci = L.base_ci;
L.base_ci = try lmem.Mreallocarray(L, lstate.CallInfo, L.base_ci, @intCast(L.size_ci), newsize, L.header.memcat);
L.size_ci = @intCast(newsize);
L.ci = @ptrFromInt((@intFromPtr(L.ci) - @intFromPtr(oldci)) + @intFromPtr(L.base_ci));
L.end_ci = L.base_ci.? + newsize - 1;
}
pub fn Dgrowstack(L: *lua.State, n: usize) Errorset.Memory!void {
try Dreallocstack(L, getgrownstacksize(L, n), false);
}
pub inline fn @"resume"(L: *lua.State, from: ?*lua.State, narg: i32) lua.Status {
return @enumFromInt(c.lua_resume(@ptrCast(L), @ptrCast(from), narg));
}
pub inline fn resumeerror(L: *lua.State, from: ?*lua.State) lua.Status {
return @enumFromInt(c.lua_resumeerror(@ptrCast(L), @ptrCast(from)));
}
pub fn yield(L: *lua.State, nresults: u32) !i32 {
if (L.nCcalls > L.baseCcalls)
try ldebug.GrunerrorL(L, "attempt to yield across metamethod/C-call boundary", .{});
L.base = L.top - nresults;
L.curr_status = @intFromEnum(lua.Status.Yield);
return -1;
}
pub fn @"break"(L: *lua.State) !i32 {
if (L.nCcalls > L.baseCcalls)
try ldebug.GrunerrorL(L, "attempt to yield across metamethod/C-call boundary", .{});
L.curr_status = @intFromEnum(lua.Status.Break);
return -1;
}
pub fn isyieldable(L: *lua.State) bool {
return L.nCcalls <= L.baseCcalls;
}
+164
View File
@@ -0,0 +1,164 @@
const std = @import("std");
const lua = @import("lua.zig");
const lgc = @import("lgc.zig");
const lmem = @import("lmem.zig");
const lstate = @import("lstate.zig");
const lcommon = @import("lcommon.zig");
const lobject = @import("lobject.zig");
pub inline fn sizeCclosure(n: u8) usize {
return @offsetOf(lobject.Closure, "d") + @offsetOf(lobject.Closure.ValueUnion.C, "upvals") + (@sizeOf(lobject.TValue) * @as(usize, @intCast(n)));
}
pub inline fn sizeLclosure(n: u8) usize {
return @offsetOf(lobject.Closure, "d") + @offsetOf(lobject.Closure.ValueUnion.L, "uprefs") + (@sizeOf(lobject.TValue) * @as(usize, @intCast(n)));
}
pub inline fn getproto(cl: *lobject.Closure) *lobject.Proto {
return cl.d.l.p;
}
pub fn Fnewproto(L: *lua.State) !*lobject.Proto {
const f = try lmem.Mnewgco(L, lobject.Proto, @sizeOf(lobject.Proto), L.activememcat);
lgc.Cinit(L, @ptrCast(@alignCast(f)), @intFromEnum(lua.Type.Proto));
f.nups = 0;
f.numparams = 0;
f.is_vararg = 0;
f.maxstacksize = 0;
f.flags = 0;
f.k = null;
f.code = null;
f.p = null;
f.codeentry = null;
f.execdata = null;
f.exectarget = 0;
f.lineinfo = null;
f.abslineinfo = null;
f.locvars = null;
f.upvalues = null;
f.source = null;
f.debugname = null;
f.debuginsn = null;
f.typeinfo = null;
f.userdata = null;
f.gclist = null;
f.sizecode = 0;
f.sizep = 0;
f.sizelocvars = 0;
f.sizeupvalues = 0;
f.sizek = 0;
f.sizelineinfo = 0;
f.linegaplog2 = 0;
f.linedefined = 0;
f.bytecodeid = 0;
f.sizetypeinfo = 0;
f.feedbackvec = null;
f.feedbackvecsize = 0;
f.funid = 0;
f.optimized = null;
f.deoptimized = null;
f.cost = 0;
return f;
}
pub fn FnewLclosure(L: *lua.State, nelems: u8, e: *lobject.LuaTable, p: *lobject.Proto) !*lobject.Closure {
const c = try lmem.Mnewgco(L, lobject.Closure, sizeCclosure(nelems), L.activememcat);
lgc.Cinit(L, @ptrCast(@alignCast(c)), @intFromEnum(lua.Type.Function));
c.isC = 0;
c.env = e;
c.nupvalues = nelems;
c.stacksize = p.maxstacksize;
c.preload = 0;
c.d.l.p = p;
for (0..nelems) |i|
c.d.l.upreferences()[i].setnilvalue();
return c;
}
pub fn FnewCclosure(L: *lua.State, nelems: u8, e: *lobject.LuaTable) !*lobject.Closure {
const c = try lmem.Mnewgco(L, lobject.Closure, sizeCclosure(nelems), L.activememcat);
lgc.Cinit(L, @ptrCast(@alignCast(c)), @intFromEnum(lua.Type.Function));
c.isC = 1;
c.env = e;
c.nupvalues = nelems;
c.stacksize = lua.config.MINSTACK;
c.preload = 0;
c.d.c.f = null;
c.d.c.cont = null;
c.d.c.debugname = null;
return c;
}
pub fn Ffreeupval(L: *lua.State, uv: *lobject.UpVal, page: *lmem.lua_Page) void {
lmem.Mfreegco(L, uv.obj2gco(), @sizeOf(lobject.UpVal), uv.header.memcat, page); // free upvalue
}
pub fn Fclose(L: *lua.State, level: *lobject.TValue) void {
const g = L.global;
var uv: ?*lobject.UpVal = L.openupval;
const lvl_num = @intFromPtr(level);
while (uv != null and @intFromPtr(uv.?.v) >= lvl_num) : (uv = L.openupval) {
const u = uv.?;
const o: *lstate.GCObject = u.obj2gco();
std.debug.assert(!lgc.isblack(o) and u.upisopen());
std.debug.assert(!lgc.isdead(g, o));
// unlink value *before* closing it since value storage overlaps
L.openupval = u.u.open.threadnext;
Fcloseupval(L, u, false);
}
}
pub fn Fcloseupval(L: *lua.State, uv: *lobject.UpVal, dead: bool) void {
// unlink value from all lists *before* closing it since value storage overlaps
std.debug.assert(uv.u.open.next.?.u.open.prev == uv and uv.u.open.prev.?.u.open.next == uv);
uv.u.open.next.?.u.open.prev = uv.u.open.prev;
uv.u.open.prev.?.u.open.next = uv.u.open.next;
if (dead)
return;
uv.u.value.setobj(L, uv.v);
uv.v = &uv.u.value;
lgc.Cupvalclosed(L, uv);
}
pub fn Ffreeproto(L: *lua.State, f: *lobject.Proto, page: *lmem.lua_Page) void {
lmem.Mfreearray(L, lcommon.Instruction, f.code, @intCast(f.sizecode), f.header.memcat);
lmem.Mfreearray(L, ?*lobject.Proto, f.p, @intCast(f.sizep), f.header.memcat);
lmem.Mfreearray(L, lobject.TValue, f.k, @intCast(f.sizek), f.header.memcat);
if (f.lineinfo) |li|
lmem.Mfreearray(L, u8, li, @intCast(f.sizelineinfo), f.header.memcat);
lmem.Mfreearray(L, lobject.LocVar, f.locvars, @intCast(f.sizelocvars), f.header.memcat);
lmem.Mfreearray(L, ?*lobject.TString, f.upvalues, @intCast(f.sizeupvalues), f.header.memcat);
if (f.debuginsn) |di|
lmem.Mfreearray(L, u8, di, @intCast(f.sizecode), f.header.memcat);
if (f.execdata) |_|
L.global.ecb.destroy.?(L, @ptrCast(f));
if (f.typeinfo) |ti|
lmem.Mfreearray(L, u8, ti, @intCast(f.sizetypeinfo), f.header.memcat);
if (f.feedbackvec) |fv|
lmem.Mfreearray(L, lobject.FeedbackVectorSlot, fv, f.feedbackvecsize, f.header.memcat);
lmem.Mfreegco(L, f.obj2gco(), @sizeOf(lobject.Proto), f.header.memcat, page);
}
pub fn Ffreeclosure(L: *lua.State, c: *lobject.Closure, page: *lmem.lua_Page) void {
const size = if (c.isC != 0) sizeCclosure(c.nupvalues) else sizeLclosure(c.nupvalues);
lmem.Mfreegco(L, c.obj2gco(), size, c.header.memcat, page);
}
+1129
View File
File diff suppressed because it is too large Load Diff
+223
View File
@@ -0,0 +1,223 @@
const std = @import("std");
const lgc = @import("lgc.zig");
const lua = @import("lua.zig");
const lmem = @import("lmem.zig");
const lstate = @import("lstate.zig");
const ludata = @import("ludata.zig");
const lobject = @import("lobject.zig");
fn validateobjref(g: *const lstate.global_State, f: *lstate.GCObject, t: *lstate.GCObject) void {
std.debug.assert(!lgc.isdead(g, t));
if (lgc.keepinvariant(g)) {
// basic incremental invariant: black can't point to white
std.debug.assert(!(lgc.isblack(f) and lgc.iswhite(t)));
}
}
fn validateref(g: *const lstate.global_State, f: *lstate.GCObject, v: *lobject.TValue) void {
if (v.iscollectable()) {
std.debug.assert(v.ttype() == v.gcvalue().gch.header.tt);
validateobjref(g, f, v.gcvalue());
}
}
fn validatetable(g: *const lstate.global_State, h: *lobject.LuaTable) void {
const sizenode = @as(u32, 1) << @intCast(h.lsizenode);
std.debug.assert(h.bound.lastfree <= sizenode);
if (h.metatable) |mt|
validateobjref(g, h.obj2gco(), mt.obj2gco());
for (0..@intCast(h.sizearray)) |i|
validateref(g, h.obj2gco(), &h.array.?[i]);
for (0..sizenode) |i| {
const n = &h.node[i];
std.debug.assert(n.gkey().ttype() != @intFromEnum(lua.Type.Deadkey) or n.gval().ttisnil());
std.debug.assert(@as(isize, @intCast(i)) + n.gnext() >= 0 and @as(isize, @intCast(i)) + n.gnext() < sizenode);
if (!n.gval().ttisnil()) {
var k: lobject.TValue = undefined;
k.tt = n.gkey().ttype();
k.value = n.gkey().value;
validateref(g, h.obj2gco(), &k);
validateref(g, h.obj2gco(), n.gval());
}
}
}
fn validateclosure(g: *const lstate.global_State, cl: *lobject.Closure) void {
validateobjref(g, cl.obj2gco(), cl.env.obj2gco());
if (cl.isC != 0) {
for (cl.d.c.upvalues()[0..cl.nupvalues]) |*upval|
validateref(g, cl.obj2gco(), upval);
} else {
std.debug.assert(cl.nupvalues == cl.d.l.p.nups);
validateobjref(g, cl.obj2gco(), cl.d.l.p.obj2gco());
for (cl.d.l.upreferences()[0..cl.nupvalues]) |*upref|
validateref(g, cl.obj2gco(), upref);
}
}
fn validatestack(g: *const lstate.global_State, l: *lua.State) void {
validateobjref(g, @ptrCast(@alignCast(l)), l.gt.?.obj2gco());
for (l.base_ci.?[0 .. (l.ci.? - l.base_ci.?) + 1]) |*ci| {
std.debug.assert(@intFromPtr(l.stack) <= @intFromPtr(ci.base));
std.debug.assert(@intFromPtr(ci.func) <= @intFromPtr(ci.base) and @intFromPtr(ci.base) <= @intFromPtr(ci.top));
std.debug.assert(@intFromPtr(ci.top) <= @intFromPtr(l.stack_last));
}
// note: stack refs can violate gc invariant so we only check for liveness
for (l.stack[0..(l.top - l.stack)]) |*o|
o.checkliveness(g);
if (l.namecall) |nc|
validateobjref(g, @ptrCast(@alignCast(l)), nc.obj2gco());
var upval: ?*lobject.UpVal = l.openupval;
while (upval) |uv| : (upval = uv.u.open.threadnext) {
std.debug.assert(uv.header.tt == @intFromEnum(lua.Type.UpVal));
std.debug.assert(uv.upisopen());
std.debug.assert(uv.u.open.next.?.u.open.prev == uv and uv.u.open.prev.?.u.open.next == uv);
std.debug.assert(!lgc.isblack(uv.obj2gco()));
}
}
fn validateproto(g: *const lstate.global_State, f: *lobject.Proto) void {
if (f.source) |src|
validateobjref(g, f.obj2gco(), src.obj2gco());
if (f.debugname) |name|
validateobjref(g, f.obj2gco(), name.obj2gco());
for (0..@intCast(f.sizek)) |i|
validateref(g, f.obj2gco(), &f.k.?[i]);
for (0..@intCast(f.sizeupvalues)) |i|
if (f.upvalues.?[i]) |uv|
validateobjref(g, f.obj2gco(), uv.obj2gco());
for (0..@intCast(f.sizep)) |i|
if (f.p.?[i]) |proto|
validateobjref(g, f.obj2gco(), proto.obj2gco());
for (0..@intCast(f.sizelocvars)) |i|
if (f.locvars.?[i].varname) |varname|
validateobjref(g, f.obj2gco(), varname.obj2gco());
}
fn validateclass(g: *const lstate.global_State, lco: *lobject.LuauClass) void {
const obj = lco.obj2gco();
validateobjref(g, obj, lco.name.obj2gco());
validateobjref(g, obj, lco.memberstooffset.obj2gco());
for (0..lco.numberofallmembers) |i| {
validateobjref(g, obj, lco.offsettomember[i].obj2gco());
if (i >= lco.numberofinstancemembers)
validateref(g, obj, &lco.staticmembers[i - @as(u32, @intCast(lco.numberofinstancemembers))]);
}
validateobjref(g, obj, lco.metatable.obj2gco());
if (lco.instancemetatable) |mt|
validateobjref(g, obj, mt.obj2gco());
}
fn validateobject(g: *const lstate.global_State, inst: *lobject.LuauObject) void {
const obj = inst.obj2gco();
validateobjref(g, obj, inst.lclass.obj2gco());
for (0..inst.numberofmembers) |i|
validateref(g, obj, &inst.members[i]);
}
fn validateobj(g: *const lstate.global_State, o: *lstate.GCObject) void {
if (lgc.isdead(g, o)) {
std.debug.assert(g.gcstate == lgc.GCSsweep);
return;
}
switch (o.gch.header.tt) {
@intFromEnum(lua.Type.String), @intFromEnum(lua.Type.Buffer) => {},
@intFromEnum(lua.Type.Table) => validatetable(g, o.toh()),
@intFromEnum(lua.Type.Function) => validateclosure(g, o.tocl()),
@intFromEnum(lua.Type.Userdata) => if (o.tou().metatable) |mt|
validateobjref(g, o, mt.obj2gco()),
@intFromEnum(lua.Type.Thread) => validatestack(g, o.toth()),
@intFromEnum(lua.Type.Proto) => validateproto(g, o.top()),
@intFromEnum(lua.Type.UpVal) => validateref(g, o, o.touv().v),
@intFromEnum(lua.Type.Class) => validateclass(g, o.toclass()),
@intFromEnum(lua.Type.Object) => validateobject(g, o.toobject()),
else => unreachable,
}
}
fn validategraylist(g: *const lstate.global_State, obj: ?*lstate.GCObject) void {
if (!lgc.keepinvariant(g))
return;
var so: ?*lstate.GCObject = obj;
while (so) |o| {
std.debug.assert(lgc.isgray(o));
switch (o.gch.header.tt) {
@intFromEnum(lua.Type.Table) => so = o.toh().gclist,
@intFromEnum(lua.Type.Function) => so = o.tocl().gclist,
@intFromEnum(lua.Type.Thread) => so = o.toth().gclist,
@intFromEnum(lua.Type.Class) => so = o.toclass().gclist,
@intFromEnum(lua.Type.Object) => so = o.toobject().gclist,
@intFromEnum(lua.Type.Proto) => so = o.top().gclist,
else => unreachable,
}
}
}
fn validategco(L: *lua.State, _: ?*lmem.lua_Page, gco: *lstate.GCObject) bool {
const g = L.global;
validateobj(g, gco);
return false;
}
pub fn Cvalidate(L: *lua.State) void {
const g = L.global;
std.debug.assert(!lgc.isdead(g, @ptrCast(@alignCast(g.mainthread))));
g.registry.checkliveness(g);
for (0..lua.Type.T_COUNT) |i|
if (g.mt[i]) |mt|
std.debug.assert(!lgc.isdead(g, mt.obj2gco()));
for (0..lua.config.UTAG_LIMIT) |i|
if (g.udatamt[i]) |mt|
std.debug.assert(!lgc.isdead(g, mt.obj2gco()));
for (0..ludata.UTAG_INTERNAL_LIMIT) |i| {
g.udatadirect[i].indextm.checkliveness(g);
g.udatadirect[i].newindextm.checkliveness(g);
g.udatadirect[i].namecalltm.checkliveness(g);
if (g.udatadirectfields[i]) |f|
std.debug.assert(!lgc.isdead(g, f.obj2gco()));
}
validategraylist(g, g.weak);
validategraylist(g, g.gray);
validategraylist(g, g.grayagain);
_ = validategco(@ptrCast(L), null, @ptrCast(@alignCast(g.mainthread)));
lmem.Mvisitgco(L, *lua.State, L, validategco);
var upval: ?*lobject.UpVal = g.uvhead.u.open.next.?;
while (upval != &g.uvhead) : (upval = upval.?.u.open.next) {
std.debug.assert(upval.?.header.tt == @intFromEnum(lua.Type.UpVal));
std.debug.assert(upval.?.upisopen());
std.debug.assert(upval.?.u.open.next.?.u.open.prev == upval and upval.?.u.open.prev.?.u.open.next == upval);
std.debug.assert(!lgc.isblack(upval.?.obj2gco()));
}
}
+16
View File
@@ -0,0 +1,16 @@
const c = @import("c");
const std = @import("std");
const lua = @import("lua.zig");
pub fn Lopenlibs(L: *lua.State) !void {
c.luaL_openlibs(@ptrCast(L));
}
pub fn Lsandbox(L: *lua.State) !void {
c.luaL_sandbox(@ptrCast(L));
}
pub fn Lsandboxthread(L: *lua.State) !void {
c.luaL_sandboxthread(@ptrCast(L));
}
+7
View File
@@ -0,0 +1,7 @@
const c = @import("c");
const lua = @import("lua.zig");
pub inline fn open(L: *lua.State) void {
_ = c.luaopen_math(@ptrCast(L));
}
+720
View File
@@ -0,0 +1,720 @@
// 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
const std = @import("std");
const builtin = @import("builtin");
const lua = @import("lua.zig");
const lobject = @import("lobject.zig");
const lstate = @import("lstate.zig");
const ldo = @import("ldo.zig");
const ldebug = @import("ldebug.zig");
const Errorset = @import("errorset.zig");
const Error = Errorset.Memory;
//
// 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).
//
// When LUAU_ASSERTENABLED is enabled, all non-GCO pages are also linked in a list (global_State::allpages).
// Because this list is not strictly required for runtime operations, it is only tracked for the purposes of
// debugging. While overhead of linking those pages together is very small, unnecessary operations are avoided.
//
// 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.
//
//
// The sizes of most 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.
//
fn ABISWITCH(b64: comptime_int, b32: comptime_int) comptime_int {
if (@sizeOf(*anyopaque) == 8)
return b64
else
return b32;
}
comptime {
if (lua.config.VECTOR_SIZE == 4) {
std.debug.assert(@sizeOf(lobject.TValue) == ABISWITCH(24, 24)); // size mismatch for value
std.debug.assert(@sizeOf(lobject.LuaNode) == ABISWITCH(48, 48)); // size mismatch for table entry
} else {
std.debug.assert(@sizeOf(lobject.TValue) == ABISWITCH(16, 16)); // size mismatch for value
std.debug.assert(@sizeOf(lobject.LuaNode) == ABISWITCH(32, 32)); // size mismatch for table entry
}
std.debug.assert(@offsetOf(lobject.TString, "data") == ABISWITCH(24, 20)); // size mismatch for string header
std.debug.assert(@sizeOf(lobject.LuaTable) == ABISWITCH(48, 32)); // size mismatch for table header
std.debug.assert(@offsetOf(lobject.Buffer, "data") == ABISWITCH(8, 8)); // size mismatch for buffer header
// The userdata is designed to provide 16 byte alignment for 16 byte and larger userdata sizes
std.debug.assert(@offsetOf(lobject.Udata, "data") == 16); // data must be at precise offset provide proper alignment
}
const kSizeClasses = lua.config.SIZECLASSES;
// Controls the number of entries in SizeClassConfig and define the maximum possible paged allocation size
// Modifications require updates the SizeClassConfig initialization
const kMaxSmallSize = 1024;
// Effective limit on object size to use paged allocation
// Can be modified without additional changes to code, provided it is smaller or equal to kMaxSmallSize
const kMaxSmallSizeUsed = 1024;
const kLargePageThreshold = 512; // larger pages are used for objects larger than this size to fit more of them into a page
// constant factor to reduce our page sizes by, to increase the chances that pages we allocate will
// allow external allocators to allocate them without wasting space due to rounding introduced by their heap meta data
const kExternalAllocatorMetaDataReduction = 24;
const kSmallPageSize = 16 * 1024 - kExternalAllocatorMetaDataReduction;
const kLargePageSize = 32 * 1024 - kExternalAllocatorMetaDataReduction;
const kBlockHeader = if (@sizeOf(f64) > @sizeOf(*anyopaque)) @sizeOf(f64) else @sizeOf(*anyopaque); // suitable for aligning double & void* on all platforms
const kGCOLinkOffset = (@sizeOf(lobject.GCheader) + @sizeOf(*anyopaque) - 1) & ~@as(usize, @sizeOf(*anyopaque) - 1); // GCO pages contain freelist links after the GC header
const SizeClassConfig = extern struct {
const values = generateSizeOfClass();
sizeOfClass: [kSizeClasses]c_int = values[0],
classForSize: [kMaxSmallSize + 1]i8 = values[1],
classCount: c_int = values[2],
fn generateSizeOfClass() struct { [kSizeClasses]c_int, [kMaxSmallSize + 1]i8, comptime_int } {
var classCount: comptime_int = 0;
var sizeOfClass: [kSizeClasses]c_int = [_]c_int{0} ** kSizeClasses;
var classForSize: [kMaxSmallSize + 1]i8 = [_]i8{-1} ** (kMaxSmallSize + 1);
// 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
// - after the third cutoff we allocate size classes in multiples of 64
// this balances internal fragmentation vs external fragmentation
for ((8 / 8)..(64 / 8)) |i| {
sizeOfClass[classCount] = i * 8;
classCount += 1;
}
for ((64 / 16)..(256 / 16)) |i| {
sizeOfClass[classCount] = i * 16;
classCount += 1;
}
for ((256 / 32)..(512 / 32)) |i| {
sizeOfClass[classCount] = i * 32;
classCount += 1;
}
for ((512 / 64)..(1024 / 64) + 1) |i| {
sizeOfClass[classCount] = i * 64;
classCount += 1;
}
std.debug.assert(classCount <= kSizeClasses);
// fill the lookup table for all classes
for (0..classCount) |klass|
classForSize[sizeOfClass[klass]] = @as(i8, @intCast(klass));
// fill the gaps in lookup table
{
var size = kMaxSmallSize - 1;
@setEvalBranchQuota(kMaxSmallSize + 128);
while (size >= 0) : (size -= 1) {
if (classForSize[size] < 0)
classForSize[size] = classForSize[size + 1];
}
}
return .{ sizeOfClass, classForSize, classCount };
}
};
const kSizeClassConfig: SizeClassConfig = .{};
// size class for a block of size sz; returns -1 for size=0 because empty allocations take no space
inline fn sizeclass(sz: usize) i8 {
return if (sz -% 1 < kMaxSmallSizeUsed) kSizeClassConfig.classForSize[sz] else -1;
}
inline fn debugpageset(set: *?*lua_Page) ?*?*lua_Page {
switch (comptime builtin.mode) {
.ReleaseFast, .ReleaseSmall => return null, // ReleaseFast and ReleaseSmall defines NDEBUG
else => return set, // ReleaseSafe and Debug does not define NDEBUG
}
}
// metadata for a block is stored in the first pointer of the block
inline fn metadata(block: *anyopaque) *?*anyopaque {
return @as(*?*anyopaque, @ptrCast(@alignCast(block)));
}
inline fn freegcolink(block: *anyopaque) *?*anyopaque {
return @as(*?*anyopaque, @ptrFromInt(@intFromPtr(block) + kGCOLinkOffset));
}
pub const lua_Page = extern struct {
// list of pages with free blocks
prev: ?*lua_Page = null,
next: ?*lua_Page = null,
// list of all pages
listprev: ?*lua_Page = null,
listnext: ?*lua_Page = null,
pageSize: c_int = 0, // page size in bytes, including page header
blockSize: c_int = 0, // block size in bytes, including block header
freeList: ?*anyopaque = null, // next free block in this page; linked with metadata()/freegcolink()
freeNext: c_int = 0, // next free block offset in this page
busyBlocks: c_int = 0, // number of blocks allocated out of this page
// provide additional padding based on current object size to provide 16 byte alignment of data
// later static_assert checks that this requirement is held
padding: [if (@sizeOf(*anyopaque) == 8) 8 else 12]u8 = undefined,
data: [1]u8,
};
comptime {
std.debug.assert(@offsetOf(lua_Page, "data") % 16 == 0); // data must be 16 byte aligned to provide properly aligned allocation of userdata objects
}
fn Mtoobig(L: *lua.State) noreturn {
ldebug.GrunerrorL(L, "memory allocation error: block too big");
}
fn newpage(L: *lua.State, pageset: ?*?*lua_Page, pageSize: usize, blockSize: c_int, blockCount: c_int) Error!*lua_Page {
const g = L.global;
std.debug.assert(pageSize - @offsetOf(lua_Page, "data") >= blockSize * blockCount);
const page = @as(*lua_Page, @ptrCast(@alignCast(
(g.frealloc.?)(g.ud, null, 0, pageSize) orelse return Error.OutOfMemory,
)));
// ASAN_POISON_MEMORY_REGION(...); // TODO: ASAN support
// setup page header
page.* = .{
.prev = null,
.next = null,
.listprev = null,
.listnext = null,
.pageSize = @intCast(pageSize),
.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
.freeList = null,
.freeNext = (blockCount - 1) * blockSize,
.busyBlocks = 0,
.data = undefined,
};
if (pageset) |set| {
page.listnext = set.*;
if (page.listnext) |next|
next.listprev = page;
set.* = page;
}
return page;
}
// this is part of a cold path in newblock and newgcoblock
// it is marked as noinline to prevent it from being inlined into those functions
// if it is inlined, then the compiler may determine those functions are "too big" to be profitably inlined, which results in reduced performance
noinline fn newclasspage(L: *lua.State, freepageset: [*]?*lua_Page, pageset: ?*?*lua_Page, sizeClass: u8, storeMetadata: bool) Error!*lua_Page {
const sizeOfClass = kSizeClassConfig.sizeOfClass[sizeClass];
const pageSize: usize = if (sizeOfClass > @as(c_int, kLargePageThreshold)) kLargePageSize else kSmallPageSize;
const blockSize = sizeOfClass + @as(c_int, if (storeMetadata) kBlockHeader else 0);
const blockCount = @divTrunc(pageSize - @offsetOf(lua_Page, "data"), @as(usize, @intCast(blockSize)));
const page = try newpage(L, pageset, pageSize, blockSize, @intCast(blockCount));
// prepend a page to page freelist (which is empty because we only ever allocate a new page when it is!)
std.debug.assert(freepageset[sizeClass] == null);
freepageset[sizeClass] = page;
return page;
}
fn freepage(L: *lua.State, pageset: ?*?*lua_Page, page: *lua_Page) void {
const g = L.global;
if (pageset) |set| {
// remove page from alllist
if (page.listnext) |next|
next.listprev = page.listprev;
if (page.listprev) |prev|
prev.listnext = page.listnext
else if (set.* == page)
set.* = page.listnext;
}
// so long
_ = (g.frealloc.?)(g.ud, @ptrCast(page), @intCast(page.pageSize), 0);
}
fn freeclasspage(L: *lua.State, freepageset: [*]?*lua_Page, pageset: ?*?*lua_Page, page: *lua_Page, sizeClass: u8) void {
// remove page from freelist
if (page.next) |next|
next.prev = page.prev;
if (page.prev) |prev|
prev.next = page.next
else if (freepageset[sizeClass] == page)
freepageset[sizeClass] = page.next;
freepage(L, pageset, page);
}
fn newblock(L: *lua.State, sizeClass: u8) Error!*anyopaque {
const g = L.global;
const page = g.freepages[sizeClass] orelse blk: {
// slow path: no page in the freelist, allocate a new one
break :blk try newclasspage(L, &g.freepages, debugpageset(&g.allpages), sizeClass, true);
};
std.debug.assert(page.prev == null);
std.debug.assert(page.freeList != null or page.freeNext >= 0);
std.debug.assert(page.blockSize == kSizeClassConfig.sizeOfClass[sizeClass] + kBlockHeader);
var block: *anyopaque = undefined;
if (page.freeNext >= 0) {
block = @ptrFromInt(@intFromPtr(&page.data) + @as(usize, @intCast(page.freeNext)));
// ASAN_UNPOISON_MEMORY_REGION(...); // TODO: ASAN support
page.freeNext -= page.blockSize;
page.busyBlocks += 1;
} else {
block = page.freeList.?;
// ASAN_UNPOISON_MEMORY_REGION(...); // TODO: ASAN support
page.freeList = metadata(block).*;
page.busyBlocks += 1;
}
// the first word in a block point back to the page
metadata(block).* = @ptrCast(@alignCast(page));
// if we allocate the last block out of a page, we need to remove it from free list
if (page.freeList == null and page.freeNext < 0) {
g.freepages[sizeClass] = page.next;
if (page.next) |next|
next.prev = null;
page.next = null;
}
// the user data is right after the metadata
return @ptrFromInt(@intFromPtr(block) + kBlockHeader);
}
fn newgcoblock(L: *lua.State, sizeClass: u8) Error!*anyopaque {
const g = L.global;
const page = g.freegcopages[sizeClass] orelse blk: {
// slow path: no page in the freelist, allocate a new one
break :blk try newclasspage(L, &g.freegcopages, &g.allgcopages, sizeClass, false);
};
std.debug.assert(page.prev == null);
std.debug.assert(page.freeList != null or page.freeNext >= 0);
std.debug.assert(page.blockSize == kSizeClassConfig.sizeOfClass[sizeClass]);
var block: *anyopaque = undefined;
if (page.freeNext >= 0) {
block = @ptrFromInt(@intFromPtr(&page.data) + @as(usize, @intCast(page.freeNext)));
// ASAN_UNPOISON_MEMORY_REGION(...); // TODO: ASAN support
page.freeNext -= page.blockSize;
page.busyBlocks += 1;
} else {
block = page.freeList.?;
// ASAN_UNPOISON_MEMORY_REGION(...); // TODO: ASAN support
page.freeList = freegcolink(block).*;
page.busyBlocks += 1;
}
// if we allocate the last block out of a page, we need to remove it from free list
if (page.freeList == null and page.freeNext < 0) {
g.freegcopages[sizeClass] = page.next;
if (page.next) |next|
next.prev = null;
page.next = null;
}
return block;
}
fn freeblock(L: *lua.State, sizeClass: u8, iblock: *anyopaque) void {
const g = L.global;
// the user data is right after the metadata
const block: *anyopaque = @ptrFromInt(@intFromPtr(iblock) - kBlockHeader);
const page = @as(*lua_Page, @ptrCast(@alignCast(metadata(block).*)));
std.debug.assert(page.busyBlocks > 0);
std.debug.assert(page.blockSize == kSizeClassConfig.sizeOfClass[sizeClass] + kBlockHeader);
std.debug.assert(@intFromPtr(block) >= @intFromPtr(&page.data) and @intFromPtr(block) < @intFromPtr(page) + @as(usize, @intCast(page.pageSize)));
// if the page wasn't in the page free list, it should be now since it got a block!
if (page.freeList == null and page.freeNext < 0) {
std.debug.assert(page.prev == null);
std.debug.assert(page.next == null);
page.next = g.freepages[sizeClass];
if (page.next) |next|
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(...); // TODO: ASAN support
page.busyBlocks -= 1;
// if it's the last block in the page, we don't need the page
if (page.busyBlocks == 0)
freeclasspage(L, &g.freepages, debugpageset(&g.allpages), page, sizeClass);
}
fn freegcoblock(L: *lua.State, sizeClass: u8, block: *anyopaque, page: *lua_Page) void {
std.debug.assert(page.busyBlocks > 0);
std.debug.assert(page.blockSize == kSizeClassConfig.sizeOfClass[sizeClass]);
std.debug.assert(@intFromPtr(block) >= @intFromPtr(&page.data) and @intFromPtr(block) < @intFromPtr(page) + @as(usize, @intCast(page.pageSize)));
const 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 == null and page.freeNext < 0) {
std.debug.assert(page.prev == null);
std.debug.assert(page.next == null);
page.next = g.freegcopages[sizeClass];
if (page.next) |next|
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(...); // TODO: ASAN support
page.busyBlocks -= 1;
// 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);
}
pub fn Mnew_(L: *lua.State, nsize: usize, memcat: u8) Error!*anyopaque {
const g = L.global;
const nclass = sizeclass(nsize);
const block = if (nclass >= 0)
newblock(L, @intCast(nclass))
else
(g.frealloc.?)(g.ud, null, 0, nsize) orelse return Error.OutOfMemory;
g.totalbytes += nsize;
g.memcatbytes[memcat] += nsize;
if (g.cb.onallocate) |onallocate| {
@branchHint(.unlikely);
onallocate(L, 0, nsize);
}
return block;
}
pub fn Mnewgco_(L: *lua.State, nsize: usize, memcat: u8) Error!*lstate.GCObject {
// we need to accommodate space for link for free blocks (freegcolink)
std.debug.assert(nsize >= kGCOLinkOffset + @sizeOf(*anyopaque));
const g = L.global;
const nclass = sizeclass(nsize);
var block: *anyopaque = undefined;
if (nclass >= 0) {
block = try newgcoblock(L, @intCast(nclass));
} else {
const page = try newpage(L, &g.allgcopages, @offsetOf(lua_Page, "data") + nsize, @intCast(nsize), 1);
block = @ptrCast(@alignCast(&page.data));
// ASAN_UNPOISON_MEMORY_REGION(...); // TODO: ASAN support
page.freeNext -= page.blockSize;
page.busyBlocks += 1;
}
g.totalbytes += nsize;
g.memcatbytes[memcat] += nsize;
if (g.cb.onallocate) |onallocate| {
@branchHint(.unlikely);
onallocate(L, 0, nsize);
}
return @ptrCast(@alignCast(block));
}
pub inline fn Mnewgco(L: *lua.State, comptime T: type, nsize: usize, memcat: u8) !*T {
return @ptrCast(@alignCast(try Mnewgco_(L, nsize, memcat)));
}
pub fn Mfree_(L: *lua.State, block: ?*anyopaque, osize: usize, memcat: u8) void {
const g = L.global;
std.debug.assert((osize == 0) == (block == null));
const oclass = sizeclass(osize);
if (oclass >= 0)
freeblock(L, @intCast(oclass), block.?)
else
_ = (g.frealloc.?)(g.ud, @ptrCast(block), osize, 0);
g.totalbytes -= osize;
g.memcatbytes[memcat] -= osize;
}
pub fn Mfreegco_(L: *lua.State, block: ?*lstate.GCObject, osize: usize, memcat: u8, page: *lua_Page) void {
const g = L.global;
std.debug.assert((osize == 0) == (block == null));
const oclass = sizeclass(osize);
if (oclass >= 0) {
block.?.gch.header.tt = @intFromEnum(lua.Type.Nil);
freegcoblock(L, @intCast(oclass), @ptrCast(@alignCast(block.?)), page);
} else {
std.debug.assert(page.busyBlocks == 1);
std.debug.assert(page.blockSize == osize);
std.debug.assert(@intFromPtr(block.?) == @intFromPtr(&page.data));
freepage(L, &g.allgcopages, page);
}
g.totalbytes -= osize;
g.memcatbytes[memcat] -= osize;
}
pub inline fn Mfreegco(L: *lua.State, p: *lstate.GCObject, size: usize, memcat: u8, page: *lua_Page) void {
std.debug.assert(p.gch.header.tt >= @intFromEnum(lua.Type.String));
Mfreegco_(L, p, size, memcat, page);
}
pub fn Mrealloc_(L: *lua.State, block: ?*anyopaque, osize: usize, nsize: usize, memcat: u8) Error!?*anyopaque {
const g = L.global;
std.debug.assert((osize == 0) == (block == null));
const nclass = sizeclass(nsize);
const oclass = sizeclass(osize);
var result: ?*anyopaque = undefined;
// if either block needs to be allocated using a block allocator, we can't use realloc directly
if (nclass >= 0 or oclass >= 0) {
result = if (nclass >= 0)
try newblock(L, @intCast(nclass))
else
(g.frealloc.?)(g.ud, null, 0, nsize) orelse if (nsize > 0) return Error.OutOfMemory else null;
if (osize > 0 and nsize > 0) {
const tsize = @min(osize, nsize);
@memcpy(
@as([*]u8, @ptrCast(@alignCast(result)))[0..tsize],
@as([*]u8, @ptrCast(@alignCast(block.?)))[0..tsize],
);
}
if (oclass >= 0)
freeblock(L, @intCast(oclass), block.?)
else
_ = (g.frealloc.?)(g.ud, block, osize, 0);
} else {
result = (g.frealloc.?)(g.ud, block, osize, nsize) orelse if (nsize > 0) return Error.OutOfMemory else null;
}
std.debug.assert((nsize == 0) == (result == null));
g.totalbytes = (g.totalbytes - osize) + nsize;
if (nsize < osize)
g.memcatbytes[memcat] -= osize - nsize
else
g.memcatbytes[memcat] += nsize - osize;
if (g.cb.onallocate) |onallocate| {
@branchHint(.unlikely);
onallocate(L, osize, nsize);
}
return result;
}
pub inline fn Marraysize_(n: usize, e: usize) Error!usize {
if (n <= @divTrunc(std.math.maxInt(usize), e)) return n * e else return Error.BlockTooBig;
}
pub inline fn Mnewarray(L: *lua.State, comptime T: type, n: usize, memcat: u8) Error![*]T {
return @ptrCast(@alignCast(try Mnew_(L, try Marraysize_(n, @sizeOf(T)), memcat)));
}
pub inline fn Mfreearray(L: *lua.State, comptime T: type, b: ?[*]T, n: usize, memcat: u8) void {
Mfree_(L, @ptrCast(@alignCast(b)), n * @sizeOf(T), memcat);
}
pub inline fn Mreallocarray(L: *lua.State, comptime T: type, v: ?[*]T, oldn: usize, n: usize, memcat: u8) Error!?[*]T {
return @ptrCast(@alignCast((try Mrealloc_(L, @ptrCast(@alignCast(v)), oldn * @sizeOf(T), try Marraysize_(n, @sizeOf(T)), memcat))));
}
pub fn Mgetpagewalkinfo(page: *lua_Page, start: *[*]u8, end: *[*]u8, busyBlocks: *c_int, blockSize: *c_int) void {
const blockCount = @divTrunc(page.pageSize - @offsetOf(lua_Page, "data"), page.blockSize);
std.debug.assert(page.freeNext >= -page.blockSize and page.freeNext <= (blockCount - 1) * page.blockSize);
const data = @as([*]u8, @ptrCast(@alignCast(&page.data))); // silences ubsan when indexing page->data
start.* = data[@intCast(page.freeNext + page.blockSize)..];
end.* = data[@intCast(blockCount * page.blockSize)..];
busyBlocks.* = page.busyBlocks;
blockSize.* = page.blockSize;
}
pub fn Mgetpageinfo(page: *lua_Page, pageBlocks: *c_int, busyBlocks: *c_int, blockSize: *c_int, pageSize: *c_int) void {
pageBlocks.* = @divTrunc(page.pageSize - @offsetOf(lua_Page, "data"), page.blockSize);
busyBlocks.* = page.busyBlocks;
blockSize.* = page.blockSize;
pageSize.* = page.pageSize;
}
pub fn Mgetnextpage(page: *lua_Page) ?*lua_Page {
return page.listnext;
}
pub fn Mvisitpage(
page: *lua_Page,
comptime T: type,
context: T,
comptime visitor: fn (context: T, page: *lua_Page, gco: *lstate.GCObject) bool,
) void {
var start: [*]u8 = undefined;
var end: [*]u8 = undefined;
var busyBlocks: c_int = 0;
var blockSize: c_int = 0;
Mgetpagewalkinfo(page, &start, &end, &busyBlocks, &blockSize);
var pos: [*]u8 = start;
while (pos != end) : (pos += @as(u32, @intCast(blockSize))) {
const gco: *lstate.GCObject = @ptrCast(@alignCast(pos));
// skip memory blocks that are already freed
if (gco.gch.header.tt == @intFromEnum(lua.Type.Nil))
continue;
// when true is returned it means that the element was deleted
if (visitor(context, page, gco)) {
std.debug.assert(busyBlocks > 0);
busyBlocks -= 1;
// if the last block was removed, page would be removed as well
if (busyBlocks == 0)
break;
}
}
}
pub fn Mvisitgco(
L: *lua.State,
comptime T: type,
context: T,
comptime visitor: fn (context: T, page: *lua_Page, gco: *lstate.GCObject) bool,
) void {
const g = L.global;
var curr: ?*lua_Page = g.allgcopages;
while (curr) |page| {
const next = page.listnext; // block visit might destroy the page
Mvisitpage(page, T, context, visitor);
curr = next;
}
}
+58
View File
@@ -0,0 +1,58 @@
const std = @import("std");
const c = @import("c");
const lua = @import("lua.zig");
pub inline fn inumisnan(x: anytype) bool {
comptime switch (@typeInfo(@TypeOf(x))) {
.comptime_float, .float => {},
.comptime_int, .int => {},
else => @compileError("Unsupported type"),
};
return x != x;
}
pub inline fn inumeq(a: anytype, b: f64) bool {
comptime switch (@typeInfo(@TypeOf(a))) {
.comptime_float, .comptime_int => return @as(f64, a) == b,
.float => return @as(f64, @floatCast(a)) == b,
.int => return @as(f64, @floatFromInt(a)) == b,
else => @compileError("Unsupported type"),
};
}
pub inline fn iveceq(a: []const f32, b: []const f32) bool {
if (comptime lua.config.VECTOR_SIZE == 4)
return a[0] == b[0] and a[1] == b[1] and a[2] == b[2] and a[3] == b[3]
else
return a[0] == b[0] and a[1] == b[1] and a[2] == b[2];
}
pub inline fn ivecisnan(x: []const f32) bool {
if (comptime lua.config.VECTOR_SIZE == 4)
return x[0] != x[0] or x[1] != x[1] or x[2] != x[2] or x[3] != x[3]
else
return x[0] != x[0] or x[1] != x[1] or x[2] != x[2];
}
pub inline fn inum2int(x: f64) i32 {
return @truncate(@as(i53, @intFromFloat(x)));
}
pub const I_MAXNUM2STR = 48;
pub fn printspecial(buf: []u8, sign: u1, fraction: u64) []u8 {
if (fraction == 0) {
const _inf = "-inf";
const len = _inf.len - (1 - sign);
@memcpy(buf[0..len], _inf[1 - sign ..]);
return buf[0..len];
} else {
@memcpy(buf[0..3], "nan");
return buf[0..3];
}
}
pub fn inum2str(buf: []u8, x: f64) []u8 {
return std.fmt.bufPrint(buf, "{d}", .{x}) catch unreachable;
}
+943
View File
@@ -0,0 +1,943 @@
const c = @import("c");
const std = @import("std");
const lgc = @import("lgc.zig");
const lua = @import("lua.zig");
const lstate = @import("lstate.zig");
const lcommon = @import("lcommon.zig");
const lnumutils = @import("lnumutils.zig");
const Errorset = @import("errorset.zig");
pub const CommonHeader = extern struct {
tt: u8,
marked: u8,
memcat: u8,
};
pub const GCheader = extern struct {
header: CommonHeader,
pub inline fn ttype(this: *const GCheader) c_int {
return this.header.tt;
}
};
pub const Value = extern union {
gc: ?*lstate.GCObject,
p: ?*anyopaque,
n: f64,
b: c_int,
l: i64,
/// v[0], v[1] live here; v[2] lives in TValue::extra
v: [2]f32,
};
///
/// Tagged Values
///
pub const TValue = extern struct {
value: Value,
extra: [lua.config.EXTRA_SIZE]c_int = undefined,
tt: c_int,
pub inline fn ttype(this: *const TValue) c_int {
return this.tt;
}
pub inline fn typeOf(obj: *const TValue) lua.Type {
return @enumFromInt(obj.ttype());
}
pub inline fn ttisnil(obj: *const TValue) bool {
return obj.ttype() == @intFromEnum(lua.Type.Nil);
}
pub inline fn ttisnumber(obj: *const TValue) bool {
return obj.ttype() == @intFromEnum(lua.Type.Number);
}
pub inline fn ttisinteger(obj: *const TValue) bool {
return obj.ttype() == @intFromEnum(lua.Type.Integer);
}
pub inline fn ttisstring(obj: *const TValue) bool {
return obj.ttype() == @intFromEnum(lua.Type.String);
}
pub inline fn ttistable(obj: *const TValue) bool {
return obj.ttype() == @intFromEnum(lua.Type.Table);
}
pub inline fn ttisfunction(obj: *const TValue) bool {
return obj.ttype() == @intFromEnum(lua.Type.Function);
}
pub inline fn ttisboolean(obj: *const TValue) bool {
return obj.ttype() == @intFromEnum(lua.Type.Boolean);
}
pub inline fn ttisuserdata(obj: *const TValue) bool {
return obj.ttype() == @intFromEnum(lua.Type.Userdata);
}
pub inline fn ttisthread(obj: *const TValue) bool {
return obj.ttype() == @intFromEnum(lua.Type.Thread);
}
pub inline fn ttisbuffer(obj: *const TValue) bool {
return obj.ttype() == @intFromEnum(lua.Type.Buffer);
}
pub inline fn ttislightuserdata(obj: *const TValue) bool {
return obj.ttype() == @intFromEnum(lua.Type.LightUserdata);
}
pub inline fn ttisvector(obj: *const TValue) bool {
return obj.ttype() == @intFromEnum(lua.Type.Vector);
}
pub inline fn ttisupval(obj: *const TValue) bool {
return obj.ttype() == @intFromEnum(lua.Type.UpVal);
}
pub inline fn ttisclass(obj: *const TValue) bool {
return obj.ttype() == @intFromEnum(lua.Type.Class);
}
pub inline fn ttisobject(obj: *const TValue) bool {
return obj.ttype() == @intFromEnum(lua.Type.Object);
}
pub inline fn obj2gco(obj: *TValue) *lstate.GCObject {
std.debug.assert(obj.iscollectable());
return @ptrCast(@alignCast(obj));
}
pub inline fn gcvalue(obj: *const TValue) *lstate.GCObject {
std.debug.assert(obj.iscollectable());
return obj.value.gc.?;
}
pub inline fn pvalue(obj: *const TValue) ?*anyopaque {
std.debug.assert(obj.ttislightuserdata());
return obj.value.p;
}
pub inline fn nvalue(obj: *const TValue) f64 {
std.debug.assert(obj.ttisnumber());
return obj.value.n;
}
pub inline fn lvalue(obj: *const TValue) i64 {
std.debug.assert(obj.ttisinteger());
return obj.value.l;
}
pub inline fn vvalue(obj: *const TValue) []const f32 {
std.debug.assert(obj.ttisvector());
return @as([*]const f32, @ptrCast(&obj.value.v))[0..lua.config.VECTOR_SIZE];
}
pub inline fn tsvalue(obj: *const TValue) *TString {
std.debug.assert(obj.ttisstring());
return &obj.value.gc.?.ts;
}
pub inline fn uvalue(obj: *const TValue) *Udata {
std.debug.assert(obj.ttisuserdata());
return &obj.value.gc.?.u;
}
pub inline fn clvalue(obj: *const TValue) *Closure {
std.debug.assert(obj.ttisfunction());
return &obj.value.gc.?.cl;
}
pub inline fn hvalue(obj: *const TValue) *LuaTable {
std.debug.assert(obj.ttistable());
return &obj.value.gc.?.h;
}
pub inline fn bvalue(obj: *const TValue) bool {
std.debug.assert(obj.ttisboolean());
return obj.value.b != 0;
}
pub inline fn thvalue(obj: *const TValue) *lstate.lua_State {
std.debug.assert(obj.ttisthread());
return &obj.value.gc.?.th;
}
pub inline fn bufvalue(obj: *const TValue) *Buffer {
std.debug.assert(obj.ttisbuffer());
return &obj.value.gc.?.buf;
}
pub inline fn upvalue(obj: *TValue) *UpVal {
std.debug.assert(obj.ttisupval());
return &obj.value.gc.?.uv;
}
pub inline fn classvalue(obj: *const TValue) *LuauClass {
std.debug.assert(obj.ttisclass());
return &obj.value.gc.?.lclass;
}
pub inline fn objectvalue(obj: *const TValue) *LuauObject {
std.debug.assert(obj.ttisobject());
return &obj.value.gc.?.lobject;
}
pub inline fn svalue(obj: *const TValue) [*c]const u8 {
return obj.tsvalue().getstr();
}
pub inline fn l_isfalse(obj: *const TValue) bool {
return obj.ttisnil() or (obj.ttisboolean() and !obj.bvalue());
}
pub inline fn lightuserdatatag(obj: *const TValue) u8 {
std.debug.assert(obj.ttislightuserdata());
return @intCast(obj.extra[0]);
}
pub inline fn checkliveness(obj: *const TValue, g: *const lstate.global_State) void {
std.debug.assert(!obj.iscollectable() or ((obj.ttype() == obj.value.gc.?.gch.header.tt) and !lgc.isdead(g, obj.value.gc.?)));
}
pub inline fn setnilvalue(obj: *TValue) void {
obj.settype(.Nil);
}
pub inline fn setnvalue(obj: *TValue, x: f64) void {
obj.value.n = x;
obj.settype(.Number);
}
pub inline fn setlvalue(obj: *TValue, x: i64) void {
obj.value.l = x;
obj.settype(.Integer);
}
pub inline fn setvvalue(obj: *TValue, x: f32, y: f32, z: f32, w: ?f32) void {
const i_v: [*]f32 = @ptrCast(&obj.value.v);
i_v[0] = x;
i_v[1] = y;
i_v[2] = z;
if (comptime lua.config.VECTOR_SIZE == 4)
i_v[3] = w orelse 0;
obj.settype(.Vector);
}
pub inline fn setpvalue(obj: *TValue, x: ?*anyopaque, tag: u32) void {
obj.value.p = x;
obj.extra[0] = @intCast(tag);
obj.settype(.LightUserdata);
}
pub inline fn setbvalue(obj: *TValue, x: bool) void {
obj.value.b = if (x) 1 else 0;
obj.settype(.Boolean);
}
pub inline fn setsvalue(obj: *TValue, L: *lstate.lua_State, x: *TString) void {
obj.value.gc = @ptrCast(@alignCast(x));
obj.settype(.String);
obj.checkliveness(L.global);
}
pub inline fn setuvalue(obj: *TValue, L: *lstate.lua_State, x: *Udata) void {
obj.value.gc = @ptrCast(@alignCast(x));
obj.settype(.Userdata);
obj.checkliveness(L.global);
}
pub inline fn setthvalue(obj: *TValue, L: *lstate.lua_State, x: *lstate.lua_State) void {
obj.value.gc = @ptrCast(@alignCast(x));
obj.settype(.Thread);
obj.checkliveness(L.global);
}
pub inline fn setbufvalue(obj: *TValue, L: *lstate.lua_State, x: *Buffer) void {
obj.value.gc = @ptrCast(@alignCast(x));
obj.settype(.Buffer);
obj.checkliveness(L.global);
}
pub inline fn setclvalue(obj: *TValue, L: *lstate.lua_State, x: *Closure) void {
obj.value.gc = @ptrCast(@alignCast(x));
obj.settype(.Function);
obj.checkliveness(L.global);
}
pub inline fn sethvalue(obj: *TValue, L: *lstate.lua_State, x: *LuaTable) void {
obj.value.gc = @ptrCast(@alignCast(x));
obj.settype(.Table);
obj.checkliveness(L.global);
}
pub inline fn setptvalue(obj: *TValue, L: *lstate.lua_State, x: *Proto) void {
obj.value.gc = @ptrCast(@alignCast(x));
obj.settype(.Proto);
obj.checkliveness(L.global);
}
pub inline fn setupvalue(obj: *TValue, L: *lstate.lua_State, x: *UpVal) void {
obj.value.gc = @ptrCast(@alignCast(x));
obj.settype(.UpVal);
obj.checkliveness(L.global);
}
pub inline fn setobj(obj: *TValue, L: *lstate.lua_State, o2: *const TValue) void {
obj.* = o2.*;
obj.checkliveness(L.global);
}
pub inline fn setclassvalue(obj: *TValue, L: *lstate.lua_State, x: *LuauClass) void {
obj.value.gc = @ptrCast(@alignCast(x));
obj.settype(.Class);
obj.checkliveness(L.global);
}
pub inline fn setobjectvalue(obj: *TValue, L: *lstate.lua_State, x: *LuauObject) void {
obj.value.gc = @ptrCast(@alignCast(x));
obj.settype(.Object);
obj.checkliveness(L.global);
}
pub inline fn settype(obj: *TValue, t: lua.Type) void {
obj.tt = @intFromEnum(t);
}
pub inline fn iscollectable(o: *const TValue) bool {
return o.ttype() >= @intFromEnum(lua.Type.String);
}
pub inline fn iscfunction(o: *const TValue) bool {
return o.ttype() == @intFromEnum(lua.Type.Function) and o.clvalue().isC != 0;
}
pub inline fn isLfunction(o: *const TValue) bool {
return o.ttype() == @intFromEnum(lua.Type.Function) and o.clvalue().isC == 0;
}
};
pub const LU_TAG_ITERATOR = lua.config.UTAG_LIMIT;
pub inline fn checkliveness() void {}
pub const StkId = [*]TValue;
pub const TString = extern struct {
header: CommonHeader,
// 1 byte padding
atom: i16,
// 2 byte padding
/// next string in the hash table bucket
next: ?*TString,
hash: c_uint,
len: c_uint,
/// string data is allocated right after the header
data: [1]u8,
pub inline fn obj2gco(obj: *TString) *lstate.GCObject {
return @ptrCast(@alignCast(obj));
}
pub inline fn gdata(s: *TString) [*]u8 {
return @ptrCast(@alignCast(&s.data));
}
pub inline fn getstr(s: *const TString) [*c]const u8 {
return @ptrCast(@alignCast(&s.data));
}
pub inline fn toSlice(s: *const TString) [:0]const u8 {
return s.getstr()[0..s.len :0];
}
};
pub const Udata = extern struct {
header: CommonHeader,
tag: u8,
len: c_int,
metatable: ?*LuaTable,
data: [1]u8 align(8),
pub inline fn obj2gco(obj: *Udata) *lstate.GCObject {
return @ptrCast(@alignCast(obj));
}
};
pub const Buffer = extern struct {
header: CommonHeader,
len: c_uint,
data: [1]u8 align(8),
pub inline fn obj2gco(obj: *Buffer) *lstate.GCObject {
return @ptrCast(@alignCast(obj));
}
};
pub const FeedbackVectorSlotKind = enum(u8) {
CallTarget,
};
pub const FeedbackVectorSlot = extern struct {
kind: FeedbackVectorSlotKind,
data: extern union {
call_target: extern struct {
pc: u32,
proto: u32,
hits: u32,
},
},
};
///
/// Function Prototypes
///
pub const Proto = extern struct {
header: CommonHeader,
/// number of upvalues
nups: u8,
numparams: u8,
is_vararg: u8,
maxstacksize: u8,
flags: u8,
/// constants used by the function
k: ?[*]TValue,
/// function bytecode
code: ?[*]lcommon.Instruction,
/// functions defined inside the function
p: ?[*]?*Proto,
codeentry: ?*const lcommon.Instruction,
execdata: ?*anyopaque,
exectarget: usize,
lineinfo: ?[*]u8, // for each instruction, line number as a delta from baseline
abslineinfo: ?[*]u8, // baseline line info, one entry for each 1<<linegaplog2 instructions; allocated after lineinfo
locvars: ?[*]LocVar, // information about local variables
upvalues: ?[*]?*TString, // upvalue names
source: ?*TString,
debugname: ?*TString,
debuginsn: ?[*]u8, // a copy of code[] array with just opcodes
typeinfo: ?[*]u8,
userdata: ?*anyopaque,
gclist: ?*lstate.GCObject,
sizecode: c_int,
sizep: c_int,
sizelocvars: c_int,
sizeupvalues: c_int,
sizek: c_int,
sizelineinfo: c_int,
linegaplog2: c_int,
linedefined: c_int,
bytecodeid: c_int,
sizetypeinfo: c_int,
feedbackvec: ?[*]FeedbackVectorSlot,
feedbackvecsize: u32,
funid: u32,
optimized: ?*Proto,
deoptimized: ?*Proto,
cost: u64,
pub inline fn obj2gco(obj: *Proto) *lstate.GCObject {
return @ptrCast(@alignCast(obj));
}
};
pub const LocVar = extern struct {
varname: ?*TString,
/// first point where variable is active
startpc: c_int,
/// first point where variable is dead
endpc: c_int,
/// register slot, relative to base, where variable is stored
reg: u8,
};
///
/// Upvalues
///
pub const UpVal = extern struct {
header: CommonHeader,
/// set if reachable from an alive thread (only valid during atomic)
markedopen: u8,
// 4 byte padding (x64)
/// points to stack or to its own value
v: *TValue,
u: extern union {
/// the value (when closed)
value: TValue,
open: extern struct {
// global double linked list (when open)
prev: ?*UpVal,
next: ?*UpVal,
// thread linked list (when open)
threadnext: ?*UpVal,
},
},
pub inline fn obj2gco(obj: *UpVal) *lstate.GCObject {
return @ptrCast(@alignCast(obj));
}
pub inline fn upisopen(up: *const UpVal) bool {
return up.v != &up.u.value;
}
};
///
/// Closures
///
pub const Closure = extern struct {
header: CommonHeader,
isC: u8,
nupvalues: u8,
stacksize: u8,
preload: u8,
gclist: ?*lstate.GCObject,
env: *LuaTable,
d: ValueUnion,
pub const ValueUnion = extern union {
c: C,
l: L,
pub const C = extern struct {
f: ?lua.CFunction,
cont: ?lua.Continuation,
debugname: [*c]const u8,
upvals: [1]TValue,
pub inline fn upvalues(cc: *C) [*]TValue {
return @as([*]TValue, @ptrCast(&cc.upvals));
}
};
pub const L = extern struct {
p: *Proto,
uprefs: [1]TValue,
pub inline fn upreferences(ll: *L) [*]TValue {
return @as([*]TValue, @ptrCast(&ll.uprefs));
}
};
};
pub inline fn obj2gco(obj: *Closure) *lstate.GCObject {
return @ptrCast(@alignCast(obj));
}
};
pub const TKey = extern struct {
value: Value,
extra: [lua.config.EXTRA_SIZE]c_int,
pi: Packed = undefined,
pub const Packed = packed struct(u32) {
tt: u4, // type
next: i28, // next in the chain
pub fn withtt(tt: u4) [4]u8 {
return @bitCast(Packed{ .tt = tt, .next = 0 });
}
};
pub inline fn ttype(this: *const TKey) u4 {
return this.pi.tt;
}
pub inline fn typeOf(obj: *const TKey) lua.Type {
return @enumFromInt(obj.ttype());
}
pub inline fn setttype(this: *TKey, t: lua.Type) void {
this.pi.tt = @intFromEnum(t);
}
pub inline fn ttisnil(obj: *const TKey) bool {
return obj.ttype() == @intFromEnum(lua.Type.Nil);
}
pub inline fn ttisnumber(obj: *const TKey) bool {
return obj.ttype() == @intFromEnum(lua.Type.Number);
}
pub inline fn ttisinteger(obj: *const TKey) bool {
return obj.ttype() == @intFromEnum(lua.Type.Integer);
}
pub inline fn ttisstring(obj: *const TKey) bool {
return obj.ttype() == @intFromEnum(lua.Type.String);
}
pub inline fn ttistable(obj: *const TKey) bool {
return obj.ttype() == @intFromEnum(lua.Type.Table);
}
pub inline fn ttisfunction(obj: *const TKey) bool {
return obj.ttype() == @intFromEnum(lua.Type.Function);
}
pub inline fn ttisboolean(obj: *const TKey) bool {
return obj.ttype() == @intFromEnum(lua.Type.Boolean);
}
pub inline fn ttisuserdata(obj: *const TKey) bool {
return obj.ttype() == @intFromEnum(lua.Type.Userdata);
}
pub inline fn ttisthread(obj: *const TKey) bool {
return obj.ttype() == @intFromEnum(lua.Type.Thread);
}
pub inline fn ttisbuffer(obj: *const TKey) bool {
return obj.ttype() == @intFromEnum(lua.Type.Buffer);
}
pub inline fn ttislightuserdata(obj: *const TKey) bool {
return obj.ttype() == @intFromEnum(lua.Type.LightUserdata);
}
pub inline fn ttisvector(obj: *const TKey) bool {
return obj.ttype() == @intFromEnum(lua.Type.Vector);
}
pub inline fn ttisupval(obj: *const TKey) bool {
return obj.ttype() == @intFromEnum(lua.Type.UpVal);
}
pub inline fn gcvalue(obj: *const TKey) *lstate.GCObject {
std.debug.assert(obj.iscollectable());
return obj.value.gc.?;
}
pub inline fn pvalue(obj: *const TKey) ?*anyopaque {
std.debug.assert(obj.ttislightuserdata());
return obj.value.p;
}
pub inline fn nvalue(obj: *const TKey) f64 {
std.debug.assert(obj.ttisnumber());
return obj.value.n;
}
pub inline fn lvalue(obj: *const TKey) i64 {
std.debug.assert(obj.ttisinteger());
return obj.value.l;
}
pub inline fn vvalue(obj: *const TKey) []const f32 {
std.debug.assert(obj.ttisvector());
return @as([*]const f32, @ptrCast(&obj.value.v))[0..lua.config.VECTOR_SIZE];
}
pub inline fn tsvalue(obj: *const TKey) *TString {
std.debug.assert(obj.ttisstring());
return &obj.value.gc.?.ts;
}
pub inline fn uvalue(obj: *const TKey) *Udata {
std.debug.assert(obj.ttisuserdata());
return &obj.value.gc.?.u;
}
pub inline fn clvalue(obj: *const TKey) *Closure {
std.debug.assert(obj.ttisfunction());
return &obj.value.gc.?.cl;
}
pub inline fn hvalue(obj: *const TKey) *LuaTable {
std.debug.assert(obj.ttistable());
return &obj.value.gc.?.h;
}
pub inline fn bvalue(obj: *const TKey) bool {
std.debug.assert(obj.ttisboolean());
return obj.value.b != 0;
}
pub inline fn thvalue(obj: *const TKey) *lstate.lua_State {
std.debug.assert(obj.ttisthread());
return &obj.value.gc.?.th;
}
pub inline fn bufvalue(obj: *const TKey) *Buffer {
std.debug.assert(obj.ttisbuffer());
return &obj.value.gc.?.buf;
}
pub inline fn upvalue(obj: *TKey) *UpVal {
std.debug.assert(obj.ttisupval());
return &obj.value.gc.?.uv;
}
pub inline fn svalue(obj: *const TKey) [*c]const u8 {
return obj.tsvalue().getstr();
}
pub inline fn lightuserdatatag(obj: *const TKey) c_int {
std.debug.assert(obj.ttislightuserdata());
return obj.extra[0];
}
pub inline fn iscollectable(o: *const TKey) bool {
return o.ttype() >= @intFromEnum(lua.Type.String);
}
pub inline fn setnilvalue(obj: *TKey) void {
obj.pi.tt = @intFromEnum(lua.Type.Nil);
}
pub inline fn next(this: *TKey) i28 {
return this.pi.next;
}
};
pub const LuaNode = extern struct {
val: TValue,
key: TKey,
pub inline fn gkey(this: *LuaNode) *TKey {
return &this.key;
}
pub inline fn gval(this: *LuaNode) *TValue {
return &this.val;
}
pub inline fn gnext(this: *LuaNode) i28 {
return this.key.pi.next;
}
pub inline fn add_num(this: *LuaNode, n: anytype) *LuaNode {
switch (@typeInfo(@TypeOf(n))) {
.comptime_int => return @ptrCast(@as([*]LuaNode, @ptrCast(this)) + @as(usize, @intCast(n))),
.int => |i| {
if (i.signedness == .unsigned)
return @ptrCast(@as([*]LuaNode, @ptrCast(this)) + @as(usize, @intCast(n)))
else {
if (n < 0)
return @ptrCast(@as([*]LuaNode, @ptrCast(this)) - @as(usize, @intCast(-n)))
else
return @ptrCast(@as([*]LuaNode, @ptrCast(this)) + @as(usize, @intCast(n)));
}
},
else => @compileError("n must be an integer"),
}
}
pub fn sub(this: *LuaNode, ptr: *LuaNode) isize {
if (@intFromPtr(ptr) > @intFromPtr(this))
return -@as(isize, @intCast(@as([*]LuaNode, @ptrCast(ptr)) - @as([*]LuaNode, @ptrCast(this))));
return @intCast(@as([*]LuaNode, @ptrCast(this)) - @as([*]LuaNode, @ptrCast(ptr)));
}
};
pub inline fn setnodekey(L: *lstate.lua_State, node: *LuaNode, obj: *const TValue) void {
node.key.value = obj.value;
@memcpy(node.key.extra[0..lua.config.EXTRA_SIZE], obj.extra[0..lua.config.EXTRA_SIZE]);
node.key.pi.tt = @intCast(obj.tt);
obj.checkliveness(L.global);
}
pub inline fn getnodekey(L: *lstate.lua_State, obj: *TValue, node: *const LuaNode) void {
obj.value = node.key.value;
@memcpy(obj.extra[0..lua.config.EXTRA_SIZE], node.key.extra[0..lua.config.EXTRA_SIZE]);
obj.tt = @intCast(node.key.pi.tt);
obj.checkliveness(L.global);
}
pub const LuaTable = extern struct {
header: CommonHeader,
/// 1<<p means tagmethod(p) is not present
tmcache: u8,
/// sandboxing feature to prohibit writes to table
readonly: u8,
/// environment doesn't share globals with other scripts
safeenv: u8,
/// log2 of size of `node' array
lsizenode: u8,
/// (1<<lsizenode)-1, truncated to 8 bits
nodemask8: u8,
/// size of `array' array
sizearray: c_int,
bound: extern union {
/// any free position is before this position
lastfree: c_int,
/// negated 'boundary' of `array' array; iff aboundary < 0
aboundary: c_int,
},
metatable: ?*LuaTable,
array: ?[*]TValue, // array part
node: [*]LuaNode,
gclist: ?*lstate.GCObject,
pub inline fn obj2gco(obj: *LuaTable) *lstate.GCObject {
return @ptrCast(@alignCast(obj));
}
pub inline fn gnode(t: *const LuaTable, i: usize) [*]LuaNode {
return t.node + i;
}
};
pub const LuauClass = extern struct {
header: CommonHeader,
gclist: ?*lstate.GCObject,
name: *TString,
/// Mapping from offset to static members (only methods for now).
staticmembers: [*]TValue,
/// Mapping from member name to offset.
memberstooffset: *LuaTable,
/// Mapping from offset to member name.
offsettomember: [*]*TString,
/// Metatable for this *class object*. At time of writing this only contains
/// __call, but we may add more metamethods to class objects in the future.
metatable: *LuaTable,
/// Metatable for instances of this class. NULL until the first metamethod
/// is added via luaR_addclassmember.
instancemetatable: ?*LuaTable,
/// Number of instance members that we expect instances of this class object
/// to have.
numberofinstancemembers: u32,
// Total number of members that we expect this class object to have between
// instance and static members.
//
// We store this number as an optimization. It's pretty rare that we need
// to reference the specific number of static members, but it's very common
// to reference the total number of members (for validating hot paths in
// the interpreter) and the number of instance members (branching on
// instance or static members, creating class instances).
numberofallmembers: u32,
pub inline fn obj2gco(obj: *LuauClass) *lstate.GCObject {
return @ptrCast(@alignCast(obj));
}
};
pub const LuauObject = extern struct {
header: CommonHeader,
gclist: ?*lstate.GCObject,
/// The class object that this value is an instance of.
lclass: *LuauClass,
/// The number of members that this instance contains. We need this in order
/// to free ourselves if we got swept in the same GC cycle as our class
/// pointer.
numberofmembers: u32,
/// The fields of this instance.
members: [*]TValue,
pub inline fn obj2gco(obj: *LuauObject) *lstate.GCObject {
return @ptrCast(@alignCast(obj));
}
};
pub inline fn lmod(comptime T: type, s: u32, size: T) T {
std.debug.assert(size & (size - 1) == 0);
return s & (size - 1);
}
pub inline fn twoto(x: if (@sizeOf(usize) == 8) u6 else u5) usize {
return @as(usize, 1) << x;
}
pub inline fn sizenode(t: *const LuaTable) usize {
return @intCast(@as(usize, 1) << @truncate(t.lsizenode));
}
extern "c" const luaO_nilobject_: TValue;
pub const Onilobject = &luaO_nilobject_;
pub inline fn ceillog2(x: u32) i32 {
return Olog2(x - 1) + 1;
}
pub fn Olog2(i: u32) i32 {
// zig fmt: off
const log_2: [256]u8 = [_]u8{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};
// zig fmt: on
var x: u32 = i;
var l: i32 = -1;
while (x >= 256) {
l += 8;
x >>= 8;
}
return l + log_2[x];
}
pub fn OrawequalObj(t1: *const TValue, t2: *const TValue) bool {
if (t1.ttype() != t2.ttype())
return false;
switch (t1.typeOf()) {
.None => unreachable,
.Nil => return true,
.Number => return t1.nvalue() == t2.nvalue(),
.Integer => return t1.lvalue() == t2.lvalue(),
.Vector => return lnumutils.iveceq(t1.vvalue(), t2.vvalue()),
.Boolean => return t1.bvalue() == t2.bvalue(),
.LightUserdata => return t1.pvalue() == t2.pvalue() and t1.lightuserdatatag() == t2.lightuserdatatag(),
inline else => |t| {
comptime std.debug.assert(t.istypecollectable());
return t1.gcvalue() == t2.gcvalue();
},
}
}
pub fn OrawequalKey(t1: *const TKey, t2: *const TValue) bool {
if (t1.ttype() != t2.ttype())
return false;
switch (t1.typeOf()) {
.None => unreachable,
.Nil => return true,
.Number => return t1.nvalue() == t2.nvalue(),
.Integer => return t1.lvalue() == t2.lvalue(),
.Vector => return lnumutils.iveceq(t1.vvalue(), t2.vvalue()),
.Boolean => return t1.bvalue() == t2.bvalue(),
.LightUserdata => return t1.pvalue() == t2.pvalue() and t1.lightuserdatatag() == t2.lightuserdatatag(),
inline else => |t| {
comptime std.debug.assert(t.istypecollectable());
return t1.gcvalue() == t2.gcvalue();
},
}
}
pub fn Opushvfstring(L: *lua.State, comptime fmt: []const u8, args: anytype) Errorset.Table!void {
var buf: [lua.config.BUFFERSIZE]u8 = undefined;
const fstr = std.fmt.bufPrint(&buf, fmt, args) catch |err| @panic(@errorName(err));
try L.pushlstring(fstr);
}
pub inline fn Opushfstring(L: *lua.State, comptime fmt: []const u8, args: anytype) Errorset.Table!void {
try Opushvfstring(L, fmt, args);
}
// pub fn Ochunkid(out: []u8, comptime source: []const u8) []u8 {
// c.luaO
// }
test "size match" {
const Sizes = struct {
extern "c" const GCObject_size: u8;
extern "c" const GCheader_size: u8;
extern "c" const Value_size: u8;
extern "c" const TValue_size: u8;
extern "c" const TString_size: u8;
extern "c" const Udata_size: u8;
extern "c" const LuauBuffer_size: u8;
extern "c" const Proto_size: u8;
extern "c" const LocVar_size: u8;
extern "c" const UpVal_size: u8;
extern "c" const Closure_size: u8;
extern "c" const TKey_size: u8;
extern "c" const LuaNode_size: u8;
extern "c" const LuaTable_size: u8;
extern "c" const LuauClass_size: u8;
extern "c" const LuauObject_size: u8;
extern "c" const TString_data_offset: u8;
extern "c" const Udata_data_offset: u8;
extern "c" const LuauBuffer_data_offset: u8;
};
try std.testing.expect(Sizes.GCObject_size == @sizeOf(lstate.GCObject));
try std.testing.expect(Sizes.GCheader_size == @sizeOf(GCheader));
try std.testing.expect(Sizes.Value_size == @sizeOf(Value));
try std.testing.expect(Sizes.TValue_size == @sizeOf(TValue));
try std.testing.expect(Sizes.TString_size == @sizeOf(TString));
try std.testing.expect(Sizes.Udata_size == @sizeOf(Udata));
try std.testing.expect(Sizes.LuauBuffer_size == @sizeOf(Buffer));
try std.testing.expect(Sizes.Proto_size == @sizeOf(Proto));
try std.testing.expect(Sizes.LocVar_size == @sizeOf(LocVar));
try std.testing.expect(Sizes.UpVal_size == @sizeOf(UpVal));
try std.testing.expect(Sizes.Closure_size == @sizeOf(Closure));
try std.testing.expect(Sizes.TKey_size == @sizeOf(TKey));
try std.testing.expect(Sizes.LuaNode_size == @sizeOf(LuaNode));
try std.testing.expect(Sizes.LuaTable_size == @sizeOf(LuaTable));
try std.testing.expect(Sizes.LuauClass_size == @sizeOf(LuauClass));
try std.testing.expect(Sizes.LuauObject_size == @sizeOf(LuauObject));
try std.testing.expect(Sizes.TString_data_offset == @offsetOf(TString, "data"));
try std.testing.expect(Sizes.Udata_data_offset == @offsetOf(Udata, "data"));
try std.testing.expect(Sizes.LuauBuffer_data_offset == @offsetOf(Buffer, "data"));
}
+7
View File
@@ -0,0 +1,7 @@
const c = @import("c");
const lua = @import("lua.zig");
pub inline fn open(L: *lua.State) void {
_ = c.luaopen_os(@ptrCast(L));
}
+9
View File
@@ -0,0 +1,9 @@
const c = @import("c");
const lua = @import("lua.zig");
extern "c" fn lua_clock() f64;
pub fn clock() f64 {
return lua_clock();
}
+1011
View File
File diff suppressed because it is too large Load Diff
+213
View File
@@ -0,0 +1,213 @@
const std = @import("std");
const builtin = @import("builtin");
const lua = @import("lua.zig");
const lobject = @import("lobject.zig");
const lgc = @import("lgc.zig");
const lmem = @import("lmem.zig");
const Errorset = @import("errorset.zig");
/// string size limit
pub const MAXSSIZE = (1 << 30);
/// string atoms are not defined by default; the storage is 16-bit integer
pub const ATOM_UNDEF = -32768;
inline fn sizestring(len: usize) usize {
return @offsetOf(lobject.TString, "data") + len + 1;
}
pub inline fn Snew(L: *lua.State, s: []const u8) Errorset.Memory!*lobject.TString {
return Snewlstr(L, s);
}
pub inline fn Sfix(s: *lobject.TString) void {
s.header.marked |= lgc.bitmask(lgc.FIXEDBIT);
}
pub inline fn Supdateatom(L: *lua.State, ts: *lobject.TString) void {
if (ts.atom == ATOM_UNDEF)
ts.atom = if (L.global.cb.useratom) |useratom| useratom(L, @ptrCast(@alignCast(&ts.data)), @intCast(ts.len)) else -1;
}
pub fn Shash(str: []const u8) u32 {
// Note that this hashing algorithm is replicated in BytecodeBuilder.cpp, BytecodeBuilder::getStringHash
var src = str;
var len: usize = str.len;
var a: u32 = 0;
var b: u32 = 0;
var h: u32 = @truncate(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) : (len -= 12) {
a +%= std.mem.readInt(u32, src[0..4], builtin.cpu.arch.endian());
b +%= std.mem.readInt(u32, src[4..8], builtin.cpu.arch.endian());
h +%= std.mem.readInt(u32, src[8..12], builtin.cpu.arch.endian());
// mix
a ^= h;
a -%= ((h >> 14) | (h << (32 - 14)));
b ^= a;
b -%= ((a >> 11) | (a << (32 - 11)));
h ^= b;
h -%= ((b >> 25) | (b << (32 - 25)));
src = src[12..];
}
// original Lua 5.1 hash for compatibility (exact match when len<32)
var i: usize = len;
while (i > 0) : (i -= 1)
h ^= (h << 5) +% (h >> 2) +% src[i - 1];
return h;
}
pub fn Sresize(L: *lua.State, newsize: usize) Errorset.Memory!void {
const newhash = try lmem.Mnewarray(L, ?*lobject.TString, newsize, 0);
const tb = &L.global.strt;
for (0..newsize) |i|
newhash[i] = null;
// rehash
for (0..@intCast(tb.size)) |i| {
var p: ?*lobject.TString = tb.hash.?[i];
while (p) |node| { // for each node in the list
const next = node.next; // save next
const h = node.hash;
const h1 = lobject.lmod(usize, h, newsize); // new position
std.debug.assert(h % newsize == h1);
node.next = newhash[h1]; // chain it
newhash[h1] = node;
p = next;
}
}
lmem.Mfreearray(L, ?*lobject.TString, tb.hash, @intCast(tb.size), 0);
tb.size = @intCast(newsize);
tb.hash = newhash;
}
fn newlstr(L: *lua.State, str: []const u8, hash: u32) Errorset.Memory!*lobject.TString {
const l = str.len;
if (l > MAXSSIZE)
return error.BlockTooBig;
const ts = try lmem.Mnewgco(L, lobject.TString, sizestring(l), L.activememcat);
lgc.Cinit(L, @ptrCast(@alignCast(ts)), @intFromEnum(lua.Type.String));
ts.atom = ATOM_UNDEF;
ts.hash = hash;
ts.len = @intCast(l);
@memcpy(ts.gdata()[0..l], str[0..l]);
ts.gdata()[l] = 0; // ending 0
const tb = &L.global.strt;
const h: u32 = lobject.lmod(u32, hash, @intCast(tb.size));
ts.next = tb.hash.?[h]; // chain new entry
tb.hash.?[h] = ts;
tb.nuse += 1;
if (tb.nuse > tb.size and tb.size <= @divTrunc(std.math.maxInt(i32), 2))
try Sresize(L, @intCast(tb.size * 2)); // too crowded
return ts;
}
pub fn Sbufstart(L: *lua.State, size: usize) Errorset.Memory!*lobject.TString {
if (size > MAXSSIZE)
return error.BlockTooBig;
const ts = try lmem.Mnewgco(L, lobject.TString, sizestring(size), L.activememcat);
lgc.Cinit(L, @ptrCast(@alignCast(ts)), @intFromEnum(lua.Type.String));
ts.atom = ATOM_UNDEF;
ts.hash = 0; // computed in Sbuffinish
ts.len = @intCast(size);
ts.next = null;
return ts;
}
pub fn Sbuffinish(L: *lua.State, ts: *lobject.TString) Errorset.Memory!*lobject.TString {
const h = Shash(ts.gdata()[0..ts.len]);
const tb = &L.global.strt;
const bucket: u32 = lobject.lmod(u32, h, @intCast(tb.size));
// search if we already have this string in the hash table
var el: ?*lobject.TString = tb.hash.?[bucket];
while (el) |node| : (el = node.next) {
if (node.len == ts.len and std.mem.eql(u8, node.gdata()[0..ts.len], ts.gdata()[0..ts.len])) {
// string may be dead
if (lgc.isdead(L.global, @ptrCast(@alignCast(node))))
lgc.changewhite(@ptrCast(@alignCast(node)));
return node;
}
}
std.debug.assert(ts.next == null);
ts.hash = h;
ts.gdata()[ts.len] = 0; // ending 0
ts.next = tb.hash.?[bucket]; // chain new entry
tb.hash.?[bucket] = ts;
tb.nuse += 1;
if (tb.nuse > tb.size and tb.size <= @divTrunc(std.math.maxInt(i32), 2))
try Sresize(L, @intCast(tb.size * 2)); // too crowded
return ts;
}
fn findstrnode(L: *lua.State, str: []const u8, h: u32) ?*lobject.TString {
var el = L.global.strt.hash.?[lobject.lmod(u32, h, @intCast(L.global.strt.size))];
while (el) |node| : (el = node.next) {
if (node.len == str.len and std.mem.eql(u8, node.gdata()[0..node.len], str[0..str.len])) {
// string may be dead
if (lgc.isdead(L.global, @ptrCast(@alignCast(node))))
lgc.changewhite(@ptrCast(@alignCast(node)));
return node;
}
}
return null; // not found
}
pub fn Snewlstr(L: *lua.State, str: []const u8) Errorset.Memory!*lobject.TString {
const h = Shash(str);
if (findstrnode(L, str, h)) |el|
return el;
return newlstr(L, str, h); // not found
}
pub fn Sassumelstr(L: *lua.State, str: []const u8) ?*lobject.TString {
const h = Shash(str);
return findstrnode(L, str, h);
}
fn unlinkstr(L: *lua.State, ts: *lobject.TString) bool {
const g = L.global;
var p = &g.strt.hash.?[lobject.lmod(u32, ts.hash, @intCast(g.strt.size))];
while (p.*) |node| {
if (node == ts) {
p.* = node.next;
return true;
} else {
p = &node.next;
}
}
return false;
}
pub fn Sfree(L: *lua.State, ts: *lobject.TString, page: *lmem.lua_Page) void {
if (unlinkstr(L, ts))
L.global.strt.nuse -= 1
else
std.debug.assert(ts.next == null); // orphaned string buffer
lmem.Mfreegco(L, ts.obj2gco(), sizestring(ts.len), ts.header.memcat, page);
}
+7
View File
@@ -0,0 +1,7 @@
const c = @import("c");
const lua = @import("lua.zig");
pub inline fn open(L: *lua.State) void {
_ = c.luaopen_string(@ptrCast(L));
}
+700
View File
@@ -0,0 +1,700 @@
const c = @import("c");
const std = @import("std");
const lua = @import("lua.zig");
const lobject = @import("lobject.zig");
const lstate = @import("lstate.zig");
const ldebug = @import("ldebug.zig");
const lgc = @import("lgc.zig");
const lmem = @import("lmem.zig");
const lnumutils = @import("lnumutils.zig");
const Errorset = @import("errorset.zig");
const Error = Errorset.Table;
const MAXBITS = 26;
const MAXSIZE = 1 << MAXBITS;
const TValue = lobject.TValue;
const LuaNode = lobject.LuaNode;
const LuaTable = lobject.LuaTable;
const LUA_VECTOR_SIZE = lua.config.VECTOR_SIZE;
// const Hdummynode: LuaNode = .{
// .val = .{ .extra = undefined, .tt = @intFromEnum(lua.Type.Nil), .value = undefined },
// .key = .{ .extra = undefined, .pi = .{ .tt = @intFromEnum(lua.Type.Nil), .next = 0 }, .value = undefined },
// };
extern "c" const luaH_dummynode: LuaNode;
pub const dummynode = &luaH_dummynode;
pub inline fn invalidateTMcache(t: *LuaTable) void {
t.tmcache = 0;
}
pub inline fn hashpow2(t: *const LuaTable, n: u32) [*]LuaNode {
return t.gnode(lobject.lmod(usize, n, lobject.sizenode(t)));
}
pub inline fn hashstr(t: *const LuaTable, str: *const lobject.TString) [*]LuaNode {
return hashpow2(t, str.hash);
}
pub inline fn hashboolean(t: *const LuaTable, b: bool) [*]LuaNode {
return hashpow2(t, if (b) 1 else 0);
}
pub fn hashpointer(t: *const LuaTable, p: ?*const anyopaque) [*]LuaNode {
// we discard the high 32-bit portion of the pointer on 64-bit platforms as it doesn't carry much entropy anyway
var h: u32 = if (p) |ptr| @truncate(@intFromPtr(ptr)) else 0;
// MurmurHash3 32-bit finalizer
h ^= h >> 16;
h *%= 0x85ebca6b;
h ^= h >> 13;
h *%= 0xc2b2ae35;
h ^= h >> 16;
return hashpow2(t, h);
}
fn hashnum(t: *const LuaTable, n: f64) [*]LuaNode {
comptime std.debug.assert(@sizeOf(f64) == @sizeOf(u32) * 2); // expected a 8-byte double;
var i: [2]u32 = undefined;
@memcpy(i[0..], &@as([2]u32, @bitCast(n)));
// mask out sign bit to make sure -0 and 0 hash to the same value
var h1: u32 = i[0];
var h2: u32 = i[1] & 0x7fffffff;
// finalizer from MurmurHash64B
const m: u32 = 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);
}
fn hashint(t: *const LuaTable, n: i64) [*]LuaNode {
comptime std.debug.assert(@sizeOf(i64) == @sizeOf(u32) * 2); // expected a 8-byte integer;
var i: [2]u32 = undefined;
@memcpy(i[0..], &@as([2]u32, @bitCast(n)));
var h1: u32 = i[0];
var h2: u32 = i[1];
// finalizer from MurmurHash64B
const m: u32 = 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);
}
fn hashvec(t: *const LuaTable, v: []const f32) [*]LuaNode {
var i: [LUA_VECTOR_SIZE]u32 = undefined;
@memcpy(i[0..], (@as([*]const u32, @ptrCast(@alignCast(v.ptr))))[0..LUA_VECTOR_SIZE]);
// convert -0 to 0 to make sure they hash to the same value
i[0] = if (i[0] == 0x80000000) 0 else i[0];
i[1] = if (i[1] == 0x80000000) 0 else i[1];
i[2] = if (i[2] == 0x80000000) 0 else 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
var h: u32 = (i[0] * 73856093) ^ (i[1] * 19349663) ^ (i[2] * 83492791);
if (comptime LUA_VECTOR_SIZE == 4) {
i[3] = if (i[3] == 0x80000000) 0 else i[3];
i[3] ^= i[3] >> 17;
h ^= i[3] * 39916801;
}
return hashpow2(t, h);
}
fn mainposition(t: *const LuaTable, key: *const TValue) [*]LuaNode {
comptime std.debug.assert(@sizeOf(LuaNode) == @sizeOf(TValue) * 2);
comptime std.debug.assert(@alignOf(LuaNode) == @alignOf(TValue));
return switch (key.typeOf()) {
.Number => hashnum(t, key.nvalue()),
.Integer => hashint(t, key.lvalue()),
.Vector => hashvec(t, key.vvalue()),
.String => hashstr(t, key.tsvalue()),
.Boolean => hashboolean(t, key.bvalue()),
.LightUserdata => hashpointer(t, key.pvalue()),
else => hashpointer(t, @ptrCast(@alignCast(key.gcvalue()))),
};
}
///
/// returns the index for `key` if `key` is an appropriate key to live in
/// the array part of the table, -1 otherwise.
///
fn arrayindex(key: f64) i32 {
const i: i32 = lnumutils.inum2int(key);
return if (@as(f64, @floatFromInt(i)) == key) i else -1;
}
// {=============================================================
// Rehash
// ==============================================================
pub inline fn maybesetaboundary(t: *LuaTable, boundary: i32) void {
if (t.bound.aboundary <= 0)
t.bound.aboundary = -boundary;
}
pub inline fn getaboundary(t: *LuaTable) c_int {
return if (t.bound.aboundary < 0) -t.bound.aboundary else t.sizearray;
}
fn computesizes(nums: []const u32, narray: *usize) usize {
var i: usize = 0;
var twotoi: usize = 1; // 2^i
var a: usize = 0; // number of elements smaller than 2^i
var na: usize = 0; // number of elements to go to array part
var n: usize = 0; // optimal size for array part
while (@divTrunc(twotoi, 2) < narray.*) : (i += 1) {
defer twotoi *= 2;
if (nums[i] > 0) {
a += nums[i];
if (a > @divTrunc(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;
std.debug.assert(@divTrunc(narray.*, 2) <= na and na <= narray.*);
return na;
}
fn countint(key: f64, nums: []u32) u1 {
const k = arrayindex(key);
if (0 < k and k <= MAXSIZE) {
// is `key' an appropriate array index?
nums[@intCast(lobject.ceillog2(@intCast(k)))] += 1; // count as such
return 1;
}
return 0;
}
fn numusearray(t: *const LuaTable, nums: []u32) usize {
var lg: u8 = 0;
var ttlg: i32 = 1; // 2^lg
var ause: usize = 0; // summation of `nums'
var i: u32 = 1; // count to traverse all array keys
while (lg <= MAXBITS) : (lg += 1) { // for each slice
defer ttlg *= 2;
var lc: u32 = 0; // counter
var lim: i32 = ttlg;
if (lim > t.sizearray) {
lim = t.sizearray; // adjust upper limit
if (i > lim)
break; // no more elements to count
}
while (i <= lim) : (i += 1) {
if (!t.array.?[i - 1].ttisnil())
lc += 1;
}
nums[lg] = lc;
ause += lc;
}
return ause;
}
fn numusehash(t: *const LuaTable, nums: []u32, pnasize: *usize) u32 {
var totaluse: u32 = 0; // total number of elements
var ause: usize = 0; // summation of `nums'
var i: usize = lobject.sizenode(t);
while (i > 0) : (i -= 1) {
const n: *LuaNode = @ptrCast(t.gnode(i - 1));
if (!n.gval().ttisnil()) {
if (n.gkey().ttisnumber())
ause += countint(n.gkey().nvalue(), nums);
totaluse += 1;
}
}
pnasize.* += ause;
return totaluse;
}
fn setarrayvector(L: *lua.State, t: *LuaTable, size: usize) Error!void {
if (size > MAXSIZE)
return error.@"table overflow";
t.array = try lmem.Mreallocarray(L, TValue, t.array, @intCast(t.sizearray), size, t.header.memcat);
var i: usize = @intCast(t.sizearray);
while (i < size) : (i += 1)
t.array.?[i].setnilvalue();
t.sizearray = @intCast(size);
}
fn setnodevector(L: *lua.State, t: *LuaTable, newsize: usize) Error!void {
var size: usize = newsize;
var lsize: u8 = 0;
if (size == 0) { // no elements to hash part?
t.node = @ptrCast(@alignCast(@constCast(dummynode))); // use common `dummynode'
} else {
lsize = @intCast(lobject.ceillog2(@truncate(newsize)));
if (lsize > MAXBITS)
return error.@"table overflow";
size = lobject.twoto(@intCast(lsize));
t.node = try lmem.Mnewarray(L, LuaNode, size, t.header.memcat);
for (0..size) |i| {
const n: *LuaNode = @ptrCast(t.gnode(i));
n.key.pi.next = 0;
n.gkey().setnilvalue();
n.gval().setnilvalue();
}
}
t.lsizenode = lsize;
t.nodemask8 = @truncate((@as(usize, 1) << @truncate(lsize)) - 1);
t.bound.lastfree = @intCast(size); // all positions are free
}
fn arrayornewkey(L: *lua.State, t: *LuaTable, key: *const TValue) Error!*TValue {
if (key.ttisnumber()) {
const n = key.nvalue();
const k = lnumutils.inum2int(n);
if (@as(f64, @floatFromInt(k)) == n and k - 1 < t.sizearray)
return &t.array.?[@intCast(k - 1)];
}
return newkey(L, t, key);
}
fn resize(L: *lua.State, t: *LuaTable, nasize: usize, nhsize: usize) Error!void {
if (nasize > MAXSIZE or nhsize > MAXSIZE)
return error.@"table overflow";
const oldasize: i32 = t.sizearray;
const oldhsize: u8 = t.lsizenode;
const nold = t.node; // save old hash ...
if (nasize > oldasize) // array part must grow?
try setarrayvector(L, t, nasize);
// create new hash part with appropriate size
try setnodevector(L, t, nhsize);
// used for the migration check at the end
const nnew = t.node;
if (nasize < oldasize) { // array part must shrink?
t.sizearray = @intCast(nasize);
// re-insert elements from vanishing slice
var i: usize = nasize;
while (i < oldasize) : (i += 1) {
if (!t.array.?[i].ttisnil()) {
var ok: TValue = undefined;
ok.setnvalue(@floatFromInt(i + 1));
(try newkey(L, t, &ok)).setobj(L, &t.array.?[i]);
}
}
// shrink array
t.array = try lmem.Mreallocarray(L, TValue, t.array, @intCast(oldasize), nasize, t.header.memcat);
}
// used for the migration check at the end
const anew = t.array;
// re-insert elements from hash part
var i: usize = lobject.twoto(@truncate(oldhsize));
while (i > 0) : (i -= 1) {
const old: *LuaNode = @ptrCast(nold + i - 1);
if (!old.gval().ttisnil()) {
var ok: TValue = undefined;
lobject.getnodekey(L, &ok, old);
(try arrayornewkey(L, t, &ok)).setobj(L, old.gval());
}
}
// make sure we haven't recursively rehashed during element migration
std.debug.assert(nnew == t.node);
std.debug.assert(anew == t.array);
if (@as(*LuaNode, @ptrCast(nold)) != dummynode)
lmem.Mfreearray(L, LuaNode, nold, lobject.twoto(@truncate(oldhsize)), t.header.memcat); // free old array
}
fn adjustasize(t: *LuaTable, size: usize, ek: ?*const TValue) usize {
const tbound: bool = @as(*LuaNode, @ptrCast(t.node)) != dummynode or size < t.sizearray;
const ekindex: i32 = if (ek != null and ek.?.ttisnumber()) arrayindex(ek.?.nvalue()) else -1;
// move the array size up until the boundary is guaranteed to be inside the array part
var adjusted_size = size;
while (adjusted_size + 1 == ekindex or (tbound and !Hgetnum(t, @intCast(adjusted_size + 1)).ttisnil()))
adjusted_size += 1;
return adjusted_size;
}
pub fn Hresizearray(L: *lua.State, t: *LuaTable, nasize: usize) Error!void {
const nsize = if (@as(*LuaNode, @ptrCast(t.node)) == dummynode) 0 else lobject.sizenode(t);
const asize = adjustasize(t, nasize, null);
try resize(L, t, asize, nsize);
}
pub fn Hresizehash(L: *lua.State, t: *LuaTable, nhsize: usize) Error!void {
try resize(L, t, @intCast(t.sizearray), nhsize);
}
fn rehash(L: *lua.State, t: *LuaTable, ek: *const TValue) Error!void {
var nums: [MAXBITS + 1]u32 = [_]u32{0} ** (MAXBITS + 1);
var nasize = numusearray(t, nums[0..]); // count keys in array part
var totaluse: usize = nasize; // all those keys are integer keys
totaluse += numusehash(t, nums[0..], &nasize); // count keys in hash part
// count extra key
if (ek.ttisnumber())
nasize += countint(ek.nvalue(), nums[0..]);
totaluse += 1;
// compute new size for array part
const na = computesizes(nums[0..], &nasize);
var nh = totaluse - na;
// enforce the boundary invariant; for performance, only do hash lookups if we must
const nadjusted = adjustasize(t, nasize, ek);
// count how many extra elements belong to array part instead of hash part
const aextra = nadjusted - nasize;
if (aextra != 0) {
// we no longer need to store those extra array elements in hash part
nh -= aextra;
// because hash nodes are twice as large as array nodes, the memory we saved for hash parts can be used by array part
// this follows the general sparse array part optimization where array is allocated when 50% occupation is reached
nasize = nadjusted + aextra;
// since the size was changed, it's again important to enforce the boundary invariant at the new size
nasize = adjustasize(t, nasize, ek);
}
// resize the table to new computed sizes
try resize(L, t, nasize, nh);
}
pub fn Hnew(L: *lua.State, narray: u32, nhash: u32) Error!*LuaTable {
const t = try lmem.Mnewgco(L, LuaTable, @sizeOf(LuaTable), L.header.memcat);
lgc.Cinit(L, @ptrCast(@alignCast(t)), @intFromEnum(lua.Type.Table));
t.metatable = null;
t.tmcache = ~(@as(u8, 0));
t.array = null;
t.sizearray = 0;
t.bound.lastfree = 0;
t.lsizenode = 0;
t.readonly = 0;
t.safeenv = 0;
t.nodemask8 = 0;
t.node = @ptrCast(@alignCast(@constCast(dummynode)));
if (narray > 0)
try setarrayvector(L, t, narray);
if (nhash > 0)
try setnodevector(L, t, nhash);
return t;
}
pub fn Hfree(L: *lua.State, t: *LuaTable, page: *lmem.lua_Page) void {
if (@as(*LuaNode, @ptrCast(t.node)) != dummynode)
lmem.Mfreearray(L, LuaNode, t.node, lobject.sizenode(t), t.header.memcat);
if (t.array) |arr|
lmem.Mfreearray(L, TValue, arr, @intCast(t.sizearray), t.header.memcat);
lmem.Mfreegco(L, t.obj2gco(), @sizeOf(LuaTable), t.header.memcat, page);
}
fn getfreepos(t: *LuaTable) ?*LuaNode {
while (t.bound.lastfree > 0) {
t.bound.lastfree -= 1;
const n: *LuaNode = @ptrCast(t.gnode(@intCast(t.bound.lastfree)));
if (n.gkey().ttisnil())
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.
//
fn newkey(L: *lua.State, t: *LuaTable, key: *const TValue) Error!*TValue {
// enforce boundary invariant
if (key.ttisnumber() and key.nvalue() == @as(f64, @floatFromInt(t.sizearray + 1))) {
try rehash(L, t, key); // grow table
// after rehash, numeric keys might be located in the new array part, but won't be found in the node part
return arrayornewkey(L, t, key);
}
var mp: *LuaNode = @ptrCast(mainposition(t, key));
if (!mp.gval().ttisnil() or mp == dummynode) {
const n = getfreepos(t) orelse {
// cannot find a free place?
try rehash(L, t, key); // grow table
// after rehash, numeric keys might be located in the new array part, but won't be found in the node part
return arrayornewkey(L, t, key);
}; // get a free place
std.debug.assert(@as(*LuaNode, @ptrCast(n)) != dummynode);
var mk: TValue = undefined;
lobject.getnodekey(L, &mk, mp);
var othern: *LuaNode = @ptrCast(mainposition(t, &mk));
if (othern != mp) { // is colliding node out of its main position?
// yes; move colliding node into free position
while (othern.add_num(othern.gnext()) != mp)
othern = othern.add_num(othern.gnext()); // find previous
othern.key.pi.next = @truncate(n.sub(othern)); // redo the chain with `n' in place of `mp'
n.* = mp.*; // copy colliding node into free pos. (mp->next also goes)
if (mp.gnext() != 0) {
n.key.pi.next += @truncate(mp.sub(n)); // correct 'next'
mp.key.pi.next = 0; // now 'mp' is free
}
mp.gval().setnilvalue();
} else { // colliding node is in its own main position
// new node will go into free position
if (mp.gnext() != 0)
n.key.pi.next = @truncate((mp.add_num(mp.gnext())).sub(n)) // chain new position
else
std.debug.assert(n.gnext() == 0);
mp.key.pi.next = @truncate(n.sub(mp));
mp = n;
}
}
lobject.setnodekey(L, mp, key);
lgc.Cbarriert(L, t, key);
std.debug.assert(mp.gval().ttisnil());
return mp.gval();
}
//
// search function for integers
//
pub fn Hgetnum(t: *LuaTable, key: i32) *const TValue {
// (1 <= key && key <= t->sizearray)
if (@as(u32, @intCast(key - 1)) < @as(u32, @intCast(t.sizearray)))
return &t.array.?[@as(u32, @intCast(key - 1))]
else if (@as(*LuaNode, @ptrCast(t.node)) != dummynode) {
// hash fallback
const nk: f64 = @floatFromInt(key);
var n = hashnum(t, nk);
while (true) { // check whether `key' is somewhere in the chain
if (n[0].gkey().ttisnumber() and n[0].gkey().nvalue() == nk)
return n[0].gval(); // that's it
if (n[0].gnext() == 0)
break;
n = @ptrCast(n[0].add_num(n[0].gnext()));
}
}
return lobject.Onilobject;
}
pub fn Hgetstr(t: *LuaTable, key: *lobject.TString) *const TValue {
var n: *LuaNode = @ptrCast(hashstr(t, key));
while (true) { // check whether `key' is somewhere in the chain
if (n.gkey().ttisstring() and n.gkey().tsvalue() == key)
return n.gval(); // that's it
if (n.gnext() == 0)
break;
n = n.add_num(n.gnext());
}
return lobject.Onilobject;
}
pub fn Hget(t: *LuaTable, key: *const TValue) *const TValue {
switch (key.typeOf()) {
.Nil => return lobject.Onilobject,
.String => return Hgetstr(t, key.tsvalue()),
.Number => {
const k = lnumutils.inum2int(key.nvalue());
if (@as(f64, @floatFromInt(k)) == key.nvalue()) // index is int?
return Hgetnum(t, k); // use specialized version
// else go through
},
else => {},
}
var n = mainposition(t, key);
while (true) { // check whether `key' is somewhere in the chain
if (lobject.OrawequalKey(n[0].gkey(), key))
return n[0].gval(); // that's it
if (n[0].gnext() == 0)
break;
n = @ptrCast(n[0].add_num(n[0].gnext()));
}
return lobject.Onilobject; // not found
}
pub fn Hset(L: *lua.State, t: *LuaTable, key: *const TValue) Error!*TValue {
const p = Hget(t, key);
invalidateTMcache(t);
if (p != lobject.Onilobject)
return @constCast(p)
else
return try Hnewkey(L, t, key);
}
pub fn Hnewkey(L: *lua.State, t: *LuaTable, key: *const TValue) Error!*TValue {
if (key.ttisnil())
return error.@"table index is nil";
if (key.ttisnumber() and lnumutils.inumisnan(key.nvalue()))
return error.@"table index is nan";
if (key.ttisvector() and lnumutils.ivecisnan(key.vvalue()))
return error.@"table index contains nan";
return newkey(L, t, key);
}
pub fn Hsetnum(L: *lua.State, t: *LuaTable, key: i32) Error!*TValue {
// (1 <= key && key <= t->sizearray)
if (key - 1 < t.sizearray)
return &t.array.?[@intCast(key - 1)];
// hash fallback
const p = Hgetnum(t, key);
if (p != lobject.Onilobject)
return @constCast(p)
else {
var k: TValue = undefined;
k.setnvalue(@floatFromInt(key));
return newkey(L, t, &k);
}
}
pub fn Hsetstr(L: *lua.State, t: *LuaTable, key: *lobject.TString) Error!*TValue {
const p = Hgetstr(t, key);
invalidateTMcache(t);
if (p != lobject.Onilobject)
return @constCast(p)
else {
var k: TValue = undefined;
k.setsvalue(L, key);
return newkey(L, t, &k);
}
}
fn updateaboundary(t: *LuaTable, boundary: u32) u32 {
if (boundary < t.sizearray and t.array.?[boundary - 1].ttisnil()) {
if (boundary >= 2 and !t.array.?[boundary - 2].ttisnil()) {
maybesetaboundary(t, @intCast(boundary - 1));
return boundary - 1;
}
} else if (boundary + 1 < t.sizearray and !t.array.?[boundary].ttisnil() and t.array.?[boundary + 1].ttisnil()) {
maybesetaboundary(t, @intCast(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).
pub fn Hgetn(t: *LuaTable) usize {
const boundary = getaboundary(t);
const array_size: usize = @intCast(t.sizearray);
if (boundary > 0) {
if (!t.array.?[array_size - 1].ttisnil() and @as(*lobject.LuaNode, @ptrCast(t.node)) == dummynode)
return @intCast(array_size); // fast-path: the end of the array in `t' already refers to a boundary
if (boundary < array_size and !t.array.?[@as(u32, @intCast(boundary)) - 1].ttisnil() and t.array.?[@intCast(boundary)].ttisnil())
return @intCast(boundary); // fast-path: boundary already refers to a boundary in `t'
const foundboundary = updateaboundary(t, @intCast(boundary));
if (foundboundary > 0)
return @intCast(foundboundary);
}
if (array_size > 0 and t.array.?[array_size - 1].ttisnil()) {
// "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.
var base = t.array.?;
var rest = array_size;
var half = rest >> 1;
while (half > 0) : (half = rest >> 1) {
base = if (base[half].ttisnil()) base else base[half..];
rest -= half;
}
const _boundary = @as(usize, if (!base[0].ttisnil()) 1 else 0) + (base - t.array.?);
maybesetaboundary(t, @intCast(_boundary));
return _boundary;
} else {
// validate boundary invariant
std.debug.assert(@as(*lobject.LuaNode, @ptrCast(t.node)) == dummynode or Hgetnum(t, @intCast(array_size + 1)).ttisnil());
return array_size;
}
}
pub fn Hclone(L: *lua.State, tt: *LuaTable) Error!*LuaTable {
const t = try lmem.Mnewgco(L, LuaTable, @sizeOf(LuaTable), L.activememcat);
lgc.Cinit(L, @ptrCast(@alignCast(t)), @intFromEnum(lua.Type.Table));
t.metatable = tt.metatable;
t.tmcache = tt.tmcache;
t.array = null;
t.sizearray = 0;
t.lsizenode = 0;
t.nodemask8 = 0;
t.readonly = 0;
t.safeenv = 0;
t.node = @ptrCast(@constCast(dummynode));
t.bound.lastfree = 0;
if (tt.sizearray > 0) {
t.array = try lmem.Mnewarray(L, TValue, @intCast(tt.sizearray), tt.header.memcat);
maybesetaboundary(t, getaboundary(tt));
t.sizearray = tt.sizearray;
@memcpy(t.array.?[0..@intCast(tt.sizearray)], tt.array.?[0..@intCast(tt.sizearray)]);
}
if (@as(*LuaNode, @ptrCast(tt.node)) != dummynode) {
const size = @as(usize, 1) << @as(if (@sizeOf(usize) == 8) u6 else u5, @truncate(tt.lsizenode));
t.node = try lmem.Mnewarray(L, LuaNode, size, tt.header.memcat);
t.lsizenode = tt.lsizenode;
t.nodemask8 = tt.nodemask8;
@memcpy(t.node[0..@intCast(size)], tt.node[0..@intCast(size)]);
t.bound.lastfree = tt.bound.lastfree;
}
return t;
}
pub fn Hclear(tt: *LuaTable) void {
// clear array part
for (0..@intCast(tt.sizearray)) |i|
tt.array.?[i].setnilvalue();
maybesetaboundary(tt, 0);
// clear hash part
if (@as(*LuaNode, @ptrCast(tt.node)) != dummynode) {
const size = lobject.sizenode(tt);
tt.bound.lastfree = @intCast(size);
for (0..@intCast(size)) |i| {
const n = tt.gnode(i);
n[0].gkey().setttype(.Nil);
n[0].gval().setnilvalue();
n[0].key.pi.next = 0;
}
}
// back to empty -> no tag methods present
tt.tmcache = ~@as(u8, 0);
}
+7
View File
@@ -0,0 +1,7 @@
const c = @import("c");
const lua = @import("lua.zig");
pub inline fn open(L: *lua.State) void {
_ = c.luaopen_table(@ptrCast(L));
}
+132
View File
@@ -0,0 +1,132 @@
const std = @import("std");
const lua = @import("lua.zig");
const lobject = @import("lobject.zig");
const lstate = @import("lstate.zig");
const ltable = @import("ltable.zig");
const lstring = @import("lstring.zig");
const Errorset = @import("errorset.zig");
pub const TMS = enum {
TM_INDEX,
TM_NEWINDEX,
TM_MODE,
TM_NAMECALL,
TM_CALL,
TM_ITER,
TM_LEN,
TM_EQ, // last tag method with `fast' access
TM_ADD,
TM_SUB,
TM_MUL,
TM_DIV,
TM_IDIV,
TM_MOD,
TM_POW,
TM_UNM,
TM_LT,
TM_LE,
TM_CONCAT,
TM_TYPE,
TM_METATABLE,
TM_N, // number of elements in the enum
};
pub const N: comptime_int = @intFromEnum(TMS.TM_N);
pub const typenames = [_][:0]const u8{
// ORDER TYPE
"nil",
"boolean",
"userdata",
"number",
"integer",
"vector",
"string",
"table",
"function",
"userdata",
"thread",
"buffer",
"class",
"object",
};
pub const eventname = [_][:0]const u8{
// ORDER TM
"__index",
"__newindex",
"__mode",
"__namecall",
"__call",
"__iter",
"__len",
"__eq",
"__add",
"__sub",
"__mul",
"__div",
"__idiv",
"__mod",
"__pow",
"__unm",
"__lt",
"__le",
"__concat",
"__type",
"__metatable",
};
comptime {
if (typenames.len != lua.Type.T_COUNT)
@compileError("typenames size mismatch");
if (eventname.len != N)
@compileError("eventname size mismatch");
if (@intFromEnum(TMS.TM_EQ) >= 8)
@compileError("fasttm optimization stores a bitfield with metamethods in a byte");
}
pub fn Tinit(L: *lua.State) Errorset.Memory!void {
for (0..@intCast(lua.Type.T_COUNT)) |i| {
L.global.ttname[i] = try lstring.Snew(L, typenames[i]);
lstring.Sfix(L.global.ttname[i]); // never collect these names
}
for (0..N) |i| {
L.global.tmname[i] = try lstring.Snew(L, eventname[i]);
lstring.Sfix(L.global.tmname[i]); // never collect these names
}
}
pub const LONGEST_TYPENAME_SIZE = res: {
var large = 0;
for (typenames) |name|
large = @max(large, name.len);
break :res large;
};
pub fn gfasttm(g: *lstate.global_State, et: ?*lobject.LuaTable, event: TMS) ?*const lobject.TValue {
const mt = et orelse return null;
if (mt.tmcache & (@as(usize, 1) << @intFromEnum(event)) > 0)
return null;
return Tgettm(mt, event, g.tmname[@intFromEnum(event)]);
}
pub fn Tgettm(events: *lobject.LuaTable, e: TMS, ename: *lobject.TString) ?*const lobject.TValue {
const tm = ltable.Hgetstr(events, ename);
if (tm.ttisnil()) {
// no tag method?
events.tmcache |= @truncate(@as(usize, 1) << @intFromEnum(e)); // cache this fact
return null;
}
return tm;
}
+268
View File
@@ -0,0 +1,268 @@
const std = @import("std");
pub const c = @import("c");
const lstate = @import("lstate.zig");
pub const config = @import("luaconf.zig");
pub const MULTRET = c.LUA_MULTRET;
// pseudo-indices
pub const REGISTRYINDEX = c.LUA_REGISTRYINDEX;
pub const GLOBALSINDEX = c.LUA_GLOBALSINDEX;
pub const ENVIRONINDEX = c.LUA_ENVIRONINDEX;
pub fn upvalueindex(i: i32) i32 {
return GLOBALSINDEX - i;
}
pub fn ispseudo(i: i32) bool {
return i <= REGISTRYINDEX;
}
// thread status; 0 is OK
pub const Status = enum(u3) {
Ok = 0,
Yield,
ErrRun,
/// legacy error code, preserved for compatibility
ErrSyntax,
ErrMem,
ErrErr,
/// yielded for a debug breakpoint
Break,
pub fn check(s: Status) !Status {
switch (s) {
.ErrErr, .ErrRun => return error.Runtime,
.ErrMem => return error.OutOfMemory,
.ErrSyntax => return error.BadSyntax,
else => return s,
}
}
};
pub const CoStatus = enum(u3) {
/// running
Running = 0,
/// suspended
Suspended,
/// 'normal' (it resumed another coroutine)
Normal,
/// finished
Finished,
/// finished with error
FinishedErr,
};
pub const State = lstate.lua_State;
pub const CFunction = *const fn (L: *State) callconv(.c) c_int;
pub const Continuation = *const fn (L: *State, status: c_int) callconv(.c) c_int;
pub const Destructor = *const fn (L: *State, ?*anyopaque) callconv(.c) void;
pub const Coverage = *const fn (?*anyopaque, [*c]const u8, c_int, c_int, [*c]const c_int, usize) callconv(.c) void;
///
/// prototype for memory-allocation functions
///
pub const Alloc = *const fn (ud: ?*anyopaque, ptr: ?*anyopaque, osize: usize, nsize: usize) callconv(.c) ?*anyopaque;
///
/// basic types
///
pub const TNONE = c.LUA_TNONE;
/// Must be a signed integer because LuaType.none is -1
pub const Type = enum(i6) {
None = TNONE,
Nil = c.LUA_TNIL, // must be 0 due to lua_isnoneornil
Boolean = c.LUA_TBOOLEAN, // must be 1 due to l_isfalse
LightUserdata = c.LUA_TLIGHTUSERDATA,
Number = c.LUA_TNUMBER,
Integer = c.LUA_TINTEGER,
Vector = c.LUA_TVECTOR,
String = c.LUA_TSTRING, // all types above this must be value types, all types below this must be GC types - see iscollectable
Table = c.LUA_TTABLE,
Function = c.LUA_TFUNCTION,
Userdata = c.LUA_TUSERDATA,
Thread = c.LUA_TTHREAD,
Buffer = c.LUA_TBUFFER,
Class = c.LUA_TCLASS,
Object = c.LUA_TOBJECT,
// values below this line are used in GCObject tags but may never show up in TValue type tags
/// LUA_TDEADKEY is used in TKey to identify Luau table entries that have the value set to nil,
/// so that we can remove the strong reference to the key.
Deadkey = c.LUA_TDEADKEY,
// These values should never show up in TValue tag types.
Proto = c.LUA_TPROTO,
UpVal = c.LUA_TUPVAL,
// the count of TValue type tags
pub const T_COUNT = c.LUA_T_COUNT;
pub inline fn isnoneornil(t: Type) bool {
return t == .None or t == .Nil;
}
pub inline fn istypecollectable(comptime t: Type) bool {
return @intFromEnum(t) >= @intFromEnum(Type.String);
}
};
// type of numbers in Luau
pub const Number = c.lua_Number;
// type for integer functions
pub const Integer = c.lua_Integer;
// unsigned integer type
pub const Unsigned = c.lua_Unsigned;
///
/// garbage-collection function and options
///
pub const GCOp = enum(u4) {
// stop and resume incremental garbage collection
Stop = c.LUA_GCSTOP,
Restart = c.LUA_GCRESTART,
// run a full GC cycle; not recommended for latency sensitive applications
Collect = c.LUA_GCCOLLECT,
// return the heap size in KB and the remainder in bytes
Count = c.LUA_GCCOUNT,
CountB = c.LUA_GCCOUNTB,
// return 1 if GC is active (not stopped); note that GC may not be actively collecting even if it's running
IsRunning = c.LUA_GCISRUNNING,
///
/// perform an explicit GC step, with the step size specified in KB
///
/// garbage collection is handled by 'assists' that perform some amount of GC work matching pace of allocation
/// explicit GC steps allow to perform some amount of work at custom points to offset the need for GC assists
/// note that GC might also be paused for some duration (until bytes allocated meet the threshold)
/// if an explicit step is performed during this pause, it will trigger the start of the next collection cycle
///
Step = c.LUA_GCSTEP,
///
/// tune GC parameters G (goal), S (step multiplier) and step size (usually best left ignored)
///
/// garbage collection is incremental and tries to maintain the heap size to balance memory and performance overhead
/// this overhead is determined by G (goal) which is the ratio between total heap size and the amount of live data in it
/// G is specified in percentages; by default G=200% which means that the heap is allowed to grow to ~2x the size of live data.
///
/// collector tries to collect S% of allocated bytes by interrupting the application after step size bytes were allocated.
/// when S is too small, collector may not be able to catch up and the effective goal that can be reached will be larger.
/// S is specified in percentages; by default S=200% which means that collector will run at ~2x the pace of allocations.
///
/// it is recommended to set S in the interval [100 / (G - 100), 100 + 100 / (G - 100))] with a minimum value of 150%; for example:
/// - for G=200%, S should be in the interval [150%, 200%]
/// - for G=150%, S should be in the interval [200%, 300%]
/// - for G=125%, S should be in the interval [400%, 500%]
///
SetGoal = c.LUA_GCSETGOAL,
SetStepMul = c.LUA_GCSETSTEPMUL,
SetStepSize = c.LUA_GCSETSTEPSIZE,
};
///
/// reference system, can be used to pin objects
///
pub const NOREF = c.LUA_NOREF;
pub const REFNIL = c.LUA_REFNIL;
pub const Hook = *const fn (?*State, [*c]c.lua_Debug) callconv(.c) void;
pub const Debug = struct {
what: Context = .lua,
name: ?[:0]const u8 = null,
source: ?[:0]const u8 = null,
short_src: ?[]u8 = null,
linedefined: ?u32 = null,
currentline: ?u32 = null,
nupvals: u8 = 0,
nparams: u8 = 0,
isvararg: u8 = 0,
ssbuf: [config.IDSIZE:0]u8,
pub const Context = enum {
lua,
c,
main,
tail,
};
pub fn fromLua(self: *Debug, ar: c.lua_Debug, options: []const u8) void {
if (std.mem.indexOf(u8, options, "n")) |_| {
if (ar.name != null)
self.name = std.mem.span(ar.name);
}
if (std.mem.indexOf(u8, options, "s")) |_| {
self.source = std.mem.span(ar.source);
const short_src: [:0]const u8 = std.mem.span(ar.short_src);
@memcpy(self.ssbuf[0..short_src.len], short_src[0.. :0]);
self.short_src = self.ssbuf[0..short_src.len];
if (ar.linedefined >= 0)
self.linedefined = @intCast(ar.linedefined);
self.what = blk: {
const what = std.mem.span(ar.what);
if (std.mem.eql(u8, "Lua", what)) break :blk .lua;
if (std.mem.eql(u8, "C", what)) break :blk .c;
if (std.mem.eql(u8, "main", what)) break :blk .main;
if (std.mem.eql(u8, "tail", what)) break :blk .tail;
unreachable;
};
}
if (std.mem.indexOf(u8, options, "l")) |_| {
if (ar.currentline >= 0)
self.currentline = @intCast(ar.currentline);
}
if (std.mem.indexOf(u8, options, "u")) |_|
self.nupvals = ar.nupvals;
if (std.mem.indexOf(u8, options, "a")) |_| {
self.nparams = ar.nparams;
self.isvararg = ar.isvararg;
}
}
};
/// 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
pub const Callbacks = extern struct {
/// arbitrary userdata pointer that is never overwritten by Luau
userdata: ?*anyopaque = null,
/// gets called at safepoints (loop back edges, call/ret, gc) if set
interrupt: ?*const fn (L: *State, gc: c_int) callconv(.c) void = null,
/// gets called when an unprotected error is raised (if longjmp is used)
panic: ?*const fn (L: *State, errcode: c_int) callconv(.c) void = null,
/// gets called when L is created (LP == parent) or destroyed (LP == NULL)
userthread: ?*const fn (LP: ?*State, L: *State) callconv(.c) void = null,
/// gets called when a string is created; returned atom can be retrieved via tostringatom
useratom: ?*const fn (L: *State, s: [*c]const u8, l: usize) callconv(.c) i16 = null,
/// gets called when BREAK instruction is encountered
debugbreak: ?*const fn (L: *State, ar: *c.lua_Debug) callconv(.c) void = null,
/// gets called after each instruction in single step mode
debugstep: ?*const fn (L: *State, ar: *c.lua_Debug) callconv(.c) void = null,
/// gets called when thread execution is interrupted by break in another thread
debuginterrupt: ?*const fn (L: *State, ar: *c.lua_Debug) callconv(.c) void = null,
/// gets called when protected call results in an error
debugprotectederror: ?*const fn (L: *State) callconv(.c) void = null,
/// gets called when memory is allocated
onallocate: ?*const fn (L: *State, osize: usize, nsize: usize) callconv(.c) void = null,
};
+57
View File
@@ -0,0 +1,57 @@
const c = @import("c");
const config = @import("config");
pub const LUAU_VERSION = config.luau_version;
/// Can be used to reconfigure internal error handling to use longjmp instead of C++ EH
pub const USE_LONGJMP = c.LUA_USE_LONGJMP;
/// LUA_IDSIZE gives the maximum size for the description of the source
pub const IDSIZE = c.LUA_IDSIZE;
/// LUA_MINSTACK is the guaranteed number of Lua stack slots available to a C function
pub const MINSTACK = c.LUA_MINSTACK;
/// LUAI_MAXCSTACK limits the number of Lua stack slots that a C function can use
pub const I_MAXCSTACK = c.LUAI_MAXCSTACK;
/// LUAI_MAXCALLS limits the number of nested calls
pub const I_MAXCALLS = c.LUAI_MAXCALLS;
/// LUAI_MAXCCALLS is the maximum depth for nested C calls; this limit depends on native stack size
pub const I_MAXCCALLS = c.LUAI_MAXCCALLS;
/// buffer size used for on-stack string operations; this limit depends on native stack size
pub const BUFFERSIZE = c.LUA_BUFFERSIZE;
/// number of valid Lua userdata tags
pub const UTAG_LIMIT = c.LUA_UTAG_LIMIT;
/// number of valid Lua lightuserdata tags
pub const LUTAG_LIMIT = c.LUA_LUTAG_LIMIT;
/// upper bound for number of size classes used by page allocator
pub const SIZECLASSES = c.LUA_SIZECLASSES;
/// available number of separate memory categories
pub const MEMORY_CATEGORIES = c.LUA_MEMORY_CATEGORIES;
/// extra storage for execution callbacks in global state
pub const EXECUTION_CALLBACK_STORAGE = c.LUA_EXECUTION_CALLBACK_STORAGE;
/// minimum size for the string table (must be power of 2)
pub const MINSTRTABSIZE = c.LUA_MINSTRTABSIZE;
/// maximum number of captures supported by pattern matching
pub const MAXCAPTURES = c.LUA_MAXCAPTURES;
pub const I_USER_ALIGNMENT_T = extern union {
u: f64,
s: *anyopaque,
l: c_long,
};
/// The length of Luau vector values, either 3 or 4.
pub const VECTOR_SIZE = if (config.use_4_vector) 4 else 3;
pub const EXTRA_SIZE = (VECTOR_SIZE - 2);
+57
View File
@@ -0,0 +1,57 @@
const std = @import("std");
const builtin = @import("builtin");
const lua = @import("lua.zig");
const lobject = @import("lobject.zig");
const lgc = @import("lgc.zig");
const lmem = @import("lmem.zig");
const Errorset = @import("errorset.zig");
/// special tag value is used for user data with inline dtors
pub const UTAG_IDTOR = lua.config.UTAG_LIMIT;
/// special tag value is used for newproxy-created user data (all other user data objects are host-exposed)
pub const UTAG_PROXY = (lua.config.UTAG_LIMIT + 1);
/// must be updated if more internal tags are added
pub const UTAG_INTERNAL_LIMIT = UTAG_PROXY + 1;
pub inline fn sizeudata(len: usize) usize {
return @offsetOf(lobject.Udata, "data") + (if (len > 16) ((len + 15) & ~@as(usize, 15)) else len);
}
pub fn Unewudata(L: *lua.State, s: usize, tag: u8) Errorset.Memory!*lobject.Udata {
if (s > std.math.maxInt(i32) - @sizeOf(lobject.Udata))
return error.BlockTooBig;
const u = try lmem.Mnewgco(L, lobject.Udata, sizeudata(s), L.activememcat);
lgc.Cinit(L, @ptrCast(@alignCast(u)), @intFromEnum(lua.Type.Userdata));
u.metatable = null;
u.len = @intCast(s);
u.tag = tag;
return u;
}
pub fn Ufreeudata(L: *lua.State, u: *lobject.Udata, page: *lmem.lua_Page) void {
if (u.tag < lua.config.UTAG_LIMIT) {
// TODO: access to L here is highly unsafe since this is called during internal GC traversal
// certain operations such as lua_getthreaddata are okay, but by and large this risks crashes on improper use
if (L.global.udatagc[u.tag]) |dtor|
dtor(L, @ptrCast(@alignCast(&u.data)));
} else if (u.tag == UTAG_IDTOR) {
const InlineDtor = *const fn (data: ?*anyopaque) callconv(.c) void;
var dtor: ?InlineDtor = null;
dtor = @ptrFromInt(std.mem.readVarInt(
usize,
(@as([*]u8, @ptrCast(&u.data)) + @as(u32, @intCast(u.len)) - @sizeOf(InlineDtor))[0..@sizeOf(InlineDtor)],
builtin.cpu.arch.endian(),
));
if (dtor) |d|
d(@ptrCast(@alignCast(&u.data)));
}
lmem.Mfreegco(L, u.obj2gco(), sizeudata(@intCast(u.len)), u.header.memcat, page);
}
+7
View File
@@ -0,0 +1,7 @@
const c = @import("c");
const lua = @import("lua.zig");
pub inline fn open(L: *lua.State) void {
_ = c.luaopen_utf8(@ptrCast(L));
}
+7
View File
@@ -0,0 +1,7 @@
const c = @import("c");
const lua = @import("lua.zig");
pub inline fn open(L: *lua.State) void {
_ = c.luaopen_vector(@ptrCast(L));
}
+7
View File
@@ -0,0 +1,7 @@
const lua = @import("lua.zig");
const lobject = @import("lobject.zig");
const lvmutils = @import("lvmutils.zig");
pub inline fn equalobj(L: *lua.State, o1: *const lobject.TValue, o2: *const lobject.TValue) bool {
return o1.ttype() == o2.ttype() and lvmutils.Vequalval(L, o1, o2);
}
+9
View File
@@ -0,0 +1,9 @@
const c = @import("c");
const std = @import("std");
const lua = @import("lua.zig");
pub inline fn load(L: *lua.State, chunkname: [:0]const u8, bytecode: []const u8, env: i32) !void {
if (c.luau_load(@ptrCast(L), chunkname.ptr, bytecode.ptr, bytecode.len, env) != 0)
return error.Fail;
}
+128
View File
@@ -0,0 +1,128 @@
const std = @import("std");
const lua = @import("lua.zig");
const ltm = @import("ltm.zig");
const ldebug = @import("ldebug.zig");
const lobject = @import("lobject.zig");
const lstring = @import("lstring.zig");
const lnumutils = @import("lnumutils.zig");
pub fn Vtonumber(obj: *const lobject.TValue, n: *lobject.TValue) ?*const lobject.TValue {
if (obj.ttisnumber())
return obj;
if (obj.ttisstring()) {
const num = std.fmt.parseFloat(f64, std.mem.span(obj.svalue())) catch return null;
n.setnvalue(num);
return n;
}
return null;
}
pub fn Vtostring(L: *lua.State, obj: *lobject.TValue) bool {
if (!obj.ttisnumber())
return false
else {
var s: [lnumutils.I_MAXNUM2STR]u8 = undefined;
const n = obj.nvalue();
const e = lnumutils.inum2str(&s, n);
obj.setsvalue(L, lstring.Snewlstr(L, e) catch return false);
return true;
}
}
// pub fn Vlessthan(L: *lua.State, l: *const lobject.TValue, r: *const lobject.TValue) bool {
// if (l.ttype() != r.ttype()) {
// ldebug.Gordererror();
// }
// }
fn get_compTM(L: *lua.State, mt1: *lobject.LuaTable, mt2: *lobject.LuaTable, event: ltm.TMS) ?*const lobject.TValue {
const tm1 = ltm.gfasttm(L.global, mt1, event) orelse return null;
if (mt1 == mt2)
return tm1;
const tm2 = ltm.gfasttm(L.global, mt2, event) orelse return null;
if (lobject.OrawequalObj(tm1, tm2))
return tm1;
return null;
}
// pub fn Vequalval(L: *lua.State, t1: *const lobject.TValue, t2: *const lobject.TValue) bool {
// std.debug.assert(t1.ttype() == t2.ttype());
// switch (t1.ttype()) {
// .Nil => return true,
// .Number => return t1.nvalue() == t2.nvalue(),
// .Vector => lnumutils.iveceq(t1.vvalue(), t2.vvalue()),
// .Boolean => return t1.bvalue() == t2.bvalue(),
// .LightUserdata => return t1.pvalue() == t2.pvalue() and t1.lightuserdatatag() == t2.lightuserdatatag(),
// .Userdata => {
// const tm = get_compTM(L, t1.u.table, t2.u.table, ltm.TMS.TM_EQ) orelse return t1.hvalue() == t2.hvalue();
// callTMres(L, L.top, tm, t1, t2);
// return !(L.top.ttisnil() or L.top.ttisboolean() and !L.top.bvalue());
// },
// }
// }
test Vtonumber {
const allocator = std.testing.allocator;
{
var n = lobject.TValue{ .tt = @intFromEnum(lua.Type.None), .value = undefined };
const obj = lobject.TValue{ .tt = @intFromEnum(lua.Type.Number), .value = .{ .n = 1.0 } };
try std.testing.expect(Vtonumber(&obj, &n) == &obj);
}
{
var n = lobject.TValue{ .tt = @intFromEnum(lua.Type.None), .value = undefined };
const obj = lobject.TValue{ .tt = @intFromEnum(lua.Type.Boolean), .value = .{ .b = 1 } };
try std.testing.expect(Vtonumber(&obj, &n) == null);
}
{
const GCObject = @import("lstate.zig").GCObject;
const gc_buf = try allocator.alloc(u8, @sizeOf(GCObject) + 4);
defer allocator.free(gc_buf);
const gc: *GCObject = @ptrCast(@alignCast(gc_buf[0..@sizeOf(GCObject)]));
gc.* = .{
.gch = .{
.header = .{
.tt = @intFromEnum(lua.Type.String),
.marked = 0,
.memcat = 0,
},
},
};
const data = gc_buf[@offsetOf(lobject.TString, "data")..];
data[0] = '1';
data[1] = '2';
data[2] = '3';
data[3] = 0;
var n = lobject.TValue{ .tt = @intFromEnum(lua.Type.None), .value = undefined };
const obj = lobject.TValue{ .tt = @intFromEnum(lua.Type.String), .value = .{ .gc = gc } };
try std.testing.expect(Vtonumber(&obj, &n) == &n);
try std.testing.expect(n.ttisnumber());
try std.testing.expect(n.nvalue() == 123.0);
}
{
const GCObject = @import("lstate.zig").GCObject;
const gc_buf = try allocator.alloc(u8, @sizeOf(GCObject) + 2);
defer allocator.free(gc_buf);
const gc: *GCObject = @ptrCast(@alignCast(gc_buf[0..@sizeOf(GCObject)]));
gc.* = .{
.gch = .{
.header = .{
.tt = @intFromEnum(lua.Type.String),
.marked = 0,
.memcat = 0,
},
},
};
const data = gc_buf[@offsetOf(lobject.TString, "data")..];
data[0] = 'b';
data[1] = 0;
var n = lobject.TValue{ .tt = @intFromEnum(lua.Type.None), .value = undefined };
const obj = lobject.TValue{ .tt = @intFromEnum(lua.Type.String), .value = .{ .gc = gc } };
try std.testing.expect(Vtonumber(&obj, &n) == null);
}
}
+1591
View File
File diff suppressed because it is too large Load Diff
+125
View File
@@ -0,0 +1,125 @@
#include <bridge.h>
#include "Luau/Common.h"
#include "ldo.h"
#include "lclass.h"
#include <cstdio>
#include <cstdlib>
#include <string>
#include <vector>
LUAU_FASTFLAG(DebugLuauUserDefinedClasses);
LUAU_FASTFLAG(DebugLuauUserDefinedClassesRuntime);
LUAU_FASTFLAG(LuauAllowGlobalDeclarationToBeCalledClass);
LUAU_FASTFLAG(LuauIntegerType2);
ZIG_EXPORT void xsh_set_luau_flags()
{
std::printf("SETTING LUAU FLAGS\n");
FFlag::DebugLuauUserDefinedClasses.value = true;
FFlag::DebugLuauUserDefinedClassesRuntime.value = true;
FFlag::LuauAllowGlobalDeclarationToBeCalledClass.value = true;
FFlag::LuauIntegerType2.value = true;
}
static int assertionHandler(const char *expr, const char *file, int line, const char *function)
{
printf("%s(%d): ASSERTION FAILED: %s\n", file, line, expr);
return 1;
}
ZIG_EXPORT void zig_registerAssertionHandler()
{
Luau::assertHandler() = assertionHandler;
}
ZIG_EXPORT void ZIG_FN(luau_free)(void *ptr)
{
free(ptr);
}
ZIG_EXPORT void ZIG_FN(delete_any)(void* value)
{
operator delete(value);
}
ZIG_EXPORT void* ZIG_FN(new_any)(size_t size)
{
return operator new(size);
}
ZIG_EXPORT size_t ZIG_FN(string_size)(std::string *str)
{
return str->size();
}
ZIG_EXPORT const char* ZIG_FN(string_c_str)(std::string *str)
{
return str->c_str();
}
ZIG_EXPORT Luau::FValue<bool>* zig_luau_getFValueList_bool()
{
return Luau::FValue<bool>::list;
}
ZIG_EXPORT Luau::FValue<int>* zig_luau_getFValueList_int()
{
return Luau::FValue<int>::list;
}
ZIG_EXPORT l_noret zig_luau_luaD_throw(lua_State *L, int errcode)
{
luaD_throw(L, errcode);
}
#if defined(__wasm__)
#include <functional>
#define LUAU_TRY_CATCH(trying, catching) zig_luau_try_catch_js(trying, catching)
#define LUAU_THROW(e) zig_luau_throw_js(e)
#define LUAU_EXTERNAL_TRY_CATCH
#if not defined(LUAU_WASM_ENV_NAME)
#define LUAU_WASM_ENV_NAME "env"
#endif
struct TryCatchContext
{
std::function<void()> trying;
std::function<void(const std::exception &)> catching;
};
// only clang compilers support C/C++ -> wasm so it's safe to use the attribute here
__attribute__((import_module(LUAU_WASM_ENV_NAME), import_name("try_catch"))) void zig_luau_try_catch_js_impl(TryCatchContext *context);
__attribute__((import_module(LUAU_WASM_ENV_NAME), import_name("throw"))) void zig_luau_throw_js_impl(const std::exception *e);
void zig_luau_try_catch_js(std::function<void()> trying, std::function<void(const std::exception &)> catching)
{
auto context = TryCatchContext{trying, catching};
zig_luau_try_catch_js_impl(&context);
}
void zig_luau_throw_js(const std::exception &e)
{
zig_luau_throw_js_impl(&e);
}
ZIG_EXPORT void zig_luau_try_impl(TryCatchContext *context)
{
context->trying();
}
ZIG_EXPORT void zig_luau_catch_impl(TryCatchContext *context, const std::exception &e)
{
context->catching(e);
}
#endif
ZIG_EXPORT int ZIG_FN(luaR_createobject)(lua_State *L)
{
return luaR_createobject(L);
}
+15
View File
@@ -0,0 +1,15 @@
#ifndef LUAU_HEADERS
#define LUAU_HEADERS
#include "lua.h"
#include "lualib.h"
#include "luacode.h"
#if (defined(__x86_64__) || defined(__amd64__) || defined(__aarch64__) || defined(__arm64__) || defined(__ARM64__)) && !defined(__BIG_ENDIAN__)
#include "luacodegen.h"
#endif
#define ZIG_EXPORT extern "C"
#define ZIG_FN(name) zig_##name
#endif // LUAU_HEADERS
+173
View File
@@ -0,0 +1,173 @@
const std = @import("std");
const builtin = @import("builtin");
extern "c" fn zig_string_size(self: *const String) usize;
extern "c" fn zig_string_c_str(self: *const String) [*c]const u8;
pub fn BasicString(comptime value_type: type) type {
return extern struct {
const __min_cap = if ((@sizeOf(__long) - 1) / @sizeOf(value_type) > 2)
(@sizeOf(__long) - 1) / @sizeOf(value_type)
else
2;
const __long = extern struct {
capacity: usize,
size: usize,
ptr: [*c]const value_type,
};
const __short = extern struct {
len: u8,
buffer: [__min_cap - 1:0]value_type,
};
data: extern union {
short: __short,
long: __long,
},
pub inline fn isShort(self: *const String) bool {
return self.data.short.len & 1 == 0;
}
pub fn size(self: *const String) usize {
// if (self.isShort())
// return self.data.short.len >> 1;
// return self.data.long.size;
return zig_string_size(self);
}
pub fn c_str(self: *const String) [*c]const u8 {
// if (self.isShort())
// return self.data.short.buffer;
// return self.data.long.ptr;
return zig_string_c_str(self);
}
pub fn slice(self: *const String) []const u8 {
// if (self.isShort()) {
// const len = self.data.short.len >> 1;
// return self.data.short.buffer[0..len];
// }
// const len = self.data.long.size;
// return self.data.long.ptr[0..len];
const len = zig_string_size(self);
return zig_string_c_str(self)[0..len];
}
};
}
// LLVM: 24
// GCC/MSVC: 32
// data: [24]u8 align(8),
pub const String = BasicString(u8);
comptime {
switch (@sizeOf(usize)) {
4 => {
std.testing.expectEqual(12, @sizeOf(String)) catch @panic("String must be 12 bytes");
std.testing.expectEqual(4, @alignOf(String)) catch @panic("String must be 4-byte aligned");
},
8 => {
std.testing.expectEqual(24, @sizeOf(String)) catch @panic("String must be 24 bytes");
std.testing.expectEqual(8, @alignOf(String)) catch @panic("String must be 8-byte aligned");
},
else => @compileError("Unsupported pointer size"),
}
}
pub fn Exception(comptime T: type) type {
return extern struct {
vtable: *const anyopaque,
value: T,
};
}
pub fn Optional(comptime T: type) type {
return extern struct {
value: T = undefined,
has: bool,
pub fn to(self: @This()) ?T {
if (self.has) {
return self.value;
} else {
return null;
}
}
pub const nullopt = @This(){ .has = false };
};
}
pub fn Vector(comptime T: type) type {
return extern struct {
begin: [*]T,
end: [*]T,
capacity_end: [*]T,
const This = @This();
pub fn iterator(self: This) Iterator {
return .{
.current = self.begin,
.end = self.end,
};
}
pub fn size(self: This) usize {
// divExact would not work if the sizeOf(T) doesn't match the C++ std::vector<T>
return @divExact(@intFromPtr(self.end) - @intFromPtr(self.begin), @sizeOf(T));
}
pub fn empty(self: This) bool {
return self.size() == 0;
}
pub fn front(self: This) ?T {
if (self.begin == self.end)
return null
else
return self.begin[0];
}
pub fn back(self: This) ?T {
const i = self.size();
if (i == 0)
return null;
return self.begin[i - 1];
}
pub fn capacity(self: This) usize {
// divExact would not work if the sizeOf(T) doesn't match the C++ std::vector<T>
return @divExact(@intFromPtr(self.capacity_end) - @intFromPtr(self.begin), @sizeOf(T));
}
pub fn at(self: This, pos: usize) T {
std.debug.assert(!self.empty());
std.debug.assert(pos < self.size());
return self.begin[pos];
}
pub const Iterator = struct {
current: [*]T,
end: [*]T,
pub fn next(self: *Iterator) ?T {
if (self.current == self.end)
return null
else {
const value = self.current[0];
self.current = self.current[1..];
return value;
}
}
};
};
}
pub fn Pair(comptime First: type, comptime Second: type) type {
return extern struct {
first: First,
second: Second,
};
}
+346
View File
@@ -0,0 +1,346 @@
const std = @import("std");
const builtin = @import("builtin");
const build_config = @import("config");
pub const codegen = @import("CodeGen/lcodegen.zig");
pub const Analysis = if (build_config.buildAnalysis) struct {
pub const Frontend = @import("Analysis/Frontend.zig");
pub const FileResolver = @import("Analysis/FileResolver.zig");
pub const AstJsonEncoder = @import("Analysis/AstJsonEncoder.zig");
pub const GenericConfigResolver = @import("Analysis/GenericConfigResolver.zig");
test {
inline for (@typeInfo(@This()).@"struct".decls) |decl|
std.testing.refAllDecls(@field(@This(), decl.name));
}
} else void;
pub const Ast = if (build_config.buildAst) struct {
pub const Ast = @import("Ast/Ast.zig");
pub const Cst = @import("Ast/Cst.zig");
pub const Allocator = @import("Ast/Allocator.zig");
pub const Lexer = @import("Ast/Lexer.zig");
pub const Parser = @import("Ast/Parser.zig");
pub const Location = @import("Ast/Location.zig");
test {
inline for (@typeInfo(@This()).@"struct".decls) |decl|
std.testing.refAllDecls(@field(@This(), decl.name));
}
} else void;
pub const Inliner = if (build_config.buildInliner) struct {
pub const luajitinliner = @import("Inliner/luajitinliner.zig");
test {
inline for (@typeInfo(@This()).@"struct".decls) |decl|
std.testing.refAllDecls(@field(@This(), decl.name));
}
} else void;
pub const Common = struct {
pub const DenseHash = @import("Common/DenseHash.zig");
pub const Bytecode = @import("Common/Bytecode.zig");
pub const BytecodeUtils = @import("Common/BytecodeUtils.zig");
pub const ExperimentalFlags = @import("Common/ExperimentalFlags.zig");
pub const Variant = @import("Common/Variant.zig");
test {
inline for (@typeInfo(@This()).@"struct".decls) |decl|
std.testing.refAllDecls(@field(@This(), decl.name));
}
};
pub const Compiler = if (build_config.buildCompiler) struct {
pub const luacode = @import("Compiler/luacode.zig");
pub const Compiler = @import("Compiler/Compiler.zig");
test {
inline for (@typeInfo(@This()).@"struct".decls) |decl|
std.testing.refAllDecls(@field(@This(), decl.name));
}
} else void;
pub const VM = if (build_config.buildVM) struct {
pub const lua = @import("VM/lua.zig");
pub const ldo = @import("VM/ldo.zig");
pub const lgc = @import("VM/lgc.zig");
pub const ltm = @import("VM/ltm.zig");
pub const zapi = @import("VM/zapi.zig");
pub const lapi = @import("VM/lapi.zig");
pub const laux = @import("VM/laux.zig");
pub const lperf = @import("VM/lperf.zig");
pub const linit = @import("VM/linit.zig");
pub const lmem = @import("VM/lmem.zig");
pub const lstate = @import("VM/lstate.zig");
pub const lstring = @import("VM/lstring.zig");
pub const ltable = @import("VM/ltable.zig");
pub const ludata = @import("VM/ludata.zig");
pub const lbuffer = @import("VM/lbuffer.zig");
pub const lclass = @import("VM/lclass.zig");
pub const lfunc = @import("VM/lfunc.zig");
pub const ldebug = @import("VM/ldebug.zig");
pub const lobject = @import("VM/lobject.zig");
pub const lvmload = @import("VM/lvmload.zig");
pub const lcommon = @import("VM/lcommon.zig");
pub const lvm = @import("VM/lvm.zig");
pub const lvmutils = @import("VM/lvmutils.zig");
pub const lgcdebug = @import("VM/lgcdebug.zig");
// libraries
pub const lbitlib = @import("VM/lbitlib.zig");
pub const lbaselib = @import("VM/lbaselib.zig");
pub const lcorolib = @import("VM/lcorolib.zig");
pub const ldblib = @import("VM/ldblib.zig");
pub const lmathlib = @import("VM/lmathlib.zig");
pub const loslib = @import("VM/loslib.zig");
pub const lstrlib = @import("VM/lstrlib.zig");
pub const ltablib = @import("VM/ltablib.zig");
pub const lutf8lib = @import("VM/lutf8lib.zig");
pub const lveclib = @import("VM/lveclib.zig");
// extra
pub const Errorset = @import("VM/errorset.zig");
test {
inline for (@typeInfo(@This()).@"struct".decls) |decl|
std.testing.refAllDecls(@field(@This(), decl.name));
}
} else void;
pub const cpp_std = @import("cpp_std.zig");
test {
_ = Analysis;
_ = Ast;
_ = Common;
_ = Compiler;
_ = VM;
_ = Inliner;
_ = cpp_std;
}
//
// VM
//
pub const LUAU_VERSION = VM.lua.config.LUAU_VERSION;
pub const VECTOR_SIZE = VM.lua.config.VECTOR_SIZE;
pub const State = VM.lua.State;
//
// Compiler
//
pub const compile = Compiler.luacode.compile;
pub const CompileOptions = Compiler.Compiler.CompileOptions;
const c_FlagGroup = extern struct {
names: [*c][*c]const u8,
types: [*c]c_int,
size: usize,
};
fn FValue(comptime T: type) type {
return extern struct {
const Self = @This();
value: T,
dynamic: bool,
name: [*c]const u8,
next: ?*Self,
pub const Iterator = struct {
state: *Self,
consumed: bool = false,
pub fn next(self: *Iterator) ?*Self {
const state = self.state;
if (!self.consumed) {
self.consumed = true;
return state;
}
const n = state.next orelse return null;
self.state = n;
return n;
}
};
pub fn iterator(self: *Self) Iterator {
return .{
.state = self,
};
}
};
}
/// This function is defined in luau.cpp and must be called to define the assertion printer
extern "c" fn zig_registerAssertionHandler() void;
extern "c" fn zig_luau_getFValueList_bool() *FValue(bool);
extern "c" fn zig_luau_getFValueList_int() *FValue(c_int);
// NCG Workarounds - Minimal Debug Support for NCG
/// Luau.CodeGen mock __register_frame for a workaround Luau NCG
export fn __register_frame(frame: *const u8) void {
_ = frame;
}
/// Luau.CodeGen mock __deregister_frame for a workaround Luau NCG
export fn __deregister_frame(frame: *const u8) void {
_ = frame;
}
pub const FFlags = struct {
pub fn Get(comptime T: type) *FValue(if (T == i32) c_int else T) {
if (T == bool)
return zig_luau_getFValueList_bool()
else if (T == c_int or T == i32)
return zig_luau_getFValueList_int()
else
@compileError("Unsupported type");
}
pub fn SetByName(comptime T: type, name: []const u8, value: T) !void {
var iter = Get(T).iterator();
while (iter.next()) |flag| {
if (std.mem.eql(u8, std.mem.span(flag.name), name)) {
flag.value = value;
return;
}
}
return error.UnknownFlag;
}
pub fn GetByName(comptime T: type, name: []const u8) ?*FValue(if (T == i32) c_int else T) {
var iter = Get(T).iterator();
while (iter.next()) |flag| {
if (std.mem.eql(u8, std.mem.span(flag.name), name))
return flag;
}
return null;
}
};
pub const Metamethods = struct {
pub const index = "__index";
pub const newindex = "__newindex";
pub const call = "__call";
pub const concat = "__concat";
pub const unm = "__unm";
pub const add = "__add";
pub const sub = "__sub";
pub const mul = "__mul";
pub const div = "__div";
pub const idiv = "__idiv";
pub const mod = "__mod";
pub const pow = "__pow";
pub const tostring = "__tostring";
pub const metatable = "__metatable";
pub const eq = "__eq";
pub const lt = "__lt";
pub const le = "__le";
pub const mode = "__mode";
pub const len = "__len";
pub const iter = "__iter";
pub const typename = "__type";
pub const namecall = "__namecall";
};
pub const CodeGen = if (build_config.buildCodeGen) struct {
pub fn Supported() bool {
return codegen.supported();
}
pub fn Create(luau: *VM.lua.State) void {
codegen.create(luau);
}
pub fn Compile(luau: *VM.lua.State, idx: i32) void {
codegen.compile(luau, @intCast(idx));
}
} else struct {
pub fn Supported() bool {
return false;
}
pub fn Create(_: *VM.lua.State) void {
@panic("CodeGen is not supported on " ++ @tagName(builtin.target.cpu.arch));
}
pub fn Compile(_: *VM.lua.State, _: i32) void {
@panic("CodeGen is not supported on " ++ @tagName(builtin.target.cpu.arch));
}
};
const alignment = @alignOf(std.c.max_align_t);
/// Allows Luau to allocate memory using a Zig allocator passed in via data.
fn alloc(data: ?*anyopaque, ptr: ?*anyopaque, osize: usize, nsize: usize) callconv(.c) ?*align(alignment) anyopaque {
// just like malloc() returns a pointer "which is suitably aligned for any built-in type",
// the memory allocated by this function should also be aligned for any type that Lua may
// desire to allocate. use the largest alignment for the target
const allocator_ptr: *std.mem.Allocator = @ptrCast(@alignCast(data.?));
if (@as(?[*]align(alignment) u8, @ptrCast(@alignCast(ptr)))) |prev_ptr| {
const prev_slice = prev_ptr[0..osize];
// when nsize is zero the allocator must behave like free and return null
if (nsize == 0) {
allocator_ptr.free(prev_slice);
return null;
}
// when nsize is not zero the allocator must behave like realloc
const new_ptr = allocator_ptr.realloc(prev_slice, nsize) catch return null;
return new_ptr.ptr;
} else if (nsize == 0) {
return null;
} else {
// ptr is null, allocate a new block of memory
const new_ptr = allocator_ptr.alignedAlloc(u8, .fromByteUnits(alignment), nsize) catch return null;
return new_ptr.ptr;
}
}
pub fn getallocator(luau: *VM.lua.State) std.mem.Allocator {
var data: ?*std.mem.Allocator = undefined;
_ = luau.getallocf(@ptrCast(&data));
if (data) |allocator_ptr| {
// Although the Allocator is passed to Lua as a pointer, return a
// copy to make use more convenient.
return allocator_ptr.*;
}
@panic("Lua.allocator() invalid on Lua states created without a Zig allocator");
}
/// Initialize a Luau state with the given allocator
pub fn init(allocator_ptr: *const std.mem.Allocator) !*VM.lua.State {
zig_registerAssertionHandler();
return try VM.lstate.newstate(alloc, @constCast(allocator_ptr));
}
comptime {
if (builtin.target.cpu.arch.isWasm() and build_config.wasm_cxa_exceptions) {
_ = struct {
var exception_buf: [4096]u8 = undefined;
var exception_fba = std.heap.FixedBufferAllocator.init(exception_buf[0..]);
export fn __cxa_allocate_exception(size: usize) callconv(.c) [*]u8 {
const data = exception_fba.allocator().alloc(u8, size + 4) catch unreachable;
std.mem.writeInt(u32, data[0..4], size, .little);
return data[4..].ptr;
}
export fn __cxa_free_exception(data: [*]const u8) callconv(.c) void {
const size = std.mem.readInt(u32, (data - 4)[0..4], .little);
exception_fba.allocator().free(data[0 .. size + 4]);
}
// should NEVER be called as we override in the luau upstream dependency
export fn __cxa_throw(thrown_exception: *u8, cpp_type_info: *anyopaque, dest: *const fn () callconv(.c) void) callconv(.c) noreturn {
_ = thrown_exception;
_ = cpp_type_info;
_ = dest;
unreachable;
}
var threaded: ?std.Io.Threaded = null;
export fn clock() callconv(.c) i64 {
if (threaded == null) {
threaded = std.Io.Threaded.init(std.heap.c_allocator, .{});
}
return std.Io.Timestamp.now(threaded.?.io(), .cpu_process).toMilliseconds();
}
};
}
}
+1949
View File
File diff suppressed because it is too large Load Diff