mirror of
https://github.com/luau-lang/luau.git
synced 2025-05-04 10:33:46 +01:00
We've made a few small changes to reduce the amount of stack we use when typechecking nested method calls (eg `foo:bar():baz():quux()`). We've also fixed a small bytecode compiler issue that caused us to emit redundant jump instructions in code that conditionally uses `break` or `continue`. On the new solver, we've switched to a new, better way to handle augmentations to unsealed tables. We've also made some substantial improvements to type inference and error reporting on function calls. These things should both be on par with the old solver now. The main improvements to the native code generator have been elimination of some redundant type tag checks. Also, we are starting to inline particular fastcalls directly to IR. --------- Co-authored-by: Arseny Kapoulkine <arseny.kapoulkine@gmail.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
450 lines
12 KiB
C++
450 lines
12 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_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::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_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::POW_NUM:
|
|
return "POW_NUM";
|
|
case IrCmd::UNM_NUM:
|
|
return "UNM_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_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::TABLE_LEN:
|
|
return "TABLE_LEN";
|
|
case IrCmd::NEW_TABLE:
|
|
return "NEW_TABLE";
|
|
case IrCmd::DUP_TABLE:
|
|
return "DUP_TABLE";
|
|
case IrCmd::NUM_TO_INDEX:
|
|
return "NUM_TO_INDEX";
|
|
case IrCmd::INT_TO_NUM:
|
|
return "INT_TO_NUM";
|
|
case IrCmd::ADJUST_STACK_TO_REG:
|
|
return "ADJUST_STACK_TO_REG";
|
|
case IrCmd::ADJUST_STACK_TO_TOP:
|
|
return "ADJUST_STACK_TO_TOP";
|
|
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::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::LOP_SETLIST:
|
|
return "LOP_SETLIST";
|
|
case IrCmd::LOP_NAMECALL:
|
|
return "LOP_NAMECALL";
|
|
case IrCmd::LOP_CALL:
|
|
return "LOP_CALL";
|
|
case IrCmd::LOP_RETURN:
|
|
return "LOP_RETURN";
|
|
case IrCmd::LOP_FASTCALL:
|
|
return "LOP_FASTCALL";
|
|
case IrCmd::LOP_FASTCALL1:
|
|
return "LOP_FASTCALL1";
|
|
case IrCmd::LOP_FASTCALL2:
|
|
return "LOP_FASTCALL2";
|
|
case IrCmd::LOP_FASTCALL2K:
|
|
return "LOP_FASTCALL2K";
|
|
case IrCmd::LOP_FORGLOOP:
|
|
return "LOP_FORGLOOP";
|
|
case IrCmd::LOP_FORGLOOP_FALLBACK:
|
|
return "LOP_FORGLOOP_FALLBACK";
|
|
case IrCmd::LOP_FORGPREP_XNEXT_FALLBACK:
|
|
return "LOP_FORGPREP_XNEXT_FALLBACK";
|
|
case IrCmd::LOP_AND:
|
|
return "LOP_AND";
|
|
case IrCmd::LOP_ANDK:
|
|
return "LOP_ANDK";
|
|
case IrCmd::LOP_OR:
|
|
return "LOP_OR";
|
|
case IrCmd::LOP_ORK:
|
|
return "LOP_ORK";
|
|
case IrCmd::LOP_COVERAGE:
|
|
return "LOP_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";
|
|
}
|
|
|
|
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::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::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%u", op.index);
|
|
break;
|
|
case IrOpKind::VmConst:
|
|
append(ctx.result, "K%u", op.index);
|
|
break;
|
|
case IrOpKind::VmUpvalue:
|
|
append(ctx.result, "U%u", op.index);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void toString(std::string& result, IrConst constant)
|
|
{
|
|
switch (constant.kind)
|
|
{
|
|
case IrConstKind::Bool:
|
|
append(result, constant.valueBool ? "true" : "false");
|
|
break;
|
|
case IrConstKind::Int:
|
|
append(result, "%di", constant.valueInt);
|
|
break;
|
|
case IrConstKind::Uint:
|
|
append(result, "%uu", constant.valueUint);
|
|
break;
|
|
case IrConstKind::Double:
|
|
append(result, "%.17g", constant.valueDouble);
|
|
break;
|
|
case IrConstKind::Tag:
|
|
result.append(getTagName(constant.valueTag));
|
|
break;
|
|
}
|
|
}
|
|
|
|
void toStringDetailed(IrToStringContext& ctx, const IrInst& inst, uint32_t index)
|
|
{
|
|
size_t start = ctx.result.size();
|
|
|
|
toString(ctx, inst, index);
|
|
padToDetailColumn(ctx.result, start);
|
|
|
|
if (inst.useCount == 0 && hasSideEffects(inst.cmd))
|
|
append(ctx.result, "; %%%u, has side-effects\n", index);
|
|
else
|
|
append(ctx.result, "; useCount: %d, lastUse: %%%u\n", inst.useCount, inst.lastUse);
|
|
}
|
|
|
|
void toStringDetailed(IrToStringContext& ctx, const IrBlock& block, uint32_t index)
|
|
{
|
|
size_t start = ctx.result.size();
|
|
|
|
toString(ctx, block, index);
|
|
padToDetailColumn(ctx.result, start);
|
|
|
|
append(ctx.result, "; useCount: %d\n", block.useCount);
|
|
}
|
|
|
|
std::string toString(IrFunction& function, bool includeDetails)
|
|
{
|
|
std::string result;
|
|
IrToStringContext ctx{result, function.blocks, function.constants};
|
|
|
|
for (size_t i = 0; i < function.blocks.size(); i++)
|
|
{
|
|
IrBlock& block = function.blocks[i];
|
|
|
|
if (block.kind == IrBlockKind::Dead)
|
|
continue;
|
|
|
|
if (includeDetails)
|
|
{
|
|
toStringDetailed(ctx, block, uint32_t(i));
|
|
}
|
|
else
|
|
{
|
|
toString(ctx, block, uint32_t(i));
|
|
ctx.result.append("\n");
|
|
}
|
|
|
|
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++)
|
|
{
|
|
IrInst& inst = function.instructions[index];
|
|
|
|
// Skip pseudo instructions unless they are still referenced
|
|
if (isPseudo(inst.cmd) && inst.useCount == 0)
|
|
continue;
|
|
|
|
append(ctx.result, " ");
|
|
|
|
if (includeDetails)
|
|
{
|
|
toStringDetailed(ctx, inst, index);
|
|
}
|
|
else
|
|
{
|
|
toString(ctx, inst, index);
|
|
ctx.result.append("\n");
|
|
}
|
|
}
|
|
|
|
append(ctx.result, "\n");
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
std::string dump(IrFunction& function)
|
|
{
|
|
std::string result = toString(function, /* includeDetails */ true);
|
|
|
|
printf("%s\n", result.c_str());
|
|
|
|
return result;
|
|
}
|
|
|
|
} // namespace CodeGen
|
|
} // namespace Luau
|