luau-src-rs/luau/Compiler/src/BytecodeBuilder.cpp

1772 lines
48 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 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_JUMPIFEQK:
case LOP_JUMPIFNOTEQK:
case LOP_FASTCALL2:
case LOP_FASTCALL2K:
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_FORGLOOP:
case LOP_FORGPREP_INEXT:
case LOP_FORGLOOP_INEXT:
case LOP_FORGPREP_NEXT:
case LOP_FORGLOOP_NEXT:
case LOP_JUMPBACK:
case LOP_JUMPIFEQK:
case LOP_JUMPIFNOTEQK:
return true;
default:
return false;
}
}
inline bool isSkipC(LuauOpcode op)
{
switch (op)
{
case LOP_LOADB:
case LOP_FASTCALL:
case LOP_FASTCALL1:
case LOP_FASTCALL2:
case LOP_FASTCALL2K:
return true;
default:
return false;
}
}
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())
, stringTable({nullptr, 0})
, encoder(encoder)
{
LUAU_ASSERT(stringTable.find(StringRef{"", 0}) == nullptr);
}
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
func.data.reserve(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)();
insns.clear();
lines.clear();
constants.clear();
protos.clear();
jumps.clear();
tableShapes.clear();
debugLocals.clear();
debugUpvals.clear();
constantMap.clear();
tableShapeMap.clear();
}
void BytecodeBuilder::setMainFunction(uint32_t fid)
{
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());
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)
{
uint32_t id = uint32_t(protos.size());
if (id >= kMaxClosureCount)
return -1;
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))));
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);
}
uint32_t BytecodeBuilder::getDebugPC() const
{
return uint32_t(insns.size());
}
void BytecodeBuilder::finalize()
{
LUAU_ASSERT(bytecode.empty());
bytecode = char(LBC_VERSION_FUTURE);
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
{
// 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
std::vector<int> baseline((lines.size() - 1) / span + 1);
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 < baseline.size(); ++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;
}
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 for valid bytecode blobs)
std::string result;
result += char(0);
result += message;
return result;
}
#ifdef LUAU_ASSERTENABLED
void BytecodeBuilder::validate() 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];
// first pass: tag instruction offsets so that we can validate jumps
std::vector<uint8_t> insnvalid(insns.size(), false);
for (size_t i = 0; i < insns.size();)
{
uint8_t op = LUAU_INSN_OP(insns[i]);
insnvalid[i] = true;
i += getOpLength(LuauOpcode(op));
LUAU_ASSERT(i <= insns.size());
}
// second pass: validate the rest of the bytecode
for (size_t i = 0; i < insns.size();)
{
uint32_t insn = insns[i];
uint8_t op = 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));
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_JUMPIFEQK:
case LOP_JUMPIFNOTEQK:
VREG(LUAU_INSN_A(insn));
VCONSTANY(insns[i + 1]);
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:
VREG(LUAU_INSN_A(insn) + 2); // for loop protocol: A, A+1, A+2 are used for iteration
VJUMP(LUAU_INSN_D(insn));
break;
case LOP_FORGLOOP:
VREG(
LUAU_INSN_A(insn) + 2 + insns[i + 1]); // forg loop protocol: A, A+1, A+2 are used for iteration protocol; A+3, ... are loop variables
VJUMP(LUAU_INSN_D(insn));
LUAU_ASSERT(insns[i + 1] >= 1);
break;
case LOP_FORGPREP_INEXT:
case LOP_FORGLOOP_INEXT:
case LOP_FORGPREP_NEXT:
case LOP_FORGLOOP_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:
case LCT_REF:
VREG(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(LuauOpcode(op));
LUAU_ASSERT(i <= insns.size());
}
#undef VREG
#undef VREGEND
#undef VUPVAL
#undef VCONST
#undef VCONSTANY
#undef VJUMP
}
#endif
const uint32_t* BytecodeBuilder::dumpInstruction(const uint32_t* code, std::string& result) 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\n", LUAU_INSN_A(insn), LUAU_INSN_D(insn));
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\n", LUAU_INSN_A(insn), *code++);
break;
case LOP_SETGLOBAL:
formatAppend(result, "SETGLOBAL R%d K%d\n", LUAU_INSN_A(insn), *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\n", LUAU_INSN_A(insn), LUAU_INSN_D(insn));
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\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), *code++);
break;
case LOP_SETTABLEKS:
formatAppend(result, "SETTABLEKS R%d R%d K%d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), *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\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), *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 %+d\n", LUAU_INSN_D(insn));
break;
case LOP_JUMPIF:
formatAppend(result, "JUMPIF R%d %+d\n", LUAU_INSN_A(insn), LUAU_INSN_D(insn));
break;
case LOP_JUMPIFNOT:
formatAppend(result, "JUMPIFNOT R%d %+d\n", LUAU_INSN_A(insn), LUAU_INSN_D(insn));
break;
case LOP_JUMPIFEQ:
formatAppend(result, "JUMPIFEQ R%d R%d %+d\n", LUAU_INSN_A(insn), *code++, LUAU_INSN_D(insn));
break;
case LOP_JUMPIFLE:
formatAppend(result, "JUMPIFLE R%d R%d %+d\n", LUAU_INSN_A(insn), *code++, LUAU_INSN_D(insn));
break;
case LOP_JUMPIFLT:
formatAppend(result, "JUMPIFLT R%d R%d %+d\n", LUAU_INSN_A(insn), *code++, LUAU_INSN_D(insn));
break;
case LOP_JUMPIFNOTEQ:
formatAppend(result, "JUMPIFNOTEQ R%d R%d %+d\n", LUAU_INSN_A(insn), *code++, LUAU_INSN_D(insn));
break;
case LOP_JUMPIFNOTLE:
formatAppend(result, "JUMPIFNOTLE R%d R%d %+d\n", LUAU_INSN_A(insn), *code++, LUAU_INSN_D(insn));
break;
case LOP_JUMPIFNOTLT:
formatAppend(result, "JUMPIFNOTLT R%d R%d %+d\n", LUAU_INSN_A(insn), *code++, LUAU_INSN_D(insn));
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\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn));
break;
case LOP_SUBK:
formatAppend(result, "SUBK R%d R%d K%d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn));
break;
case LOP_MULK:
formatAppend(result, "MULK R%d R%d K%d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn));
break;
case LOP_DIVK:
formatAppend(result, "DIVK R%d R%d K%d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn));
break;
case LOP_MODK:
formatAppend(result, "MODK R%d R%d K%d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn));
break;
case LOP_POWK:
formatAppend(result, "POWK R%d R%d K%d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn));
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\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn));
break;
case LOP_ORK:
formatAppend(result, "ORK R%d R%d K%d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn));
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 %+d\n", LUAU_INSN_A(insn), LUAU_INSN_D(insn));
break;
case LOP_FORNLOOP:
formatAppend(result, "FORNLOOP R%d %+d\n", LUAU_INSN_A(insn), LUAU_INSN_D(insn));
break;
case LOP_FORGLOOP:
formatAppend(result, "FORGLOOP R%d %+d %d\n", LUAU_INSN_A(insn), LUAU_INSN_D(insn), *code++);
break;
case LOP_FORGPREP_INEXT:
formatAppend(result, "FORGPREP_INEXT R%d %+d\n", LUAU_INSN_A(insn), LUAU_INSN_D(insn));
break;
case LOP_FORGLOOP_INEXT:
formatAppend(result, "FORGLOOP_INEXT R%d %+d\n", LUAU_INSN_A(insn), LUAU_INSN_D(insn));
break;
case LOP_FORGPREP_NEXT:
formatAppend(result, "FORGPREP_NEXT R%d %+d\n", LUAU_INSN_A(insn), LUAU_INSN_D(insn));
break;
case LOP_FORGLOOP_NEXT:
formatAppend(result, "FORGLOOP_NEXT R%d %+d\n", LUAU_INSN_A(insn), LUAU_INSN_D(insn));
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\n", LUAU_INSN_A(insn), LUAU_INSN_D(insn));
break;
case LOP_BREAK:
formatAppend(result, "BREAK\n");
break;
case LOP_JUMPBACK:
formatAppend(result, "JUMPBACK %+d\n", LUAU_INSN_D(insn));
break;
case LOP_LOADKX:
formatAppend(result, "LOADKX R%d K%d\n", LUAU_INSN_A(insn), *code++);
break;
case LOP_JUMPX:
formatAppend(result, "JUMPX %+d\n", LUAU_INSN_E(insn));
break;
case LOP_FASTCALL:
formatAppend(result, "FASTCALL %d %+d\n", LUAU_INSN_A(insn), LUAU_INSN_C(insn));
break;
case LOP_FASTCALL1:
formatAppend(result, "FASTCALL1 %d R%d %+d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn));
break;
case LOP_FASTCALL2:
{
uint32_t aux = *code++;
formatAppend(result, "FASTCALL2 %d R%d R%d %+d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), aux, LUAU_INSN_C(insn));
break;
}
case LOP_FASTCALL2K:
{
uint32_t aux = *code++;
formatAppend(result, "FASTCALL2K %d R%d K%d %+d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), aux, LUAU_INSN_C(insn));
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_JUMPIFEQK:
formatAppend(result, "JUMPIFEQK R%d K%d %+d\n", LUAU_INSN_A(insn), *code++, LUAU_INSN_D(insn));
break;
case LOP_JUMPIFNOTEQK:
formatAppend(result, "JUMPIFNOTEQK R%d K%d %+d\n", LUAU_INSN_A(insn), *code++, LUAU_INSN_D(insn));
break;
default:
LUAU_ASSERT(!"Unsupported opcode");
}
return code;
}
std::string BytecodeBuilder::dumpCurrentFunction() const
{
if ((dumpFlags & Dump_Code) == 0)
return std::string();
const uint32_t* code = insns.data();
const uint32_t* codeEnd = insns.data() + insns.size();
int lastLine = -1;
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]);
}
}
while (code != codeEnd)
{
uint8_t op = LUAU_INSN_OP(*code);
if (op == LOP_PREPVARARGS)
{
// Don't emit function header in bytecode - it's used for call dispatching and doesn't contain "interesting" information
code++;
continue;
}
if (dumpFlags & Dump_Source)
{
int line = lines[code - insns.data()];
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[code - insns.data()]);
}
code = dumpInstruction(code, result);
}
return result;
}
void BytecodeBuilder::setDumpSource(const std::string& source)
{
dumpSource.clear();
std::string::size_type pos = 0;
while (pos != std::string::npos)
{
std::string::size_type 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;
}
} // namespace Luau