v0.3.6+luau536

This commit is contained in:
Alex Orlenko 2022-07-17 22:35:35 +01:00
parent b7ad6f4b91
commit 1e930d30cb
No known key found for this signature in database
GPG Key ID: 4C150C250863B96D
18 changed files with 819 additions and 79 deletions

View File

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

View File

@ -24,6 +24,8 @@ bool lua_telemetry_parsed_named_non_function_type = false;
LUAU_FASTFLAGVARIABLE(LuauErrorParseIntegerIssues, false)
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuaReportParseIntegerIssues, false)
LUAU_FASTFLAGVARIABLE(LuauAlwaysCaptureHotComments, false)
bool lua_telemetry_parsed_out_of_range_bin_integer = false;
bool lua_telemetry_parsed_out_of_range_hex_integer = false;
bool lua_telemetry_parsed_double_prefix_hex_integer = false;
@ -2918,21 +2920,23 @@ AstTypeError* Parser::reportTypeAnnotationError(const Location& location, const
void Parser::nextLexeme()
{
if (options.captureComments)
if (options.captureComments || FFlag::LuauAlwaysCaptureHotComments)
{
Lexeme::Type type = lexer.next(/* skipComments= */ false, true).type;
while (type == Lexeme::BrokenComment || type == Lexeme::Comment || type == Lexeme::BlockComment)
{
const Lexeme& lexeme = lexer.current();
commentLocations.push_back(Comment{lexeme.type, lexeme.location});
if (options.captureComments)
commentLocations.push_back(Comment{lexeme.type, lexeme.location});
// Subtlety: Broken comments are weird because we record them as comments AND pass them to the parser as a lexeme.
// The parser will turn this into a proper syntax error.
if (lexeme.type == Lexeme::BrokenComment)
return;
// Comments starting with ! are called "hot comments" and contain directives for type checking / linting
// Comments starting with ! are called "hot comments" and contain directives for type checking / linting / compiling
if (lexeme.type == Lexeme::Comment && lexeme.length && lexeme.data[0] == '!')
{
const char* text = lexeme.data;

View File

@ -8,8 +8,8 @@
namespace Luau
{
class AstStatBlock;
class AstNameTable;
struct ParseResult;
class BytecodeBuilder;
class BytecodeEncoder;
@ -58,7 +58,7 @@ private:
};
// compiles bytecode into bytecode builder using either a pre-parsed AST or parsing it from source; throws on errors
void compileOrThrow(BytecodeBuilder& bytecode, AstStatBlock* root, const AstNameTable& names, const CompileOptions& options = {});
void compileOrThrow(BytecodeBuilder& bytecode, const ParseResult& parseResult, const AstNameTable& names, const CompileOptions& options = {});
void compileOrThrow(BytecodeBuilder& bytecode, const std::string& source, const CompileOptions& options = {}, const ParseOptions& parseOptions = {});
// compiles bytecode into a bytecode blob, that either contains the valid bytecode or an encoded error that luau_load can decode

View File

@ -0,0 +1,463 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "BuiltinFolding.h"
#include "Luau/Bytecode.h"
#include <math.h>
namespace Luau
{
namespace Compile
{
const double kRadDeg = 3.14159265358979323846 / 180.0;
static Constant cvar()
{
return Constant();
}
static Constant cbool(bool v)
{
Constant res = {Constant::Type_Boolean};
res.valueBoolean = v;
return res;
}
static Constant cnum(double v)
{
Constant res = {Constant::Type_Number};
res.valueNumber = v;
return res;
}
static Constant cstring(const char* v)
{
Constant res = {Constant::Type_String};
res.stringLength = unsigned(strlen(v));
res.valueString = v;
return res;
}
static Constant ctype(const Constant& c)
{
LUAU_ASSERT(c.type != Constant::Type_Unknown);
switch (c.type)
{
case Constant::Type_Nil:
return cstring("nil");
case Constant::Type_Boolean:
return cstring("boolean");
case Constant::Type_Number:
return cstring("number");
case Constant::Type_String:
return cstring("string");
default:
LUAU_ASSERT(!"Unsupported constant type");
return cvar();
}
}
static uint32_t bit32(double v)
{
// convert through signed 64-bit integer to match runtime behavior and gracefully truncate negative integers
return uint32_t(int64_t(v));
}
Constant foldBuiltin(int bfid, const Constant* args, size_t count)
{
switch (bfid)
{
case LBF_MATH_ABS:
if (count == 1 && args[0].type == Constant::Type_Number)
return cnum(fabs(args[0].valueNumber));
break;
case LBF_MATH_ACOS:
if (count == 1 && args[0].type == Constant::Type_Number)
return cnum(acos(args[0].valueNumber));
break;
case LBF_MATH_ASIN:
if (count == 1 && args[0].type == Constant::Type_Number)
return cnum(asin(args[0].valueNumber));
break;
case LBF_MATH_ATAN2:
if (count == 2 && args[0].type == Constant::Type_Number && args[1].type == Constant::Type_Number)
return cnum(atan2(args[0].valueNumber, args[1].valueNumber));
break;
case LBF_MATH_ATAN:
if (count == 1 && args[0].type == Constant::Type_Number)
return cnum(atan(args[0].valueNumber));
break;
case LBF_MATH_CEIL:
if (count == 1 && args[0].type == Constant::Type_Number)
return cnum(ceil(args[0].valueNumber));
break;
case LBF_MATH_COSH:
if (count == 1 && args[0].type == Constant::Type_Number)
return cnum(cosh(args[0].valueNumber));
break;
case LBF_MATH_COS:
if (count == 1 && args[0].type == Constant::Type_Number)
return cnum(cos(args[0].valueNumber));
break;
case LBF_MATH_DEG:
if (count == 1 && args[0].type == Constant::Type_Number)
return cnum(args[0].valueNumber / kRadDeg);
break;
case LBF_MATH_EXP:
if (count == 1 && args[0].type == Constant::Type_Number)
return cnum(exp(args[0].valueNumber));
break;
case LBF_MATH_FLOOR:
if (count == 1 && args[0].type == Constant::Type_Number)
return cnum(floor(args[0].valueNumber));
break;
case LBF_MATH_FMOD:
if (count == 2 && args[0].type == Constant::Type_Number && args[1].type == Constant::Type_Number)
return cnum(fmod(args[0].valueNumber, args[1].valueNumber));
break;
// Note: FREXP isn't folded since it returns multiple values
case LBF_MATH_LDEXP:
if (count == 2 && args[0].type == Constant::Type_Number && args[1].type == Constant::Type_Number)
return cnum(ldexp(args[0].valueNumber, int(args[1].valueNumber)));
break;
case LBF_MATH_LOG10:
if (count == 1 && args[0].type == Constant::Type_Number)
return cnum(log10(args[0].valueNumber));
break;
case LBF_MATH_LOG:
if (count == 1 && args[0].type == Constant::Type_Number)
return cnum(log(args[0].valueNumber));
else if (count == 2 && args[0].type == Constant::Type_Number && args[1].type == Constant::Type_Number)
{
if (args[1].valueNumber == 2.0)
return cnum(log2(args[0].valueNumber));
else if (args[1].valueNumber == 10.0)
return cnum(log10(args[0].valueNumber));
else
return cnum(log(args[0].valueNumber) / log(args[1].valueNumber));
}
break;
case LBF_MATH_MAX:
if (count >= 1 && args[0].type == Constant::Type_Number)
{
double r = args[0].valueNumber;
for (size_t i = 1; i < count; ++i)
{
if (args[i].type != Constant::Type_Number)
return cvar();
double a = args[i].valueNumber;
r = (a > r) ? a : r;
}
return cnum(r);
}
break;
case LBF_MATH_MIN:
if (count >= 1 && args[0].type == Constant::Type_Number)
{
double r = args[0].valueNumber;
for (size_t i = 1; i < count; ++i)
{
if (args[i].type != Constant::Type_Number)
return cvar();
double a = args[i].valueNumber;
r = (a < r) ? a : r;
}
return cnum(r);
}
break;
// Note: MODF isn't folded since it returns multiple values
case LBF_MATH_POW:
if (count == 2 && args[0].type == Constant::Type_Number && args[1].type == Constant::Type_Number)
return cnum(pow(args[0].valueNumber, args[1].valueNumber));
break;
case LBF_MATH_RAD:
if (count == 1 && args[0].type == Constant::Type_Number)
return cnum(args[0].valueNumber * kRadDeg);
break;
case LBF_MATH_SINH:
if (count == 1 && args[0].type == Constant::Type_Number)
return cnum(sinh(args[0].valueNumber));
break;
case LBF_MATH_SIN:
if (count == 1 && args[0].type == Constant::Type_Number)
return cnum(sin(args[0].valueNumber));
break;
case LBF_MATH_SQRT:
if (count == 1 && args[0].type == Constant::Type_Number)
return cnum(sqrt(args[0].valueNumber));
break;
case LBF_MATH_TANH:
if (count == 1 && args[0].type == Constant::Type_Number)
return cnum(tanh(args[0].valueNumber));
break;
case LBF_MATH_TAN:
if (count == 1 && args[0].type == Constant::Type_Number)
return cnum(tan(args[0].valueNumber));
break;
case LBF_BIT32_ARSHIFT:
if (count == 2 && args[0].type == Constant::Type_Number && args[1].type == Constant::Type_Number)
{
uint32_t u = bit32(args[0].valueNumber);
int s = int(args[1].valueNumber);
if (unsigned(s) < 32)
return cnum(double(uint32_t(int32_t(u) >> s)));
}
break;
case LBF_BIT32_BAND:
if (count >= 1 && args[0].type == Constant::Type_Number)
{
uint32_t r = bit32(args[0].valueNumber);
for (size_t i = 1; i < count; ++i)
{
if (args[i].type != Constant::Type_Number)
return cvar();
r &= bit32(args[i].valueNumber);
}
return cnum(double(r));
}
break;
case LBF_BIT32_BNOT:
if (count == 1 && args[0].type == Constant::Type_Number)
return cnum(double(uint32_t(~bit32(args[0].valueNumber))));
break;
case LBF_BIT32_BOR:
if (count >= 1 && args[0].type == Constant::Type_Number)
{
uint32_t r = bit32(args[0].valueNumber);
for (size_t i = 1; i < count; ++i)
{
if (args[i].type != Constant::Type_Number)
return cvar();
r |= bit32(args[i].valueNumber);
}
return cnum(double(r));
}
break;
case LBF_BIT32_BXOR:
if (count >= 1 && args[0].type == Constant::Type_Number)
{
uint32_t r = bit32(args[0].valueNumber);
for (size_t i = 1; i < count; ++i)
{
if (args[i].type != Constant::Type_Number)
return cvar();
r ^= bit32(args[i].valueNumber);
}
return cnum(double(r));
}
break;
case LBF_BIT32_BTEST:
if (count >= 1 && args[0].type == Constant::Type_Number)
{
uint32_t r = bit32(args[0].valueNumber);
for (size_t i = 1; i < count; ++i)
{
if (args[i].type != Constant::Type_Number)
return cvar();
r &= bit32(args[i].valueNumber);
}
return cbool(r != 0);
}
break;
case LBF_BIT32_EXTRACT:
if (count == 3 && args[0].type == Constant::Type_Number && args[1].type == Constant::Type_Number && args[2].type == Constant::Type_Number)
{
uint32_t u = bit32(args[0].valueNumber);
int f = int(args[1].valueNumber);
int w = int(args[2].valueNumber);
if (f >= 0 && w > 0 && f + w <= 32)
{
uint32_t m = ~(0xfffffffeu << (w - 1));
return cnum(double((u >> f) & m));
}
}
break;
case LBF_BIT32_LROTATE:
if (count == 2 && args[0].type == Constant::Type_Number && args[1].type == Constant::Type_Number)
{
uint32_t u = bit32(args[0].valueNumber);
int s = int(args[1].valueNumber);
return cnum(double((u << (s & 31)) | (u >> ((32 - s) & 31))));
}
break;
case LBF_BIT32_LSHIFT:
if (count == 2 && args[0].type == Constant::Type_Number && args[1].type == Constant::Type_Number)
{
uint32_t u = bit32(args[0].valueNumber);
int s = int(args[1].valueNumber);
if (unsigned(s) < 32)
return cnum(double(u << s));
}
break;
case LBF_BIT32_REPLACE:
if (count == 4 && args[0].type == Constant::Type_Number && args[1].type == Constant::Type_Number && args[2].type == Constant::Type_Number &&
args[3].type == Constant::Type_Number)
{
uint32_t n = bit32(args[0].valueNumber);
uint32_t v = bit32(args[1].valueNumber);
int f = int(args[2].valueNumber);
int w = int(args[3].valueNumber);
if (f >= 0 && w > 0 && f + w <= 32)
{
uint32_t m = ~(0xfffffffeu << (w - 1));
return cnum(double((n & ~(m << f)) | ((v & m) << f)));
}
}
break;
case LBF_BIT32_RROTATE:
if (count == 2 && args[0].type == Constant::Type_Number && args[1].type == Constant::Type_Number)
{
uint32_t u = bit32(args[0].valueNumber);
int s = int(args[1].valueNumber);
return cnum(double((u >> (s & 31)) | (u << ((32 - s) & 31))));
}
break;
case LBF_BIT32_RSHIFT:
if (count == 2 && args[0].type == Constant::Type_Number && args[1].type == Constant::Type_Number)
{
uint32_t u = bit32(args[0].valueNumber);
int s = int(args[1].valueNumber);
if (unsigned(s) < 32)
return cnum(double(u >> s));
}
break;
case LBF_TYPE:
if (count == 1 && args[0].type != Constant::Type_Unknown)
return ctype(args[0]);
break;
case LBF_STRING_BYTE:
if (count == 1 && args[0].type == Constant::Type_String)
{
if (args[0].stringLength > 0)
return cnum(double(uint8_t(args[0].valueString[0])));
}
else if (count == 2 && args[0].type == Constant::Type_String && args[1].type == Constant::Type_Number)
{
int i = int(args[1].valueNumber);
if (i > 0 && unsigned(i) <= args[0].stringLength)
return cnum(double(uint8_t(args[0].valueString[i - 1])));
}
break;
case LBF_STRING_LEN:
if (count == 1 && args[0].type == Constant::Type_String)
return cnum(double(args[0].stringLength));
break;
case LBF_TYPEOF:
if (count == 1 && args[0].type != Constant::Type_Unknown)
return ctype(args[0]);
break;
case LBF_MATH_CLAMP:
if (count == 3 && args[0].type == Constant::Type_Number && args[1].type == Constant::Type_Number && args[2].type == Constant::Type_Number)
{
double min = args[1].valueNumber;
double max = args[2].valueNumber;
if (min <= max)
{
double v = args[0].valueNumber;
v = v < min ? min : v;
v = v > max ? max : v;
return cnum(v);
}
}
break;
case LBF_MATH_SIGN:
if (count == 1 && args[0].type == Constant::Type_Number)
{
double v = args[0].valueNumber;
return cnum(v > 0.0 ? 1.0 : v < 0.0 ? -1.0 : 0.0);
}
break;
case LBF_MATH_ROUND:
if (count == 1 && args[0].type == Constant::Type_Number)
return cnum(round(args[0].valueNumber));
break;
}
return cvar();
}
} // namespace Compile
} // namespace Luau

View File

@ -0,0 +1,14 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "ConstantFolding.h"
namespace Luau
{
namespace Compile
{
Constant foldBuiltin(int bfid, const Constant* args, size_t count);
} // namespace Compile
} // namespace Luau

View File

@ -40,11 +40,8 @@ Builtin getBuiltin(AstExpr* node, const DenseHashMap<AstName, Global>& globals,
}
}
int getBuiltinFunctionId(const Builtin& builtin, const CompileOptions& options)
static int getBuiltinFunctionId(const Builtin& builtin, const CompileOptions& options)
{
if (builtin.empty())
return -1;
if (builtin.isGlobal("assert"))
return LBF_ASSERT;
@ -200,5 +197,49 @@ int getBuiltinFunctionId(const Builtin& builtin, const CompileOptions& options)
return -1;
}
struct BuiltinVisitor : AstVisitor
{
DenseHashMap<AstExprCall*, int>& result;
const DenseHashMap<AstName, Global>& globals;
const DenseHashMap<AstLocal*, Variable>& variables;
const CompileOptions& options;
BuiltinVisitor(DenseHashMap<AstExprCall*, int>& result, const DenseHashMap<AstName, Global>& globals,
const DenseHashMap<AstLocal*, Variable>& variables, const CompileOptions& options)
: result(result)
, globals(globals)
, variables(variables)
, options(options)
{
}
bool visit(AstExprCall* node) override
{
Builtin builtin = node->self ? Builtin() : getBuiltin(node->func, globals, variables);
if (builtin.empty())
return true;
int bfid = getBuiltinFunctionId(builtin, options);
// getBuiltinFunctionId optimistically assumes all select() calls are builtin but actually the second argument must be a vararg
if (bfid == LBF_SELECT_VARARG && !(node->args.size == 2 && node->args.data[1]->is<AstExprVarargs>()))
bfid = -1;
if (bfid >= 0)
result[node] = bfid;
return true; // propagate to nested calls
}
};
void analyzeBuiltins(DenseHashMap<AstExprCall*, int>& result, const DenseHashMap<AstName, Global>& globals,
const DenseHashMap<AstLocal*, Variable>& variables, const CompileOptions& options, AstNode* root)
{
BuiltinVisitor visitor{result, globals, variables, options};
root->visit(&visitor);
}
} // namespace Compile
} // namespace Luau

View File

@ -35,7 +35,9 @@ struct Builtin
};
Builtin getBuiltin(AstExpr* node, const DenseHashMap<AstName, Global>& globals, const DenseHashMap<AstLocal*, Variable>& variables);
int getBuiltinFunctionId(const Builtin& builtin, const CompileOptions& options);
void analyzeBuiltins(DenseHashMap<AstExprCall*, int>& result, const DenseHashMap<AstName, Global>& globals,
const DenseHashMap<AstLocal*, Variable>& variables, const CompileOptions& options, AstNode* root);
} // namespace Compile
} // namespace Luau

View File

@ -120,6 +120,17 @@ inline bool isSkipC(LuauOpcode op)
switch (op)
{
case LOP_LOADB:
return true;
default:
return false;
}
}
inline bool isFastCall(LuauOpcode op)
{
switch (op)
{
case LOP_FASTCALL:
case LOP_FASTCALL1:
case LOP_FASTCALL2:
@ -137,6 +148,8 @@ static int getJumpTarget(uint32_t insn, uint32_t pc)
if (isJumpD(op))
return int(pc + LUAU_INSN_D(insn) + 1);
else if (isFastCall(op))
return int(pc + LUAU_INSN_C(insn) + 2);
else if (isSkipC(op) && LUAU_INSN_C(insn))
return int(pc + LUAU_INSN_C(insn) + 1);
else if (op == LOP_JUMPX)
@ -479,7 +492,7 @@ bool BytecodeBuilder::patchSkipC(size_t jumpLabel, size_t targetLabel)
unsigned int jumpInsn = insns[jumpLabel];
(void)jumpInsn;
LUAU_ASSERT(isSkipC(LuauOpcode(LUAU_INSN_OP(jumpInsn))));
LUAU_ASSERT(isSkipC(LuauOpcode(LUAU_INSN_OP(jumpInsn))) || isFastCall(LuauOpcode(LUAU_INSN_OP(jumpInsn))));
LUAU_ASSERT(LUAU_INSN_C(jumpInsn) == 0);
int offset = int(targetLabel) - int(jumpLabel) - 1;

View File

@ -25,6 +25,9 @@ LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5)
LUAU_FASTFLAGVARIABLE(LuauCompileNoIpairs, false)
LUAU_FASTFLAGVARIABLE(LuauCompileFoldBuiltins, false)
LUAU_FASTFLAGVARIABLE(LuauCompileBetterMultret, false)
namespace Luau
{
@ -75,6 +78,12 @@ static BytecodeBuilder::StringRef sref(AstArray<char> data)
return {data.data, data.size};
}
static BytecodeBuilder::StringRef sref(AstArray<const char> data)
{
LUAU_ASSERT(data.data);
return {data.data, data.size};
}
struct Compiler
{
struct RegScope;
@ -89,6 +98,7 @@ struct Compiler
, constants(nullptr)
, locstants(nullptr)
, tableShapes(nullptr)
, builtins(nullptr)
{
// preallocate some buffers that are very likely to grow anyway; this works around std::vector's inefficient growth policy for small arrays
localStack.reserve(16);
@ -245,7 +255,7 @@ struct Compiler
{
f.canInline = true;
f.stackSize = stackSize;
f.costModel = modelCost(func->body, func->args.data, func->args.size);
f.costModel = modelCost(func->body, func->args.data, func->args.size, builtins);
// track functions that only ever return a single value so that we can convert multret calls to fixedret calls
if (allPathsEndWithReturn(func->body))
@ -262,22 +272,63 @@ struct Compiler
return fid;
}
// returns true if node can return multiple values; may conservatively return true even if expr is known to return just a single value
bool isExprMultRet(AstExpr* node)
{
if (!FFlag::LuauCompileBetterMultret)
return node->is<AstExprCall>() || node->is<AstExprVarargs>();
AstExprCall* expr = node->as<AstExprCall>();
if (!expr)
return node->is<AstExprVarargs>();
// conservative version, optimized for compilation throughput
if (options.optimizationLevel <= 1)
return true;
// handles builtin calls that can be constant-folded
// without this we may omit some optimizations eg compiling fast calls without use of FASTCALL2K
if (isConstant(expr))
return false;
// handles local function calls where we know only one argument is returned
AstExprFunction* func = getFunctionExpr(expr->func);
Function* fi = func ? functions.find(func) : nullptr;
if (fi && fi->returnsOne)
return false;
// unrecognized call, so we conservatively assume multret
return true;
}
// note: this doesn't just clobber target (assuming it's temp), but also clobbers *all* allocated registers >= target!
// this is important to be able to support "multret" semantics due to Lua call frame structure
bool compileExprTempMultRet(AstExpr* node, uint8_t target)
{
if (AstExprCall* expr = node->as<AstExprCall>())
{
// Optimization: convert multret calls to functions that always return one value to fixedret calls; this facilitates inlining
// Optimization: convert multret calls that always return one value to fixedret calls; this facilitates inlining/constant folding
if (options.optimizationLevel >= 2)
{
AstExprFunction* func = getFunctionExpr(expr->func);
Function* fi = func ? functions.find(func) : nullptr;
if (fi && fi->returnsOne)
if (FFlag::LuauCompileBetterMultret)
{
compileExprTemp(node, target);
return false;
if (!isExprMultRet(node))
{
compileExprTemp(node, target);
return false;
}
}
else
{
AstExprFunction* func = getFunctionExpr(expr->func);
Function* fi = func ? functions.find(func) : nullptr;
if (fi && fi->returnsOne)
{
compileExprTemp(node, target);
return false;
}
}
}
@ -483,8 +534,7 @@ struct Compiler
varc[i] = isConstant(expr->args.data[i]);
// if the last argument only returns a single value, all following arguments are nil
if (expr->args.size != 0 &&
!(expr->args.data[expr->args.size - 1]->is<AstExprCall>() || expr->args.data[expr->args.size - 1]->is<AstExprVarargs>()))
if (expr->args.size != 0 && !isExprMultRet(expr->args.data[expr->args.size - 1]))
for (size_t i = expr->args.size; i < func->args.size && i < 8; ++i)
varc[i] = true;
@ -523,7 +573,7 @@ struct Compiler
AstLocal* var = func->args.data[i];
AstExpr* arg = i < expr->args.size ? expr->args.data[i] : nullptr;
if (i + 1 == expr->args.size && func->args.size > expr->args.size && (arg->is<AstExprCall>() || arg->is<AstExprVarargs>()))
if (i + 1 == expr->args.size && func->args.size > expr->args.size && isExprMultRet(arg))
{
// if the last argument can return multiple values, we need to compute all of them into the remaining arguments
unsigned int tail = unsigned(func->args.size - expr->args.size) + 1;
@ -591,7 +641,7 @@ struct Compiler
}
// fold constant values updated above into expressions in the function body
foldConstants(constants, variables, locstants, func->body);
foldConstants(constants, variables, locstants, builtinsFold, func->body);
bool usedFallthrough = false;
@ -632,7 +682,7 @@ struct Compiler
if (Constant* var = locstants.find(func->args.data[i]))
var->type = Constant::Type_Unknown;
foldConstants(constants, variables, locstants, func->body);
foldConstants(constants, variables, locstants, builtinsFold, func->body);
}
void compileExprCall(AstExprCall* expr, uint8_t target, uint8_t targetCount, bool targetTop = false, bool multRet = false)
@ -675,29 +725,23 @@ struct Compiler
int bfid = -1;
if (options.optimizationLevel >= 1)
{
Builtin builtin = getBuiltin(expr->func, globals, variables);
bfid = getBuiltinFunctionId(builtin, options);
}
if (options.optimizationLevel >= 1 && !expr->self)
if (const int* id = builtins.find(expr))
bfid = *id;
if (bfid == LBF_SELECT_VARARG)
{
// Optimization: compile select(_, ...) as FASTCALL1; the builtin will read variadic arguments directly
// note: for now we restrict this to single-return expressions since our runtime code doesn't deal with general cases
if (multRet == false && targetCount == 1 && expr->args.size == 2 && expr->args.data[1]->is<AstExprVarargs>())
if (multRet == false && targetCount == 1)
return compileExprSelectVararg(expr, target, targetCount, targetTop, multRet, regs);
else
bfid = -1;
}
// Optimization: for 1/2 argument fast calls use specialized opcodes
if (!expr->self && bfid >= 0 && expr->args.size >= 1 && expr->args.size <= 2)
{
AstExpr* last = expr->args.data[expr->args.size - 1];
if (!last->is<AstExprCall>() && !last->is<AstExprVarargs>())
return compileExprFastcallN(expr, target, targetCount, targetTop, multRet, regs, bfid);
}
if (bfid >= 0 && expr->args.size >= 1 && expr->args.size <= 2 && !isExprMultRet(expr->args.data[expr->args.size - 1]))
return compileExprFastcallN(expr, target, targetCount, targetTop, multRet, regs, bfid);
if (expr->self)
{
@ -2495,7 +2539,7 @@ struct Compiler
}
AstLocal* var = stat->var;
uint64_t costModel = modelCost(stat->body, &var, 1);
uint64_t costModel = modelCost(stat->body, &var, 1, builtins);
// we use a dynamic cost threshold that's based on the fixed limit boosted by the cost advantage we gain due to unrolling
bool varc = true;
@ -2533,7 +2577,7 @@ struct Compiler
locstants[var].type = Constant::Type_Number;
locstants[var].valueNumber = from + iv * step;
foldConstants(constants, variables, locstants, stat);
foldConstants(constants, variables, locstants, builtinsFold, stat);
size_t iterJumps = loopJumps.size();
@ -2561,7 +2605,7 @@ struct Compiler
// clean up fold state in case we need to recompile - normally we compile the loop body once, but due to inlining we may need to do it again
locstants[var].type = Constant::Type_Unknown;
foldConstants(constants, variables, locstants, stat);
foldConstants(constants, variables, locstants, builtinsFold, stat);
}
void compileStatFor(AstStatFor* stat)
@ -3368,7 +3412,11 @@ struct Compiler
bool visit(AstStatReturn* stat) override
{
if (stat->list.size == 1)
if (FFlag::LuauCompileBetterMultret)
{
returnsOne &= stat->list.size == 1 && !self->isExprMultRet(stat->list.data[0]);
}
else if (stat->list.size == 1)
{
AstExpr* value = stat->list.data[0];
@ -3487,6 +3535,8 @@ struct Compiler
DenseHashMap<AstExpr*, Constant> constants;
DenseHashMap<AstLocal*, Constant> locstants;
DenseHashMap<AstExprTable*, TableShape> tableShapes;
DenseHashMap<AstExprCall*, int> builtins;
const DenseHashMap<AstExprCall*, int>* builtinsFold = nullptr;
unsigned int regTop = 0;
unsigned int stackSize = 0;
@ -3502,10 +3552,21 @@ struct Compiler
std::vector<Capture> captures;
};
void compileOrThrow(BytecodeBuilder& bytecode, AstStatBlock* root, const AstNameTable& names, const CompileOptions& options)
void compileOrThrow(BytecodeBuilder& bytecode, const ParseResult& parseResult, const AstNameTable& names, const CompileOptions& inputOptions)
{
LUAU_TIMETRACE_SCOPE("compileOrThrow", "Compiler");
LUAU_ASSERT(parseResult.root);
LUAU_ASSERT(parseResult.errors.empty());
CompileOptions options = inputOptions;
for (const HotComment& hc : parseResult.hotcomments)
if (hc.header && hc.content.compare(0, 9, "optimize ") == 0)
options.optimizationLevel = std::max(0, std::min(2, atoi(hc.content.c_str() + 9)));
AstStatBlock* root = parseResult.root;
Compiler compiler(bytecode, options);
// since access to some global objects may result in values that change over time, we block imports from non-readonly tables
@ -3514,10 +3575,17 @@ void compileOrThrow(BytecodeBuilder& bytecode, AstStatBlock* root, const AstName
// this pass analyzes mutability of locals/globals and associates locals with their initial values
trackValues(compiler.globals, compiler.variables, root);
// builtin folding is enabled on optimization level 2 since we can't deoptimize folding at runtime
if (options.optimizationLevel >= 2 && FFlag::LuauCompileFoldBuiltins)
compiler.builtinsFold = &compiler.builtins;
if (options.optimizationLevel >= 1)
{
// this pass tracks which calls are builtins and can be compiled more efficiently
analyzeBuiltins(compiler.builtins, compiler.globals, compiler.variables, options, root);
// this pass analyzes constantness of expressions
foldConstants(compiler.constants, compiler.variables, compiler.locstants, root);
foldConstants(compiler.constants, compiler.variables, compiler.locstants, compiler.builtinsFold, root);
// this pass analyzes table assignments to estimate table shapes for initially empty tables
predictTableShapes(compiler.tableShapes, root);
@ -3559,9 +3627,7 @@ void compileOrThrow(BytecodeBuilder& bytecode, const std::string& source, const
if (!result.errors.empty())
throw ParseErrors(result.errors);
AstStatBlock* root = result.root;
compileOrThrow(bytecode, root, names, options);
compileOrThrow(bytecode, result, names, options);
}
std::string compile(const std::string& source, const CompileOptions& options, const ParseOptions& parseOptions, BytecodeEncoder* encoder)
@ -3584,7 +3650,7 @@ std::string compile(const std::string& source, const CompileOptions& options, co
try
{
BytecodeBuilder bcb(encoder);
compileOrThrow(bcb, result.root, names, options);
compileOrThrow(bcb, result, names, options);
return bcb.getBytecode();
}

View File

@ -1,6 +1,8 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "ConstantFolding.h"
#include "BuiltinFolding.h"
#include <math.h>
namespace Luau
@ -193,13 +195,18 @@ struct ConstantVisitor : AstVisitor
DenseHashMap<AstLocal*, Variable>& variables;
DenseHashMap<AstLocal*, Constant>& locals;
const DenseHashMap<AstExprCall*, int>* builtins;
bool wasEmpty = false;
ConstantVisitor(
DenseHashMap<AstExpr*, Constant>& constants, DenseHashMap<AstLocal*, Variable>& variables, DenseHashMap<AstLocal*, Constant>& locals)
std::vector<Constant> builtinArgs;
ConstantVisitor(DenseHashMap<AstExpr*, Constant>& constants, DenseHashMap<AstLocal*, Variable>& variables,
DenseHashMap<AstLocal*, Constant>& locals, const DenseHashMap<AstExprCall*, int>* builtins)
: constants(constants)
, variables(variables)
, locals(locals)
, builtins(builtins)
{
// since we do a single pass over the tree, if the initial state was empty we don't need to clear out old entries
wasEmpty = constants.empty() && locals.empty();
@ -253,8 +260,37 @@ struct ConstantVisitor : AstVisitor
{
analyze(expr->func);
for (size_t i = 0; i < expr->args.size; ++i)
analyze(expr->args.data[i]);
if (const int* bfid = builtins ? builtins->find(expr) : nullptr)
{
// since recursive calls to analyze() may reuse the vector we need to be careful and preserve existing contents
size_t offset = builtinArgs.size();
bool canFold = true;
builtinArgs.reserve(offset + expr->args.size);
for (size_t i = 0; i < expr->args.size; ++i)
{
Constant ac = analyze(expr->args.data[i]);
if (ac.type == Constant::Type_Unknown)
canFold = false;
else
builtinArgs.push_back(ac);
}
if (canFold)
{
LUAU_ASSERT(builtinArgs.size() == offset + expr->args.size);
result = foldBuiltin(*bfid, builtinArgs.data() + offset, expr->args.size);
}
builtinArgs.resize(offset);
}
else
{
for (size_t i = 0; i < expr->args.size; ++i)
analyze(expr->args.data[i]);
}
}
else if (AstExprIndexName* expr = node->as<AstExprIndexName>())
{
@ -395,9 +431,9 @@ struct ConstantVisitor : AstVisitor
};
void foldConstants(DenseHashMap<AstExpr*, Constant>& constants, DenseHashMap<AstLocal*, Variable>& variables,
DenseHashMap<AstLocal*, Constant>& locals, AstNode* root)
DenseHashMap<AstLocal*, Constant>& locals, const DenseHashMap<AstExprCall*, int>* builtins, AstNode* root)
{
ConstantVisitor visitor{constants, variables, locals};
ConstantVisitor visitor{constants, variables, locals, builtins};
root->visit(&visitor);
}

View File

@ -26,7 +26,7 @@ struct Constant
{
bool valueBoolean;
double valueNumber;
char* valueString = nullptr; // length stored in stringLength
const char* valueString = nullptr; // length stored in stringLength
};
bool isTruthful() const
@ -35,7 +35,7 @@ struct Constant
return type != Type_Nil && !(type == Type_Boolean && valueBoolean == false);
}
AstArray<char> getString() const
AstArray<const char> getString() const
{
LUAU_ASSERT(type == Type_String);
return {valueString, stringLength};
@ -43,7 +43,7 @@ struct Constant
};
void foldConstants(DenseHashMap<AstExpr*, Constant>& constants, DenseHashMap<AstLocal*, Variable>& variables,
DenseHashMap<AstLocal*, Constant>& locals, AstNode* root);
DenseHashMap<AstLocal*, Constant>& locals, const DenseHashMap<AstExprCall*, int>* builtins, AstNode* root);
} // namespace Compile
} // namespace Luau

View File

@ -6,6 +6,8 @@
#include <limits.h>
LUAU_FASTFLAGVARIABLE(LuauCompileModelBuiltins, false)
namespace Luau
{
namespace Compile
@ -113,11 +115,14 @@ struct Cost
struct CostVisitor : AstVisitor
{
const DenseHashMap<AstExprCall*, int>& builtins;
DenseHashMap<AstLocal*, uint64_t> vars;
Cost result;
CostVisitor()
: vars(nullptr)
CostVisitor(const DenseHashMap<AstExprCall*, int>& builtins)
: builtins(builtins)
, vars(nullptr)
{
}
@ -148,14 +153,21 @@ struct CostVisitor : AstVisitor
}
else if (AstExprCall* expr = node->as<AstExprCall>())
{
Cost cost = 3;
cost += model(expr->func);
// builtin cost modeling is different from regular calls because we use FASTCALL to compile these
// thus we use a cheaper baseline, don't account for function, and assume constant/local copy is free
bool builtin = FFlag::LuauCompileModelBuiltins && builtins.find(expr) != nullptr;
bool builtinShort = builtin && expr->args.size <= 2; // FASTCALL1/2
Cost cost = builtin ? 2 : 3;
if (!builtin)
cost += model(expr->func);
for (size_t i = 0; i < expr->args.size; ++i)
{
Cost ac = model(expr->args.data[i]);
// for constants/locals we still need to copy them to the argument list
cost += ac.model == 0 ? Cost(1) : ac;
cost += ac.model == 0 && !builtinShort ? Cost(1) : ac;
}
return cost;
@ -327,9 +339,9 @@ struct CostVisitor : AstVisitor
}
};
uint64_t modelCost(AstNode* root, AstLocal* const* vars, size_t varCount)
uint64_t modelCost(AstNode* root, AstLocal* const* vars, size_t varCount, const DenseHashMap<AstExprCall*, int>& builtins)
{
CostVisitor visitor;
CostVisitor visitor{builtins};
for (size_t i = 0; i < varCount && i < 7; ++i)
visitor.vars[vars[i]] = 0xffull << (i * 8 + 8);

View File

@ -2,6 +2,7 @@
#pragma once
#include "Luau/Ast.h"
#include "Luau/DenseHash.h"
namespace Luau
{
@ -9,7 +10,7 @@ namespace Compile
{
// cost model: 8 bytes, where first byte is the baseline cost, and the next 7 bytes are discounts for when variable #i is constant
uint64_t modelCost(AstNode* root, AstLocal* const* vars, size_t varCount);
uint64_t modelCost(AstNode* root, AstLocal* const* vars, size_t varCount, const DenseHashMap<AstExprCall*, int>& builtins);
// cost is computed as B - sum(Di * Ci), where B is baseline cost, Di is the discount for each variable and Ci is 1 when variable #i is constant
int computeCost(uint64_t model, const bool* varsConst, size_t varCount);

View File

@ -64,14 +64,14 @@ enum lua_Type
LUA_TNIL = 0, /* must be 0 due to lua_isnoneornil */
LUA_TBOOLEAN = 1, /* must be 1 due to l_isfalse */
LUA_TLIGHTUSERDATA,
LUA_TNUMBER,
LUA_TVECTOR,
LUA_TSTRING, /* all types above this must be value types, all types below this must be GC types - see iscollectable */
LUA_TTABLE,
LUA_TFUNCTION,
LUA_TUSERDATA,
@ -300,6 +300,7 @@ LUA_API uintptr_t lua_encodepointer(lua_State* L, uintptr_t p);
LUA_API double lua_clock();
LUA_API void lua_setuserdatatag(lua_State* L, int idx, int tag);
LUA_API void lua_setuserdatadtor(lua_State* L, int tag, void (*dtor)(lua_State*, void*));
LUA_API void lua_clonefunction(lua_State* L, int idx);
@ -372,7 +373,7 @@ LUA_API const char* lua_getupvalue(lua_State* L, int funcindex, int n);
LUA_API const char* lua_setupvalue(lua_State* L, int funcindex, int n);
LUA_API void lua_singlestep(lua_State* L, int enabled);
LUA_API void lua_breakpoint(lua_State* L, int funcindex, int line, int enabled);
LUA_API int lua_breakpoint(lua_State* L, int funcindex, int line, int enabled);
typedef void (*lua_Coverage)(void* context, const char* function, int linedefined, int depth, const int* hits, size_t size);

View File

@ -34,6 +34,8 @@
* therefore call luaC_checkGC before luaC_checkthreadsleep to guarantee the object is pushed to an awake thread.
*/
LUAU_FASTFLAG(LuauLazyAtoms)
const char* lua_ident = "$Lua: Lua 5.1.4 Copyright (C) 1994-2008 Lua.org, PUC-Rio $\n"
"$Authors: R. Ierusalimschy, L. H. de Figueiredo & W. Celes $\n"
"$URL: www.lua.org $\n";
@ -51,6 +53,13 @@ const char* luau_ident = "$Luau: Copyright (C) 2019-2022 Roblox Corporation $\n"
L->top++; \
}
#define updateatom(L, ts) \
if (FFlag::LuauLazyAtoms) \
{ \
if (ts->atom == ATOM_UNDEF) \
ts->atom = L->global->cb.useratom ? L->global->cb.useratom(ts->data, ts->len) : -1; \
}
static Table* getcurrenv(lua_State* L)
{
if (L->ci == L->base_ci) /* no enclosing function? */
@ -441,19 +450,25 @@ const char* lua_tostringatom(lua_State* L, int idx, int* atom)
StkId o = index2addr(L, idx);
if (!ttisstring(o))
return NULL;
const TString* s = tsvalue(o);
TString* s = tsvalue(o);
if (atom)
{
updateatom(L, s);
*atom = s->atom;
}
return getstr(s);
}
const char* lua_namecallatom(lua_State* L, int* atom)
{
const TString* s = L->namecall;
TString* s = L->namecall;
if (!s)
return NULL;
if (atom)
{
updateatom(L, s);
*atom = s->atom;
}
return getstr(s);
}
@ -1305,6 +1320,14 @@ void lua_unref(lua_State* L, int ref)
return;
}
void lua_setuserdatatag(lua_State* L, int idx, int tag)
{
api_check(L, unsigned(tag) < LUA_UTAG_LIMIT);
StkId o = index2addr(L, idx);
api_check(L, ttisuserdata(o));
uvalue(o)->tag = uint8_t(tag);
}
void lua_setuserdatadtor(lua_State* L, int tag, void (*dtor)(lua_State*, void*))
{
api_check(L, unsigned(tag) < LUA_UTAG_LIMIT);

View File

@ -12,6 +12,8 @@
#include <string.h>
#include <stdio.h>
LUAU_FASTFLAGVARIABLE(LuauDebuggerBreakpointHitOnNextBestLine, false);
static const char* getfuncname(Closure* f);
static int currentpc(lua_State* L, CallInfo* ci)
@ -367,14 +369,6 @@ void lua_singlestep(lua_State* L, int enabled)
L->singlestep = bool(enabled);
}
void lua_breakpoint(lua_State* L, int funcindex, int line, int enabled)
{
const TValue* func = luaA_toobject(L, funcindex);
api_check(L, ttisfunction(func) && !clvalue(func)->isC);
luaG_breakpoint(L, clvalue(func)->l.p, line, bool(enabled));
}
static int getmaxline(Proto* p)
{
int result = -1;
@ -394,6 +388,71 @@ static int getmaxline(Proto* p)
return result;
}
// Find the line number with instructions. If the provided line doesn't have any instruction, it should return the next line number with
// instructions.
static int getnextline(Proto* p, int line)
{
int closest = -1;
if (p->lineinfo)
{
for (int i = 0; i < p->sizecode; ++i)
{
// note: we keep prologue as is, instead opting to break at the first meaningful instruction
if (LUAU_INSN_OP(p->code[i]) == LOP_PREPVARARGS)
continue;
int current = luaG_getline(p, i);
if (current >= line)
{
closest = current;
break;
}
}
}
for (int i = 0; i < p->sizep; ++i)
{
// Find the closest line number to the intended one.
int candidate = getnextline(p->p[i], line);
if (closest == -1 || (candidate >= line && candidate < closest))
{
closest = candidate;
}
}
return closest;
}
int lua_breakpoint(lua_State* L, int funcindex, int line, int enabled)
{
int target = -1;
if (FFlag::LuauDebuggerBreakpointHitOnNextBestLine)
{
const TValue* func = luaA_toobject(L, funcindex);
api_check(L, ttisfunction(func) && !clvalue(func)->isC);
Proto* p = clvalue(func)->l.p;
// Find line number to add the breakpoint to.
target = getnextline(p, line);
if (target != -1)
{
// Add breakpoint on the exact line
luaG_breakpoint(L, p, target, bool(enabled));
}
}
else
{
const TValue* func = luaA_toobject(L, funcindex);
api_check(L, ttisfunction(func) && !clvalue(func)->isC);
luaG_breakpoint(L, clvalue(func)->l.p, line, bool(enabled));
}
return target;
}
static void getcoverage(Proto* p, int depth, int* buffer, size_t size, void* context, lua_Coverage callback)
{
memset(buffer, -1, size * sizeof(int));

View File

@ -7,6 +7,8 @@
#include <string.h>
LUAU_FASTFLAGVARIABLE(LuauLazyAtoms, false)
unsigned int luaS_hash(const char* str, size_t len)
{
// Note that this hashing algorithm is replicated in BytecodeBuilder.cpp, BytecodeBuilder::getStringHash
@ -82,7 +84,7 @@ static TString* newlstr(lua_State* L, const char* str, size_t l, unsigned int h)
ts->memcat = L->activememcat;
memcpy(ts->data, str, l);
ts->data[l] = '\0'; /* ending 0 */
ts->atom = L->global->cb.useratom ? L->global->cb.useratom(ts->data, l) : -1;
ts->atom = FFlag::LuauLazyAtoms ? ATOM_UNDEF : L->global->cb.useratom ? L->global->cb.useratom(ts->data, l) : -1;
tb = &L->global->strt;
h = lmod(h, tb->size);
ts->next = tb->hash[h]; /* chain new entry */
@ -165,7 +167,7 @@ TString* luaS_buffinish(lua_State* L, TString* ts)
ts->data[ts->len] = '\0'; // ending 0
// Complete string object
ts->atom = L->global->cb.useratom ? L->global->cb.useratom(ts->data, ts->len) : -1;
ts->atom = FFlag::LuauLazyAtoms ? ATOM_UNDEF : L->global->cb.useratom ? L->global->cb.useratom(ts->data, ts->len) : -1;
ts->next = tb->hash[bucket]; // chain new entry
tb->hash[bucket] = ts;

View File

@ -8,6 +8,9 @@
/* string size limit */
#define MAXSSIZE (1 << 30)
/* string atoms are not defined by default; the storage is 16-bit integer */
#define ATOM_UNDEF -32768
#define sizestring(len) (offsetof(TString, data) + len + 1)
#define luaS_new(L, s) (luaS_newlstr(L, s, strlen(s)))