From 1e930d30cb62b0cbe8a7d52e1f3ef31f3cfaa880 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 17 Jul 2022 22:35:35 +0100 Subject: [PATCH] v0.3.6+luau536 --- Cargo.toml | 2 +- luau/Ast/src/Parser.cpp | 10 +- luau/Compiler/include/Luau/Compiler.h | 4 +- luau/Compiler/src/BuiltinFolding.cpp | 463 ++++++++++++++++++++++++++ luau/Compiler/src/BuiltinFolding.h | 14 + luau/Compiler/src/Builtins.cpp | 49 ++- luau/Compiler/src/Builtins.h | 4 +- luau/Compiler/src/BytecodeBuilder.cpp | 15 +- luau/Compiler/src/Compiler.cpp | 136 ++++++-- luau/Compiler/src/ConstantFolding.cpp | 48 ++- luau/Compiler/src/ConstantFolding.h | 6 +- luau/Compiler/src/CostModel.cpp | 26 +- luau/Compiler/src/CostModel.h | 3 +- luau/VM/include/lua.h | 7 +- luau/VM/src/lapi.cpp | 27 +- luau/VM/src/ldebug.cpp | 75 ++++- luau/VM/src/lstring.cpp | 6 +- luau/VM/src/lstring.h | 3 + 18 files changed, 819 insertions(+), 79 deletions(-) create mode 100644 luau/Compiler/src/BuiltinFolding.cpp create mode 100644 luau/Compiler/src/BuiltinFolding.h diff --git a/Cargo.toml b/Cargo.toml index f0abda0..a7e73eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "luau0-src" -version = "0.3.5+luau535" +version = "0.3.6+luau536" authors = ["Aleksandr Orlenko "] edition = "2021" repository = "https://github.com/khvzak/luau-src-rs" diff --git a/luau/Ast/src/Parser.cpp b/luau/Ast/src/Parser.cpp index b7fa788..779bd27 100644 --- a/luau/Ast/src/Parser.cpp +++ b/luau/Ast/src/Parser.cpp @@ -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; diff --git a/luau/Compiler/include/Luau/Compiler.h b/luau/Compiler/include/Luau/Compiler.h index 65e962d..eec70d7 100644 --- a/luau/Compiler/include/Luau/Compiler.h +++ b/luau/Compiler/include/Luau/Compiler.h @@ -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 diff --git a/luau/Compiler/src/BuiltinFolding.cpp b/luau/Compiler/src/BuiltinFolding.cpp new file mode 100644 index 0000000..e76da4e --- /dev/null +++ b/luau/Compiler/src/BuiltinFolding.cpp @@ -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 + +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 diff --git a/luau/Compiler/src/BuiltinFolding.h b/luau/Compiler/src/BuiltinFolding.h new file mode 100644 index 0000000..1904e14 --- /dev/null +++ b/luau/Compiler/src/BuiltinFolding.h @@ -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 diff --git a/luau/Compiler/src/Builtins.cpp b/luau/Compiler/src/Builtins.cpp index 6bd24b6..3650c14 100644 --- a/luau/Compiler/src/Builtins.cpp +++ b/luau/Compiler/src/Builtins.cpp @@ -40,11 +40,8 @@ Builtin getBuiltin(AstExpr* node, const DenseHashMap& 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& result; + + const DenseHashMap& globals; + const DenseHashMap& variables; + + const CompileOptions& options; + + BuiltinVisitor(DenseHashMap& result, const DenseHashMap& globals, + const DenseHashMap& 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())) + bfid = -1; + + if (bfid >= 0) + result[node] = bfid; + + return true; // propagate to nested calls + } +}; + +void analyzeBuiltins(DenseHashMap& result, const DenseHashMap& globals, + const DenseHashMap& variables, const CompileOptions& options, AstNode* root) +{ + BuiltinVisitor visitor{result, globals, variables, options}; + root->visit(&visitor); +} + } // namespace Compile } // namespace Luau diff --git a/luau/Compiler/src/Builtins.h b/luau/Compiler/src/Builtins.h index 60df53a..4399c53 100644 --- a/luau/Compiler/src/Builtins.h +++ b/luau/Compiler/src/Builtins.h @@ -35,7 +35,9 @@ struct Builtin }; Builtin getBuiltin(AstExpr* node, const DenseHashMap& globals, const DenseHashMap& variables); -int getBuiltinFunctionId(const Builtin& builtin, const CompileOptions& options); + +void analyzeBuiltins(DenseHashMap& result, const DenseHashMap& globals, + const DenseHashMap& variables, const CompileOptions& options, AstNode* root); } // namespace Compile } // namespace Luau diff --git a/luau/Compiler/src/BytecodeBuilder.cpp b/luau/Compiler/src/BytecodeBuilder.cpp index 5e2669b..64327bd 100644 --- a/luau/Compiler/src/BytecodeBuilder.cpp +++ b/luau/Compiler/src/BytecodeBuilder.cpp @@ -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; diff --git a/luau/Compiler/src/Compiler.cpp b/luau/Compiler/src/Compiler.cpp index d7c8155..2b0f04f 100644 --- a/luau/Compiler/src/Compiler.cpp +++ b/luau/Compiler/src/Compiler.cpp @@ -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 data) return {data.data, data.size}; } +static BytecodeBuilder::StringRef sref(AstArray 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() || node->is(); + + AstExprCall* expr = node->as(); + if (!expr) + return node->is(); + + // 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()) { - // 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() || expr->args.data[expr->args.size - 1]->is())) + 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() || arg->is())) + 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()) + 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() && !last->is()) - 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 constants; DenseHashMap locstants; DenseHashMap tableShapes; + DenseHashMap builtins; + const DenseHashMap* builtinsFold = nullptr; unsigned int regTop = 0; unsigned int stackSize = 0; @@ -3502,10 +3552,21 @@ struct Compiler std::vector 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(); } diff --git a/luau/Compiler/src/ConstantFolding.cpp b/luau/Compiler/src/ConstantFolding.cpp index a62beeb..34f7954 100644 --- a/luau/Compiler/src/ConstantFolding.cpp +++ b/luau/Compiler/src/ConstantFolding.cpp @@ -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 namespace Luau @@ -193,13 +195,18 @@ struct ConstantVisitor : AstVisitor DenseHashMap& variables; DenseHashMap& locals; + const DenseHashMap* builtins; + bool wasEmpty = false; - ConstantVisitor( - DenseHashMap& constants, DenseHashMap& variables, DenseHashMap& locals) + std::vector builtinArgs; + + ConstantVisitor(DenseHashMap& constants, DenseHashMap& variables, + DenseHashMap& locals, const DenseHashMap* 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()) { @@ -395,9 +431,9 @@ struct ConstantVisitor : AstVisitor }; void foldConstants(DenseHashMap& constants, DenseHashMap& variables, - DenseHashMap& locals, AstNode* root) + DenseHashMap& locals, const DenseHashMap* builtins, AstNode* root) { - ConstantVisitor visitor{constants, variables, locals}; + ConstantVisitor visitor{constants, variables, locals, builtins}; root->visit(&visitor); } diff --git a/luau/Compiler/src/ConstantFolding.h b/luau/Compiler/src/ConstantFolding.h index 0a995d7..d67d928 100644 --- a/luau/Compiler/src/ConstantFolding.h +++ b/luau/Compiler/src/ConstantFolding.h @@ -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 getString() const + AstArray getString() const { LUAU_ASSERT(type == Type_String); return {valueString, stringLength}; @@ -43,7 +43,7 @@ struct Constant }; void foldConstants(DenseHashMap& constants, DenseHashMap& variables, - DenseHashMap& locals, AstNode* root); + DenseHashMap& locals, const DenseHashMap* builtins, AstNode* root); } // namespace Compile } // namespace Luau diff --git a/luau/Compiler/src/CostModel.cpp b/luau/Compiler/src/CostModel.cpp index 5608cd8..56b6c36 100644 --- a/luau/Compiler/src/CostModel.cpp +++ b/luau/Compiler/src/CostModel.cpp @@ -6,6 +6,8 @@ #include +LUAU_FASTFLAGVARIABLE(LuauCompileModelBuiltins, false) + namespace Luau { namespace Compile @@ -113,11 +115,14 @@ struct Cost struct CostVisitor : AstVisitor { + const DenseHashMap& builtins; + DenseHashMap vars; Cost result; - CostVisitor() - : vars(nullptr) + CostVisitor(const DenseHashMap& builtins) + : builtins(builtins) + , vars(nullptr) { } @@ -148,14 +153,21 @@ struct CostVisitor : AstVisitor } else if (AstExprCall* expr = node->as()) { - 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& builtins) { - CostVisitor visitor; + CostVisitor visitor{builtins}; for (size_t i = 0; i < varCount && i < 7; ++i) visitor.vars[vars[i]] = 0xffull << (i * 8 + 8); diff --git a/luau/Compiler/src/CostModel.h b/luau/Compiler/src/CostModel.h index 17defaf..e8f3e16 100644 --- a/luau/Compiler/src/CostModel.h +++ b/luau/Compiler/src/CostModel.h @@ -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& 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); diff --git a/luau/VM/include/lua.h b/luau/VM/include/lua.h index 7f9647c..187dd5c 100644 --- a/luau/VM/include/lua.h +++ b/luau/VM/include/lua.h @@ -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); diff --git a/luau/VM/src/lapi.cpp b/luau/VM/src/lapi.cpp index 3c3b7bd..8868072 100644 --- a/luau/VM/src/lapi.cpp +++ b/luau/VM/src/lapi.cpp @@ -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); diff --git a/luau/VM/src/ldebug.cpp b/luau/VM/src/ldebug.cpp index e050050..ef48609 100644 --- a/luau/VM/src/ldebug.cpp +++ b/luau/VM/src/ldebug.cpp @@ -12,6 +12,8 @@ #include #include +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)); diff --git a/luau/VM/src/lstring.cpp b/luau/VM/src/lstring.cpp index c0cd3e2..12bd67d 100644 --- a/luau/VM/src/lstring.cpp +++ b/luau/VM/src/lstring.cpp @@ -7,6 +7,8 @@ #include +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; diff --git a/luau/VM/src/lstring.h b/luau/VM/src/lstring.h index 290b64d..acdf9a1 100644 --- a/luau/VM/src/lstring.h +++ b/luau/VM/src/lstring.h @@ -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)))