mirror of
https://github.com/luau-lang/luau.git
synced 2025-05-04 10:33:46 +01:00
Hey folks, another week means another Luau release! This one features a number of bug fixes in the New Type Solver including improvements to user-defined type functions and a bunch of work to untangle some of the outstanding issues we've been seeing with constraint solving not completing in real world use. We're also continuing to make progress on crashes and other problems that affect the stability of fragment autocomplete, as we work towards delivering consistent, low-latency autocomplete for any editor environment. ## New Type Solver - Fix a bug in user-defined type functions where `print` would incorrectly insert `\1` a number of times. - Fix a bug where attempting to refine an optional generic with a type test will cause a false positive type error (fixes #1666) - Fix a bug where the `refine` type family would not skip over `*no-refine*` discriminants (partial resolution for #1424) - Fix a constraint solving bug where recursive function calls would consistently produce cyclic constraints leading to incomplete or inaccurate type inference. - Implement `readparent` and `writeparent` for class types in user-defined type functions, replacing the incorrectly included `parent` method. - Add initial groundwork (under a debug flag) for eager free type generalization, moving us towards further improvements to constraint solving incomplete errors. ## Fragment Autocomplete - Ease up some assertions to improve stability of mixed-mode use of the two type solvers (i.e. using Fragment Autocomplete on a type graph originally produced by the old type solver) - Resolve a bug with type compatibility checks causing internal compiler errors in autocomplete. ## Lexer and Parser - Improve the accuracy of the roundtrippable AST parsing mode by correctly placing closing parentheses on type groupings. - Add a getter for `offset` in the Lexer by @aduermael in #1688 - Add a second entry point to the parser to parse an expression, `parseExpr` ## Internal Contributors Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Ariel Weiss <aaronweiss@roblox.com> Co-authored-by: Aviral Goel <agoel@roblox.com> Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com> Co-authored-by: James McNellis <jmcnellis@roblox.com> Co-authored-by: Talha Pathan <tpathan@roblox.com> Co-authored-by: Vighnesh Vijay <vvijay@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com> --------- Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com> Co-authored-by: Varun Saini <61795485+vrn-sn@users.noreply.github.com> Co-authored-by: Alexander Youngblood <ayoungblood@roblox.com> Co-authored-by: Menarul Alam <malam@roblox.com> Co-authored-by: Aviral Goel <agoel@roblox.com> Co-authored-by: Vighnesh <vvijay@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
1022 lines
34 KiB
C++
1022 lines
34 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/CodeGenOptions.h"
|
|
#include "Luau/IrBuilder.h"
|
|
|
|
#include "BitUtils.h"
|
|
#include "NativeState.h"
|
|
|
|
#include "lua.h"
|
|
#include "lnumutils.h"
|
|
|
|
#include <algorithm>
|
|
#include <vector>
|
|
|
|
#include <limits.h>
|
|
#include <math.h>
|
|
|
|
LUAU_FASTFLAG(LuauVectorLibNativeDot);
|
|
|
|
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:
|
|
case IrCmd::SELECT_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::DOT_VEC:
|
|
LUAU_ASSERT(FFlag::LuauVectorLibNativeDot);
|
|
return IrValueKind::Double;
|
|
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::SELECT_NUM:
|
|
if (inst.c.kind == IrOpKind::Constant && inst.d.kind == IrOpKind::Constant)
|
|
{
|
|
double c = function.doubleOp(inst.c);
|
|
double d = function.doubleOp(inst.d);
|
|
|
|
substitute(function, inst, c == d ? inst.b : inst.a);
|
|
}
|
|
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
|