mirror of
https://github.com/luau-lang/luau.git
synced 2024-12-12 21:10:37 +00:00
b6b74b4425
# What's Changed? - Performance improvement in the old solver - Bugfixes in the new solver ## Old Solver - Mark types that do not need instantiation when being exported to prevent unnecessary work from being done ## New Solver - Refactored instances of "type family" with "type function" - Index-out-of-bounds bug fix in the resolution resolver - Subtyping reasonings are merged only if all failed --- ### Internal Contributors Co-authored-by: Aaron Weiss <aaronweiss@roblox.com> Co-authored-by: Vighnesh Vijay <vvijay@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com> --------- Co-authored-by: Aaron Weiss <aaronweiss@roblox.com> Co-authored-by: Alexander McCord <amccord@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Vighnesh <vvijay@roblox.com> Co-authored-by: Aviral Goel <agoel@roblox.com> Co-authored-by: David Cope <dcope@roblox.com> Co-authored-by: Lily Brown <lbrown@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
998 lines
33 KiB
C++
998 lines
33 KiB
C++
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
|
#include "Luau/IrUtils.h"
|
|
|
|
#include "Luau/IrBuilder.h"
|
|
|
|
#include "BitUtils.h"
|
|
#include "NativeState.h"
|
|
|
|
#include "lua.h"
|
|
#include "lnumutils.h"
|
|
|
|
#include <limits.h>
|
|
#include <math.h>
|
|
|
|
namespace Luau
|
|
{
|
|
namespace CodeGen
|
|
{
|
|
|
|
IrValueKind getCmdValueKind(IrCmd cmd)
|
|
{
|
|
switch (cmd)
|
|
{
|
|
case IrCmd::NOP:
|
|
return IrValueKind::None;
|
|
case IrCmd::LOAD_TAG:
|
|
return IrValueKind::Tag;
|
|
case IrCmd::LOAD_POINTER:
|
|
return IrValueKind::Pointer;
|
|
case IrCmd::LOAD_DOUBLE:
|
|
return IrValueKind::Double;
|
|
case IrCmd::LOAD_INT:
|
|
return IrValueKind::Int;
|
|
case IrCmd::LOAD_FLOAT:
|
|
return IrValueKind::Double;
|
|
case IrCmd::LOAD_TVALUE:
|
|
return IrValueKind::Tvalue;
|
|
case IrCmd::LOAD_ENV:
|
|
case IrCmd::GET_ARR_ADDR:
|
|
case IrCmd::GET_SLOT_NODE_ADDR:
|
|
case IrCmd::GET_HASH_NODE_ADDR:
|
|
case IrCmd::GET_CLOSURE_UPVAL_ADDR:
|
|
return IrValueKind::Pointer;
|
|
case IrCmd::STORE_TAG:
|
|
case IrCmd::STORE_EXTRA:
|
|
case IrCmd::STORE_POINTER:
|
|
case IrCmd::STORE_DOUBLE:
|
|
case IrCmd::STORE_INT:
|
|
case IrCmd::STORE_VECTOR:
|
|
case IrCmd::STORE_TVALUE:
|
|
case IrCmd::STORE_SPLIT_TVALUE:
|
|
return IrValueKind::None;
|
|
case IrCmd::ADD_INT:
|
|
case IrCmd::SUB_INT:
|
|
return IrValueKind::Int;
|
|
case IrCmd::ADD_NUM:
|
|
case IrCmd::SUB_NUM:
|
|
case IrCmd::MUL_NUM:
|
|
case IrCmd::DIV_NUM:
|
|
case IrCmd::IDIV_NUM:
|
|
case IrCmd::MOD_NUM:
|
|
case IrCmd::MIN_NUM:
|
|
case IrCmd::MAX_NUM:
|
|
case IrCmd::UNM_NUM:
|
|
case IrCmd::FLOOR_NUM:
|
|
case IrCmd::CEIL_NUM:
|
|
case IrCmd::ROUND_NUM:
|
|
case IrCmd::SQRT_NUM:
|
|
case IrCmd::ABS_NUM:
|
|
case IrCmd::SIGN_NUM:
|
|
return IrValueKind::Double;
|
|
case IrCmd::ADD_VEC:
|
|
case IrCmd::SUB_VEC:
|
|
case IrCmd::MUL_VEC:
|
|
case IrCmd::DIV_VEC:
|
|
case IrCmd::UNM_VEC:
|
|
return IrValueKind::Tvalue;
|
|
case IrCmd::NOT_ANY:
|
|
case IrCmd::CMP_ANY:
|
|
return IrValueKind::Int;
|
|
case IrCmd::JUMP:
|
|
case IrCmd::JUMP_IF_TRUTHY:
|
|
case IrCmd::JUMP_IF_FALSY:
|
|
case IrCmd::JUMP_EQ_TAG:
|
|
case IrCmd::JUMP_CMP_INT:
|
|
case IrCmd::JUMP_EQ_POINTER:
|
|
case IrCmd::JUMP_CMP_NUM:
|
|
case IrCmd::JUMP_FORN_LOOP_COND:
|
|
case IrCmd::JUMP_SLOT_MATCH:
|
|
return IrValueKind::None;
|
|
case IrCmd::TABLE_LEN:
|
|
return IrValueKind::Int;
|
|
case IrCmd::TABLE_SETNUM:
|
|
return IrValueKind::Pointer;
|
|
case IrCmd::STRING_LEN:
|
|
return IrValueKind::Int;
|
|
case IrCmd::NEW_TABLE:
|
|
case IrCmd::DUP_TABLE:
|
|
return IrValueKind::Pointer;
|
|
case IrCmd::TRY_NUM_TO_INDEX:
|
|
return IrValueKind::Int;
|
|
case IrCmd::TRY_CALL_FASTGETTM:
|
|
case IrCmd::NEW_USERDATA:
|
|
return IrValueKind::Pointer;
|
|
case IrCmd::INT_TO_NUM:
|
|
case IrCmd::UINT_TO_NUM:
|
|
return IrValueKind::Double;
|
|
case IrCmd::NUM_TO_INT:
|
|
case IrCmd::NUM_TO_UINT:
|
|
return IrValueKind::Int;
|
|
case IrCmd::NUM_TO_VEC:
|
|
case IrCmd::TAG_VECTOR:
|
|
return IrValueKind::Tvalue;
|
|
case IrCmd::ADJUST_STACK_TO_REG:
|
|
case IrCmd::ADJUST_STACK_TO_TOP:
|
|
return IrValueKind::None;
|
|
case IrCmd::FASTCALL:
|
|
return IrValueKind::None;
|
|
case IrCmd::INVOKE_FASTCALL:
|
|
return IrValueKind::Int;
|
|
case IrCmd::CHECK_FASTCALL_RES:
|
|
case IrCmd::DO_ARITH:
|
|
case IrCmd::DO_LEN:
|
|
case IrCmd::GET_TABLE:
|
|
case IrCmd::SET_TABLE:
|
|
case IrCmd::GET_IMPORT:
|
|
case IrCmd::CONCAT:
|
|
case IrCmd::GET_UPVALUE:
|
|
case IrCmd::SET_UPVALUE:
|
|
case IrCmd::CHECK_TAG:
|
|
case IrCmd::CHECK_TRUTHY:
|
|
case IrCmd::CHECK_READONLY:
|
|
case IrCmd::CHECK_NO_METATABLE:
|
|
case IrCmd::CHECK_SAFE_ENV:
|
|
case IrCmd::CHECK_ARRAY_SIZE:
|
|
case IrCmd::CHECK_SLOT_MATCH:
|
|
case IrCmd::CHECK_NODE_NO_NEXT:
|
|
case IrCmd::CHECK_NODE_VALUE:
|
|
case IrCmd::CHECK_BUFFER_LEN:
|
|
case IrCmd::CHECK_USERDATA_TAG:
|
|
case IrCmd::INTERRUPT:
|
|
case IrCmd::CHECK_GC:
|
|
case IrCmd::BARRIER_OBJ:
|
|
case IrCmd::BARRIER_TABLE_BACK:
|
|
case IrCmd::BARRIER_TABLE_FORWARD:
|
|
case IrCmd::SET_SAVEDPC:
|
|
case IrCmd::CLOSE_UPVALS:
|
|
case IrCmd::CAPTURE:
|
|
case IrCmd::SETLIST:
|
|
case IrCmd::CALL:
|
|
case IrCmd::RETURN:
|
|
case IrCmd::FORGLOOP:
|
|
case IrCmd::FORGLOOP_FALLBACK:
|
|
case IrCmd::FORGPREP_XNEXT_FALLBACK:
|
|
case IrCmd::COVERAGE:
|
|
case IrCmd::FALLBACK_GETGLOBAL:
|
|
case IrCmd::FALLBACK_SETGLOBAL:
|
|
case IrCmd::FALLBACK_GETTABLEKS:
|
|
case IrCmd::FALLBACK_SETTABLEKS:
|
|
case IrCmd::FALLBACK_NAMECALL:
|
|
case IrCmd::FALLBACK_PREPVARARGS:
|
|
case IrCmd::FALLBACK_GETVARARGS:
|
|
return IrValueKind::None;
|
|
case IrCmd::NEWCLOSURE:
|
|
return IrValueKind::Pointer;
|
|
case IrCmd::FALLBACK_DUPCLOSURE:
|
|
case IrCmd::FALLBACK_FORGPREP:
|
|
return IrValueKind::None;
|
|
case IrCmd::SUBSTITUTE:
|
|
return IrValueKind::Unknown;
|
|
case IrCmd::BITAND_UINT:
|
|
case IrCmd::BITXOR_UINT:
|
|
case IrCmd::BITOR_UINT:
|
|
case IrCmd::BITNOT_UINT:
|
|
case IrCmd::BITLSHIFT_UINT:
|
|
case IrCmd::BITRSHIFT_UINT:
|
|
case IrCmd::BITARSHIFT_UINT:
|
|
case IrCmd::BITLROTATE_UINT:
|
|
case IrCmd::BITRROTATE_UINT:
|
|
case IrCmd::BITCOUNTLZ_UINT:
|
|
case IrCmd::BITCOUNTRZ_UINT:
|
|
case IrCmd::BYTESWAP_UINT:
|
|
return IrValueKind::Int;
|
|
case IrCmd::INVOKE_LIBM:
|
|
return IrValueKind::Double;
|
|
case IrCmd::GET_TYPE:
|
|
case IrCmd::GET_TYPEOF:
|
|
return IrValueKind::Pointer;
|
|
case IrCmd::FINDUPVAL:
|
|
return IrValueKind::Pointer;
|
|
case IrCmd::BUFFER_READI8:
|
|
case IrCmd::BUFFER_READU8:
|
|
case IrCmd::BUFFER_READI16:
|
|
case IrCmd::BUFFER_READU16:
|
|
case IrCmd::BUFFER_READI32:
|
|
return IrValueKind::Int;
|
|
case IrCmd::BUFFER_WRITEI8:
|
|
case IrCmd::BUFFER_WRITEI16:
|
|
case IrCmd::BUFFER_WRITEI32:
|
|
case IrCmd::BUFFER_WRITEF32:
|
|
case IrCmd::BUFFER_WRITEF64:
|
|
return IrValueKind::None;
|
|
case IrCmd::BUFFER_READF32:
|
|
case IrCmd::BUFFER_READF64:
|
|
return IrValueKind::Double;
|
|
}
|
|
|
|
LUAU_UNREACHABLE();
|
|
}
|
|
|
|
static void removeInstUse(IrFunction& function, uint32_t instIdx)
|
|
{
|
|
IrInst& inst = function.instructions[instIdx];
|
|
|
|
CODEGEN_ASSERT(inst.useCount);
|
|
inst.useCount--;
|
|
|
|
if (inst.useCount == 0)
|
|
kill(function, inst);
|
|
}
|
|
|
|
static void removeBlockUse(IrFunction& function, uint32_t blockIdx)
|
|
{
|
|
IrBlock& block = function.blocks[blockIdx];
|
|
|
|
CODEGEN_ASSERT(block.useCount);
|
|
block.useCount--;
|
|
|
|
// Entry block is never removed because is has an implicit use
|
|
if (block.useCount == 0 && blockIdx != 0)
|
|
kill(function, block);
|
|
}
|
|
|
|
void addUse(IrFunction& function, IrOp op)
|
|
{
|
|
if (op.kind == IrOpKind::Inst)
|
|
function.instructions[op.index].useCount++;
|
|
else if (op.kind == IrOpKind::Block)
|
|
function.blocks[op.index].useCount++;
|
|
}
|
|
|
|
void removeUse(IrFunction& function, IrOp op)
|
|
{
|
|
if (op.kind == IrOpKind::Inst)
|
|
removeInstUse(function, op.index);
|
|
else if (op.kind == IrOpKind::Block)
|
|
removeBlockUse(function, op.index);
|
|
}
|
|
|
|
bool isGCO(uint8_t tag)
|
|
{
|
|
CODEGEN_ASSERT(tag < LUA_T_COUNT);
|
|
|
|
// mirrors iscollectable(o) from VM/lobject.h
|
|
return tag >= LUA_TSTRING;
|
|
}
|
|
|
|
bool isUserdataBytecodeType(uint8_t ty)
|
|
{
|
|
return ty == LBC_TYPE_USERDATA || isCustomUserdataBytecodeType(ty);
|
|
}
|
|
|
|
bool isCustomUserdataBytecodeType(uint8_t ty)
|
|
{
|
|
return ty >= LBC_TYPE_TAGGED_USERDATA_BASE && ty < LBC_TYPE_TAGGED_USERDATA_END;
|
|
}
|
|
|
|
HostMetamethod tmToHostMetamethod(int tm)
|
|
{
|
|
switch (TMS(tm))
|
|
{
|
|
case TM_ADD:
|
|
return HostMetamethod::Add;
|
|
case TM_SUB:
|
|
return HostMetamethod::Sub;
|
|
case TM_MUL:
|
|
return HostMetamethod::Mul;
|
|
case TM_DIV:
|
|
return HostMetamethod::Div;
|
|
case TM_IDIV:
|
|
return HostMetamethod::Idiv;
|
|
case TM_MOD:
|
|
return HostMetamethod::Mod;
|
|
case TM_POW:
|
|
return HostMetamethod::Pow;
|
|
case TM_UNM:
|
|
return HostMetamethod::Minus;
|
|
case TM_EQ:
|
|
return HostMetamethod::Equal;
|
|
case TM_LT:
|
|
return HostMetamethod::LessThan;
|
|
case TM_LE:
|
|
return HostMetamethod::LessEqual;
|
|
case TM_LEN:
|
|
return HostMetamethod::Length;
|
|
case TM_CONCAT:
|
|
return HostMetamethod::Concat;
|
|
default:
|
|
CODEGEN_ASSERT(!"invalid tag method for host");
|
|
break;
|
|
}
|
|
|
|
return HostMetamethod::Add;
|
|
}
|
|
|
|
void kill(IrFunction& function, IrInst& inst)
|
|
{
|
|
CODEGEN_ASSERT(inst.useCount == 0);
|
|
|
|
inst.cmd = IrCmd::NOP;
|
|
|
|
removeUse(function, inst.a);
|
|
removeUse(function, inst.b);
|
|
removeUse(function, inst.c);
|
|
removeUse(function, inst.d);
|
|
removeUse(function, inst.e);
|
|
removeUse(function, inst.f);
|
|
removeUse(function, inst.g);
|
|
|
|
inst.a = {};
|
|
inst.b = {};
|
|
inst.c = {};
|
|
inst.d = {};
|
|
inst.e = {};
|
|
inst.f = {};
|
|
inst.g = {};
|
|
}
|
|
|
|
void kill(IrFunction& function, uint32_t start, uint32_t end)
|
|
{
|
|
// Kill instructions in reverse order to avoid killing instructions that are still marked as used
|
|
for (int i = int(end); i >= int(start); i--)
|
|
{
|
|
CODEGEN_ASSERT(unsigned(i) < function.instructions.size());
|
|
IrInst& curr = function.instructions[i];
|
|
|
|
if (curr.cmd == IrCmd::NOP)
|
|
continue;
|
|
|
|
kill(function, curr);
|
|
}
|
|
}
|
|
|
|
void kill(IrFunction& function, IrBlock& block)
|
|
{
|
|
CODEGEN_ASSERT(block.useCount == 0);
|
|
|
|
block.kind = IrBlockKind::Dead;
|
|
|
|
kill(function, block.start, block.finish);
|
|
block.start = ~0u;
|
|
block.finish = ~0u;
|
|
}
|
|
|
|
void replace(IrFunction& function, IrOp& original, IrOp replacement)
|
|
{
|
|
// Add use before removing new one if that's the last one keeping target operand alive
|
|
addUse(function, replacement);
|
|
removeUse(function, original);
|
|
|
|
original = replacement;
|
|
}
|
|
|
|
void replace(IrFunction& function, IrBlock& block, uint32_t instIdx, IrInst replacement)
|
|
{
|
|
IrInst& inst = function.instructions[instIdx];
|
|
|
|
// Add uses before removing new ones if those are the last ones keeping target operand alive
|
|
addUse(function, replacement.a);
|
|
addUse(function, replacement.b);
|
|
addUse(function, replacement.c);
|
|
addUse(function, replacement.d);
|
|
addUse(function, replacement.e);
|
|
addUse(function, replacement.f);
|
|
addUse(function, replacement.g);
|
|
|
|
// An extra reference is added so block will not remove itself
|
|
block.useCount++;
|
|
|
|
// If we introduced an earlier terminating instruction, all following instructions become dead
|
|
if (!isBlockTerminator(inst.cmd) && isBlockTerminator(replacement.cmd))
|
|
{
|
|
// Block has has to be fully constructed before replacement is performed
|
|
CODEGEN_ASSERT(block.finish != ~0u);
|
|
CODEGEN_ASSERT(instIdx + 1 <= block.finish);
|
|
|
|
kill(function, instIdx + 1, block.finish);
|
|
|
|
block.finish = instIdx;
|
|
}
|
|
|
|
removeUse(function, inst.a);
|
|
removeUse(function, inst.b);
|
|
removeUse(function, inst.c);
|
|
removeUse(function, inst.d);
|
|
removeUse(function, inst.e);
|
|
removeUse(function, inst.f);
|
|
removeUse(function, inst.g);
|
|
|
|
// Inherit existing use count (last use is skipped as it will be defined later)
|
|
replacement.useCount = inst.useCount;
|
|
|
|
inst = replacement;
|
|
|
|
// Removing the earlier extra reference, this might leave the block without users without marking it as dead
|
|
// This will have to be handled by separate dead code elimination
|
|
block.useCount--;
|
|
}
|
|
|
|
void substitute(IrFunction& function, IrInst& inst, IrOp replacement)
|
|
{
|
|
CODEGEN_ASSERT(!isBlockTerminator(inst.cmd));
|
|
|
|
inst.cmd = IrCmd::SUBSTITUTE;
|
|
|
|
addUse(function, replacement);
|
|
|
|
removeUse(function, inst.a);
|
|
removeUse(function, inst.b);
|
|
removeUse(function, inst.c);
|
|
removeUse(function, inst.d);
|
|
removeUse(function, inst.e);
|
|
removeUse(function, inst.f);
|
|
removeUse(function, inst.g);
|
|
|
|
inst.a = replacement;
|
|
inst.b = {};
|
|
inst.c = {};
|
|
inst.d = {};
|
|
inst.e = {};
|
|
inst.f = {};
|
|
inst.g = {};
|
|
}
|
|
|
|
void applySubstitutions(IrFunction& function, IrOp& op)
|
|
{
|
|
if (op.kind == IrOpKind::Inst)
|
|
{
|
|
IrInst& src = function.instructions[op.index];
|
|
|
|
if (src.cmd == IrCmd::SUBSTITUTE)
|
|
{
|
|
op.kind = src.a.kind;
|
|
op.index = src.a.index;
|
|
|
|
// If we substitute with the result of a different instruction, update the use count
|
|
if (op.kind == IrOpKind::Inst)
|
|
{
|
|
IrInst& dst = function.instructions[op.index];
|
|
CODEGEN_ASSERT(dst.cmd != IrCmd::SUBSTITUTE && "chained substitutions are not allowed");
|
|
|
|
dst.useCount++;
|
|
}
|
|
|
|
CODEGEN_ASSERT(src.useCount > 0);
|
|
src.useCount--;
|
|
|
|
if (src.useCount == 0)
|
|
{
|
|
src.cmd = IrCmd::NOP;
|
|
removeUse(function, src.a);
|
|
src.a = {};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void applySubstitutions(IrFunction& function, IrInst& inst)
|
|
{
|
|
applySubstitutions(function, inst.a);
|
|
applySubstitutions(function, inst.b);
|
|
applySubstitutions(function, inst.c);
|
|
applySubstitutions(function, inst.d);
|
|
applySubstitutions(function, inst.e);
|
|
applySubstitutions(function, inst.f);
|
|
applySubstitutions(function, inst.g);
|
|
}
|
|
|
|
bool compare(double a, double b, IrCondition cond)
|
|
{
|
|
// Note: redundant bool() casts work around invalid MSVC optimization that merges cases in this switch, violating IEEE754 comparison semantics
|
|
switch (cond)
|
|
{
|
|
case IrCondition::Equal:
|
|
return a == b;
|
|
case IrCondition::NotEqual:
|
|
return a != b;
|
|
case IrCondition::Less:
|
|
return a < b;
|
|
case IrCondition::NotLess:
|
|
return !bool(a < b);
|
|
case IrCondition::LessEqual:
|
|
return a <= b;
|
|
case IrCondition::NotLessEqual:
|
|
return !bool(a <= b);
|
|
case IrCondition::Greater:
|
|
return a > b;
|
|
case IrCondition::NotGreater:
|
|
return !bool(a > b);
|
|
case IrCondition::GreaterEqual:
|
|
return a >= b;
|
|
case IrCondition::NotGreaterEqual:
|
|
return !bool(a >= b);
|
|
default:
|
|
CODEGEN_ASSERT(!"Unsupported condition");
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool compare(int a, int b, IrCondition cond)
|
|
{
|
|
switch (cond)
|
|
{
|
|
case IrCondition::Equal:
|
|
return a == b;
|
|
case IrCondition::NotEqual:
|
|
return a != b;
|
|
case IrCondition::Less:
|
|
return a < b;
|
|
case IrCondition::NotLess:
|
|
return !(a < b);
|
|
case IrCondition::LessEqual:
|
|
return a <= b;
|
|
case IrCondition::NotLessEqual:
|
|
return !(a <= b);
|
|
case IrCondition::Greater:
|
|
return a > b;
|
|
case IrCondition::NotGreater:
|
|
return !(a > b);
|
|
case IrCondition::GreaterEqual:
|
|
return a >= b;
|
|
case IrCondition::NotGreaterEqual:
|
|
return !(a >= b);
|
|
case IrCondition::UnsignedLess:
|
|
return unsigned(a) < unsigned(b);
|
|
case IrCondition::UnsignedLessEqual:
|
|
return unsigned(a) <= unsigned(b);
|
|
case IrCondition::UnsignedGreater:
|
|
return unsigned(a) > unsigned(b);
|
|
case IrCondition::UnsignedGreaterEqual:
|
|
return unsigned(a) >= unsigned(b);
|
|
default:
|
|
CODEGEN_ASSERT(!"Unsupported condition");
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void foldConstants(IrBuilder& build, IrFunction& function, IrBlock& block, uint32_t index)
|
|
{
|
|
IrInst& inst = function.instructions[index];
|
|
|
|
switch (inst.cmd)
|
|
{
|
|
case IrCmd::ADD_INT:
|
|
if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant)
|
|
{
|
|
// We need to avoid signed integer overflow, but we also have to produce a result
|
|
// So we add numbers as unsigned and use fixed-width integer types to force a two's complement evaluation
|
|
int32_t lhs = function.intOp(inst.a);
|
|
int32_t rhs = function.intOp(inst.b);
|
|
int sum = int32_t(uint32_t(lhs) + uint32_t(rhs));
|
|
|
|
substitute(function, inst, build.constInt(sum));
|
|
}
|
|
break;
|
|
case IrCmd::SUB_INT:
|
|
if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant)
|
|
{
|
|
// We need to avoid signed integer overflow, but we also have to produce a result
|
|
// So we subtract numbers as unsigned and use fixed-width integer types to force a two's complement evaluation
|
|
int32_t lhs = function.intOp(inst.a);
|
|
int32_t rhs = function.intOp(inst.b);
|
|
int sum = int32_t(uint32_t(lhs) - uint32_t(rhs));
|
|
|
|
substitute(function, inst, build.constInt(sum));
|
|
}
|
|
break;
|
|
case IrCmd::ADD_NUM:
|
|
if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant)
|
|
substitute(function, inst, build.constDouble(function.doubleOp(inst.a) + function.doubleOp(inst.b)));
|
|
break;
|
|
case IrCmd::SUB_NUM:
|
|
if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant)
|
|
substitute(function, inst, build.constDouble(function.doubleOp(inst.a) - function.doubleOp(inst.b)));
|
|
break;
|
|
case IrCmd::MUL_NUM:
|
|
if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant)
|
|
substitute(function, inst, build.constDouble(function.doubleOp(inst.a) * function.doubleOp(inst.b)));
|
|
break;
|
|
case IrCmd::DIV_NUM:
|
|
if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant)
|
|
substitute(function, inst, build.constDouble(function.doubleOp(inst.a) / function.doubleOp(inst.b)));
|
|
break;
|
|
case IrCmd::IDIV_NUM:
|
|
if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant)
|
|
substitute(function, inst, build.constDouble(luai_numidiv(function.doubleOp(inst.a), function.doubleOp(inst.b))));
|
|
break;
|
|
case IrCmd::MOD_NUM:
|
|
if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant)
|
|
substitute(function, inst, build.constDouble(luai_nummod(function.doubleOp(inst.a), function.doubleOp(inst.b))));
|
|
break;
|
|
case IrCmd::MIN_NUM:
|
|
if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant)
|
|
{
|
|
double a1 = function.doubleOp(inst.a);
|
|
double a2 = function.doubleOp(inst.b);
|
|
|
|
substitute(function, inst, build.constDouble(a1 < a2 ? a1 : a2));
|
|
}
|
|
break;
|
|
case IrCmd::MAX_NUM:
|
|
if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant)
|
|
{
|
|
double a1 = function.doubleOp(inst.a);
|
|
double a2 = function.doubleOp(inst.b);
|
|
|
|
substitute(function, inst, build.constDouble(a1 > a2 ? a1 : a2));
|
|
}
|
|
break;
|
|
case IrCmd::UNM_NUM:
|
|
if (inst.a.kind == IrOpKind::Constant)
|
|
substitute(function, inst, build.constDouble(-function.doubleOp(inst.a)));
|
|
break;
|
|
case IrCmd::FLOOR_NUM:
|
|
if (inst.a.kind == IrOpKind::Constant)
|
|
substitute(function, inst, build.constDouble(floor(function.doubleOp(inst.a))));
|
|
break;
|
|
case IrCmd::CEIL_NUM:
|
|
if (inst.a.kind == IrOpKind::Constant)
|
|
substitute(function, inst, build.constDouble(ceil(function.doubleOp(inst.a))));
|
|
break;
|
|
case IrCmd::ROUND_NUM:
|
|
if (inst.a.kind == IrOpKind::Constant)
|
|
substitute(function, inst, build.constDouble(round(function.doubleOp(inst.a))));
|
|
break;
|
|
case IrCmd::SQRT_NUM:
|
|
if (inst.a.kind == IrOpKind::Constant)
|
|
substitute(function, inst, build.constDouble(sqrt(function.doubleOp(inst.a))));
|
|
break;
|
|
case IrCmd::ABS_NUM:
|
|
if (inst.a.kind == IrOpKind::Constant)
|
|
substitute(function, inst, build.constDouble(fabs(function.doubleOp(inst.a))));
|
|
break;
|
|
case IrCmd::SIGN_NUM:
|
|
if (inst.a.kind == IrOpKind::Constant)
|
|
{
|
|
double v = function.doubleOp(inst.a);
|
|
|
|
substitute(function, inst, build.constDouble(v > 0.0 ? 1.0 : v < 0.0 ? -1.0 : 0.0));
|
|
}
|
|
break;
|
|
case IrCmd::NOT_ANY:
|
|
if (inst.a.kind == IrOpKind::Constant)
|
|
{
|
|
uint8_t a = function.tagOp(inst.a);
|
|
|
|
if (a == LUA_TNIL)
|
|
substitute(function, inst, build.constInt(1));
|
|
else if (a != LUA_TBOOLEAN)
|
|
substitute(function, inst, build.constInt(0));
|
|
else if (inst.b.kind == IrOpKind::Constant)
|
|
substitute(function, inst, build.constInt(function.intOp(inst.b) == 1 ? 0 : 1));
|
|
}
|
|
break;
|
|
case IrCmd::JUMP_EQ_TAG:
|
|
if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant)
|
|
{
|
|
if (function.tagOp(inst.a) == function.tagOp(inst.b))
|
|
replace(function, block, index, {IrCmd::JUMP, inst.c});
|
|
else
|
|
replace(function, block, index, {IrCmd::JUMP, inst.d});
|
|
}
|
|
break;
|
|
case IrCmd::JUMP_CMP_INT:
|
|
if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant)
|
|
{
|
|
if (compare(function.intOp(inst.a), function.intOp(inst.b), conditionOp(inst.c)))
|
|
replace(function, block, index, {IrCmd::JUMP, inst.d});
|
|
else
|
|
replace(function, block, index, {IrCmd::JUMP, inst.e});
|
|
}
|
|
break;
|
|
case IrCmd::JUMP_CMP_NUM:
|
|
if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant)
|
|
{
|
|
if (compare(function.doubleOp(inst.a), function.doubleOp(inst.b), conditionOp(inst.c)))
|
|
replace(function, block, index, {IrCmd::JUMP, inst.d});
|
|
else
|
|
replace(function, block, index, {IrCmd::JUMP, inst.e});
|
|
}
|
|
break;
|
|
case IrCmd::TRY_NUM_TO_INDEX:
|
|
if (inst.a.kind == IrOpKind::Constant)
|
|
{
|
|
double value = function.doubleOp(inst.a);
|
|
|
|
// To avoid undefined behavior of casting a value not representable in the target type, we check the range
|
|
if (value >= INT_MIN && value <= INT_MAX)
|
|
{
|
|
int arrIndex = int(value);
|
|
|
|
if (double(arrIndex) == value)
|
|
substitute(function, inst, build.constInt(arrIndex));
|
|
else
|
|
replace(function, block, index, {IrCmd::JUMP, inst.b});
|
|
}
|
|
else
|
|
{
|
|
replace(function, block, index, {IrCmd::JUMP, inst.b});
|
|
}
|
|
}
|
|
break;
|
|
case IrCmd::INT_TO_NUM:
|
|
if (inst.a.kind == IrOpKind::Constant)
|
|
substitute(function, inst, build.constDouble(double(function.intOp(inst.a))));
|
|
break;
|
|
case IrCmd::UINT_TO_NUM:
|
|
if (inst.a.kind == IrOpKind::Constant)
|
|
substitute(function, inst, build.constDouble(double(unsigned(function.intOp(inst.a)))));
|
|
break;
|
|
case IrCmd::NUM_TO_INT:
|
|
if (inst.a.kind == IrOpKind::Constant)
|
|
{
|
|
double value = function.doubleOp(inst.a);
|
|
|
|
// To avoid undefined behavior of casting a value not representable in the target type, we check the range
|
|
if (value >= INT_MIN && value <= INT_MAX)
|
|
substitute(function, inst, build.constInt(int(value)));
|
|
}
|
|
break;
|
|
case IrCmd::NUM_TO_UINT:
|
|
if (inst.a.kind == IrOpKind::Constant)
|
|
{
|
|
double value = function.doubleOp(inst.a);
|
|
|
|
// To avoid undefined behavior of casting a value not representable in the target type, we check the range
|
|
if (value >= 0 && value <= UINT_MAX)
|
|
substitute(function, inst, build.constInt(unsigned(function.doubleOp(inst.a))));
|
|
}
|
|
break;
|
|
case IrCmd::CHECK_TAG:
|
|
if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant)
|
|
{
|
|
if (function.tagOp(inst.a) == function.tagOp(inst.b))
|
|
kill(function, inst);
|
|
else
|
|
replace(function, block, index, {IrCmd::JUMP, inst.c}); // Shows a conflict in assumptions on this path
|
|
}
|
|
break;
|
|
case IrCmd::CHECK_TRUTHY:
|
|
if (inst.a.kind == IrOpKind::Constant)
|
|
{
|
|
if (function.tagOp(inst.a) == LUA_TNIL)
|
|
{
|
|
replace(function, block, index, {IrCmd::JUMP, inst.c}); // Shows a conflict in assumptions on this path
|
|
}
|
|
else if (function.tagOp(inst.a) == LUA_TBOOLEAN)
|
|
{
|
|
if (inst.b.kind == IrOpKind::Constant)
|
|
{
|
|
if (function.intOp(inst.b) == 0)
|
|
replace(function, block, index, {IrCmd::JUMP, inst.c}); // Shows a conflict in assumptions on this path
|
|
else
|
|
kill(function, inst);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
kill(function, inst);
|
|
}
|
|
}
|
|
break;
|
|
case IrCmd::BITAND_UINT:
|
|
if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant)
|
|
{
|
|
unsigned op1 = unsigned(function.intOp(inst.a));
|
|
unsigned op2 = unsigned(function.intOp(inst.b));
|
|
substitute(function, inst, build.constInt(op1 & op2));
|
|
}
|
|
else
|
|
{
|
|
if (inst.a.kind == IrOpKind::Constant && function.intOp(inst.a) == 0) // (0 & b) -> 0
|
|
substitute(function, inst, build.constInt(0));
|
|
else if (inst.a.kind == IrOpKind::Constant && function.intOp(inst.a) == -1) // (-1 & b) -> b
|
|
substitute(function, inst, inst.b);
|
|
else if (inst.b.kind == IrOpKind::Constant && function.intOp(inst.b) == 0) // (a & 0) -> 0
|
|
substitute(function, inst, build.constInt(0));
|
|
else if (inst.b.kind == IrOpKind::Constant && function.intOp(inst.b) == -1) // (a & -1) -> a
|
|
substitute(function, inst, inst.a);
|
|
}
|
|
break;
|
|
case IrCmd::BITXOR_UINT:
|
|
if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant)
|
|
{
|
|
unsigned op1 = unsigned(function.intOp(inst.a));
|
|
unsigned op2 = unsigned(function.intOp(inst.b));
|
|
substitute(function, inst, build.constInt(op1 ^ op2));
|
|
}
|
|
else
|
|
{
|
|
if (inst.a.kind == IrOpKind::Constant && function.intOp(inst.a) == 0) // (0 ^ b) -> b
|
|
substitute(function, inst, inst.b);
|
|
else if (inst.a.kind == IrOpKind::Constant && function.intOp(inst.a) == -1) // (-1 ^ b) -> ~b
|
|
replace(function, block, index, {IrCmd::BITNOT_UINT, inst.b});
|
|
else if (inst.b.kind == IrOpKind::Constant && function.intOp(inst.b) == 0) // (a ^ 0) -> a
|
|
substitute(function, inst, inst.a);
|
|
else if (inst.b.kind == IrOpKind::Constant && function.intOp(inst.b) == -1) // (a ^ -1) -> ~a
|
|
replace(function, block, index, {IrCmd::BITNOT_UINT, inst.a});
|
|
}
|
|
break;
|
|
case IrCmd::BITOR_UINT:
|
|
if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant)
|
|
{
|
|
unsigned op1 = unsigned(function.intOp(inst.a));
|
|
unsigned op2 = unsigned(function.intOp(inst.b));
|
|
substitute(function, inst, build.constInt(op1 | op2));
|
|
}
|
|
else
|
|
{
|
|
if (inst.a.kind == IrOpKind::Constant && function.intOp(inst.a) == 0) // (0 | b) -> b
|
|
substitute(function, inst, inst.b);
|
|
else if (inst.a.kind == IrOpKind::Constant && function.intOp(inst.a) == -1) // (-1 | b) -> -1
|
|
substitute(function, inst, build.constInt(-1));
|
|
else if (inst.b.kind == IrOpKind::Constant && function.intOp(inst.b) == 0) // (a | 0) -> a
|
|
substitute(function, inst, inst.a);
|
|
else if (inst.b.kind == IrOpKind::Constant && function.intOp(inst.b) == -1) // (a | -1) -> -1
|
|
substitute(function, inst, build.constInt(-1));
|
|
}
|
|
break;
|
|
case IrCmd::BITNOT_UINT:
|
|
if (inst.a.kind == IrOpKind::Constant)
|
|
substitute(function, inst, build.constInt(~unsigned(function.intOp(inst.a))));
|
|
break;
|
|
case IrCmd::BITLSHIFT_UINT:
|
|
if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant)
|
|
{
|
|
unsigned op1 = unsigned(function.intOp(inst.a));
|
|
int op2 = function.intOp(inst.b);
|
|
|
|
substitute(function, inst, build.constInt(op1 << (op2 & 31)));
|
|
}
|
|
else if (inst.b.kind == IrOpKind::Constant && function.intOp(inst.b) == 0)
|
|
{
|
|
substitute(function, inst, inst.a);
|
|
}
|
|
break;
|
|
case IrCmd::BITRSHIFT_UINT:
|
|
if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant)
|
|
{
|
|
unsigned op1 = unsigned(function.intOp(inst.a));
|
|
int op2 = function.intOp(inst.b);
|
|
|
|
substitute(function, inst, build.constInt(op1 >> (op2 & 31)));
|
|
}
|
|
else if (inst.b.kind == IrOpKind::Constant && function.intOp(inst.b) == 0)
|
|
{
|
|
substitute(function, inst, inst.a);
|
|
}
|
|
break;
|
|
case IrCmd::BITARSHIFT_UINT:
|
|
if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant)
|
|
{
|
|
int op1 = function.intOp(inst.a);
|
|
int op2 = function.intOp(inst.b);
|
|
|
|
// note: technically right shift of negative values is UB, but this behavior is getting defined in C++20 and all compilers do the
|
|
// right (shift) thing.
|
|
substitute(function, inst, build.constInt(op1 >> (op2 & 31)));
|
|
}
|
|
else if (inst.b.kind == IrOpKind::Constant && function.intOp(inst.b) == 0)
|
|
{
|
|
substitute(function, inst, inst.a);
|
|
}
|
|
break;
|
|
case IrCmd::BITLROTATE_UINT:
|
|
if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant)
|
|
substitute(function, inst, build.constInt(lrotate(unsigned(function.intOp(inst.a)), function.intOp(inst.b))));
|
|
else if (inst.b.kind == IrOpKind::Constant && function.intOp(inst.b) == 0)
|
|
substitute(function, inst, inst.a);
|
|
break;
|
|
case IrCmd::BITRROTATE_UINT:
|
|
if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant)
|
|
substitute(function, inst, build.constInt(rrotate(unsigned(function.intOp(inst.a)), function.intOp(inst.b))));
|
|
else if (inst.b.kind == IrOpKind::Constant && function.intOp(inst.b) == 0)
|
|
substitute(function, inst, inst.a);
|
|
break;
|
|
case IrCmd::BITCOUNTLZ_UINT:
|
|
if (inst.a.kind == IrOpKind::Constant)
|
|
substitute(function, inst, build.constInt(countlz(unsigned(function.intOp(inst.a)))));
|
|
break;
|
|
case IrCmd::BITCOUNTRZ_UINT:
|
|
if (inst.a.kind == IrOpKind::Constant)
|
|
substitute(function, inst, build.constInt(countrz(unsigned(function.intOp(inst.a)))));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
uint32_t getNativeContextOffset(int bfid)
|
|
{
|
|
switch (bfid)
|
|
{
|
|
case LBF_MATH_ACOS:
|
|
return offsetof(NativeContext, libm_acos);
|
|
case LBF_MATH_ASIN:
|
|
return offsetof(NativeContext, libm_asin);
|
|
case LBF_MATH_ATAN2:
|
|
return offsetof(NativeContext, libm_atan2);
|
|
case LBF_MATH_ATAN:
|
|
return offsetof(NativeContext, libm_atan);
|
|
case LBF_MATH_COSH:
|
|
return offsetof(NativeContext, libm_cosh);
|
|
case LBF_MATH_COS:
|
|
return offsetof(NativeContext, libm_cos);
|
|
case LBF_MATH_EXP:
|
|
return offsetof(NativeContext, libm_exp);
|
|
case LBF_MATH_LOG10:
|
|
return offsetof(NativeContext, libm_log10);
|
|
case LBF_MATH_LOG:
|
|
return offsetof(NativeContext, libm_log);
|
|
case LBF_MATH_SINH:
|
|
return offsetof(NativeContext, libm_sinh);
|
|
case LBF_MATH_SIN:
|
|
return offsetof(NativeContext, libm_sin);
|
|
case LBF_MATH_TANH:
|
|
return offsetof(NativeContext, libm_tanh);
|
|
case LBF_MATH_TAN:
|
|
return offsetof(NativeContext, libm_tan);
|
|
case LBF_MATH_FMOD:
|
|
return offsetof(NativeContext, libm_fmod);
|
|
case LBF_MATH_POW:
|
|
return offsetof(NativeContext, libm_pow);
|
|
case LBF_IR_MATH_LOG2:
|
|
return offsetof(NativeContext, libm_log2);
|
|
case LBF_MATH_LDEXP:
|
|
return offsetof(NativeContext, libm_ldexp);
|
|
default:
|
|
CODEGEN_ASSERT(!"Unsupported bfid");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void killUnusedBlocks(IrFunction& function)
|
|
{
|
|
// Start from 1 as the first block is the entry block
|
|
for (unsigned i = 1; i < function.blocks.size(); i++)
|
|
{
|
|
IrBlock& block = function.blocks[i];
|
|
|
|
if (block.kind != IrBlockKind::Dead && block.useCount == 0)
|
|
kill(function, block);
|
|
}
|
|
}
|
|
|
|
std::vector<uint32_t> getSortedBlockOrder(IrFunction& function)
|
|
{
|
|
std::vector<uint32_t> sortedBlocks;
|
|
sortedBlocks.reserve(function.blocks.size());
|
|
for (uint32_t i = 0; i < function.blocks.size(); i++)
|
|
sortedBlocks.push_back(i);
|
|
|
|
std::sort(sortedBlocks.begin(), sortedBlocks.end(), [&](uint32_t idxA, uint32_t idxB) {
|
|
const IrBlock& a = function.blocks[idxA];
|
|
const IrBlock& b = function.blocks[idxB];
|
|
|
|
// Place fallback blocks at the end
|
|
if ((a.kind == IrBlockKind::Fallback) != (b.kind == IrBlockKind::Fallback))
|
|
return (a.kind == IrBlockKind::Fallback) < (b.kind == IrBlockKind::Fallback);
|
|
|
|
// Try to order by instruction order
|
|
if (a.sortkey != b.sortkey)
|
|
return a.sortkey < b.sortkey;
|
|
|
|
// Chains of blocks are merged together by having the same sort key and consecutive chain key
|
|
return a.chainkey < b.chainkey;
|
|
});
|
|
|
|
return sortedBlocks;
|
|
}
|
|
|
|
IrBlock& getNextBlock(IrFunction& function, const std::vector<uint32_t>& sortedBlocks, IrBlock& dummy, size_t i)
|
|
{
|
|
for (size_t j = i + 1; j < sortedBlocks.size(); ++j)
|
|
{
|
|
IrBlock& block = function.blocks[sortedBlocks[j]];
|
|
if (block.kind != IrBlockKind::Dead)
|
|
return block;
|
|
}
|
|
|
|
return dummy;
|
|
}
|
|
|
|
} // namespace CodeGen
|
|
} // namespace Luau
|