mirror of
https://github.com/luau-lang/luau.git
synced 2024-12-13 21:40:43 +00:00
2294 lines
65 KiB
C++
2294 lines
65 KiB
C++
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
|
#include "Luau/BytecodeBuilder.h"
|
|
|
|
#include "Luau/StringUtils.h"
|
|
|
|
#include <algorithm>
|
|
#include <string.h>
|
|
|
|
namespace Luau
|
|
{
|
|
|
|
static_assert(LBC_VERSION_TARGET >= LBC_VERSION_MIN && LBC_VERSION_TARGET <= LBC_VERSION_MAX, "Invalid bytecode version setup");
|
|
static_assert(LBC_VERSION_MAX <= 127, "Bytecode version should be 7-bit so that we can extend the serialization to use varint transparently");
|
|
|
|
static const uint32_t kMaxConstantCount = 1 << 23;
|
|
static const uint32_t kMaxClosureCount = 1 << 15;
|
|
|
|
static const int kMaxJumpDistance = 1 << 23;
|
|
|
|
static int log2(int v)
|
|
{
|
|
LUAU_ASSERT(v);
|
|
|
|
int r = 0;
|
|
|
|
while (v >= (2 << r))
|
|
r++;
|
|
|
|
return r;
|
|
}
|
|
|
|
static void writeByte(std::string& ss, unsigned char value)
|
|
{
|
|
ss.append(reinterpret_cast<const char*>(&value), sizeof(value));
|
|
}
|
|
|
|
static void writeInt(std::string& ss, int value)
|
|
{
|
|
ss.append(reinterpret_cast<const char*>(&value), sizeof(value));
|
|
}
|
|
|
|
static void writeDouble(std::string& ss, double value)
|
|
{
|
|
ss.append(reinterpret_cast<const char*>(&value), sizeof(value));
|
|
}
|
|
|
|
static void writeVarInt(std::string& ss, unsigned int value)
|
|
{
|
|
do
|
|
{
|
|
writeByte(ss, (value & 127) | ((value > 127) << 7));
|
|
value >>= 7;
|
|
} while (value);
|
|
}
|
|
|
|
static int getOpLength(LuauOpcode op)
|
|
{
|
|
switch (op)
|
|
{
|
|
case LOP_GETGLOBAL:
|
|
case LOP_SETGLOBAL:
|
|
case LOP_GETIMPORT:
|
|
case LOP_GETTABLEKS:
|
|
case LOP_SETTABLEKS:
|
|
case LOP_NAMECALL:
|
|
case LOP_JUMPIFEQ:
|
|
case LOP_JUMPIFLE:
|
|
case LOP_JUMPIFLT:
|
|
case LOP_JUMPIFNOTEQ:
|
|
case LOP_JUMPIFNOTLE:
|
|
case LOP_JUMPIFNOTLT:
|
|
case LOP_NEWTABLE:
|
|
case LOP_SETLIST:
|
|
case LOP_FORGLOOP:
|
|
case LOP_LOADKX:
|
|
case LOP_FASTCALL2:
|
|
case LOP_FASTCALL2K:
|
|
case LOP_JUMPXEQKNIL:
|
|
case LOP_JUMPXEQKB:
|
|
case LOP_JUMPXEQKN:
|
|
case LOP_JUMPXEQKS:
|
|
return 2;
|
|
|
|
default:
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
inline bool isJumpD(LuauOpcode op)
|
|
{
|
|
switch (op)
|
|
{
|
|
case LOP_JUMP:
|
|
case LOP_JUMPIF:
|
|
case LOP_JUMPIFNOT:
|
|
case LOP_JUMPIFEQ:
|
|
case LOP_JUMPIFLE:
|
|
case LOP_JUMPIFLT:
|
|
case LOP_JUMPIFNOTEQ:
|
|
case LOP_JUMPIFNOTLE:
|
|
case LOP_JUMPIFNOTLT:
|
|
case LOP_FORNPREP:
|
|
case LOP_FORNLOOP:
|
|
case LOP_FORGPREP:
|
|
case LOP_FORGLOOP:
|
|
case LOP_FORGPREP_INEXT:
|
|
case LOP_FORGPREP_NEXT:
|
|
case LOP_JUMPBACK:
|
|
case LOP_JUMPXEQKNIL:
|
|
case LOP_JUMPXEQKB:
|
|
case LOP_JUMPXEQKN:
|
|
case LOP_JUMPXEQKS:
|
|
return true;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
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:
|
|
case LOP_FASTCALL2K:
|
|
return true;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static int getJumpTarget(uint32_t insn, uint32_t pc)
|
|
{
|
|
LuauOpcode op = LuauOpcode(LUAU_INSN_OP(insn));
|
|
|
|
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)
|
|
return int(pc + LUAU_INSN_E(insn) + 1);
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
bool BytecodeBuilder::StringRef::operator==(const StringRef& other) const
|
|
{
|
|
return (data && other.data) ? (length == other.length && memcmp(data, other.data, length) == 0) : (data == other.data);
|
|
}
|
|
|
|
bool BytecodeBuilder::TableShape::operator==(const TableShape& other) const
|
|
{
|
|
return length == other.length && memcmp(keys, other.keys, length * sizeof(keys[0])) == 0;
|
|
}
|
|
|
|
size_t BytecodeBuilder::StringRefHash::operator()(const StringRef& v) const
|
|
{
|
|
return hashRange(v.data, v.length);
|
|
}
|
|
|
|
size_t BytecodeBuilder::ConstantKeyHash::operator()(const ConstantKey& key) const
|
|
{
|
|
// finalizer from MurmurHash64B
|
|
const uint32_t m = 0x5bd1e995;
|
|
|
|
uint32_t h1 = uint32_t(key.value);
|
|
uint32_t h2 = uint32_t(key.value >> 32) ^ (key.type * m);
|
|
|
|
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 size_t(h2);
|
|
}
|
|
|
|
size_t BytecodeBuilder::TableShapeHash::operator()(const TableShape& v) const
|
|
{
|
|
// FNV-1a inspired hash (note that we feed integers instead of bytes)
|
|
uint32_t hash = 2166136261;
|
|
|
|
for (size_t i = 0; i < v.length; ++i)
|
|
{
|
|
hash ^= v.keys[i];
|
|
hash *= 16777619;
|
|
}
|
|
|
|
return hash;
|
|
}
|
|
|
|
BytecodeBuilder::BytecodeBuilder(BytecodeEncoder* encoder)
|
|
: constantMap({Constant::Type_Nil, ~0ull})
|
|
, tableShapeMap(TableShape())
|
|
, protoMap(~0u)
|
|
, stringTable({nullptr, 0})
|
|
, encoder(encoder)
|
|
{
|
|
LUAU_ASSERT(stringTable.find(StringRef{"", 0}) == nullptr);
|
|
|
|
// preallocate some buffers that are very likely to grow anyway; this works around std::vector's inefficient growth policy for small arrays
|
|
insns.reserve(32);
|
|
lines.reserve(32);
|
|
constants.reserve(16);
|
|
protos.reserve(16);
|
|
functions.reserve(8);
|
|
}
|
|
|
|
uint32_t BytecodeBuilder::beginFunction(uint8_t numparams, bool isvararg)
|
|
{
|
|
LUAU_ASSERT(currentFunction == ~0u);
|
|
|
|
uint32_t id = uint32_t(functions.size());
|
|
|
|
Function func;
|
|
func.numparams = numparams;
|
|
func.isvararg = isvararg;
|
|
|
|
functions.push_back(func);
|
|
|
|
currentFunction = id;
|
|
|
|
hasLongJumps = false;
|
|
debugLine = 0;
|
|
|
|
return id;
|
|
}
|
|
|
|
void BytecodeBuilder::endFunction(uint8_t maxstacksize, uint8_t numupvalues)
|
|
{
|
|
LUAU_ASSERT(currentFunction != ~0u);
|
|
|
|
Function& func = functions[currentFunction];
|
|
|
|
func.maxstacksize = maxstacksize;
|
|
func.numupvalues = numupvalues;
|
|
|
|
#ifdef LUAU_ASSERTENABLED
|
|
validate();
|
|
#endif
|
|
|
|
// very approximate: 4 bytes per instruction for code, 1 byte for debug line, and 1-2 bytes for aux data like constants plus overhead
|
|
func.data.reserve(32 + insns.size() * 7);
|
|
|
|
writeFunction(func.data, currentFunction);
|
|
|
|
currentFunction = ~0u;
|
|
|
|
// this call is indirect to make sure we only gain link time dependency on dumpCurrentFunction when needed
|
|
if (dumpFunctionPtr)
|
|
func.dump = (this->*dumpFunctionPtr)(func.dumpinstoffs);
|
|
|
|
insns.clear();
|
|
lines.clear();
|
|
constants.clear();
|
|
protos.clear();
|
|
jumps.clear();
|
|
tableShapes.clear();
|
|
|
|
debugLocals.clear();
|
|
debugUpvals.clear();
|
|
|
|
constantMap.clear();
|
|
tableShapeMap.clear();
|
|
protoMap.clear();
|
|
|
|
debugRemarks.clear();
|
|
debugRemarkBuffer.clear();
|
|
}
|
|
|
|
void BytecodeBuilder::setMainFunction(uint32_t fid)
|
|
{
|
|
LUAU_ASSERT(fid < functions.size());
|
|
|
|
mainFunction = fid;
|
|
}
|
|
|
|
int32_t BytecodeBuilder::addConstant(const ConstantKey& key, const Constant& value)
|
|
{
|
|
if (int32_t* cache = constantMap.find(key))
|
|
return *cache;
|
|
|
|
uint32_t id = uint32_t(constants.size());
|
|
|
|
if (id >= kMaxConstantCount)
|
|
return -1;
|
|
|
|
constantMap[key] = int32_t(id);
|
|
constants.push_back(value);
|
|
|
|
return int32_t(id);
|
|
}
|
|
|
|
unsigned int BytecodeBuilder::addStringTableEntry(StringRef value)
|
|
{
|
|
unsigned int& index = stringTable[value];
|
|
|
|
// note: bytecode serialization format uses 1-based table indices, 0 is reserved to mean nil
|
|
if (index == 0)
|
|
{
|
|
index = uint32_t(stringTable.size());
|
|
|
|
if ((dumpFlags & Dump_Code) != 0)
|
|
debugStrings.push_back(value);
|
|
}
|
|
|
|
return index;
|
|
}
|
|
|
|
int32_t BytecodeBuilder::addConstantNil()
|
|
{
|
|
Constant c = {Constant::Type_Nil};
|
|
|
|
ConstantKey k = {Constant::Type_Nil};
|
|
return addConstant(k, c);
|
|
}
|
|
|
|
int32_t BytecodeBuilder::addConstantBoolean(bool value)
|
|
{
|
|
Constant c = {Constant::Type_Boolean};
|
|
c.valueBoolean = value;
|
|
|
|
ConstantKey k = {Constant::Type_Boolean, value};
|
|
return addConstant(k, c);
|
|
}
|
|
|
|
int32_t BytecodeBuilder::addConstantNumber(double value)
|
|
{
|
|
Constant c = {Constant::Type_Number};
|
|
c.valueNumber = value;
|
|
|
|
ConstantKey k = {Constant::Type_Number};
|
|
static_assert(sizeof(k.value) == sizeof(value), "Expecting double to be 64-bit");
|
|
memcpy(&k.value, &value, sizeof(value));
|
|
|
|
return addConstant(k, c);
|
|
}
|
|
|
|
int32_t BytecodeBuilder::addConstantString(StringRef value)
|
|
{
|
|
unsigned int index = addStringTableEntry(value);
|
|
|
|
Constant c = {Constant::Type_String};
|
|
c.valueString = index;
|
|
|
|
ConstantKey k = {Constant::Type_String, index};
|
|
|
|
return addConstant(k, c);
|
|
}
|
|
|
|
int32_t BytecodeBuilder::addImport(uint32_t iid)
|
|
{
|
|
Constant c = {Constant::Type_Import};
|
|
c.valueImport = iid;
|
|
|
|
ConstantKey k = {Constant::Type_Import, iid};
|
|
|
|
return addConstant(k, c);
|
|
}
|
|
|
|
int32_t BytecodeBuilder::addConstantTable(const TableShape& shape)
|
|
{
|
|
if (int32_t* cache = tableShapeMap.find(shape))
|
|
return *cache;
|
|
|
|
uint32_t id = uint32_t(constants.size());
|
|
|
|
if (id >= kMaxConstantCount)
|
|
return -1;
|
|
|
|
Constant value = {Constant::Type_Table};
|
|
value.valueTable = uint32_t(tableShapes.size());
|
|
|
|
tableShapeMap[shape] = int32_t(id);
|
|
tableShapes.push_back(shape);
|
|
constants.push_back(value);
|
|
|
|
return int32_t(id);
|
|
}
|
|
|
|
int32_t BytecodeBuilder::addConstantClosure(uint32_t fid)
|
|
{
|
|
Constant c = {Constant::Type_Closure};
|
|
c.valueClosure = fid;
|
|
|
|
ConstantKey k = {Constant::Type_Closure, fid};
|
|
|
|
return addConstant(k, c);
|
|
}
|
|
|
|
int16_t BytecodeBuilder::addChildFunction(uint32_t fid)
|
|
{
|
|
if (int16_t* cache = protoMap.find(fid))
|
|
return *cache;
|
|
|
|
uint32_t id = uint32_t(protos.size());
|
|
|
|
if (id >= kMaxClosureCount)
|
|
return -1;
|
|
|
|
protoMap[fid] = int16_t(id);
|
|
protos.push_back(fid);
|
|
|
|
return int16_t(id);
|
|
}
|
|
|
|
void BytecodeBuilder::emitABC(LuauOpcode op, uint8_t a, uint8_t b, uint8_t c)
|
|
{
|
|
uint32_t insn = uint32_t(op) | (a << 8) | (b << 16) | (c << 24);
|
|
|
|
insns.push_back(insn);
|
|
lines.push_back(debugLine);
|
|
}
|
|
|
|
void BytecodeBuilder::emitAD(LuauOpcode op, uint8_t a, int16_t d)
|
|
{
|
|
uint32_t insn = uint32_t(op) | (a << 8) | (uint16_t(d) << 16);
|
|
|
|
insns.push_back(insn);
|
|
lines.push_back(debugLine);
|
|
}
|
|
|
|
void BytecodeBuilder::emitE(LuauOpcode op, int32_t e)
|
|
{
|
|
uint32_t insn = uint32_t(op) | (uint32_t(e) << 8);
|
|
|
|
insns.push_back(insn);
|
|
lines.push_back(debugLine);
|
|
}
|
|
|
|
void BytecodeBuilder::emitAux(uint32_t aux)
|
|
{
|
|
insns.push_back(aux);
|
|
lines.push_back(debugLine);
|
|
}
|
|
|
|
size_t BytecodeBuilder::emitLabel()
|
|
{
|
|
return insns.size();
|
|
}
|
|
|
|
bool BytecodeBuilder::patchJumpD(size_t jumpLabel, size_t targetLabel)
|
|
{
|
|
LUAU_ASSERT(jumpLabel < insns.size());
|
|
|
|
unsigned int jumpInsn = insns[jumpLabel];
|
|
(void)jumpInsn;
|
|
|
|
LUAU_ASSERT(isJumpD(LuauOpcode(LUAU_INSN_OP(jumpInsn))));
|
|
LUAU_ASSERT(LUAU_INSN_D(jumpInsn) == 0);
|
|
|
|
LUAU_ASSERT(targetLabel <= insns.size());
|
|
|
|
int offset = int(targetLabel) - int(jumpLabel) - 1;
|
|
|
|
if (int16_t(offset) == offset)
|
|
{
|
|
insns[jumpLabel] |= uint16_t(offset) << 16;
|
|
}
|
|
else if (abs(offset) < kMaxJumpDistance)
|
|
{
|
|
// our jump doesn't fit into 16 bits; we will need to repatch the bytecode sequence with jump trampolines, see expandJumps
|
|
hasLongJumps = true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
|
|
jumps.push_back({uint32_t(jumpLabel), uint32_t(targetLabel)});
|
|
return true;
|
|
}
|
|
|
|
bool BytecodeBuilder::patchSkipC(size_t jumpLabel, size_t targetLabel)
|
|
{
|
|
LUAU_ASSERT(jumpLabel < insns.size());
|
|
|
|
unsigned int jumpInsn = insns[jumpLabel];
|
|
(void)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;
|
|
|
|
if (uint8_t(offset) != offset)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
insns[jumpLabel] |= offset << 24;
|
|
return true;
|
|
}
|
|
|
|
void BytecodeBuilder::setDebugFunctionName(StringRef name)
|
|
{
|
|
unsigned int index = addStringTableEntry(name);
|
|
|
|
functions[currentFunction].debugname = index;
|
|
|
|
if (dumpFunctionPtr)
|
|
functions[currentFunction].dumpname = std::string(name.data, name.length);
|
|
}
|
|
|
|
void BytecodeBuilder::setDebugFunctionLineDefined(int line)
|
|
{
|
|
functions[currentFunction].debuglinedefined = line;
|
|
}
|
|
|
|
void BytecodeBuilder::setDebugLine(int line)
|
|
{
|
|
debugLine = line;
|
|
}
|
|
|
|
void BytecodeBuilder::pushDebugLocal(StringRef name, uint8_t reg, uint32_t startpc, uint32_t endpc)
|
|
{
|
|
unsigned int index = addStringTableEntry(name);
|
|
|
|
DebugLocal local;
|
|
local.name = index;
|
|
local.reg = reg;
|
|
local.startpc = startpc;
|
|
local.endpc = endpc;
|
|
|
|
debugLocals.push_back(local);
|
|
}
|
|
|
|
void BytecodeBuilder::pushDebugUpval(StringRef name)
|
|
{
|
|
unsigned int index = addStringTableEntry(name);
|
|
|
|
DebugUpval upval;
|
|
upval.name = index;
|
|
|
|
debugUpvals.push_back(upval);
|
|
}
|
|
|
|
size_t BytecodeBuilder::getInstructionCount() const
|
|
{
|
|
return insns.size();
|
|
}
|
|
|
|
uint32_t BytecodeBuilder::getDebugPC() const
|
|
{
|
|
return uint32_t(insns.size());
|
|
}
|
|
|
|
void BytecodeBuilder::addDebugRemark(const char* format, ...)
|
|
{
|
|
if ((dumpFlags & Dump_Remarks) == 0)
|
|
return;
|
|
|
|
size_t offset = debugRemarkBuffer.size();
|
|
|
|
va_list args;
|
|
va_start(args, format);
|
|
vformatAppend(debugRemarkBuffer, format, args);
|
|
va_end(args);
|
|
|
|
// we null-terminate all remarks to avoid storing remark length
|
|
debugRemarkBuffer += '\0';
|
|
|
|
debugRemarks.emplace_back(uint32_t(insns.size()), uint32_t(offset));
|
|
dumpRemarks.emplace_back(debugLine, debugRemarkBuffer.c_str() + offset);
|
|
}
|
|
|
|
void BytecodeBuilder::finalize()
|
|
{
|
|
LUAU_ASSERT(bytecode.empty());
|
|
|
|
// preallocate space for bytecode blob
|
|
size_t capacity = 16;
|
|
|
|
for (auto& p : stringTable)
|
|
capacity += p.first.length + 2;
|
|
|
|
for (const Function& func : functions)
|
|
capacity += func.data.size();
|
|
|
|
bytecode.reserve(capacity);
|
|
|
|
// assemble final bytecode blob
|
|
uint8_t version = getVersion();
|
|
LUAU_ASSERT(version >= LBC_VERSION_MIN && version <= LBC_VERSION_MAX);
|
|
|
|
bytecode = char(version);
|
|
|
|
writeStringTable(bytecode);
|
|
|
|
writeVarInt(bytecode, uint32_t(functions.size()));
|
|
|
|
for (const Function& func : functions)
|
|
bytecode += func.data;
|
|
|
|
LUAU_ASSERT(mainFunction < functions.size());
|
|
writeVarInt(bytecode, mainFunction);
|
|
}
|
|
|
|
void BytecodeBuilder::writeFunction(std::string& ss, uint32_t id) const
|
|
{
|
|
LUAU_ASSERT(id < functions.size());
|
|
const Function& func = functions[id];
|
|
|
|
// header
|
|
writeByte(ss, func.maxstacksize);
|
|
writeByte(ss, func.numparams);
|
|
writeByte(ss, func.numupvalues);
|
|
writeByte(ss, func.isvararg);
|
|
|
|
// instructions
|
|
writeVarInt(ss, uint32_t(insns.size()));
|
|
|
|
for (size_t i = 0; i < insns.size();)
|
|
{
|
|
uint8_t op = LUAU_INSN_OP(insns[i]);
|
|
LUAU_ASSERT(op < LOP__COUNT);
|
|
|
|
int oplen = getOpLength(LuauOpcode(op));
|
|
uint8_t openc = encoder ? encoder->encodeOp(op) : op;
|
|
|
|
writeInt(ss, openc | (insns[i] & ~0xff));
|
|
|
|
for (int j = 1; j < oplen; ++j)
|
|
writeInt(ss, insns[i + j]);
|
|
|
|
i += oplen;
|
|
}
|
|
|
|
// constants
|
|
writeVarInt(ss, uint32_t(constants.size()));
|
|
|
|
for (const Constant& c : constants)
|
|
{
|
|
switch (c.type)
|
|
{
|
|
case Constant::Type_Nil:
|
|
writeByte(ss, LBC_CONSTANT_NIL);
|
|
break;
|
|
|
|
case Constant::Type_Boolean:
|
|
writeByte(ss, LBC_CONSTANT_BOOLEAN);
|
|
writeByte(ss, c.valueBoolean);
|
|
break;
|
|
|
|
case Constant::Type_Number:
|
|
writeByte(ss, LBC_CONSTANT_NUMBER);
|
|
writeDouble(ss, c.valueNumber);
|
|
break;
|
|
|
|
case Constant::Type_String:
|
|
writeByte(ss, LBC_CONSTANT_STRING);
|
|
writeVarInt(ss, c.valueString);
|
|
break;
|
|
|
|
case Constant::Type_Import:
|
|
writeByte(ss, LBC_CONSTANT_IMPORT);
|
|
writeInt(ss, c.valueImport);
|
|
break;
|
|
|
|
case Constant::Type_Table:
|
|
{
|
|
const TableShape& shape = tableShapes[c.valueTable];
|
|
writeByte(ss, LBC_CONSTANT_TABLE);
|
|
writeVarInt(ss, uint32_t(shape.length));
|
|
for (unsigned int i = 0; i < shape.length; ++i)
|
|
writeVarInt(ss, shape.keys[i]);
|
|
break;
|
|
}
|
|
|
|
case Constant::Type_Closure:
|
|
writeByte(ss, LBC_CONSTANT_CLOSURE);
|
|
writeVarInt(ss, c.valueClosure);
|
|
break;
|
|
|
|
default:
|
|
LUAU_ASSERT(!"Unsupported constant type");
|
|
}
|
|
}
|
|
|
|
// child protos
|
|
writeVarInt(ss, uint32_t(protos.size()));
|
|
|
|
for (uint32_t child : protos)
|
|
writeVarInt(ss, child);
|
|
|
|
// debug info
|
|
writeVarInt(ss, func.debuglinedefined);
|
|
writeVarInt(ss, func.debugname);
|
|
|
|
bool hasLines = true;
|
|
|
|
for (int line : lines)
|
|
if (line == 0)
|
|
{
|
|
hasLines = false;
|
|
break;
|
|
}
|
|
|
|
if (hasLines)
|
|
{
|
|
writeByte(ss, 1);
|
|
|
|
writeLineInfo(ss);
|
|
}
|
|
else
|
|
{
|
|
writeByte(ss, 0);
|
|
}
|
|
|
|
bool hasDebug = !debugLocals.empty() || !debugUpvals.empty();
|
|
|
|
if (hasDebug)
|
|
{
|
|
writeByte(ss, 1);
|
|
|
|
writeVarInt(ss, uint32_t(debugLocals.size()));
|
|
|
|
for (const DebugLocal& l : debugLocals)
|
|
{
|
|
writeVarInt(ss, l.name);
|
|
writeVarInt(ss, l.startpc);
|
|
writeVarInt(ss, l.endpc);
|
|
writeByte(ss, l.reg);
|
|
}
|
|
|
|
writeVarInt(ss, uint32_t(debugUpvals.size()));
|
|
|
|
for (const DebugUpval& l : debugUpvals)
|
|
{
|
|
writeVarInt(ss, l.name);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
writeByte(ss, 0);
|
|
}
|
|
}
|
|
|
|
void BytecodeBuilder::writeLineInfo(std::string& ss) const
|
|
{
|
|
LUAU_ASSERT(!lines.empty());
|
|
|
|
// this function encodes lines inside each span as a 8-bit delta to span baseline
|
|
// span is always a power of two; depending on the line info input, it may need to be as low as 1
|
|
int span = 1 << 24;
|
|
|
|
// first pass: determine span length
|
|
for (size_t offset = 0; offset < lines.size(); offset += span)
|
|
{
|
|
size_t next = offset;
|
|
|
|
int min = lines[offset];
|
|
int max = lines[offset];
|
|
|
|
for (; next < lines.size() && next < offset + span; ++next)
|
|
{
|
|
min = std::min(min, lines[next]);
|
|
max = std::max(max, lines[next]);
|
|
|
|
if (max - min > 255)
|
|
break;
|
|
}
|
|
|
|
if (next < lines.size() && next - offset < size_t(span))
|
|
{
|
|
// since not all lines in the range fit in 8b delta, we need to shrink the span
|
|
// next iteration will need to reprocess some lines again since span changed
|
|
span = 1 << log2(int(next - offset));
|
|
}
|
|
}
|
|
|
|
// second pass: compute span base
|
|
int baselineOne = 0;
|
|
std::vector<int> baselineScratch;
|
|
int* baseline = &baselineOne;
|
|
size_t baselineSize = (lines.size() - 1) / span + 1;
|
|
|
|
if (baselineSize > 1)
|
|
{
|
|
// avoid heap allocation for single-element baseline which is most functions (<256 lines)
|
|
baselineScratch.resize(baselineSize);
|
|
baseline = baselineScratch.data();
|
|
}
|
|
|
|
for (size_t offset = 0; offset < lines.size(); offset += span)
|
|
{
|
|
size_t next = offset;
|
|
|
|
int min = lines[offset];
|
|
|
|
for (; next < lines.size() && next < offset + span; ++next)
|
|
min = std::min(min, lines[next]);
|
|
|
|
baseline[offset / span] = min;
|
|
}
|
|
|
|
// third pass: write resulting data
|
|
int logspan = log2(span);
|
|
|
|
writeByte(ss, uint8_t(logspan));
|
|
|
|
uint8_t lastOffset = 0;
|
|
|
|
for (size_t i = 0; i < lines.size(); ++i)
|
|
{
|
|
int delta = lines[i] - baseline[i >> logspan];
|
|
LUAU_ASSERT(delta >= 0 && delta <= 255);
|
|
|
|
writeByte(ss, uint8_t(delta) - lastOffset);
|
|
lastOffset = uint8_t(delta);
|
|
}
|
|
|
|
int lastLine = 0;
|
|
|
|
for (size_t i = 0; i < baselineSize; ++i)
|
|
{
|
|
writeInt(ss, baseline[i] - lastLine);
|
|
lastLine = baseline[i];
|
|
}
|
|
}
|
|
|
|
void BytecodeBuilder::writeStringTable(std::string& ss) const
|
|
{
|
|
std::vector<StringRef> strings(stringTable.size());
|
|
|
|
for (auto& p : stringTable)
|
|
{
|
|
LUAU_ASSERT(p.second > 0 && p.second <= strings.size());
|
|
strings[p.second - 1] = p.first;
|
|
}
|
|
|
|
writeVarInt(ss, uint32_t(strings.size()));
|
|
|
|
for (auto& s : strings)
|
|
{
|
|
writeVarInt(ss, uint32_t(s.length));
|
|
ss.append(s.data, s.length);
|
|
}
|
|
}
|
|
|
|
uint32_t BytecodeBuilder::getImportId(int32_t id0)
|
|
{
|
|
LUAU_ASSERT(unsigned(id0) < 1024);
|
|
|
|
return (1u << 30) | (id0 << 20);
|
|
}
|
|
|
|
uint32_t BytecodeBuilder::getImportId(int32_t id0, int32_t id1)
|
|
{
|
|
LUAU_ASSERT(unsigned(id0 | id1) < 1024);
|
|
|
|
return (2u << 30) | (id0 << 20) | (id1 << 10);
|
|
}
|
|
|
|
uint32_t BytecodeBuilder::getImportId(int32_t id0, int32_t id1, int32_t id2)
|
|
{
|
|
LUAU_ASSERT(unsigned(id0 | id1 | id2) < 1024);
|
|
|
|
return (3u << 30) | (id0 << 20) | (id1 << 10) | id2;
|
|
}
|
|
|
|
int BytecodeBuilder::decomposeImportId(uint32_t ids, int32_t& id0, int32_t& id1, int32_t& id2)
|
|
{
|
|
int count = ids >> 30;
|
|
id0 = count > 0 ? int(ids >> 20) & 1023 : -1;
|
|
id1 = count > 1 ? int(ids >> 10) & 1023 : -1;
|
|
id2 = count > 2 ? int(ids) & 1023 : -1;
|
|
return count;
|
|
}
|
|
|
|
uint32_t BytecodeBuilder::getStringHash(StringRef key)
|
|
{
|
|
// This hashing algorithm should match luaS_hash defined in VM/lstring.cpp for short inputs; we can't use that code directly to keep compiler and
|
|
// VM independent in terms of compilation/linking. The resulting string hashes are embedded into bytecode binary and result in a better initial
|
|
// guess for the field hashes which improves performance during initial code execution. We omit the long string processing here for simplicity, as
|
|
// it doesn't really matter on long identifiers.
|
|
const char* str = key.data;
|
|
size_t len = key.length;
|
|
|
|
unsigned int h = unsigned(len);
|
|
|
|
// original Lua 5.1 hash for compatibility (exact match when len<32)
|
|
for (size_t i = len; i > 0; --i)
|
|
h ^= (h << 5) + (h >> 2) + (uint8_t)str[i - 1];
|
|
|
|
return h;
|
|
}
|
|
|
|
void BytecodeBuilder::foldJumps()
|
|
{
|
|
// if our function has long jumps, some processing below can make jump instructions not-jumps (e.g. JUMP->RETURN)
|
|
// it's safer to skip this processing
|
|
if (hasLongJumps)
|
|
return;
|
|
|
|
for (Jump& jump : jumps)
|
|
{
|
|
uint32_t jumpLabel = jump.source;
|
|
|
|
uint32_t jumpInsn = insns[jumpLabel];
|
|
|
|
// follow jump target through forward unconditional jumps
|
|
// we only follow forward jumps to make sure the process terminates
|
|
uint32_t targetLabel = jumpLabel + 1 + LUAU_INSN_D(jumpInsn);
|
|
LUAU_ASSERT(targetLabel < insns.size());
|
|
uint32_t targetInsn = insns[targetLabel];
|
|
|
|
while (LUAU_INSN_OP(targetInsn) == LOP_JUMP && LUAU_INSN_D(targetInsn) >= 0)
|
|
{
|
|
targetLabel = targetLabel + 1 + LUAU_INSN_D(targetInsn);
|
|
LUAU_ASSERT(targetLabel < insns.size());
|
|
targetInsn = insns[targetLabel];
|
|
}
|
|
|
|
int offset = int(targetLabel) - int(jumpLabel) - 1;
|
|
|
|
// for unconditional jumps to RETURN, we can replace JUMP with RETURN
|
|
if (LUAU_INSN_OP(jumpInsn) == LOP_JUMP && LUAU_INSN_OP(targetInsn) == LOP_RETURN)
|
|
{
|
|
insns[jumpLabel] = targetInsn;
|
|
lines[jumpLabel] = lines[targetLabel];
|
|
}
|
|
else if (int16_t(offset) == offset)
|
|
{
|
|
insns[jumpLabel] &= 0xffff;
|
|
insns[jumpLabel] |= uint16_t(offset) << 16;
|
|
}
|
|
|
|
jump.target = targetLabel;
|
|
}
|
|
}
|
|
|
|
void BytecodeBuilder::expandJumps()
|
|
{
|
|
if (!hasLongJumps)
|
|
return;
|
|
|
|
// we have some jump instructions that couldn't be patched which means their offset didn't fit into 16 bits
|
|
// our strategy for replacing instructions is as follows: instead of
|
|
// OP jumpoffset
|
|
// we will synthesize a jump trampoline before our instruction (note that jump offsets are relative to next instruction):
|
|
// JUMP +1
|
|
// JUMPX jumpoffset
|
|
// OP -2
|
|
// the idea is that during forward execution, we will jump over JUMPX into OP; if OP decides to jump, it will jump to JUMPX
|
|
// JUMPX can carry a 24-bit jump offset
|
|
|
|
// jump trampolines expand the code size, which can increase existing jump distances.
|
|
// because of this, we may need to expand jumps that previously fit into 16-bit just fine.
|
|
// the worst-case expansion is 3x, so to be conservative we will repatch all jumps that have an offset >= 32767/3
|
|
const int kMaxJumpDistanceConservative = 32767 / 3;
|
|
|
|
// we will need to process jumps in order
|
|
std::sort(jumps.begin(), jumps.end(), [](const Jump& lhs, const Jump& rhs) {
|
|
return lhs.source < rhs.source;
|
|
});
|
|
|
|
// first, let's add jump thunks for every jump with a distance that's too big
|
|
// we will create new instruction buffers, with remap table keeping track of the moves: remap[oldpc] = newpc
|
|
std::vector<uint32_t> remap(insns.size());
|
|
|
|
std::vector<uint32_t> newinsns;
|
|
std::vector<int> newlines;
|
|
|
|
LUAU_ASSERT(insns.size() == lines.size());
|
|
newinsns.reserve(insns.size());
|
|
newlines.reserve(insns.size());
|
|
|
|
size_t currentJump = 0;
|
|
size_t pendingTrampolines = 0;
|
|
|
|
for (size_t i = 0; i < insns.size();)
|
|
{
|
|
uint8_t op = LUAU_INSN_OP(insns[i]);
|
|
LUAU_ASSERT(op < LOP__COUNT);
|
|
|
|
if (currentJump < jumps.size() && jumps[currentJump].source == i)
|
|
{
|
|
int offset = int(jumps[currentJump].target) - int(jumps[currentJump].source) - 1;
|
|
|
|
if (abs(offset) > kMaxJumpDistanceConservative)
|
|
{
|
|
// insert jump trampoline as described above; we keep JUMPX offset uninitialized in this pass
|
|
newinsns.push_back(LOP_JUMP | (1 << 16));
|
|
newinsns.push_back(LOP_JUMPX);
|
|
|
|
newlines.push_back(lines[i]);
|
|
newlines.push_back(lines[i]);
|
|
|
|
pendingTrampolines++;
|
|
}
|
|
|
|
currentJump++;
|
|
}
|
|
|
|
int oplen = getOpLength(LuauOpcode(op));
|
|
|
|
// copy instruction and line info to the new stream
|
|
for (int j = 0; j < oplen; ++j)
|
|
{
|
|
remap[i] = uint32_t(newinsns.size());
|
|
|
|
newinsns.push_back(insns[i]);
|
|
newlines.push_back(lines[i]);
|
|
|
|
i++;
|
|
}
|
|
}
|
|
|
|
LUAU_ASSERT(currentJump == jumps.size());
|
|
LUAU_ASSERT(pendingTrampolines > 0);
|
|
|
|
// now we need to recompute offsets for jump instructions - we could not do this in the first pass because the offsets are between *target*
|
|
// instructions
|
|
for (Jump& jump : jumps)
|
|
{
|
|
int offset = int(jump.target) - int(jump.source) - 1;
|
|
int newoffset = int(remap[jump.target]) - int(remap[jump.source]) - 1;
|
|
|
|
if (abs(offset) > kMaxJumpDistanceConservative)
|
|
{
|
|
// fix up jump trampoline
|
|
uint32_t& insnt = newinsns[remap[jump.source] - 1];
|
|
uint32_t& insnj = newinsns[remap[jump.source]];
|
|
|
|
LUAU_ASSERT(LUAU_INSN_OP(insnt) == LOP_JUMPX);
|
|
|
|
// patch JUMPX to JUMPX to target location; note that newoffset is the offset of the jump *relative to OP*, so we need to add 1 to make it
|
|
// relative to JUMPX
|
|
insnt &= 0xff;
|
|
insnt |= uint32_t(newoffset + 1) << 8;
|
|
|
|
// patch OP to OP -2
|
|
insnj &= 0xffff;
|
|
insnj |= uint16_t(-2) << 16;
|
|
|
|
pendingTrampolines--;
|
|
}
|
|
else
|
|
{
|
|
uint32_t& insn = newinsns[remap[jump.source]];
|
|
|
|
// make sure jump instruction had the correct offset before we started
|
|
LUAU_ASSERT(LUAU_INSN_D(insn) == offset);
|
|
|
|
// patch instruction with the new offset
|
|
LUAU_ASSERT(int16_t(newoffset) == newoffset);
|
|
|
|
insn &= 0xffff;
|
|
insn |= uint16_t(newoffset) << 16;
|
|
}
|
|
}
|
|
|
|
LUAU_ASSERT(pendingTrampolines == 0);
|
|
|
|
// this was hard, but we're done.
|
|
insns.swap(newinsns);
|
|
lines.swap(newlines);
|
|
}
|
|
|
|
std::string BytecodeBuilder::getError(const std::string& message)
|
|
{
|
|
// 0 acts as a special marker for error bytecode (it's equal to LBC_VERSION_TARGET for valid bytecode blobs)
|
|
std::string result;
|
|
result += char(0);
|
|
result += message;
|
|
|
|
return result;
|
|
}
|
|
|
|
uint8_t BytecodeBuilder::getVersion()
|
|
{
|
|
// This function usually returns LBC_VERSION_TARGET but may sometimes return a higher number (within LBC_VERSION_MIN/MAX) under fast flags
|
|
return LBC_VERSION_TARGET;
|
|
}
|
|
|
|
#ifdef LUAU_ASSERTENABLED
|
|
void BytecodeBuilder::validate() const
|
|
{
|
|
validateInstructions();
|
|
validateVariadic();
|
|
}
|
|
|
|
void BytecodeBuilder::validateInstructions() const
|
|
{
|
|
#define VREG(v) LUAU_ASSERT(unsigned(v) < func.maxstacksize)
|
|
#define VREGRANGE(v, count) LUAU_ASSERT(unsigned(v + (count < 0 ? 0 : count)) <= func.maxstacksize)
|
|
#define VUPVAL(v) LUAU_ASSERT(unsigned(v) < func.numupvalues)
|
|
#define VCONST(v, kind) LUAU_ASSERT(unsigned(v) < constants.size() && constants[v].type == Constant::Type_##kind)
|
|
#define VCONSTANY(v) LUAU_ASSERT(unsigned(v) < constants.size())
|
|
#define VJUMP(v) LUAU_ASSERT(size_t(i + 1 + v) < insns.size() && insnvalid[i + 1 + v])
|
|
|
|
LUAU_ASSERT(currentFunction != ~0u);
|
|
|
|
const Function& func = functions[currentFunction];
|
|
|
|
// tag instruction offsets so that we can validate jumps
|
|
std::vector<uint8_t> insnvalid(insns.size(), 0);
|
|
|
|
for (size_t i = 0; i < insns.size();)
|
|
{
|
|
uint32_t insn = insns[i];
|
|
LuauOpcode op = LuauOpcode(LUAU_INSN_OP(insn));
|
|
|
|
insnvalid[i] = true;
|
|
|
|
i += getOpLength(op);
|
|
LUAU_ASSERT(i <= insns.size());
|
|
}
|
|
|
|
std::vector<uint8_t> openCaptures;
|
|
|
|
// validate individual instructions
|
|
for (size_t i = 0; i < insns.size();)
|
|
{
|
|
uint32_t insn = insns[i];
|
|
LuauOpcode op = LuauOpcode(LUAU_INSN_OP(insn));
|
|
|
|
switch (op)
|
|
{
|
|
case LOP_LOADNIL:
|
|
VREG(LUAU_INSN_A(insn));
|
|
break;
|
|
|
|
case LOP_LOADB:
|
|
VREG(LUAU_INSN_A(insn));
|
|
LUAU_ASSERT(LUAU_INSN_B(insn) == 0 || LUAU_INSN_B(insn) == 1);
|
|
VJUMP(LUAU_INSN_C(insn));
|
|
break;
|
|
|
|
case LOP_LOADN:
|
|
VREG(LUAU_INSN_A(insn));
|
|
break;
|
|
|
|
case LOP_LOADK:
|
|
VREG(LUAU_INSN_A(insn));
|
|
VCONSTANY(LUAU_INSN_D(insn));
|
|
break;
|
|
|
|
case LOP_MOVE:
|
|
VREG(LUAU_INSN_A(insn));
|
|
VREG(LUAU_INSN_B(insn));
|
|
break;
|
|
|
|
case LOP_GETGLOBAL:
|
|
case LOP_SETGLOBAL:
|
|
VREG(LUAU_INSN_A(insn));
|
|
VCONST(insns[i + 1], String);
|
|
break;
|
|
|
|
case LOP_GETUPVAL:
|
|
case LOP_SETUPVAL:
|
|
VREG(LUAU_INSN_A(insn));
|
|
VUPVAL(LUAU_INSN_B(insn));
|
|
break;
|
|
|
|
case LOP_CLOSEUPVALS:
|
|
VREG(LUAU_INSN_A(insn));
|
|
while (openCaptures.size() && openCaptures.back() >= LUAU_INSN_A(insn))
|
|
openCaptures.pop_back();
|
|
break;
|
|
|
|
case LOP_GETIMPORT:
|
|
VREG(LUAU_INSN_A(insn));
|
|
VCONST(LUAU_INSN_D(insn), Import);
|
|
// TODO: check insn[i + 1] for conformance with 10-bit import encoding
|
|
break;
|
|
|
|
case LOP_GETTABLE:
|
|
case LOP_SETTABLE:
|
|
VREG(LUAU_INSN_A(insn));
|
|
VREG(LUAU_INSN_B(insn));
|
|
VREG(LUAU_INSN_C(insn));
|
|
break;
|
|
|
|
case LOP_GETTABLEKS:
|
|
case LOP_SETTABLEKS:
|
|
VREG(LUAU_INSN_A(insn));
|
|
VREG(LUAU_INSN_B(insn));
|
|
VCONST(insns[i + 1], String);
|
|
break;
|
|
|
|
case LOP_GETTABLEN:
|
|
case LOP_SETTABLEN:
|
|
VREG(LUAU_INSN_A(insn));
|
|
VREG(LUAU_INSN_B(insn));
|
|
break;
|
|
|
|
case LOP_NEWCLOSURE:
|
|
{
|
|
VREG(LUAU_INSN_A(insn));
|
|
LUAU_ASSERT(unsigned(LUAU_INSN_D(insn)) < protos.size());
|
|
LUAU_ASSERT(protos[LUAU_INSN_D(insn)] < functions.size());
|
|
unsigned int numupvalues = functions[protos[LUAU_INSN_D(insn)]].numupvalues;
|
|
|
|
for (unsigned int j = 0; j < numupvalues; ++j)
|
|
{
|
|
LUAU_ASSERT(i + 1 + j < insns.size());
|
|
uint32_t cinsn = insns[i + 1 + j];
|
|
LUAU_ASSERT(LUAU_INSN_OP(cinsn) == LOP_CAPTURE);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case LOP_NAMECALL:
|
|
VREG(LUAU_INSN_A(insn));
|
|
VREG(LUAU_INSN_B(insn));
|
|
VCONST(insns[i + 1], String);
|
|
LUAU_ASSERT(LUAU_INSN_OP(insns[i + 2]) == LOP_CALL);
|
|
break;
|
|
|
|
case LOP_CALL:
|
|
{
|
|
int nparams = LUAU_INSN_B(insn) - 1;
|
|
int nresults = LUAU_INSN_C(insn) - 1;
|
|
VREG(LUAU_INSN_A(insn));
|
|
VREGRANGE(LUAU_INSN_A(insn) + 1, nparams); // 1..nparams
|
|
VREGRANGE(LUAU_INSN_A(insn), nresults); // 1..nresults
|
|
}
|
|
break;
|
|
|
|
case LOP_RETURN:
|
|
{
|
|
int nresults = LUAU_INSN_B(insn) - 1;
|
|
VREGRANGE(LUAU_INSN_A(insn), nresults); // 0..nresults-1
|
|
}
|
|
break;
|
|
|
|
case LOP_JUMP:
|
|
VJUMP(LUAU_INSN_D(insn));
|
|
break;
|
|
|
|
case LOP_JUMPIF:
|
|
case LOP_JUMPIFNOT:
|
|
VREG(LUAU_INSN_A(insn));
|
|
VJUMP(LUAU_INSN_D(insn));
|
|
break;
|
|
|
|
case LOP_JUMPIFEQ:
|
|
case LOP_JUMPIFLE:
|
|
case LOP_JUMPIFLT:
|
|
case LOP_JUMPIFNOTEQ:
|
|
case LOP_JUMPIFNOTLE:
|
|
case LOP_JUMPIFNOTLT:
|
|
VREG(LUAU_INSN_A(insn));
|
|
VREG(insns[i + 1]);
|
|
VJUMP(LUAU_INSN_D(insn));
|
|
break;
|
|
|
|
case LOP_JUMPXEQKNIL:
|
|
case LOP_JUMPXEQKB:
|
|
VREG(LUAU_INSN_A(insn));
|
|
VJUMP(LUAU_INSN_D(insn));
|
|
break;
|
|
|
|
case LOP_JUMPXEQKN:
|
|
VREG(LUAU_INSN_A(insn));
|
|
VCONST(insns[i + 1] & 0xffffff, Number);
|
|
VJUMP(LUAU_INSN_D(insn));
|
|
break;
|
|
|
|
case LOP_JUMPXEQKS:
|
|
VREG(LUAU_INSN_A(insn));
|
|
VCONST(insns[i + 1] & 0xffffff, String);
|
|
VJUMP(LUAU_INSN_D(insn));
|
|
break;
|
|
|
|
case LOP_ADD:
|
|
case LOP_SUB:
|
|
case LOP_MUL:
|
|
case LOP_DIV:
|
|
case LOP_MOD:
|
|
case LOP_POW:
|
|
VREG(LUAU_INSN_A(insn));
|
|
VREG(LUAU_INSN_B(insn));
|
|
VREG(LUAU_INSN_C(insn));
|
|
break;
|
|
|
|
case LOP_ADDK:
|
|
case LOP_SUBK:
|
|
case LOP_MULK:
|
|
case LOP_DIVK:
|
|
case LOP_MODK:
|
|
case LOP_POWK:
|
|
VREG(LUAU_INSN_A(insn));
|
|
VREG(LUAU_INSN_B(insn));
|
|
VCONST(LUAU_INSN_C(insn), Number);
|
|
break;
|
|
|
|
case LOP_AND:
|
|
case LOP_OR:
|
|
VREG(LUAU_INSN_A(insn));
|
|
VREG(LUAU_INSN_B(insn));
|
|
VREG(LUAU_INSN_C(insn));
|
|
break;
|
|
|
|
case LOP_ANDK:
|
|
case LOP_ORK:
|
|
VREG(LUAU_INSN_A(insn));
|
|
VREG(LUAU_INSN_B(insn));
|
|
VCONSTANY(LUAU_INSN_C(insn));
|
|
break;
|
|
|
|
case LOP_CONCAT:
|
|
VREG(LUAU_INSN_A(insn));
|
|
VREG(LUAU_INSN_B(insn));
|
|
VREG(LUAU_INSN_C(insn));
|
|
LUAU_ASSERT(LUAU_INSN_B(insn) <= LUAU_INSN_C(insn));
|
|
break;
|
|
|
|
case LOP_NOT:
|
|
case LOP_MINUS:
|
|
case LOP_LENGTH:
|
|
VREG(LUAU_INSN_A(insn));
|
|
VREG(LUAU_INSN_B(insn));
|
|
break;
|
|
|
|
case LOP_NEWTABLE:
|
|
VREG(LUAU_INSN_A(insn));
|
|
break;
|
|
|
|
case LOP_DUPTABLE:
|
|
VREG(LUAU_INSN_A(insn));
|
|
VCONST(LUAU_INSN_D(insn), Table);
|
|
break;
|
|
|
|
case LOP_SETLIST:
|
|
{
|
|
int count = LUAU_INSN_C(insn) - 1;
|
|
VREG(LUAU_INSN_A(insn));
|
|
VREGRANGE(LUAU_INSN_B(insn), count);
|
|
}
|
|
break;
|
|
|
|
case LOP_FORNPREP:
|
|
case LOP_FORNLOOP:
|
|
// for loop protocol: A, A+1, A+2 are used for iteration
|
|
VREG(LUAU_INSN_A(insn) + 2);
|
|
VJUMP(LUAU_INSN_D(insn));
|
|
break;
|
|
|
|
case LOP_FORGPREP:
|
|
// forg loop protocol: A, A+1, A+2 are used for iteration protocol; A+3, ... are loop variables
|
|
VREG(LUAU_INSN_A(insn) + 2 + 1);
|
|
VJUMP(LUAU_INSN_D(insn));
|
|
break;
|
|
|
|
case LOP_FORGLOOP:
|
|
// forg loop protocol: A, A+1, A+2 are used for iteration protocol; A+3, ... are loop variables
|
|
VREG(LUAU_INSN_A(insn) + 2 + uint8_t(insns[i + 1]));
|
|
VJUMP(LUAU_INSN_D(insn));
|
|
LUAU_ASSERT(uint8_t(insns[i + 1]) >= 1);
|
|
break;
|
|
|
|
case LOP_FORGPREP_INEXT:
|
|
case LOP_FORGPREP_NEXT:
|
|
VREG(LUAU_INSN_A(insn) + 4); // forg loop protocol: A, A+1, A+2 are used for iteration protocol; A+3, A+4 are loop variables
|
|
VJUMP(LUAU_INSN_D(insn));
|
|
break;
|
|
|
|
case LOP_GETVARARGS:
|
|
{
|
|
int nresults = LUAU_INSN_B(insn) - 1;
|
|
VREGRANGE(LUAU_INSN_A(insn), nresults); // 0..nresults-1
|
|
}
|
|
break;
|
|
|
|
case LOP_DUPCLOSURE:
|
|
{
|
|
VREG(LUAU_INSN_A(insn));
|
|
VCONST(LUAU_INSN_D(insn), Closure);
|
|
unsigned int proto = constants[LUAU_INSN_D(insn)].valueClosure;
|
|
LUAU_ASSERT(proto < functions.size());
|
|
unsigned int numupvalues = functions[proto].numupvalues;
|
|
|
|
for (unsigned int j = 0; j < numupvalues; ++j)
|
|
{
|
|
LUAU_ASSERT(i + 1 + j < insns.size());
|
|
uint32_t cinsn = insns[i + 1 + j];
|
|
LUAU_ASSERT(LUAU_INSN_OP(cinsn) == LOP_CAPTURE);
|
|
LUAU_ASSERT(LUAU_INSN_A(cinsn) == LCT_VAL || LUAU_INSN_A(cinsn) == LCT_UPVAL);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case LOP_PREPVARARGS:
|
|
LUAU_ASSERT(LUAU_INSN_A(insn) == func.numparams);
|
|
LUAU_ASSERT(func.isvararg);
|
|
break;
|
|
|
|
case LOP_BREAK:
|
|
break;
|
|
|
|
case LOP_JUMPBACK:
|
|
VJUMP(LUAU_INSN_D(insn));
|
|
break;
|
|
|
|
case LOP_LOADKX:
|
|
VREG(LUAU_INSN_A(insn));
|
|
VCONSTANY(insns[i + 1]);
|
|
break;
|
|
|
|
case LOP_JUMPX:
|
|
VJUMP(LUAU_INSN_E(insn));
|
|
break;
|
|
|
|
case LOP_FASTCALL:
|
|
VJUMP(LUAU_INSN_C(insn));
|
|
LUAU_ASSERT(LUAU_INSN_OP(insns[i + 1 + LUAU_INSN_C(insn)]) == LOP_CALL);
|
|
break;
|
|
|
|
case LOP_FASTCALL1:
|
|
VREG(LUAU_INSN_B(insn));
|
|
VJUMP(LUAU_INSN_C(insn));
|
|
LUAU_ASSERT(LUAU_INSN_OP(insns[i + 1 + LUAU_INSN_C(insn)]) == LOP_CALL);
|
|
break;
|
|
|
|
case LOP_FASTCALL2:
|
|
VREG(LUAU_INSN_B(insn));
|
|
VJUMP(LUAU_INSN_C(insn));
|
|
LUAU_ASSERT(LUAU_INSN_OP(insns[i + 1 + LUAU_INSN_C(insn)]) == LOP_CALL);
|
|
VREG(insns[i + 1]);
|
|
break;
|
|
|
|
case LOP_FASTCALL2K:
|
|
VREG(LUAU_INSN_B(insn));
|
|
VJUMP(LUAU_INSN_C(insn));
|
|
LUAU_ASSERT(LUAU_INSN_OP(insns[i + 1 + LUAU_INSN_C(insn)]) == LOP_CALL);
|
|
VCONSTANY(insns[i + 1]);
|
|
break;
|
|
|
|
case LOP_COVERAGE:
|
|
break;
|
|
|
|
case LOP_CAPTURE:
|
|
switch (LUAU_INSN_A(insn))
|
|
{
|
|
case LCT_VAL:
|
|
VREG(LUAU_INSN_B(insn));
|
|
break;
|
|
|
|
case LCT_REF:
|
|
VREG(LUAU_INSN_B(insn));
|
|
openCaptures.push_back(LUAU_INSN_B(insn));
|
|
break;
|
|
|
|
case LCT_UPVAL:
|
|
VUPVAL(LUAU_INSN_B(insn));
|
|
break;
|
|
|
|
default:
|
|
LUAU_ASSERT(!"Unsupported capture type");
|
|
}
|
|
break;
|
|
|
|
default:
|
|
LUAU_ASSERT(!"Unsupported opcode");
|
|
}
|
|
|
|
i += getOpLength(op);
|
|
LUAU_ASSERT(i <= insns.size());
|
|
}
|
|
|
|
// all CAPTURE REF instructions must have a CLOSEUPVALS instruction after them in the bytecode stream
|
|
// this doesn't guarantee safety as it doesn't perform basic block based analysis, but if this fails
|
|
// then the bytecode is definitely unsafe to run since the compiler won't generate backwards branches
|
|
// except for loop edges
|
|
LUAU_ASSERT(openCaptures.empty());
|
|
|
|
#undef VREG
|
|
#undef VREGEND
|
|
#undef VUPVAL
|
|
#undef VCONST
|
|
#undef VCONSTANY
|
|
#undef VJUMP
|
|
}
|
|
|
|
void BytecodeBuilder::validateVariadic() const
|
|
{
|
|
// validate MULTRET sequences: instructions that produce a variadic sequence and consume one must come in pairs
|
|
// we classify instructions into four groups: producers, consumers, neutral and others
|
|
// any producer (an instruction that produces more than one value) must be followed by 0 or more neutral instructions
|
|
// and a consumer (that consumes more than one value); these form a variadic sequence.
|
|
// except for producer, no instruction in the variadic sequence may be a jump target.
|
|
// from the execution perspective, producer adjusts L->top to point to one past the last result, neutral instructions
|
|
// leave L->top unmodified, and consumer adjusts L->top back to the stack frame end.
|
|
// consumers invalidate all values after L->top after they execute (which we currently don't validate)
|
|
bool variadicSeq = false;
|
|
|
|
std::vector<uint8_t> insntargets(insns.size(), 0);
|
|
|
|
for (size_t i = 0; i < insns.size();)
|
|
{
|
|
uint32_t insn = insns[i];
|
|
LuauOpcode op = LuauOpcode(LUAU_INSN_OP(insn));
|
|
|
|
int target = getJumpTarget(insn, uint32_t(i));
|
|
|
|
if (target >= 0 && !isFastCall(op))
|
|
{
|
|
LUAU_ASSERT(unsigned(target) < insns.size());
|
|
|
|
insntargets[target] = true;
|
|
}
|
|
|
|
i += getOpLength(op);
|
|
LUAU_ASSERT(i <= insns.size());
|
|
}
|
|
|
|
for (size_t i = 0; i < insns.size();)
|
|
{
|
|
uint32_t insn = insns[i];
|
|
LuauOpcode op = LuauOpcode(LUAU_INSN_OP(insn));
|
|
|
|
if (variadicSeq)
|
|
{
|
|
// no instruction inside the sequence, including the consumer, may be a jump target
|
|
// this guarantees uninterrupted L->top adjustment flow
|
|
LUAU_ASSERT(!insntargets[i]);
|
|
}
|
|
|
|
if (op == LOP_CALL)
|
|
{
|
|
// note: calls may end one variadic sequence and start a new one
|
|
|
|
if (LUAU_INSN_B(insn) == 0)
|
|
{
|
|
// consumer instruction ens a variadic sequence
|
|
LUAU_ASSERT(variadicSeq);
|
|
variadicSeq = false;
|
|
}
|
|
else
|
|
{
|
|
// CALL is not a neutral instruction so it can't be present in a variadic sequence unless it's a consumer
|
|
LUAU_ASSERT(!variadicSeq);
|
|
}
|
|
|
|
if (LUAU_INSN_C(insn) == 0)
|
|
{
|
|
// producer instruction starts a variadic sequence
|
|
LUAU_ASSERT(!variadicSeq);
|
|
variadicSeq = true;
|
|
}
|
|
}
|
|
else if (op == LOP_GETVARARGS && LUAU_INSN_B(insn) == 0)
|
|
{
|
|
// producer instruction starts a variadic sequence
|
|
LUAU_ASSERT(!variadicSeq);
|
|
variadicSeq = true;
|
|
}
|
|
else if ((op == LOP_RETURN && LUAU_INSN_B(insn) == 0) || (op == LOP_SETLIST && LUAU_INSN_C(insn) == 0))
|
|
{
|
|
// consumer instruction ends a variadic sequence
|
|
LUAU_ASSERT(variadicSeq);
|
|
variadicSeq = false;
|
|
}
|
|
else if (op == LOP_FASTCALL)
|
|
{
|
|
int callTarget = int(i + LUAU_INSN_C(insn) + 1);
|
|
LUAU_ASSERT(unsigned(callTarget) < insns.size() && LUAU_INSN_OP(insns[callTarget]) == LOP_CALL);
|
|
|
|
if (LUAU_INSN_B(insns[callTarget]) == 0)
|
|
{
|
|
// consumer instruction ends a variadic sequence; however, we can't terminate it yet because future analysis of CALL will do it
|
|
// during FASTCALL fallback, the instructions between this and CALL consumer are going to be executed before L->top so they must
|
|
// be neutral; as such, we will defer termination of variadic sequence until CALL analysis
|
|
LUAU_ASSERT(variadicSeq);
|
|
}
|
|
else
|
|
{
|
|
// FASTCALL is not a neutral instruction so it can't be present in a variadic sequence unless it's linked to CALL consumer
|
|
LUAU_ASSERT(!variadicSeq);
|
|
}
|
|
|
|
// note: if FASTCALL is linked to a CALL producer, the instructions between FASTCALL and CALL are technically not part of an executed
|
|
// variadic sequence since they are never executed if FASTCALL does anything, so it's okay to skip their validation until CALL
|
|
// (we can't simply start a variadic sequence here because that would trigger assertions during linked CALL validation)
|
|
}
|
|
else if (op == LOP_CLOSEUPVALS || op == LOP_NAMECALL || op == LOP_GETIMPORT || op == LOP_MOVE || op == LOP_GETUPVAL || op == LOP_GETGLOBAL ||
|
|
op == LOP_GETTABLEKS || op == LOP_COVERAGE)
|
|
{
|
|
// instructions inside a variadic sequence must be neutral (can't change L->top)
|
|
// while there are many neutral instructions like this, here we check that the instruction is one of the few
|
|
// that we'd expect to exist in FASTCALL fallback sequences or between consecutive CALLs for encoding reasons
|
|
}
|
|
else
|
|
{
|
|
LUAU_ASSERT(!variadicSeq);
|
|
}
|
|
|
|
i += getOpLength(op);
|
|
LUAU_ASSERT(i <= insns.size());
|
|
}
|
|
|
|
LUAU_ASSERT(!variadicSeq);
|
|
}
|
|
#endif
|
|
|
|
static bool printableStringConstant(const char* str, size_t len)
|
|
{
|
|
for (size_t i = 0; i < len; ++i)
|
|
{
|
|
if (unsigned(str[i]) < ' ')
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void BytecodeBuilder::dumpConstant(std::string& result, int k) const
|
|
{
|
|
LUAU_ASSERT(unsigned(k) < constants.size());
|
|
const Constant& data = constants[k];
|
|
|
|
switch (data.type)
|
|
{
|
|
case Constant::Type_Nil:
|
|
formatAppend(result, "nil");
|
|
break;
|
|
case Constant::Type_Boolean:
|
|
formatAppend(result, "%s", data.valueBoolean ? "true" : "false");
|
|
break;
|
|
case Constant::Type_Number:
|
|
formatAppend(result, "%.17g", data.valueNumber);
|
|
break;
|
|
case Constant::Type_String:
|
|
{
|
|
const StringRef& str = debugStrings[data.valueString - 1];
|
|
|
|
if (printableStringConstant(str.data, str.length))
|
|
{
|
|
if (str.length < 32)
|
|
formatAppend(result, "'%.*s'", int(str.length), str.data);
|
|
else
|
|
formatAppend(result, "'%.*s'...", 32, str.data);
|
|
}
|
|
break;
|
|
}
|
|
case Constant::Type_Import:
|
|
{
|
|
int id0 = -1, id1 = -1, id2 = -1;
|
|
if (int count = decomposeImportId(data.valueImport, id0, id1, id2))
|
|
{
|
|
{
|
|
const Constant& id = constants[id0];
|
|
LUAU_ASSERT(id.type == Constant::Type_String && id.valueString <= debugStrings.size());
|
|
|
|
const StringRef& str = debugStrings[id.valueString - 1];
|
|
formatAppend(result, "%.*s", int(str.length), str.data);
|
|
}
|
|
|
|
if (count > 1)
|
|
{
|
|
const Constant& id = constants[id1];
|
|
LUAU_ASSERT(id.type == Constant::Type_String && id.valueString <= debugStrings.size());
|
|
|
|
const StringRef& str = debugStrings[id.valueString - 1];
|
|
formatAppend(result, ".%.*s", int(str.length), str.data);
|
|
}
|
|
|
|
if (count > 2)
|
|
{
|
|
const Constant& id = constants[id2];
|
|
LUAU_ASSERT(id.type == Constant::Type_String && id.valueString <= debugStrings.size());
|
|
|
|
const StringRef& str = debugStrings[id.valueString - 1];
|
|
formatAppend(result, ".%.*s", int(str.length), str.data);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case Constant::Type_Table:
|
|
formatAppend(result, "{...}");
|
|
break;
|
|
case Constant::Type_Closure:
|
|
{
|
|
const Function& func = functions[data.valueClosure];
|
|
|
|
if (!func.dumpname.empty())
|
|
formatAppend(result, "'%s'", func.dumpname.c_str());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void BytecodeBuilder::dumpInstruction(const uint32_t* code, std::string& result, int targetLabel) const
|
|
{
|
|
uint32_t insn = *code++;
|
|
|
|
switch (LUAU_INSN_OP(insn))
|
|
{
|
|
case LOP_LOADNIL:
|
|
formatAppend(result, "LOADNIL R%d\n", LUAU_INSN_A(insn));
|
|
break;
|
|
|
|
case LOP_LOADB:
|
|
if (LUAU_INSN_C(insn))
|
|
formatAppend(result, "LOADB R%d %d +%d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn));
|
|
else
|
|
formatAppend(result, "LOADB R%d %d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn));
|
|
break;
|
|
|
|
case LOP_LOADN:
|
|
formatAppend(result, "LOADN R%d %d\n", LUAU_INSN_A(insn), LUAU_INSN_D(insn));
|
|
break;
|
|
|
|
case LOP_LOADK:
|
|
formatAppend(result, "LOADK R%d K%d [", LUAU_INSN_A(insn), LUAU_INSN_D(insn));
|
|
dumpConstant(result, LUAU_INSN_D(insn));
|
|
result.append("]\n");
|
|
break;
|
|
|
|
case LOP_MOVE:
|
|
formatAppend(result, "MOVE R%d R%d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn));
|
|
break;
|
|
|
|
case LOP_GETGLOBAL:
|
|
formatAppend(result, "GETGLOBAL R%d K%d [", LUAU_INSN_A(insn), *code);
|
|
dumpConstant(result, *code);
|
|
result.append("]\n");
|
|
code++;
|
|
break;
|
|
|
|
case LOP_SETGLOBAL:
|
|
formatAppend(result, "SETGLOBAL R%d K%d [", LUAU_INSN_A(insn), *code);
|
|
dumpConstant(result, *code);
|
|
result.append("]\n");
|
|
code++;
|
|
break;
|
|
|
|
case LOP_GETUPVAL:
|
|
formatAppend(result, "GETUPVAL R%d %d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn));
|
|
break;
|
|
|
|
case LOP_SETUPVAL:
|
|
formatAppend(result, "SETUPVAL R%d %d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn));
|
|
break;
|
|
|
|
case LOP_CLOSEUPVALS:
|
|
formatAppend(result, "CLOSEUPVALS R%d\n", LUAU_INSN_A(insn));
|
|
break;
|
|
|
|
case LOP_GETIMPORT:
|
|
formatAppend(result, "GETIMPORT R%d %d [", LUAU_INSN_A(insn), LUAU_INSN_D(insn));
|
|
dumpConstant(result, LUAU_INSN_D(insn));
|
|
result.append("]\n");
|
|
code++; // AUX
|
|
break;
|
|
|
|
case LOP_GETTABLE:
|
|
formatAppend(result, "GETTABLE R%d R%d R%d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn));
|
|
break;
|
|
|
|
case LOP_SETTABLE:
|
|
formatAppend(result, "SETTABLE R%d R%d R%d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn));
|
|
break;
|
|
|
|
case LOP_GETTABLEKS:
|
|
formatAppend(result, "GETTABLEKS R%d R%d K%d [", LUAU_INSN_A(insn), LUAU_INSN_B(insn), *code);
|
|
dumpConstant(result, *code);
|
|
result.append("]\n");
|
|
code++;
|
|
break;
|
|
|
|
case LOP_SETTABLEKS:
|
|
formatAppend(result, "SETTABLEKS R%d R%d K%d [", LUAU_INSN_A(insn), LUAU_INSN_B(insn), *code);
|
|
dumpConstant(result, *code);
|
|
result.append("]\n");
|
|
code++;
|
|
break;
|
|
|
|
case LOP_GETTABLEN:
|
|
formatAppend(result, "GETTABLEN R%d R%d %d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn) + 1);
|
|
break;
|
|
|
|
case LOP_SETTABLEN:
|
|
formatAppend(result, "SETTABLEN R%d R%d %d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn) + 1);
|
|
break;
|
|
|
|
case LOP_NEWCLOSURE:
|
|
formatAppend(result, "NEWCLOSURE R%d P%d\n", LUAU_INSN_A(insn), LUAU_INSN_D(insn));
|
|
break;
|
|
|
|
case LOP_NAMECALL:
|
|
formatAppend(result, "NAMECALL R%d R%d K%d [", LUAU_INSN_A(insn), LUAU_INSN_B(insn), *code);
|
|
dumpConstant(result, *code);
|
|
result.append("]\n");
|
|
code++;
|
|
break;
|
|
|
|
case LOP_CALL:
|
|
formatAppend(result, "CALL R%d %d %d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn) - 1, LUAU_INSN_C(insn) - 1);
|
|
break;
|
|
|
|
case LOP_RETURN:
|
|
formatAppend(result, "RETURN R%d %d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn) - 1);
|
|
break;
|
|
|
|
case LOP_JUMP:
|
|
formatAppend(result, "JUMP L%d\n", targetLabel);
|
|
break;
|
|
|
|
case LOP_JUMPIF:
|
|
formatAppend(result, "JUMPIF R%d L%d\n", LUAU_INSN_A(insn), targetLabel);
|
|
break;
|
|
|
|
case LOP_JUMPIFNOT:
|
|
formatAppend(result, "JUMPIFNOT R%d L%d\n", LUAU_INSN_A(insn), targetLabel);
|
|
break;
|
|
|
|
case LOP_JUMPIFEQ:
|
|
formatAppend(result, "JUMPIFEQ R%d R%d L%d\n", LUAU_INSN_A(insn), *code++, targetLabel);
|
|
break;
|
|
|
|
case LOP_JUMPIFLE:
|
|
formatAppend(result, "JUMPIFLE R%d R%d L%d\n", LUAU_INSN_A(insn), *code++, targetLabel);
|
|
break;
|
|
|
|
case LOP_JUMPIFLT:
|
|
formatAppend(result, "JUMPIFLT R%d R%d L%d\n", LUAU_INSN_A(insn), *code++, targetLabel);
|
|
break;
|
|
|
|
case LOP_JUMPIFNOTEQ:
|
|
formatAppend(result, "JUMPIFNOTEQ R%d R%d L%d\n", LUAU_INSN_A(insn), *code++, targetLabel);
|
|
break;
|
|
|
|
case LOP_JUMPIFNOTLE:
|
|
formatAppend(result, "JUMPIFNOTLE R%d R%d L%d\n", LUAU_INSN_A(insn), *code++, targetLabel);
|
|
break;
|
|
|
|
case LOP_JUMPIFNOTLT:
|
|
formatAppend(result, "JUMPIFNOTLT R%d R%d L%d\n", LUAU_INSN_A(insn), *code++, targetLabel);
|
|
break;
|
|
|
|
case LOP_ADD:
|
|
formatAppend(result, "ADD R%d R%d R%d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn));
|
|
break;
|
|
|
|
case LOP_SUB:
|
|
formatAppend(result, "SUB R%d R%d R%d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn));
|
|
break;
|
|
|
|
case LOP_MUL:
|
|
formatAppend(result, "MUL R%d R%d R%d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn));
|
|
break;
|
|
|
|
case LOP_DIV:
|
|
formatAppend(result, "DIV R%d R%d R%d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn));
|
|
break;
|
|
|
|
case LOP_MOD:
|
|
formatAppend(result, "MOD R%d R%d R%d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn));
|
|
break;
|
|
|
|
case LOP_POW:
|
|
formatAppend(result, "POW R%d R%d R%d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn));
|
|
break;
|
|
|
|
case LOP_ADDK:
|
|
formatAppend(result, "ADDK R%d R%d K%d [", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn));
|
|
dumpConstant(result, LUAU_INSN_C(insn));
|
|
result.append("]\n");
|
|
break;
|
|
|
|
case LOP_SUBK:
|
|
formatAppend(result, "SUBK R%d R%d K%d [", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn));
|
|
dumpConstant(result, LUAU_INSN_C(insn));
|
|
result.append("]\n");
|
|
break;
|
|
|
|
case LOP_MULK:
|
|
formatAppend(result, "MULK R%d R%d K%d [", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn));
|
|
dumpConstant(result, LUAU_INSN_C(insn));
|
|
result.append("]\n");
|
|
break;
|
|
|
|
case LOP_DIVK:
|
|
formatAppend(result, "DIVK R%d R%d K%d [", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn));
|
|
dumpConstant(result, LUAU_INSN_C(insn));
|
|
result.append("]\n");
|
|
break;
|
|
|
|
case LOP_MODK:
|
|
formatAppend(result, "MODK R%d R%d K%d [", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn));
|
|
dumpConstant(result, LUAU_INSN_C(insn));
|
|
result.append("]\n");
|
|
break;
|
|
|
|
case LOP_POWK:
|
|
formatAppend(result, "POWK R%d R%d K%d [", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn));
|
|
dumpConstant(result, LUAU_INSN_C(insn));
|
|
result.append("]\n");
|
|
break;
|
|
|
|
case LOP_AND:
|
|
formatAppend(result, "AND R%d R%d R%d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn));
|
|
break;
|
|
|
|
case LOP_OR:
|
|
formatAppend(result, "OR R%d R%d R%d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn));
|
|
break;
|
|
|
|
case LOP_ANDK:
|
|
formatAppend(result, "ANDK R%d R%d K%d [", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn));
|
|
dumpConstant(result, LUAU_INSN_C(insn));
|
|
result.append("]\n");
|
|
break;
|
|
|
|
case LOP_ORK:
|
|
formatAppend(result, "ORK R%d R%d K%d [", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn));
|
|
dumpConstant(result, LUAU_INSN_C(insn));
|
|
result.append("]\n");
|
|
break;
|
|
|
|
case LOP_CONCAT:
|
|
formatAppend(result, "CONCAT R%d R%d R%d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn));
|
|
break;
|
|
|
|
case LOP_NOT:
|
|
formatAppend(result, "NOT R%d R%d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn));
|
|
break;
|
|
|
|
case LOP_MINUS:
|
|
formatAppend(result, "MINUS R%d R%d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn));
|
|
break;
|
|
|
|
case LOP_LENGTH:
|
|
formatAppend(result, "LENGTH R%d R%d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn));
|
|
break;
|
|
|
|
case LOP_NEWTABLE:
|
|
formatAppend(result, "NEWTABLE R%d %d %d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn) == 0 ? 0 : 1 << (LUAU_INSN_B(insn) - 1), *code++);
|
|
break;
|
|
|
|
case LOP_DUPTABLE:
|
|
formatAppend(result, "DUPTABLE R%d %d\n", LUAU_INSN_A(insn), LUAU_INSN_D(insn));
|
|
break;
|
|
|
|
case LOP_SETLIST:
|
|
formatAppend(result, "SETLIST R%d R%d %d [%d]\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn) - 1, *code++);
|
|
break;
|
|
|
|
case LOP_FORNPREP:
|
|
formatAppend(result, "FORNPREP R%d L%d\n", LUAU_INSN_A(insn), targetLabel);
|
|
break;
|
|
|
|
case LOP_FORNLOOP:
|
|
formatAppend(result, "FORNLOOP R%d L%d\n", LUAU_INSN_A(insn), targetLabel);
|
|
break;
|
|
|
|
case LOP_FORGPREP:
|
|
formatAppend(result, "FORGPREP R%d L%d\n", LUAU_INSN_A(insn), targetLabel);
|
|
break;
|
|
|
|
case LOP_FORGLOOP:
|
|
formatAppend(result, "FORGLOOP R%d L%d %d%s\n", LUAU_INSN_A(insn), targetLabel, uint8_t(*code), int(*code) < 0 ? " [inext]" : "");
|
|
code++;
|
|
break;
|
|
|
|
case LOP_FORGPREP_INEXT:
|
|
formatAppend(result, "FORGPREP_INEXT R%d L%d\n", LUAU_INSN_A(insn), targetLabel);
|
|
break;
|
|
|
|
case LOP_FORGPREP_NEXT:
|
|
formatAppend(result, "FORGPREP_NEXT R%d L%d\n", LUAU_INSN_A(insn), targetLabel);
|
|
break;
|
|
|
|
case LOP_GETVARARGS:
|
|
formatAppend(result, "GETVARARGS R%d %d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn) - 1);
|
|
break;
|
|
|
|
case LOP_DUPCLOSURE:
|
|
formatAppend(result, "DUPCLOSURE R%d K%d [", LUAU_INSN_A(insn), LUAU_INSN_D(insn));
|
|
dumpConstant(result, LUAU_INSN_D(insn));
|
|
result.append("]\n");
|
|
break;
|
|
|
|
case LOP_BREAK:
|
|
formatAppend(result, "BREAK\n");
|
|
break;
|
|
|
|
case LOP_JUMPBACK:
|
|
formatAppend(result, "JUMPBACK L%d\n", targetLabel);
|
|
break;
|
|
|
|
case LOP_LOADKX:
|
|
formatAppend(result, "LOADKX R%d K%d [", LUAU_INSN_A(insn), *code);
|
|
dumpConstant(result, *code);
|
|
result.append("]\n");
|
|
code++;
|
|
break;
|
|
|
|
case LOP_JUMPX:
|
|
formatAppend(result, "JUMPX L%d\n", targetLabel);
|
|
break;
|
|
|
|
case LOP_FASTCALL:
|
|
formatAppend(result, "FASTCALL %d L%d\n", LUAU_INSN_A(insn), targetLabel);
|
|
break;
|
|
|
|
case LOP_FASTCALL1:
|
|
formatAppend(result, "FASTCALL1 %d R%d L%d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), targetLabel);
|
|
break;
|
|
|
|
case LOP_FASTCALL2:
|
|
formatAppend(result, "FASTCALL2 %d R%d R%d L%d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), *code, targetLabel);
|
|
code++;
|
|
break;
|
|
|
|
case LOP_FASTCALL2K:
|
|
formatAppend(result, "FASTCALL2K %d R%d K%d L%d [", LUAU_INSN_A(insn), LUAU_INSN_B(insn), *code, targetLabel);
|
|
dumpConstant(result, *code);
|
|
result.append("]\n");
|
|
code++;
|
|
break;
|
|
|
|
case LOP_COVERAGE:
|
|
formatAppend(result, "COVERAGE\n");
|
|
break;
|
|
|
|
case LOP_CAPTURE:
|
|
formatAppend(result, "CAPTURE %s %c%d\n",
|
|
LUAU_INSN_A(insn) == LCT_UPVAL ? "UPVAL"
|
|
: LUAU_INSN_A(insn) == LCT_REF ? "REF"
|
|
: LUAU_INSN_A(insn) == LCT_VAL ? "VAL"
|
|
: "",
|
|
LUAU_INSN_A(insn) == LCT_UPVAL ? 'U' : 'R', LUAU_INSN_B(insn));
|
|
break;
|
|
|
|
case LOP_JUMPXEQKNIL:
|
|
formatAppend(result, "JUMPXEQKNIL R%d L%d%s\n", LUAU_INSN_A(insn), targetLabel, *code >> 31 ? " NOT" : "");
|
|
code++;
|
|
break;
|
|
|
|
case LOP_JUMPXEQKB:
|
|
formatAppend(result, "JUMPXEQKB R%d %d L%d%s\n", LUAU_INSN_A(insn), *code & 1, targetLabel, *code >> 31 ? " NOT" : "");
|
|
code++;
|
|
break;
|
|
|
|
case LOP_JUMPXEQKN:
|
|
formatAppend(result, "JUMPXEQKN R%d K%d L%d%s [", LUAU_INSN_A(insn), *code & 0xffffff, targetLabel, *code >> 31 ? " NOT" : "");
|
|
dumpConstant(result, *code & 0xffffff);
|
|
result.append("]\n");
|
|
code++;
|
|
break;
|
|
|
|
case LOP_JUMPXEQKS:
|
|
formatAppend(result, "JUMPXEQKS R%d K%d L%d%s [", LUAU_INSN_A(insn), *code & 0xffffff, targetLabel, *code >> 31 ? " NOT" : "");
|
|
dumpConstant(result, *code & 0xffffff);
|
|
result.append("]\n");
|
|
code++;
|
|
break;
|
|
|
|
default:
|
|
LUAU_ASSERT(!"Unsupported opcode");
|
|
}
|
|
}
|
|
|
|
std::string BytecodeBuilder::dumpCurrentFunction(std::vector<int>& dumpinstoffs) const
|
|
{
|
|
if ((dumpFlags & Dump_Code) == 0)
|
|
return std::string();
|
|
|
|
int lastLine = -1;
|
|
size_t nextRemark = 0;
|
|
|
|
std::string result;
|
|
|
|
if (dumpFlags & Dump_Locals)
|
|
{
|
|
for (size_t i = 0; i < debugLocals.size(); ++i)
|
|
{
|
|
const DebugLocal& l = debugLocals[i];
|
|
|
|
LUAU_ASSERT(l.startpc < l.endpc);
|
|
LUAU_ASSERT(l.startpc < lines.size());
|
|
LUAU_ASSERT(l.endpc <= lines.size()); // endpc is exclusive in the debug info, but it's more intuitive to print inclusive data
|
|
|
|
// it would be nice to emit name as well but it requires reverse lookup through stringtable
|
|
formatAppend(result, "local %d: reg %d, start pc %d line %d, end pc %d line %d\n", int(i), l.reg, l.startpc, lines[l.startpc],
|
|
l.endpc - 1, lines[l.endpc - 1]);
|
|
}
|
|
}
|
|
|
|
std::vector<int> labels(insns.size(), -1);
|
|
|
|
// annotate valid jump targets with 0
|
|
for (size_t i = 0; i < insns.size();)
|
|
{
|
|
int target = getJumpTarget(insns[i], uint32_t(i));
|
|
|
|
if (target >= 0)
|
|
{
|
|
LUAU_ASSERT(size_t(target) < insns.size());
|
|
labels[target] = 0;
|
|
}
|
|
|
|
i += getOpLength(LuauOpcode(LUAU_INSN_OP(insns[i])));
|
|
LUAU_ASSERT(i <= insns.size());
|
|
}
|
|
|
|
int nextLabel = 0;
|
|
|
|
// compute label ids (sequential integers for all jump targets)
|
|
for (size_t i = 0; i < labels.size(); ++i)
|
|
if (labels[i] == 0)
|
|
labels[i] = nextLabel++;
|
|
|
|
dumpinstoffs.resize(insns.size() + 1, -1);
|
|
|
|
for (size_t i = 0; i < insns.size();)
|
|
{
|
|
const uint32_t* code = &insns[i];
|
|
uint8_t op = LUAU_INSN_OP(*code);
|
|
|
|
dumpinstoffs[i] = int(result.size());
|
|
|
|
if (op == LOP_PREPVARARGS)
|
|
{
|
|
// Don't emit function header in bytecode - it's used for call dispatching and doesn't contain "interesting" information
|
|
i++;
|
|
continue;
|
|
}
|
|
|
|
if (dumpFlags & Dump_Remarks)
|
|
{
|
|
while (nextRemark < debugRemarks.size() && debugRemarks[nextRemark].first == i)
|
|
{
|
|
formatAppend(result, "REMARK %s\n", debugRemarkBuffer.c_str() + debugRemarks[nextRemark].second);
|
|
nextRemark++;
|
|
}
|
|
}
|
|
|
|
if (dumpFlags & Dump_Source)
|
|
{
|
|
int line = lines[i];
|
|
|
|
if (line > 0 && line != lastLine)
|
|
{
|
|
LUAU_ASSERT(size_t(line - 1) < dumpSource.size());
|
|
formatAppend(result, "%5d: %s\n", line, dumpSource[line - 1].c_str());
|
|
lastLine = line;
|
|
}
|
|
}
|
|
|
|
if (dumpFlags & Dump_Lines)
|
|
formatAppend(result, "%d: ", lines[i]);
|
|
|
|
if (labels[i] != -1)
|
|
formatAppend(result, "L%d: ", labels[i]);
|
|
|
|
int target = getJumpTarget(*code, uint32_t(i));
|
|
|
|
dumpInstruction(code, result, target >= 0 ? labels[target] : -1);
|
|
|
|
i += getOpLength(LuauOpcode(op));
|
|
LUAU_ASSERT(i <= insns.size());
|
|
}
|
|
|
|
dumpinstoffs[insns.size()] = int(result.size());
|
|
|
|
return result;
|
|
}
|
|
|
|
void BytecodeBuilder::setDumpSource(const std::string& source)
|
|
{
|
|
dumpSource.clear();
|
|
|
|
size_t pos = 0;
|
|
|
|
while (pos != std::string::npos)
|
|
{
|
|
size_t next = source.find('\n', pos);
|
|
|
|
if (next == std::string::npos)
|
|
{
|
|
dumpSource.push_back(source.substr(pos));
|
|
pos = next;
|
|
}
|
|
else
|
|
{
|
|
dumpSource.push_back(source.substr(pos, next - pos));
|
|
pos = next + 1;
|
|
}
|
|
|
|
if (!dumpSource.back().empty() && dumpSource.back().back() == '\r')
|
|
dumpSource.back().pop_back();
|
|
}
|
|
}
|
|
|
|
std::string BytecodeBuilder::dumpFunction(uint32_t id) const
|
|
{
|
|
LUAU_ASSERT(id < functions.size());
|
|
|
|
return functions[id].dump;
|
|
}
|
|
|
|
std::string BytecodeBuilder::dumpEverything() const
|
|
{
|
|
std::string result;
|
|
|
|
for (size_t i = 0; i < functions.size(); ++i)
|
|
{
|
|
std::string debugname = functions[i].dumpname.empty() ? "??" : functions[i].dumpname;
|
|
|
|
formatAppend(result, "Function %d (%s):\n", int(i), debugname.c_str());
|
|
|
|
result += functions[i].dump;
|
|
result += "\n";
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
std::string BytecodeBuilder::dumpSourceRemarks() const
|
|
{
|
|
std::string result;
|
|
|
|
size_t nextRemark = 0;
|
|
|
|
std::vector<std::pair<int, std::string>> remarks = dumpRemarks;
|
|
std::sort(remarks.begin(), remarks.end());
|
|
|
|
for (size_t i = 0; i < dumpSource.size(); ++i)
|
|
{
|
|
const std::string& line = dumpSource[i];
|
|
|
|
size_t indent = 0;
|
|
while (indent < line.length() && (line[indent] == ' ' || line[indent] == '\t'))
|
|
indent++;
|
|
|
|
while (nextRemark < remarks.size() && remarks[nextRemark].first == int(i + 1))
|
|
{
|
|
formatAppend(result, "%.*s-- remark: %s\n", int(indent), line.c_str(), remarks[nextRemark].second.c_str());
|
|
nextRemark++;
|
|
|
|
// skip duplicate remarks (due to inlining/unrolling)
|
|
while (nextRemark < remarks.size() && remarks[nextRemark] == remarks[nextRemark - 1])
|
|
nextRemark++;
|
|
}
|
|
|
|
result += line;
|
|
|
|
if (i + 1 < dumpSource.size())
|
|
result += '\n';
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void BytecodeBuilder::annotateInstruction(std::string& result, uint32_t fid, uint32_t instpos) const
|
|
{
|
|
if ((dumpFlags & Dump_Code) == 0)
|
|
return;
|
|
|
|
LUAU_ASSERT(fid < functions.size());
|
|
|
|
const Function& function = functions[fid];
|
|
const std::string& dump = function.dump;
|
|
const std::vector<int>& dumpinstoffs = function.dumpinstoffs;
|
|
|
|
uint32_t next = instpos + 1;
|
|
|
|
LUAU_ASSERT(next < dumpinstoffs.size());
|
|
|
|
// Skip locations of multi-dword instructions
|
|
while (next < dumpinstoffs.size() && dumpinstoffs[next] == -1)
|
|
next++;
|
|
|
|
formatAppend(result, "%.*s", dumpinstoffs[next] - dumpinstoffs[instpos], dump.data() + dumpinstoffs[instpos]);
|
|
}
|
|
|
|
} // namespace Luau
|