#include "Luau/OptimizeConstProp.h"
#include "Luau/DenseHash.h"
#include "Luau/IrData.h"
#include "Luau/IrBuilder.h"
#include "Luau/IrUtils.h"
#include "lua.h"
#include <limits.h>
#include <math.h>
#include <array>
#include <utility>
#include <vector>
LUAU_FASTINTVARIABLE(LuauCodeGenMinLinearBlockPath, 3)
LUAU_FASTINTVARIABLE(LuauCodeGenReuseSlotLimit, 64)
LUAU_FASTINTVARIABLE(LuauCodeGenReuseUdataTagLimit, 64)
LUAU_FASTFLAGVARIABLE(DebugLuauAbortingChecks)
LUAU_FASTFLAG(LuauVectorLibNativeDot);
LUAU_FASTFLAGVARIABLE(LuauCodeGenArithOpt);
namespace Luau
{
namespace CodeGen
{
struct RegisterInfo
{
uint8_t tag = 0xff;
IrOp value;
uint32_t version = 0;
bool knownNotReadonly = false;
bool knownNoMetatable = false;
int knownTableArraySize = -1;
};
struct RegisterLink
{
uint8_t reg = 0;
uint32_t version = 0;
};
struct ConstPropState
{
ConstPropState(IrFunction& function)
: function(function)
, valueMap({})
{
}
uint8_t tryGetTag(IrOp op)
{
if (RegisterInfo* info = tryGetRegisterInfo(op))
return info->tag;
return 0xff;
}
void updateTag(IrOp op, uint8_t tag)
{
if (RegisterInfo* info = tryGetRegisterInfo(op))
info->tag = tag;
}
void saveTag(IrOp op, uint8_t tag)
{
if (RegisterInfo* info = tryGetRegisterInfo(op))
{
if (info->tag != tag)
{
info->tag = tag;
info->version++;
}
}
}
IrOp tryGetValue(IrOp op)
{
if (RegisterInfo* info = tryGetRegisterInfo(op))
return info->value;
return IrOp{IrOpKind::None, 0u};
}
void saveValue(IrOp op, IrOp value)
{
CODEGEN_ASSERT(value.kind == IrOpKind::Constant);
if (RegisterInfo* info = tryGetRegisterInfo(op))
{
if (info->value != value)
{
info->value = value;
info->knownNotReadonly = false;
info->knownNoMetatable = false;
info->knownTableArraySize = -1;
info->version++;
}
}
}
void invalidate(RegisterInfo& reg, bool invalidateTag, bool invalidateValue)
{
if (invalidateTag)
{
reg.tag = 0xff;
}
if (invalidateValue)
{
reg.value = {};
reg.knownNotReadonly = false;
reg.knownNoMetatable = false;
reg.knownTableArraySize = -1;
}
reg.version++;
}
void invalidateTag(IrOp regOp)
{
maxReg = vmRegOp(regOp) > maxReg ? vmRegOp(regOp) : maxReg;
invalidate(regs[vmRegOp(regOp)], true, false);
}
void invalidateValue(IrOp regOp)
{
maxReg = vmRegOp(regOp) > maxReg ? vmRegOp(regOp) : maxReg;
invalidate(regs[vmRegOp(regOp)], false, true);
}
void invalidate(IrOp regOp)
{
maxReg = vmRegOp(regOp) > maxReg ? vmRegOp(regOp) : maxReg;
invalidate(regs[vmRegOp(regOp)], true, true);
}
void invalidateRegistersFrom(int firstReg)
{
for (int i = firstReg; i <= maxReg; ++i)
invalidate(regs[i], true, true);
}
void invalidateRegisterRange(int firstReg, int count)
{
if (count == -1)
{
invalidateRegistersFrom(firstReg);
}
else
{
for (int i = firstReg; i < firstReg + count && i <= maxReg; ++i)
invalidate(regs[i], true, true);
}
}
void invalidateCapturedRegisters()
{
for (int i = 0; i <= maxReg; ++i)
{
if (function.cfg.captured.regs.test(i))
invalidate(regs[i], true, true);
}
}
void invalidateValuePropagation()
{
valueMap.clear();
tryNumToIndexCache.clear();
}
void invalidateHeapTableData()
{
getSlotNodeCache.clear();
checkSlotMatchCache.clear();
getArrAddrCache.clear();
checkArraySizeCache.clear();
}
void invalidateHeapBufferData()
{
checkBufferLenCache.clear();
}
void invalidateUserdataData()
{
useradataTagCache.clear();
}
void invalidateHeap()
{
for (int i = 0; i <= maxReg; ++i)
invalidateHeap(regs[i]);
invalidateHeapTableData();
}
void invalidateHeap(RegisterInfo& reg)
{
reg.knownNotReadonly = false;
reg.knownNoMetatable = false;
reg.knownTableArraySize = -1;
}
void invalidateUserCall()
{
invalidateHeap();
invalidateCapturedRegisters();
inSafeEnv = false;
}
void invalidateTableArraySize()
{
for (int i = 0; i <= maxReg; ++i)
invalidateTableArraySize(regs[i]);
invalidateHeapTableData();
}
void invalidateTableArraySize(RegisterInfo& reg)
{
reg.knownTableArraySize = -1;
}
void createRegLink(uint32_t instIdx, IrOp regOp)
{
CODEGEN_ASSERT(!instLink.contains(instIdx));
instLink[instIdx] = RegisterLink{uint8_t(vmRegOp(regOp)), regs[vmRegOp(regOp)].version};
}
RegisterInfo* tryGetRegisterInfo(IrOp op)
{
if (op.kind == IrOpKind::VmReg)
{
maxReg = vmRegOp(op) > maxReg ? vmRegOp(op) : maxReg;
return ®s[vmRegOp(op)];
}
if (RegisterLink* link = tryGetRegLink(op))
{
maxReg = int(link->reg) > maxReg ? int(link->reg) : maxReg;
return ®s[link->reg];
}
return nullptr;
}
RegisterLink* tryGetRegLink(IrOp instOp)
{
if (instOp.kind != IrOpKind::Inst)
return nullptr;
if (RegisterLink* link = instLink.find(instOp.index))
{
if (link->version < regs[link->reg].version)
return nullptr;
return link;
}
return nullptr;
}
IrInst versionedVmRegLoad(IrCmd loadCmd, IrOp op)
{
CODEGEN_ASSERT(op.kind == IrOpKind::VmReg);
uint32_t version = regs[vmRegOp(op)].version;
CODEGEN_ASSERT(version <= 0xffffff);
op.index = vmRegOp(op) | (version << 8);
return IrInst{loadCmd, op};
}
uint32_t* getPreviousInstIndex(const IrInst& inst)
{
CODEGEN_ASSERT(useValueNumbering);
if (uint32_t* prevIdx = valueMap.find(inst))
{
if (function.instructions[*prevIdx].useCount != 0)
return prevIdx;
}
return nullptr;
}
uint32_t* getPreviousVersionedLoadIndex(IrCmd cmd, IrOp vmReg)
{
CODEGEN_ASSERT(vmReg.kind == IrOpKind::VmReg);
return getPreviousInstIndex(versionedVmRegLoad(cmd, vmReg));
}
std::pair<IrCmd, uint32_t> getPreviousVersionedLoadForTag(uint8_t tag, IrOp vmReg)
{
if (useValueNumbering && !function.cfg.captured.regs.test(vmRegOp(vmReg)))
{
if (tag == LUA_TBOOLEAN)
{
if (uint32_t* prevIdx = getPreviousVersionedLoadIndex(IrCmd::LOAD_INT, vmReg))
return std::make_pair(IrCmd::LOAD_INT, *prevIdx);
}
else if (tag == LUA_TNUMBER)
{
if (uint32_t* prevIdx = getPreviousVersionedLoadIndex(IrCmd::LOAD_DOUBLE, vmReg))
return std::make_pair(IrCmd::LOAD_DOUBLE, *prevIdx);
}
else if (isGCO(tag))
{
if (uint32_t* prevIdx = getPreviousVersionedLoadIndex(IrCmd::LOAD_POINTER, vmReg))
return std::make_pair(IrCmd::LOAD_POINTER, *prevIdx);
}
}
return std::make_pair(IrCmd::NOP, kInvalidInstIdx);
}
void substituteOrRecord(IrInst& inst, uint32_t instIdx)
{
if (!useValueNumbering)
return;
if (uint32_t* prevIdx = getPreviousInstIndex(inst))
{
substitute(function, inst, IrOp{IrOpKind::Inst, *prevIdx});
return;
}
valueMap[inst] = instIdx;
}
void substituteOrRecordVmRegLoad(IrInst& loadInst)
{
CODEGEN_ASSERT(loadInst.a.kind == IrOpKind::VmReg);
if (!useValueNumbering)
return;
if (function.cfg.captured.regs.test(vmRegOp(loadInst.a)))
return;
IrInst versionedLoad = versionedVmRegLoad(loadInst.cmd, loadInst.a);
if (uint32_t* prevIdx = getPreviousInstIndex(versionedLoad))
{
if (!instLink.contains(*prevIdx))
createRegLink(*prevIdx, loadInst.a);
substitute(function, loadInst, IrOp{IrOpKind::Inst, *prevIdx});
return;
}
uint32_t instIdx = function.getInstIndex(loadInst);
valueMap[versionedLoad] = instIdx;
createRegLink(instIdx, loadInst.a);
}
void forwardVmRegStoreToLoad(const IrInst& storeInst, IrCmd loadCmd)
{
CODEGEN_ASSERT(storeInst.a.kind == IrOpKind::VmReg);
CODEGEN_ASSERT(storeInst.b.kind == IrOpKind::Inst);
if (!useValueNumbering)
return;
if (function.cfg.captured.regs.test(vmRegOp(storeInst.a)))
return;
valueMap[versionedVmRegLoad(loadCmd, storeInst.a)] = storeInst.b.index;
}
void clear()
{
for (int i = 0; i <= maxReg; ++i)
regs[i] = RegisterInfo();
maxReg = 0;
inSafeEnv = false;
checkedGc = false;
instLink.clear();
invalidateValuePropagation();
invalidateHeapTableData();
invalidateHeapBufferData();
invalidateUserdataData();
}
IrFunction& function;
bool useValueNumbering = false;
std::array<RegisterInfo, 256> regs;
int maxReg = 0;
bool inSafeEnv = false;
bool checkedGc = false;
DenseHashMap<uint32_t, RegisterLink> instLink{~0u};
DenseHashMap<IrInst, uint32_t, IrInstHash, IrInstEq> valueMap;
std::vector<uint32_t> tryNumToIndexCache;
std::vector<uint32_t> getSlotNodeCache; std::vector<uint32_t> checkSlotMatchCache;
std::vector<uint32_t> getArrAddrCache;
std::vector<uint32_t> checkArraySizeCache;
std::vector<uint32_t> checkBufferLenCache;
std::vector<uint32_t> useradataTagCache; };
static void handleBuiltinEffects(ConstPropState& state, LuauBuiltinFunction bfid, uint32_t firstReturnReg, int nresults)
{
switch (bfid)
{
case LBF_NONE:
case LBF_ASSERT:
case LBF_MATH_ABS:
case LBF_MATH_ACOS:
case LBF_MATH_ASIN:
case LBF_MATH_ATAN2:
case LBF_MATH_ATAN:
case LBF_MATH_CEIL:
case LBF_MATH_COSH:
case LBF_MATH_COS:
case LBF_MATH_DEG:
case LBF_MATH_EXP:
case LBF_MATH_FLOOR:
case LBF_MATH_FMOD:
case LBF_MATH_FREXP:
case LBF_MATH_LDEXP:
case LBF_MATH_LOG10:
case LBF_MATH_LOG:
case LBF_MATH_MAX:
case LBF_MATH_MIN:
case LBF_MATH_MODF:
case LBF_MATH_POW:
case LBF_MATH_RAD:
case LBF_MATH_SINH:
case LBF_MATH_SIN:
case LBF_MATH_SQRT:
case LBF_MATH_TANH:
case LBF_MATH_TAN:
case LBF_BIT32_ARSHIFT:
case LBF_BIT32_BAND:
case LBF_BIT32_BNOT:
case LBF_BIT32_BOR:
case LBF_BIT32_BXOR:
case LBF_BIT32_BTEST:
case LBF_BIT32_EXTRACT:
case LBF_BIT32_LROTATE:
case LBF_BIT32_LSHIFT:
case LBF_BIT32_REPLACE:
case LBF_BIT32_RROTATE:
case LBF_BIT32_RSHIFT:
case LBF_TYPE:
case LBF_STRING_BYTE:
case LBF_STRING_CHAR:
case LBF_STRING_LEN:
case LBF_TYPEOF:
case LBF_STRING_SUB:
case LBF_MATH_CLAMP:
case LBF_MATH_SIGN:
case LBF_MATH_ROUND:
case LBF_RAWGET:
case LBF_RAWEQUAL:
case LBF_TABLE_UNPACK:
case LBF_VECTOR:
case LBF_BIT32_COUNTLZ:
case LBF_BIT32_COUNTRZ:
case LBF_SELECT_VARARG:
case LBF_RAWLEN:
case LBF_BIT32_EXTRACTK:
case LBF_GETMETATABLE:
case LBF_TONUMBER:
case LBF_TOSTRING:
case LBF_BIT32_BYTESWAP:
case LBF_BUFFER_READI8:
case LBF_BUFFER_READU8:
case LBF_BUFFER_WRITEU8:
case LBF_BUFFER_READI16:
case LBF_BUFFER_READU16:
case LBF_BUFFER_WRITEU16:
case LBF_BUFFER_READI32:
case LBF_BUFFER_READU32:
case LBF_BUFFER_WRITEU32:
case LBF_BUFFER_READF32:
case LBF_BUFFER_WRITEF32:
case LBF_BUFFER_READF64:
case LBF_BUFFER_WRITEF64:
case LBF_VECTOR_MAGNITUDE:
case LBF_VECTOR_NORMALIZE:
case LBF_VECTOR_CROSS:
case LBF_VECTOR_DOT:
case LBF_VECTOR_FLOOR:
case LBF_VECTOR_CEIL:
case LBF_VECTOR_ABS:
case LBF_VECTOR_SIGN:
case LBF_VECTOR_CLAMP:
case LBF_VECTOR_MIN:
case LBF_VECTOR_MAX:
break;
case LBF_TABLE_INSERT:
state.invalidateHeap();
return; case LBF_RAWSET:
state.invalidateHeap();
break;
case LBF_SETMETATABLE:
state.invalidateHeap(); break;
}
state.invalidateRegistersFrom(firstReturnReg);
}
static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& function, IrBlock& block, IrInst& inst, uint32_t index)
{
switch (inst.cmd)
{
case IrCmd::LOAD_TAG:
if (uint8_t tag = state.tryGetTag(inst.a); tag != 0xff)
{
substitute(function, inst, build.constTag(tag));
}
else if (inst.a.kind == IrOpKind::VmReg)
{
state.substituteOrRecordVmRegLoad(inst);
}
break;
case IrCmd::LOAD_POINTER:
if (inst.a.kind == IrOpKind::VmReg)
state.substituteOrRecordVmRegLoad(inst);
break;
case IrCmd::LOAD_DOUBLE:
{
IrOp value = state.tryGetValue(inst.a);
if (function.asDoubleOp(value))
substitute(function, inst, value);
else if (inst.a.kind == IrOpKind::VmReg)
state.substituteOrRecordVmRegLoad(inst);
break;
}
case IrCmd::LOAD_INT:
{
IrOp value = state.tryGetValue(inst.a);
if (function.asIntOp(value))
substitute(function, inst, value);
else if (inst.a.kind == IrOpKind::VmReg)
state.substituteOrRecordVmRegLoad(inst);
break;
}
case IrCmd::LOAD_FLOAT:
break;
case IrCmd::LOAD_TVALUE:
if (inst.a.kind == IrOpKind::VmReg)
state.substituteOrRecordVmRegLoad(inst);
break;
case IrCmd::STORE_TAG:
if (inst.a.kind == IrOpKind::VmReg)
{
const IrOp source = inst.a;
IrCmd activeLoadCmd = IrCmd::NOP;
uint32_t activeLoadValue = kInvalidInstIdx;
if (inst.b.kind == IrOpKind::Constant)
{
uint8_t value = function.tagOp(inst.b);
std::tie(activeLoadCmd, activeLoadValue) = state.getPreviousVersionedLoadForTag(value, source);
if (state.tryGetTag(source) == value)
kill(function, inst);
else
state.saveTag(source, value);
}
else
{
state.invalidateTag(source);
}
if (activeLoadValue != kInvalidInstIdx)
state.valueMap[state.versionedVmRegLoad(activeLoadCmd, source)] = activeLoadValue;
}
break;
case IrCmd::STORE_EXTRA:
break;
case IrCmd::STORE_POINTER:
if (inst.a.kind == IrOpKind::VmReg)
{
state.invalidateValue(inst.a);
if (inst.b.kind == IrOpKind::Inst)
{
state.forwardVmRegStoreToLoad(inst, IrCmd::LOAD_POINTER);
if (IrInst* instOp = function.asInstOp(inst.b); instOp && instOp->cmd == IrCmd::NEW_TABLE)
{
if (RegisterInfo* info = state.tryGetRegisterInfo(inst.a))
{
info->knownNotReadonly = true;
info->knownNoMetatable = true;
info->knownTableArraySize = function.uintOp(instOp->a);
}
}
}
}
break;
case IrCmd::STORE_DOUBLE:
if (inst.a.kind == IrOpKind::VmReg)
{
if (inst.b.kind == IrOpKind::Constant)
{
if (state.tryGetValue(inst.a) == inst.b)
kill(function, inst);
else
state.saveValue(inst.a, inst.b);
}
else
{
state.invalidateValue(inst.a);
state.forwardVmRegStoreToLoad(inst, IrCmd::LOAD_DOUBLE);
}
}
break;
case IrCmd::STORE_INT:
if (inst.a.kind == IrOpKind::VmReg)
{
if (inst.b.kind == IrOpKind::Constant)
{
if (state.tryGetValue(inst.a) == inst.b)
kill(function, inst);
else
state.saveValue(inst.a, inst.b);
}
else
{
state.invalidateValue(inst.a);
state.forwardVmRegStoreToLoad(inst, IrCmd::LOAD_INT);
}
}
break;
case IrCmd::STORE_VECTOR:
state.invalidateValue(inst.a);
break;
case IrCmd::STORE_TVALUE:
if (inst.a.kind == IrOpKind::VmReg || inst.a.kind == IrOpKind::Inst)
{
if (inst.a.kind == IrOpKind::VmReg)
{
if (inst.b.kind == IrOpKind::Inst)
{
if (uint32_t* prevIdx = state.getPreviousVersionedLoadIndex(IrCmd::LOAD_TVALUE, inst.a))
{
if (*prevIdx == inst.b.index)
{
kill(function, inst);
break;
}
}
}
state.invalidate(inst.a);
}
uint8_t tag = state.tryGetTag(inst.b);
if (tag == 0xff)
{
if (IrInst* arg = function.asInstOp(inst.b))
{
if (arg->cmd == IrCmd::TAG_VECTOR)
tag = LUA_TVECTOR;
if (arg->cmd == IrCmd::LOAD_TVALUE && arg->c.kind != IrOpKind::None)
tag = function.tagOp(arg->c);
}
}
IrOp value = state.tryGetValue(inst.b);
if (inst.a.kind == IrOpKind::VmReg)
{
if (tag != 0xff)
state.saveTag(inst.a, tag);
if (value.kind != IrOpKind::None)
state.saveValue(inst.a, value);
}
IrCmd activeLoadCmd = IrCmd::NOP;
uint32_t activeLoadValue = kInvalidInstIdx;
if (tag != 0xff && state.tryGetRegLink(inst.b) != nullptr)
{
if (IrInst* arg = function.asInstOp(inst.b); arg && arg->cmd == IrCmd::LOAD_TVALUE && arg->a.kind == IrOpKind::VmReg)
{
std::tie(activeLoadCmd, activeLoadValue) = state.getPreviousVersionedLoadForTag(tag, arg->a);
if (activeLoadValue != kInvalidInstIdx)
value = IrOp{IrOpKind::Inst, activeLoadValue};
}
}
bool canSplitTvalueStore = false;
if (tag == LUA_TBOOLEAN &&
(value.kind == IrOpKind::Inst || (value.kind == IrOpKind::Constant && function.constOp(value).kind == IrConstKind::Int)))
canSplitTvalueStore = true;
else if (tag == LUA_TNUMBER &&
(value.kind == IrOpKind::Inst || (value.kind == IrOpKind::Constant && function.constOp(value).kind == IrConstKind::Double)))
canSplitTvalueStore = true;
else if (tag != 0xff && isGCO(tag) && value.kind == IrOpKind::Inst)
canSplitTvalueStore = true;
if (canSplitTvalueStore)
{
replace(function, block, index, {IrCmd::STORE_SPLIT_TVALUE, inst.a, build.constTag(tag), value, inst.c});
if (inst.a.kind == IrOpKind::VmReg && activeLoadValue != kInvalidInstIdx)
state.valueMap[state.versionedVmRegLoad(activeLoadCmd, inst.a)] = activeLoadValue;
}
else if (inst.a.kind == IrOpKind::VmReg)
{
state.forwardVmRegStoreToLoad(inst, IrCmd::LOAD_TVALUE);
}
}
break;
case IrCmd::STORE_SPLIT_TVALUE:
if (inst.a.kind == IrOpKind::VmReg)
{
state.invalidate(inst.a);
state.saveTag(inst.a, function.tagOp(inst.b));
if (inst.c.kind == IrOpKind::Constant)
state.saveValue(inst.a, inst.c);
}
break;
case IrCmd::JUMP_IF_TRUTHY:
if (uint8_t tag = state.tryGetTag(inst.a); tag != 0xff)
{
if (tag == LUA_TNIL)
replace(function, block, index, {IrCmd::JUMP, inst.c});
else if (tag != LUA_TBOOLEAN)
replace(function, block, index, {IrCmd::JUMP, inst.b});
}
break;
case IrCmd::JUMP_IF_FALSY:
if (uint8_t tag = state.tryGetTag(inst.a); tag != 0xff)
{
if (tag == LUA_TNIL)
replace(function, block, index, {IrCmd::JUMP, inst.b});
else if (tag != LUA_TBOOLEAN)
replace(function, block, index, {IrCmd::JUMP, inst.c});
}
break;
case IrCmd::JUMP_EQ_TAG:
{
uint8_t tagA = inst.a.kind == IrOpKind::Constant ? function.tagOp(inst.a) : state.tryGetTag(inst.a);
uint8_t tagB = inst.b.kind == IrOpKind::Constant ? function.tagOp(inst.b) : state.tryGetTag(inst.b);
if (tagA != 0xff && tagB != 0xff)
{
if (tagA == tagB)
replace(function, block, index, {IrCmd::JUMP, inst.c});
else
replace(function, block, index, {IrCmd::JUMP, inst.d});
}
else if (inst.a == inst.b)
{
replace(function, block, index, {IrCmd::JUMP, inst.c});
}
break;
}
case IrCmd::JUMP_CMP_INT:
{
std::optional<int> valueA = function.asIntOp(inst.a.kind == IrOpKind::Constant ? inst.a : state.tryGetValue(inst.a));
std::optional<int> valueB = function.asIntOp(inst.b.kind == IrOpKind::Constant ? inst.b : state.tryGetValue(inst.b));
if (valueA && valueB)
{
if (compare(*valueA, *valueB, conditionOp(inst.c)))
replace(function, block, index, {IrCmd::JUMP, inst.c});
else
replace(function, block, index, {IrCmd::JUMP, inst.d});
}
break;
}
case IrCmd::JUMP_CMP_NUM:
{
std::optional<double> valueA = function.asDoubleOp(inst.a.kind == IrOpKind::Constant ? inst.a : state.tryGetValue(inst.a));
std::optional<double> valueB = function.asDoubleOp(inst.b.kind == IrOpKind::Constant ? inst.b : state.tryGetValue(inst.b));
if (valueA && valueB)
{
if (compare(*valueA, *valueB, conditionOp(inst.c)))
replace(function, block, index, {IrCmd::JUMP, inst.d});
else
replace(function, block, index, {IrCmd::JUMP, inst.e});
}
break;
}
case IrCmd::JUMP_FORN_LOOP_COND:
{
std::optional<double> step = function.asDoubleOp(inst.c.kind == IrOpKind::Constant ? inst.c : state.tryGetValue(inst.c));
if (!step)
break;
std::optional<double> idx = function.asDoubleOp(inst.a.kind == IrOpKind::Constant ? inst.a : state.tryGetValue(inst.a));
std::optional<double> limit = function.asDoubleOp(inst.b.kind == IrOpKind::Constant ? inst.b : state.tryGetValue(inst.b));
if (*step > 0)
{
if (idx && limit)
{
if (compare(*idx, *limit, IrCondition::NotLessEqual))
replace(function, block, index, {IrCmd::JUMP, inst.e});
else
replace(function, block, index, {IrCmd::JUMP, inst.d});
}
else
{
replace(function, block, index, IrInst{IrCmd::JUMP_CMP_NUM, inst.a, inst.b, build.cond(IrCondition::NotLessEqual), inst.e, inst.d});
}
}
else
{
if (idx && limit)
{
if (compare(*limit, *idx, IrCondition::NotLessEqual))
replace(function, block, index, {IrCmd::JUMP, inst.e});
else
replace(function, block, index, {IrCmd::JUMP, inst.d});
}
else
{
replace(function, block, index, IrInst{IrCmd::JUMP_CMP_NUM, inst.b, inst.a, build.cond(IrCondition::NotLessEqual), inst.e, inst.d});
}
}
break;
}
case IrCmd::GET_UPVALUE:
state.invalidate(inst.a);
break;
case IrCmd::SET_UPVALUE:
if (inst.b.kind == IrOpKind::VmReg)
{
if (uint8_t tag = state.tryGetTag(inst.b); tag != 0xff)
{
replace(function, inst.c, build.constTag(tag));
}
}
break;
case IrCmd::CHECK_TAG:
{
uint8_t b = function.tagOp(inst.b);
uint8_t tag = state.tryGetTag(inst.a);
if (tag == 0xff)
{
if (IrOp value = state.tryGetValue(inst.a); value.kind == IrOpKind::Constant)
{
if (function.constOp(value).kind == IrConstKind::Double)
tag = LUA_TNUMBER;
}
}
if (tag != 0xff)
{
if (tag == b)
{
if (FFlag::DebugLuauAbortingChecks)
replace(function, inst.c, build.undef());
else
kill(function, inst);
}
else
{
replace(function, block, index, {IrCmd::JUMP, inst.c}); }
}
else
{
state.updateTag(inst.a, b); }
break;
}
case IrCmd::CHECK_TRUTHY:
break;
case IrCmd::CHECK_READONLY:
if (RegisterInfo* info = state.tryGetRegisterInfo(inst.a))
{
if (info->knownNotReadonly)
{
if (FFlag::DebugLuauAbortingChecks)
replace(function, inst.b, build.undef());
else
kill(function, inst);
}
else
{
info->knownNotReadonly = true;
}
}
break;
case IrCmd::CHECK_NO_METATABLE:
if (RegisterInfo* info = state.tryGetRegisterInfo(inst.a))
{
if (info->knownNoMetatable)
{
if (FFlag::DebugLuauAbortingChecks)
replace(function, inst.b, build.undef());
else
kill(function, inst);
}
else
{
info->knownNoMetatable = true;
}
}
break;
case IrCmd::CHECK_SAFE_ENV:
if (state.inSafeEnv)
{
if (FFlag::DebugLuauAbortingChecks)
replace(function, inst.a, build.undef());
else
kill(function, inst);
}
else
{
state.inSafeEnv = true;
}
break;
case IrCmd::CHECK_BUFFER_LEN:
{
std::optional<int> bufferOffset = function.asIntOp(inst.b.kind == IrOpKind::Constant ? inst.b : state.tryGetValue(inst.b));
int accessSize = function.intOp(inst.c);
CODEGEN_ASSERT(accessSize > 0);
if (bufferOffset)
{
if (*bufferOffset < 0 || unsigned(*bufferOffset) + unsigned(accessSize) >= unsigned(INT_MAX))
{
replace(function, block, index, {IrCmd::JUMP, inst.d});
break;
}
}
for (uint32_t prevIdx : state.checkBufferLenCache)
{
IrInst& prev = function.instructions[prevIdx];
if (prev.a != inst.a || prev.c != inst.c)
continue;
if (prev.b == inst.b)
{
if (FFlag::DebugLuauAbortingChecks)
replace(function, inst.d, build.undef());
else
kill(function, inst);
return; }
else if (inst.b.kind == IrOpKind::Constant && prev.b.kind == IrOpKind::Constant)
{
int currBound = function.intOp(inst.b);
int prevBound = function.intOp(prev.b);
CODEGEN_ASSERT(currBound >= 0);
CODEGEN_ASSERT(prevBound >= 0);
if (unsigned(currBound) >= unsigned(prevBound))
replace(function, prev.b, inst.b);
if (FFlag::DebugLuauAbortingChecks)
replace(function, inst.d, build.undef());
else
kill(function, inst);
return; }
}
if (int(state.checkBufferLenCache.size()) < FInt::LuauCodeGenReuseSlotLimit)
state.checkBufferLenCache.push_back(index);
break;
}
case IrCmd::CHECK_USERDATA_TAG:
{
for (uint32_t prevIdx : state.useradataTagCache)
{
IrInst& prev = function.instructions[prevIdx];
if (prev.cmd == IrCmd::CHECK_USERDATA_TAG)
{
if (prev.a != inst.a || prev.b != inst.b)
continue;
}
else if (prev.cmd == IrCmd::NEW_USERDATA)
{
if (inst.a.kind != IrOpKind::Inst || prevIdx != inst.a.index || prev.b != inst.b)
continue;
}
if (FFlag::DebugLuauAbortingChecks)
replace(function, inst.c, build.undef());
else
kill(function, inst);
return; }
if (int(state.useradataTagCache.size()) < FInt::LuauCodeGenReuseUdataTagLimit)
state.useradataTagCache.push_back(index);
break;
}
case IrCmd::BUFFER_READI8:
case IrCmd::BUFFER_READU8:
case IrCmd::BUFFER_WRITEI8:
case IrCmd::BUFFER_READI16:
case IrCmd::BUFFER_READU16:
case IrCmd::BUFFER_WRITEI16:
case IrCmd::BUFFER_READI32:
case IrCmd::BUFFER_WRITEI32:
case IrCmd::BUFFER_READF32:
case IrCmd::BUFFER_WRITEF32:
case IrCmd::BUFFER_READF64:
case IrCmd::BUFFER_WRITEF64:
break;
case IrCmd::CHECK_GC:
if (state.checkedGc)
{
kill(function, inst);
}
else
{
state.checkedGc = true;
state.invalidateHeapTableData();
}
break;
case IrCmd::BARRIER_OBJ:
case IrCmd::BARRIER_TABLE_FORWARD:
if (inst.b.kind == IrOpKind::VmReg)
{
if (uint8_t tag = state.tryGetTag(inst.b); tag != 0xff)
{
if (!isGCO(tag))
kill(function, inst);
else
replace(function, inst.c, build.constTag(tag));
}
}
break;
case IrCmd::FASTCALL:
{
LuauBuiltinFunction bfid = LuauBuiltinFunction(function.uintOp(inst.a));
int firstReturnReg = vmRegOp(inst.b);
int nresults = function.intOp(inst.d);
handleBuiltinEffects(state, bfid, firstReturnReg, nresults);
switch (bfid)
{
case LBF_MATH_MODF:
case LBF_MATH_FREXP:
state.updateTag(IrOp{IrOpKind::VmReg, uint8_t(firstReturnReg)}, LUA_TNUMBER);
if (nresults > 1)
state.updateTag(IrOp{IrOpKind::VmReg, uint8_t(firstReturnReg + 1)}, LUA_TNUMBER);
break;
default:
break;
}
break;
}
case IrCmd::INVOKE_FASTCALL:
handleBuiltinEffects(state, LuauBuiltinFunction(function.uintOp(inst.a)), vmRegOp(inst.b), function.intOp(inst.g));
break;
case IrCmd::NOP:
case IrCmd::LOAD_ENV:
break;
case IrCmd::GET_ARR_ADDR:
for (uint32_t prevIdx : state.getArrAddrCache)
{
const IrInst& prev = function.instructions[prevIdx];
if (prev.a == inst.a && prev.b == inst.b)
{
substitute(function, inst, IrOp{IrOpKind::Inst, prevIdx});
return; }
}
if (int(state.getArrAddrCache.size()) < FInt::LuauCodeGenReuseSlotLimit)
state.getArrAddrCache.push_back(index);
break;
case IrCmd::GET_SLOT_NODE_ADDR:
for (uint32_t prevIdx : state.getSlotNodeCache)
{
const IrInst& prev = function.instructions[prevIdx];
if (prev.a == inst.a && prev.c == inst.c)
{
substitute(function, inst, IrOp{IrOpKind::Inst, prevIdx});
return; }
}
if (int(state.getSlotNodeCache.size()) < FInt::LuauCodeGenReuseSlotLimit)
state.getSlotNodeCache.push_back(index);
break;
case IrCmd::GET_HASH_NODE_ADDR:
case IrCmd::GET_CLOSURE_UPVAL_ADDR:
break;
case IrCmd::ADD_INT:
case IrCmd::SUB_INT:
state.substituteOrRecord(inst, index);
break;
case IrCmd::ADD_NUM:
case IrCmd::SUB_NUM:
if (FFlag::LuauCodeGenArithOpt)
{
if (std::optional<double> k = function.asDoubleOp(inst.b.kind == IrOpKind::Constant ? inst.b : state.tryGetValue(inst.b)))
{
if (*k == 0.0 && bool(signbit(*k)) == (inst.cmd == IrCmd::ADD_NUM))
substitute(function, inst, inst.a);
else
state.substituteOrRecord(inst, index);
}
else
state.substituteOrRecord(inst, index);
}
else
state.substituteOrRecord(inst, index);
break;
case IrCmd::MUL_NUM:
if (FFlag::LuauCodeGenArithOpt)
{
if (std::optional<double> k = function.asDoubleOp(inst.b.kind == IrOpKind::Constant ? inst.b : state.tryGetValue(inst.b)))
{
if (*k == 1.0) substitute(function, inst, inst.a);
else if (*k == 2.0) replace(function, block, index, {IrCmd::ADD_NUM, inst.a, inst.a});
else if (*k == -1.0) replace(function, block, index, {IrCmd::UNM_NUM, inst.a});
else
state.substituteOrRecord(inst, index);
}
else
state.substituteOrRecord(inst, index);
}
else
state.substituteOrRecord(inst, index);
break;
case IrCmd::DIV_NUM:
if (FFlag::LuauCodeGenArithOpt)
{
if (std::optional<double> k = function.asDoubleOp(inst.b.kind == IrOpKind::Constant ? inst.b : state.tryGetValue(inst.b)))
{
if (*k == 1.0) substitute(function, inst, inst.a);
else if (*k == -1.0) replace(function, block, index, {IrCmd::UNM_NUM, inst.a});
else if (int exp = 0; frexp(*k, &exp) == 0.5 && exp >= -1000 && exp <= 1000) replace(function, block, index, {IrCmd::MUL_NUM, inst.a, build.constDouble(1.0 / *k)});
else
state.substituteOrRecord(inst, index);
}
else
state.substituteOrRecord(inst, index);
}
else
state.substituteOrRecord(inst, index);
break;
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:
case IrCmd::NOT_ANY:
state.substituteOrRecord(inst, index);
break;
case IrCmd::CMP_ANY:
state.invalidateUserCall();
break;
case IrCmd::JUMP:
case IrCmd::JUMP_EQ_POINTER:
case IrCmd::JUMP_SLOT_MATCH:
case IrCmd::TABLE_LEN:
break;
case IrCmd::TABLE_SETNUM:
state.invalidateTableArraySize();
break;
case IrCmd::STRING_LEN:
case IrCmd::NEW_TABLE:
case IrCmd::DUP_TABLE:
break;
case IrCmd::TRY_NUM_TO_INDEX:
for (uint32_t prevIdx : state.tryNumToIndexCache)
{
const IrInst& prev = function.instructions[prevIdx];
if (prev.a == inst.a)
{
substitute(function, inst, IrOp{IrOpKind::Inst, prevIdx});
return; }
}
if (int(state.tryNumToIndexCache.size()) < FInt::LuauCodeGenReuseSlotLimit)
state.tryNumToIndexCache.push_back(index);
break;
case IrCmd::TRY_CALL_FASTGETTM:
break;
case IrCmd::NEW_USERDATA:
if (int(state.useradataTagCache.size()) < FInt::LuauCodeGenReuseUdataTagLimit)
state.useradataTagCache.push_back(index);
break;
case IrCmd::INT_TO_NUM:
case IrCmd::UINT_TO_NUM:
state.substituteOrRecord(inst, index);
break;
case IrCmd::NUM_TO_INT:
if (IrInst* src = function.asInstOp(inst.a); src && src->cmd == IrCmd::INT_TO_NUM)
substitute(function, inst, src->a);
else
state.substituteOrRecord(inst, index);
break;
case IrCmd::NUM_TO_UINT:
if (IrInst* src = function.asInstOp(inst.a); src && src->cmd == IrCmd::UINT_TO_NUM)
substitute(function, inst, src->a);
else
state.substituteOrRecord(inst, index);
break;
case IrCmd::CHECK_ARRAY_SIZE:
{
std::optional<int> arrayIndex = function.asIntOp(inst.b.kind == IrOpKind::Constant ? inst.b : state.tryGetValue(inst.b));
if (arrayIndex && *arrayIndex < 0)
{
replace(function, block, index, {IrCmd::JUMP, inst.c});
break;
}
if (RegisterInfo* info = state.tryGetRegisterInfo(inst.a); info && arrayIndex)
{
if (info->knownTableArraySize >= 0)
{
if (unsigned(*arrayIndex) < unsigned(info->knownTableArraySize))
{
if (FFlag::DebugLuauAbortingChecks)
replace(function, inst.c, build.undef());
else
kill(function, inst);
}
else
{
replace(function, block, index, {IrCmd::JUMP, inst.c});
}
break;
}
}
for (uint32_t prevIdx : state.checkArraySizeCache)
{
const IrInst& prev = function.instructions[prevIdx];
if (prev.a != inst.a)
continue;
bool sameBoundary = prev.b == inst.b;
if (!sameBoundary && inst.b.kind == IrOpKind::Constant && prev.b.kind == IrOpKind::Constant &&
unsigned(function.intOp(inst.b)) < unsigned(function.intOp(prev.b)))
sameBoundary = true;
if (sameBoundary)
{
if (FFlag::DebugLuauAbortingChecks)
replace(function, inst.c, build.undef());
else
kill(function, inst);
return; }
}
if (int(state.checkArraySizeCache.size()) < FInt::LuauCodeGenReuseSlotLimit)
state.checkArraySizeCache.push_back(index);
break;
}
case IrCmd::CHECK_SLOT_MATCH:
for (uint32_t prevIdx : state.checkSlotMatchCache)
{
const IrInst& prev = function.instructions[prevIdx];
if (prev.a == inst.a && prev.b == inst.b)
{
replace(function, block, index, {IrCmd::CHECK_NODE_VALUE, inst.a, inst.c});
return; }
}
if (int(state.checkSlotMatchCache.size()) < FInt::LuauCodeGenReuseSlotLimit)
state.checkSlotMatchCache.push_back(index);
break;
case IrCmd::ADD_VEC:
case IrCmd::SUB_VEC:
case IrCmd::MUL_VEC:
case IrCmd::DIV_VEC:
case IrCmd::DOT_VEC:
if (inst.cmd == IrCmd::DOT_VEC)
LUAU_ASSERT(FFlag::LuauVectorLibNativeDot);
if (IrInst* a = function.asInstOp(inst.a); a && a->cmd == IrCmd::TAG_VECTOR)
replace(function, inst.a, a->a);
if (IrInst* b = function.asInstOp(inst.b); b && b->cmd == IrCmd::TAG_VECTOR)
replace(function, inst.b, b->a);
break;
case IrCmd::UNM_VEC:
if (IrInst* a = function.asInstOp(inst.a); a && a->cmd == IrCmd::TAG_VECTOR)
replace(function, inst.a, a->a);
break;
case IrCmd::CHECK_NODE_NO_NEXT:
case IrCmd::CHECK_NODE_VALUE:
case IrCmd::BARRIER_TABLE_BACK:
case IrCmd::RETURN:
case IrCmd::COVERAGE:
case IrCmd::SET_SAVEDPC: case IrCmd::CLOSE_UPVALS: case IrCmd::CAPTURE:
case IrCmd::SUBSTITUTE:
case IrCmd::ADJUST_STACK_TO_REG: case IrCmd::ADJUST_STACK_TO_TOP: case IrCmd::CHECK_FASTCALL_RES: 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::BITRROTATE_UINT:
case IrCmd::BITLROTATE_UINT:
case IrCmd::BITCOUNTLZ_UINT:
case IrCmd::BITCOUNTRZ_UINT:
case IrCmd::BYTESWAP_UINT:
case IrCmd::INVOKE_LIBM:
case IrCmd::GET_TYPE:
case IrCmd::GET_TYPEOF:
case IrCmd::FINDUPVAL:
case IrCmd::NUM_TO_VEC:
case IrCmd::TAG_VECTOR:
break;
case IrCmd::DO_ARITH:
state.invalidate(inst.a);
state.invalidateUserCall();
break;
case IrCmd::DO_LEN:
state.invalidate(inst.a);
state.invalidateUserCall();
state.saveTag(inst.a, LUA_TNUMBER);
break;
case IrCmd::GET_TABLE:
state.invalidate(inst.a);
state.invalidateUserCall();
break;
case IrCmd::SET_TABLE:
state.invalidateUserCall();
break;
case IrCmd::GET_IMPORT:
state.invalidate(inst.a);
state.invalidateUserCall();
break;
case IrCmd::CONCAT:
state.invalidateRegisterRange(vmRegOp(inst.a), function.uintOp(inst.b));
state.invalidateUserCall(); break;
case IrCmd::INTERRUPT:
state.invalidateUserCall();
break;
case IrCmd::SETLIST:
if (RegisterInfo* info = state.tryGetRegisterInfo(inst.b); info && info->knownTableArraySize >= 0)
replace(function, inst.f, build.constUint(info->knownTableArraySize));
state.invalidateValuePropagation();
state.invalidateHeapTableData();
state.invalidateHeapBufferData();
break;
case IrCmd::CALL:
state.invalidateRegistersFrom(vmRegOp(inst.a));
state.invalidateUserCall();
state.invalidateValuePropagation();
break;
case IrCmd::FORGLOOP:
state.invalidateRegistersFrom(vmRegOp(inst.a) + 2);
state.invalidateValuePropagation();
state.invalidateHeapTableData();
state.invalidateHeapBufferData();
break;
case IrCmd::FORGLOOP_FALLBACK:
state.invalidateRegistersFrom(vmRegOp(inst.a) + 2); state.invalidateUserCall();
break;
case IrCmd::FORGPREP_XNEXT_FALLBACK:
break;
case IrCmd::FALLBACK_GETGLOBAL:
state.invalidate(inst.b);
state.invalidateUserCall();
break;
case IrCmd::FALLBACK_SETGLOBAL:
state.invalidateUserCall();
break;
case IrCmd::FALLBACK_GETTABLEKS:
state.invalidate(inst.b);
state.invalidateUserCall();
break;
case IrCmd::FALLBACK_SETTABLEKS:
state.invalidateUserCall();
break;
case IrCmd::FALLBACK_NAMECALL:
state.invalidate(IrOp{inst.b.kind, vmRegOp(inst.b) + 0u});
state.invalidate(IrOp{inst.b.kind, vmRegOp(inst.b) + 1u});
state.invalidateUserCall();
break;
case IrCmd::FALLBACK_PREPVARARGS:
break;
case IrCmd::FALLBACK_GETVARARGS:
state.invalidateRegisterRange(vmRegOp(inst.b), function.intOp(inst.c));
break;
case IrCmd::NEWCLOSURE:
break;
case IrCmd::FALLBACK_DUPCLOSURE:
state.invalidate(inst.b);
break;
case IrCmd::FALLBACK_FORGPREP:
state.invalidate(IrOp{inst.b.kind, vmRegOp(inst.b) + 0u});
state.invalidate(IrOp{inst.b.kind, vmRegOp(inst.b) + 1u});
state.invalidate(IrOp{inst.b.kind, vmRegOp(inst.b) + 2u});
state.invalidateUserCall();
break;
}
}
static void constPropInBlock(IrBuilder& build, IrBlock& block, ConstPropState& state)
{
IrFunction& function = build.function;
for (uint32_t index = block.start; index <= block.finish; index++)
{
CODEGEN_ASSERT(index < function.instructions.size());
IrInst& inst = function.instructions[index];
applySubstitutions(function, inst);
foldConstants(build, function, block, index);
constPropInInst(state, build, function, block, inst, index);
}
}
static void constPropInBlockChain(IrBuilder& build, std::vector<uint8_t>& visited, IrBlock* block, ConstPropState& state)
{
IrFunction& function = build.function;
state.clear();
const uint32_t startSortkey = block->sortkey;
uint32_t chainPos = 0;
while (block)
{
uint32_t blockIdx = function.getBlockIndex(*block);
CODEGEN_ASSERT(!visited[blockIdx]);
visited[blockIdx] = true;
constPropInBlock(build, *block, state);
state.invalidateValuePropagation();
state.invalidateHeapTableData();
state.invalidateHeapBufferData();
state.invalidateUserdataData();
block->sortkey = startSortkey;
block->chainkey = chainPos++;
IrInst& termInst = function.instructions[block->finish];
IrBlock* nextBlock = nullptr;
if (termInst.cmd == IrCmd::JUMP && termInst.a.kind == IrOpKind::Block)
{
IrBlock& target = function.blockOp(termInst.a);
uint32_t targetIdx = function.getBlockIndex(target);
if (target.useCount == 1 && !visited[targetIdx] && target.kind != IrBlockKind::Fallback)
{
if (getLiveOutValueCount(function, target) != 0)
break;
block->expectedNextBlock = function.getBlockIndex(target);
nextBlock = ⌖
}
}
block = nextBlock;
}
}
static std::vector<uint32_t> collectDirectBlockJumpPath(IrFunction& function, std::vector<uint8_t>& visited, IrBlock* block)
{
CODEGEN_ASSERT(getLiveOutValueCount(function, *block) == 0);
std::vector<uint32_t> path;
while (block)
{
IrInst& termInst = function.instructions[block->finish];
IrBlock* nextBlock = nullptr;
if (termInst.cmd == IrCmd::JUMP && termInst.a.kind == IrOpKind::Block)
{
IrBlock& target = function.blockOp(termInst.a);
uint32_t targetIdx = function.getBlockIndex(target);
if (!visited[targetIdx] && target.kind == IrBlockKind::Internal)
{
auto [liveIns, liveOuts] = getLiveInOutValueCount(function, target);
if (liveIns == 0 && liveOuts == 0)
{
visited[targetIdx] = true;
path.push_back(targetIdx);
nextBlock = ⌖
}
}
}
block = nextBlock;
}
return path;
}
static void tryCreateLinearBlock(IrBuilder& build, std::vector<uint8_t>& visited, IrBlock& startingBlock, ConstPropState& state)
{
IrFunction& function = build.function;
uint32_t blockIdx = function.getBlockIndex(startingBlock);
CODEGEN_ASSERT(!visited[blockIdx]);
visited[blockIdx] = true;
IrInst& termInst = function.instructions[startingBlock.finish];
if (termInst.cmd != IrCmd::JUMP)
return;
if (termInst.a.kind != IrOpKind::Block)
return;
if (function.blockOp(termInst.a).useCount == 1)
return;
uint32_t targetBlockIdx = termInst.a.index;
std::vector<uint32_t> path = collectDirectBlockJumpPath(function, visited, &startingBlock);
if (int(path.size()) < FInt::LuauCodeGenMinLinearBlockPath)
return;
state.clear();
constPropInBlock(build, startingBlock, state);
CODEGEN_ASSERT(function.instructions[startingBlock.finish].a.index == targetBlockIdx);
const uint32_t startingSortKey = startingBlock.sortkey;
const uint32_t startingChainKey = startingBlock.chainkey;
IrOp newBlock = build.block(IrBlockKind::Linearized);
visited.push_back(false);
build.beginBlock(newBlock);
function.blocks[newBlock.index].sortkey = startingSortKey;
function.blocks[newBlock.index].chainkey = startingChainKey + 1;
function.blocks[blockIdx].expectedNextBlock = newBlock.index;
replace(function, termInst.a, newBlock);
for (uint32_t pathBlockIdx : path)
build.clone(function.blocks[pathBlockIdx], true);
if (function.cfg.in.size() == newBlock.index)
{
CODEGEN_ASSERT(function.cfg.in.size() == function.cfg.out.size());
CODEGEN_ASSERT(function.cfg.in.size() == function.cfg.def.size());
function.cfg.in.push_back(function.cfg.in[path.front()]);
function.cfg.out.push_back(function.cfg.out[path.back()]);
function.cfg.def.push_back({});
RegisterSet& def = function.cfg.def.back();
for (uint32_t pathBlockIdx : path)
{
const RegisterSet& pathDef = function.cfg.def[pathBlockIdx];
def.regs |= pathDef.regs;
if (pathDef.varargSeq && function.cfg.out.back().varargSeq)
{
def.varargSeq = true;
def.varargStart = pathDef.varargStart;
}
}
function.cfg.predecessorsOffsets.push_back(uint32_t(function.cfg.predecessors.size()));
function.cfg.predecessors.push_back(blockIdx);
}
IrBlock& linearBlock = function.blockOp(newBlock);
constPropInBlock(build, linearBlock, state);
}
void constPropInBlockChains(IrBuilder& build, bool useValueNumbering)
{
IrFunction& function = build.function;
ConstPropState state{function};
state.useValueNumbering = useValueNumbering;
std::vector<uint8_t> visited(function.blocks.size(), false);
for (IrBlock& block : function.blocks)
{
if (block.kind == IrBlockKind::Fallback || block.kind == IrBlockKind::Dead)
continue;
if (visited[function.getBlockIndex(block)])
continue;
constPropInBlockChain(build, visited, &block, state);
}
}
void createLinearBlocks(IrBuilder& build, bool useValueNumbering)
{
IrFunction& function = build.function;
ConstPropState state{function};
state.useValueNumbering = useValueNumbering;
std::vector<uint8_t> visited(function.blocks.size(), false);
size_t originalBlockCount = function.blocks.size();
for (size_t i = 0; i < originalBlockCount; i++)
{
IrBlock& block = function.blocks[i];
if (block.kind == IrBlockKind::Fallback || block.kind == IrBlockKind::Dead)
continue;
if (visited[function.getBlockIndex(block)])
continue;
tryCreateLinearBlock(build, visited, block, state);
}
}
} }