luau-src-rs/luau/CodeGen/src/IrDump.cpp

764 lines
21 KiB
C++

// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/IrDump.h"
#include "Luau/IrUtils.h"
#include "lua.h"
#include <stdarg.h>
namespace Luau
{
namespace CodeGen
{
static const char* textForCondition[] = {
"eq", "not_eq", "lt", "not_lt", "le", "not_le", "gt", "not_gt", "ge", "not_ge", "u_lt", "u_le", "u_gt", "u_ge"};
static_assert(sizeof(textForCondition) / sizeof(textForCondition[0]) == size_t(IrCondition::Count), "all conditions have to be covered");
const int kDetailsAlignColumn = 60;
LUAU_PRINTF_ATTR(2, 3)
static void append(std::string& result, const char* fmt, ...)
{
char buf[256];
va_list args;
va_start(args, fmt);
vsnprintf(buf, sizeof(buf), fmt, args);
va_end(args);
result.append(buf);
}
static void padToDetailColumn(std::string& result, size_t lineStart)
{
int pad = kDetailsAlignColumn - int(result.size() - lineStart);
if (pad > 0)
result.append(pad, ' ');
}
static const char* getTagName(uint8_t tag)
{
switch (tag)
{
case LUA_TNIL:
return "tnil";
case LUA_TBOOLEAN:
return "tboolean";
case LUA_TLIGHTUSERDATA:
return "tlightuserdata";
case LUA_TNUMBER:
return "tnumber";
case LUA_TVECTOR:
return "tvector";
case LUA_TSTRING:
return "tstring";
case LUA_TTABLE:
return "ttable";
case LUA_TFUNCTION:
return "tfunction";
case LUA_TUSERDATA:
return "tuserdata";
case LUA_TTHREAD:
return "tthread";
default:
LUAU_ASSERT(!"Unknown type tag");
LUAU_UNREACHABLE();
}
}
const char* getCmdName(IrCmd cmd)
{
switch (cmd)
{
case IrCmd::NOP:
return "NOP";
case IrCmd::LOAD_TAG:
return "LOAD_TAG";
case IrCmd::LOAD_POINTER:
return "LOAD_POINTER";
case IrCmd::LOAD_DOUBLE:
return "LOAD_DOUBLE";
case IrCmd::LOAD_INT:
return "LOAD_INT";
case IrCmd::LOAD_TVALUE:
return "LOAD_TVALUE";
case IrCmd::LOAD_NODE_VALUE_TV:
return "LOAD_NODE_VALUE_TV";
case IrCmd::LOAD_ENV:
return "LOAD_ENV";
case IrCmd::GET_ARR_ADDR:
return "GET_ARR_ADDR";
case IrCmd::GET_SLOT_NODE_ADDR:
return "GET_SLOT_NODE_ADDR";
case IrCmd::GET_HASH_NODE_ADDR:
return "GET_HASH_NODE_ADDR";
case IrCmd::STORE_TAG:
return "STORE_TAG";
case IrCmd::STORE_POINTER:
return "STORE_POINTER";
case IrCmd::STORE_DOUBLE:
return "STORE_DOUBLE";
case IrCmd::STORE_INT:
return "STORE_INT";
case IrCmd::STORE_VECTOR:
return "STORE_VECTOR";
case IrCmd::STORE_TVALUE:
return "STORE_TVALUE";
case IrCmd::STORE_NODE_VALUE_TV:
return "STORE_NODE_VALUE_TV";
case IrCmd::ADD_INT:
return "ADD_INT";
case IrCmd::SUB_INT:
return "SUB_INT";
case IrCmd::ADD_NUM:
return "ADD_NUM";
case IrCmd::SUB_NUM:
return "SUB_NUM";
case IrCmd::MUL_NUM:
return "MUL_NUM";
case IrCmd::DIV_NUM:
return "DIV_NUM";
case IrCmd::MOD_NUM:
return "MOD_NUM";
case IrCmd::MIN_NUM:
return "MIN_NUM";
case IrCmd::MAX_NUM:
return "MAX_NUM";
case IrCmd::UNM_NUM:
return "UNM_NUM";
case IrCmd::FLOOR_NUM:
return "FLOOR_NUM";
case IrCmd::CEIL_NUM:
return "CEIL_NUM";
case IrCmd::ROUND_NUM:
return "ROUND_NUM";
case IrCmd::SQRT_NUM:
return "SQRT_NUM";
case IrCmd::ABS_NUM:
return "ABS_NUM";
case IrCmd::NOT_ANY:
return "NOT_ANY";
case IrCmd::JUMP:
return "JUMP";
case IrCmd::JUMP_IF_TRUTHY:
return "JUMP_IF_TRUTHY";
case IrCmd::JUMP_IF_FALSY:
return "JUMP_IF_FALSY";
case IrCmd::JUMP_EQ_TAG:
return "JUMP_EQ_TAG";
case IrCmd::JUMP_EQ_INT:
return "JUMP_EQ_INT";
case IrCmd::JUMP_LT_INT:
return "JUMP_LT_INT";
case IrCmd::JUMP_GE_UINT:
return "JUMP_GE_UINT";
case IrCmd::JUMP_EQ_POINTER:
return "JUMP_EQ_POINTER";
case IrCmd::JUMP_CMP_NUM:
return "JUMP_CMP_NUM";
case IrCmd::JUMP_CMP_ANY:
return "JUMP_CMP_ANY";
case IrCmd::JUMP_SLOT_MATCH:
return "JUMP_SLOT_MATCH";
case IrCmd::TABLE_LEN:
return "TABLE_LEN";
case IrCmd::NEW_TABLE:
return "NEW_TABLE";
case IrCmd::DUP_TABLE:
return "DUP_TABLE";
case IrCmd::TRY_NUM_TO_INDEX:
return "TRY_NUM_TO_INDEX";
case IrCmd::TRY_CALL_FASTGETTM:
return "TRY_CALL_FASTGETTM";
case IrCmd::INT_TO_NUM:
return "INT_TO_NUM";
case IrCmd::UINT_TO_NUM:
return "UINT_TO_NUM";
case IrCmd::NUM_TO_INT:
return "NUM_TO_INT";
case IrCmd::NUM_TO_UINT:
return "NUM_TO_UINT";
case IrCmd::ADJUST_STACK_TO_REG:
return "ADJUST_STACK_TO_REG";
case IrCmd::ADJUST_STACK_TO_TOP:
return "ADJUST_STACK_TO_TOP";
case IrCmd::FASTCALL:
return "FASTCALL";
case IrCmd::INVOKE_FASTCALL:
return "INVOKE_FASTCALL";
case IrCmd::CHECK_FASTCALL_RES:
return "CHECK_FASTCALL_RES";
case IrCmd::DO_ARITH:
return "DO_ARITH";
case IrCmd::DO_LEN:
return "DO_LEN";
case IrCmd::GET_TABLE:
return "GET_TABLE";
case IrCmd::SET_TABLE:
return "SET_TABLE";
case IrCmd::GET_IMPORT:
return "GET_IMPORT";
case IrCmd::CONCAT:
return "CONCAT";
case IrCmd::GET_UPVALUE:
return "GET_UPVALUE";
case IrCmd::SET_UPVALUE:
return "SET_UPVALUE";
case IrCmd::PREPARE_FORN:
return "PREPARE_FORN";
case IrCmd::CHECK_TAG:
return "CHECK_TAG";
case IrCmd::CHECK_READONLY:
return "CHECK_READONLY";
case IrCmd::CHECK_NO_METATABLE:
return "CHECK_NO_METATABLE";
case IrCmd::CHECK_SAFE_ENV:
return "CHECK_SAFE_ENV";
case IrCmd::CHECK_ARRAY_SIZE:
return "CHECK_ARRAY_SIZE";
case IrCmd::CHECK_SLOT_MATCH:
return "CHECK_SLOT_MATCH";
case IrCmd::CHECK_NODE_NO_NEXT:
return "CHECK_NODE_NO_NEXT";
case IrCmd::INTERRUPT:
return "INTERRUPT";
case IrCmd::CHECK_GC:
return "CHECK_GC";
case IrCmd::BARRIER_OBJ:
return "BARRIER_OBJ";
case IrCmd::BARRIER_TABLE_BACK:
return "BARRIER_TABLE_BACK";
case IrCmd::BARRIER_TABLE_FORWARD:
return "BARRIER_TABLE_FORWARD";
case IrCmd::SET_SAVEDPC:
return "SET_SAVEDPC";
case IrCmd::CLOSE_UPVALS:
return "CLOSE_UPVALS";
case IrCmd::CAPTURE:
return "CAPTURE";
case IrCmd::SETLIST:
return "SETLIST";
case IrCmd::CALL:
return "CALL";
case IrCmd::RETURN:
return "RETURN";
case IrCmd::FORGLOOP:
return "FORGLOOP";
case IrCmd::FORGLOOP_FALLBACK:
return "FORGLOOP_FALLBACK";
case IrCmd::FORGPREP_XNEXT_FALLBACK:
return "FORGPREP_XNEXT_FALLBACK";
case IrCmd::COVERAGE:
return "COVERAGE";
case IrCmd::FALLBACK_GETGLOBAL:
return "FALLBACK_GETGLOBAL";
case IrCmd::FALLBACK_SETGLOBAL:
return "FALLBACK_SETGLOBAL";
case IrCmd::FALLBACK_GETTABLEKS:
return "FALLBACK_GETTABLEKS";
case IrCmd::FALLBACK_SETTABLEKS:
return "FALLBACK_SETTABLEKS";
case IrCmd::FALLBACK_NAMECALL:
return "FALLBACK_NAMECALL";
case IrCmd::FALLBACK_PREPVARARGS:
return "FALLBACK_PREPVARARGS";
case IrCmd::FALLBACK_GETVARARGS:
return "FALLBACK_GETVARARGS";
case IrCmd::FALLBACK_NEWCLOSURE:
return "FALLBACK_NEWCLOSURE";
case IrCmd::FALLBACK_DUPCLOSURE:
return "FALLBACK_DUPCLOSURE";
case IrCmd::FALLBACK_FORGPREP:
return "FALLBACK_FORGPREP";
case IrCmd::SUBSTITUTE:
return "SUBSTITUTE";
case IrCmd::BITAND_UINT:
return "BITAND_UINT";
case IrCmd::BITXOR_UINT:
return "BITXOR_UINT";
case IrCmd::BITOR_UINT:
return "BITOR_UINT";
case IrCmd::BITNOT_UINT:
return "BITNOT_UINT";
case IrCmd::BITLSHIFT_UINT:
return "BITLSHIFT_UINT";
case IrCmd::BITRSHIFT_UINT:
return "BITRSHIFT_UINT";
case IrCmd::BITARSHIFT_UINT:
return "BITARSHIFT_UINT";
case IrCmd::BITLROTATE_UINT:
return "BITLROTATE_UINT";
case IrCmd::BITRROTATE_UINT:
return "BITRROTATE_UINT";
case IrCmd::BITCOUNTLZ_UINT:
return "BITCOUNTLZ_UINT";
case IrCmd::BITCOUNTRZ_UINT:
return "BITCOUNTRZ_UINT";
case IrCmd::INVOKE_LIBM:
return "INVOKE_LIBM";
}
LUAU_UNREACHABLE();
}
const char* getBlockKindName(IrBlockKind kind)
{
switch (kind)
{
case IrBlockKind::Bytecode:
return "bb_bytecode";
case IrBlockKind::Fallback:
return "bb_fallback";
case IrBlockKind::Internal:
return "bb";
case IrBlockKind::Linearized:
return "bb_linear";
case IrBlockKind::Dead:
return "dead";
}
LUAU_UNREACHABLE();
}
void toString(IrToStringContext& ctx, const IrInst& inst, uint32_t index)
{
append(ctx.result, " ");
// Instructions with a result display target virtual register
if (hasResult(inst.cmd))
append(ctx.result, "%%%u = ", index);
ctx.result.append(getCmdName(inst.cmd));
auto checkOp = [&ctx](IrOp op, const char* sep) {
if (op.kind != IrOpKind::None)
{
ctx.result.append(sep);
toString(ctx, op);
}
};
checkOp(inst.a, " ");
checkOp(inst.b, ", ");
checkOp(inst.c, ", ");
checkOp(inst.d, ", ");
checkOp(inst.e, ", ");
checkOp(inst.f, ", ");
}
void toString(IrToStringContext& ctx, const IrBlock& block, uint32_t index)
{
append(ctx.result, "%s_%u", getBlockKindName(block.kind), index);
}
void toString(IrToStringContext& ctx, IrOp op)
{
switch (op.kind)
{
case IrOpKind::None:
break;
case IrOpKind::Undef:
append(ctx.result, "undef");
break;
case IrOpKind::Constant:
toString(ctx.result, ctx.constants[op.index]);
break;
case IrOpKind::Condition:
LUAU_ASSERT(op.index < uint32_t(IrCondition::Count));
ctx.result.append(textForCondition[op.index]);
break;
case IrOpKind::Inst:
append(ctx.result, "%%%u", op.index);
break;
case IrOpKind::Block:
append(ctx.result, "%s_%u", getBlockKindName(ctx.blocks[op.index].kind), op.index);
break;
case IrOpKind::VmReg:
append(ctx.result, "R%d", vmRegOp(op));
break;
case IrOpKind::VmConst:
append(ctx.result, "K%d", vmConstOp(op));
break;
case IrOpKind::VmUpvalue:
append(ctx.result, "U%d", vmUpvalueOp(op));
break;
}
}
void toString(std::string& result, IrConst constant)
{
switch (constant.kind)
{
case IrConstKind::Int:
append(result, "%di", constant.valueInt);
break;
case IrConstKind::Uint:
append(result, "%uu", constant.valueUint);
break;
case IrConstKind::Double:
if (constant.valueDouble != constant.valueDouble)
append(result, "nan");
else
append(result, "%.17g", constant.valueDouble);
break;
case IrConstKind::Tag:
result.append(getTagName(constant.valueTag));
break;
}
}
static void appendBlockSet(IrToStringContext& ctx, BlockIteratorWrapper blocks)
{
bool comma = false;
for (uint32_t target : blocks)
{
if (comma)
append(ctx.result, ", ");
comma = true;
toString(ctx, ctx.blocks[target], target);
}
}
static void appendRegisterSet(IrToStringContext& ctx, const RegisterSet& rs, const char* separator)
{
bool comma = false;
for (size_t i = 0; i < rs.regs.size(); i++)
{
if (rs.regs.test(i))
{
if (comma)
ctx.result.append(separator);
comma = true;
append(ctx.result, "R%d", int(i));
}
}
if (rs.varargSeq)
{
if (comma)
ctx.result.append(separator);
append(ctx.result, "R%d...", rs.varargStart);
}
}
static RegisterSet getJumpTargetExtraLiveIn(IrToStringContext& ctx, const IrBlock& block, uint32_t blockIdx, const IrInst& inst)
{
RegisterSet extraRs;
if (blockIdx >= ctx.cfg.in.size())
return extraRs;
const RegisterSet& defRs = ctx.cfg.in[blockIdx];
// Find first block argument, for guard instructions (isNonTerminatingJump), that's the first and only one
LUAU_ASSERT(isNonTerminatingJump(inst.cmd));
IrOp op = inst.a;
if (inst.b.kind == IrOpKind::Block)
op = inst.b;
else if (inst.c.kind == IrOpKind::Block)
op = inst.c;
else if (inst.d.kind == IrOpKind::Block)
op = inst.d;
else if (inst.e.kind == IrOpKind::Block)
op = inst.e;
else if (inst.f.kind == IrOpKind::Block)
op = inst.f;
if (op.kind == IrOpKind::Block && op.index < ctx.cfg.in.size())
{
const RegisterSet& inRs = ctx.cfg.in[op.index];
extraRs.regs = inRs.regs & ~defRs.regs;
if (inRs.varargSeq)
requireVariadicSequence(extraRs, defRs, inRs.varargStart);
}
return extraRs;
}
void toStringDetailed(IrToStringContext& ctx, const IrBlock& block, uint32_t blockIdx, const IrInst& inst, uint32_t instIdx, bool includeUseInfo)
{
size_t start = ctx.result.size();
toString(ctx, inst, instIdx);
if (includeUseInfo)
{
padToDetailColumn(ctx.result, start);
if (inst.useCount == 0 && hasSideEffects(inst.cmd))
{
if (isNonTerminatingJump(inst.cmd))
{
RegisterSet extraRs = getJumpTargetExtraLiveIn(ctx, block, blockIdx, inst);
if (extraRs.regs.any() || extraRs.varargSeq)
{
append(ctx.result, "; %%%u, extra in: ", instIdx);
appendRegisterSet(ctx, extraRs, ", ");
ctx.result.append("\n");
}
else
{
append(ctx.result, "; %%%u\n", instIdx);
}
}
else
{
append(ctx.result, "; %%%u\n", instIdx);
}
}
else
{
append(ctx.result, "; useCount: %d, lastUse: %%%u\n", inst.useCount, inst.lastUse);
}
}
else
{
ctx.result.append("\n");
}
}
void toStringDetailed(IrToStringContext& ctx, const IrBlock& block, uint32_t index, bool includeUseInfo)
{
// Report captured registers for entry block
if (block.useCount == 0 && block.kind != IrBlockKind::Dead && ctx.cfg.captured.regs.any())
{
append(ctx.result, "; captured regs: ");
appendRegisterSet(ctx, ctx.cfg.captured, ", ");
append(ctx.result, "\n\n");
}
size_t start = ctx.result.size();
toString(ctx, block, index);
append(ctx.result, ":");
if (includeUseInfo)
{
padToDetailColumn(ctx.result, start);
append(ctx.result, "; useCount: %d\n", block.useCount);
}
else
{
ctx.result.append("\n");
}
// Predecessor list
if (index < ctx.cfg.predecessorsOffsets.size())
{
BlockIteratorWrapper pred = predecessors(ctx.cfg, index);
if (!pred.empty())
{
append(ctx.result, "; predecessors: ");
appendBlockSet(ctx, pred);
append(ctx.result, "\n");
}
}
// Successor list
if (index < ctx.cfg.successorsOffsets.size())
{
BlockIteratorWrapper succ = successors(ctx.cfg, index);
if (!succ.empty())
{
append(ctx.result, "; successors: ");
appendBlockSet(ctx, succ);
append(ctx.result, "\n");
}
}
// Live-in VM regs
if (index < ctx.cfg.in.size())
{
const RegisterSet& in = ctx.cfg.in[index];
if (in.regs.any() || in.varargSeq)
{
append(ctx.result, "; in regs: ");
appendRegisterSet(ctx, in, ", ");
append(ctx.result, "\n");
}
}
// Live-out VM regs
if (index < ctx.cfg.out.size())
{
const RegisterSet& out = ctx.cfg.out[index];
if (out.regs.any() || out.varargSeq)
{
append(ctx.result, "; out regs: ");
appendRegisterSet(ctx, out, ", ");
append(ctx.result, "\n");
}
}
}
std::string toString(const IrFunction& function, bool includeUseInfo)
{
std::string result;
IrToStringContext ctx{result, function.blocks, function.constants, function.cfg};
for (size_t i = 0; i < function.blocks.size(); i++)
{
const IrBlock& block = function.blocks[i];
if (block.kind == IrBlockKind::Dead)
continue;
toStringDetailed(ctx, block, uint32_t(i), includeUseInfo);
if (block.start == ~0u)
{
append(ctx.result, " *empty*\n\n");
continue;
}
// To allow dumping blocks that are still being constructed, we can't rely on terminator and need a bounds check
for (uint32_t index = block.start; index <= block.finish && index < uint32_t(function.instructions.size()); index++)
{
const IrInst& inst = function.instructions[index];
// Skip pseudo instructions unless they are still referenced
if (isPseudo(inst.cmd) && inst.useCount == 0)
continue;
append(ctx.result, " ");
toStringDetailed(ctx, block, uint32_t(i), inst, index, includeUseInfo);
}
append(ctx.result, "\n");
}
return result;
}
std::string dump(const IrFunction& function)
{
std::string result = toString(function, /* includeUseInfo */ true);
printf("%s\n", result.c_str());
return result;
}
std::string toDot(const IrFunction& function, bool includeInst)
{
std::string result;
IrToStringContext ctx{result, function.blocks, function.constants, function.cfg};
auto appendLabelRegset = [&ctx](const std::vector<RegisterSet>& regSets, size_t blockIdx, const char* name) {
if (blockIdx < regSets.size())
{
const RegisterSet& rs = regSets[blockIdx];
if (rs.regs.any() || rs.varargSeq)
{
append(ctx.result, "|{%s|", name);
appendRegisterSet(ctx, rs, "|");
append(ctx.result, "}");
}
}
};
append(ctx.result, "digraph CFG {\n");
append(ctx.result, "node[shape=record]\n");
for (size_t i = 0; i < function.blocks.size(); i++)
{
const IrBlock& block = function.blocks[i];
append(ctx.result, "b%u [", unsigned(i));
if (block.kind == IrBlockKind::Fallback)
append(ctx.result, "style=filled;fillcolor=salmon;");
else if (block.kind == IrBlockKind::Bytecode)
append(ctx.result, "style=filled;fillcolor=palegreen;");
append(ctx.result, "label=\"{");
toString(ctx, block, uint32_t(i));
appendLabelRegset(ctx.cfg.in, i, "in");
if (includeInst && block.start != ~0u)
{
for (uint32_t instIdx = block.start; instIdx <= block.finish; instIdx++)
{
const IrInst& inst = function.instructions[instIdx];
// Skip pseudo instructions unless they are still referenced
if (isPseudo(inst.cmd) && inst.useCount == 0)
continue;
append(ctx.result, "|");
toString(ctx, inst, instIdx);
}
}
appendLabelRegset(ctx.cfg.def, i, "def");
appendLabelRegset(ctx.cfg.out, i, "out");
append(ctx.result, "}\"];\n");
}
for (size_t i = 0; i < function.blocks.size(); i++)
{
const IrBlock& block = function.blocks[i];
if (block.start == ~0u)
continue;
for (uint32_t instIdx = block.start; instIdx != ~0u && instIdx <= block.finish; instIdx++)
{
const IrInst& inst = function.instructions[instIdx];
auto checkOp = [&](IrOp op) {
if (op.kind == IrOpKind::Block)
{
if (function.blocks[op.index].kind != IrBlockKind::Fallback)
append(ctx.result, "b%u -> b%u [weight=10];\n", unsigned(i), op.index);
else
append(ctx.result, "b%u -> b%u;\n", unsigned(i), op.index);
}
};
checkOp(inst.a);
checkOp(inst.b);
checkOp(inst.c);
checkOp(inst.d);
checkOp(inst.e);
checkOp(inst.f);
}
}
append(ctx.result, "}\n");
return result;
}
std::string dumpDot(const IrFunction& function, bool includeInst)
{
std::string result = toDot(function, includeInst);
printf("%s\n", result.c_str());
return result;
}
} // namespace CodeGen
} // namespace Luau