// 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 #include 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 getSortedBlockOrder(IrFunction& function) { std::vector 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& 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