luau/CodeGen/src/IrUtils.cpp
Junseo Yoo b6b74b4425
Sync to upstream/release/634 (#1325)
# 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>
2024-07-12 10:03:36 -07:00

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