// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details1#pragma once23#include "Luau/Bytecode.h"4#include "Luau/DenseHash.h"5#include "Luau/IrAnalysis.h"6#include "Luau/Label.h"7#include "Luau/RegisterX64.h"8#include "Luau/RegisterA64.h"9#include "Luau/SmallVector.h"1011#include <optional>12#include <vector>1314#include <stdint.h>15#include <string.h>1617#define OP_A(inst) getOp(inst, 0)18#define OP_B(inst) getOp(inst, 1)19#define OP_C(inst) getOp(inst, 2)20#define OP_D(inst) getOp(inst, 3)21#define OP_E(inst) getOp(inst, 4)22#define OP_F(inst) getOp(inst, 5)23#define OP_G(inst) getOp(inst, 6)2425struct Proto;2627namespace Luau28{29namespace CodeGen30{3132struct LoweringStats;3334constexpr uint8_t kUnknownTag = 0xff;3536// IR extensions to LuauBuiltinFunction enum (these only exist inside IR, and start from 256 to avoid collisions)37enum38{39LBF_IR_MATH_LOG2 = 256,40};4142// IR instruction command.43// In the command description, following abbreviations are used:44// * Rn - VM stack register slot, n in 0..25445// * Kn - VM proto constant slot, n in 0..2^23-146// * UPn - VM function upvalue slot, n in 0..19947// * A, B, C, D, E, F, G are instruction arguments48enum class IrCmd : uint8_t49{50NOP,5152// Load a tag from TValue53// A: Rn or Kn54LOAD_TAG,5556// Load a pointer (*) from TValue57// A: Rn or Kn58LOAD_POINTER,5960// Load a double number from TValue61// A: Rn or Kn62LOAD_DOUBLE,6364// Load an int from TValue65// A: Rn66LOAD_INT,6768// Load a float field from vector (use FLOAT_TO_NUM to convert to double)69// A: Rn or Kn70// B: int (offset from the start of TValue)71LOAD_FLOAT,7273// Load a TValue from memory74// A: Rn or Kn or pointer (TValue)75// B: int/none (optional 'A' pointer offset)76// C: tag/none (tag of the value being loaded)77LOAD_TVALUE,7879// Load current environment table80LOAD_ENV,8182// Get pointer (TValue) to table array at index83// A: pointer (LuaTable)84// B: int85GET_ARR_ADDR,8687// Get pointer (LuaNode) to table node element at the active cached slot index88// A: pointer (LuaTable)89// B: unsigned int (pcpos)90// C: Kn91GET_SLOT_NODE_ADDR,9293// Get pointer (LuaNode) to table node element at the main position of the specified key hash94// A: pointer (LuaTable)95// B: unsigned int (hash)96GET_HASH_NODE_ADDR,9798// Get pointer (TValue) to Closure upvalue.99// A: pointer or undef (Closure)100// B: UPn101// When undef is specified, uses current function Closure.102GET_CLOSURE_UPVAL_ADDR,103104// Store a tag into TValue105// A: Rn106// B: tag107STORE_TAG,108109// Store an integer into the extra field of the TValue110// A: Rn111// B: int112STORE_EXTRA,113114// Store a pointer (*) into TValue115// A: Rn116// B: pointer117STORE_POINTER,118119// Store a double number into TValue120// A: Rn121// B: double122STORE_DOUBLE,123124// Store an int into TValue125// A: Rn126// B: int127STORE_INT,128129// Store a vector into TValue130// When optional 'E' tag is present, it is written out to the TValue as well131// A: Rn132// B: float (x)133// C: float (y)134// D: float (z)135// E: tag (optional)136STORE_VECTOR,137138// Store a TValue into memory139// A: Rn or pointer (TValue)140// B: TValue141// C: int (optional 'A' pointer offset)142STORE_TVALUE,143144// Store a pair of tag and value into memory145// A: Rn or pointer (TValue)146// B: tag (must be a constant)147// C: int/double/pointer148// D: int (optional 'A' pointer offset)149STORE_SPLIT_TVALUE,150151// Add/Sub two integers together152// A, B: int153ADD_INT,154SUB_INT,155156// Sign extend an 8-bit value157// A: int158SEXTI8_INT,159160// Sign extend a 16-bit value161// A: int162SEXTI16_INT,163164// Add/Sub/Mul/Div/Idiv/Mod two double numbers165// A, B: double166// In final x64 lowering, B can also be Rn or Kn167ADD_NUM,168SUB_NUM,169MUL_NUM,170DIV_NUM,171IDIV_NUM,172MOD_NUM,173// A * B + C174// A, B, C: double175MULADD_NUM,176177// Get the minimum/maximum of two numbers178// If one of the values is NaN, 'B' is returned as the result179// A, B: double180// In final x64 lowering, B can also be Rn or Kn181MIN_NUM,182MAX_NUM,183184// Negate a double number185// A: double186UNM_NUM,187188// Round number to negative infinity (math.floor)189// A: double190FLOOR_NUM,191192// Round number to positive infinity (math.ceil)193// A: double194CEIL_NUM,195196// Round number to nearest integer number, rounding half-way cases away from zero (math.round)197// A: double198ROUND_NUM,199200// Get square root of the argument (math.sqrt)201// A: double202SQRT_NUM,203204// Get absolute value of the argument (math.abs)205// A: double206ABS_NUM,207208// Get the sign of the argument (math.sign)209// A: double210SIGN_NUM,211212// Add/Sub/Mul/Div/Idiv/Mod two float numbers213// A, B: float214// In final x64 lowering, B can also be Rn or Kn215ADD_FLOAT,216SUB_FLOAT,217MUL_FLOAT,218DIV_FLOAT,219220// Get the minimum/maximum of two numbers221// If one of the values is NaN, 'B' is returned as the result222// A, B: float223MIN_FLOAT,224MAX_FLOAT,225226// Negate a float number227// A: float228UNM_FLOAT,229230// Round number to negative infinity231// A: float232FLOOR_FLOAT,233234// Round number to positive infinity235// A: float236CEIL_FLOAT,237238// Get square root of the argument239// A: float240SQRT_FLOAT,241242// Get absolute value of the argument243// A: float244ABS_FLOAT,245246// Get the sign of the argument247// A: float248SIGN_FLOAT,249250// Select B if C == D, otherwise select A251// A, B: double (endpoints)252// C, D: double (condition arguments)253SELECT_NUM,254255// For each lane in the vector, select B if C == D, otherwise select A256// A, B: TValue (endpoints)257// C, D: TValue (condition arguments)258SELECT_VEC,259260// Select one of the TValues based on the truthyness of A261// A: TValue262// B: TValue (if true)263// C: TValue (if false)264SELECT_IF_TRUTHY,265266// Add/Sub/Mul/Div/Idiv two vectors267// A, B: TValue (vector)268ADD_VEC,269SUB_VEC,270MUL_VEC,271DIV_VEC,272IDIV_VEC,273// Lanewise A * B + C274// A, B, C: TValue (vector)275MULADD_VEC,276277// Negate a vector278// A: TValue (vector)279UNM_VEC,280281// Get the minimum/maximum of two vector elements282// If one of the element values is NaN, 'B' is returned as the result283// A, B: TValue (vector)284MIN_VEC,285MAX_VEC,286287// Round vector elements to negative infinity288// A: TValue (vector)289FLOOR_VEC,290291// Round vector elements to positive infinity292// A: TValue (vector)293CEIL_VEC,294295// Get absolute value of vector elements296// A: TValue (vector)297ABS_VEC,298299// Compute dot product between two vectors as a float number (use FLOAT_TO_NUM to convert to double)300// A, B: TValue (vector)301DOT_VEC,302303// Extract a component of a vector (use FLOAT_TO_NUM to convert to double)304// A: TValue (vector)305// B: int (0-3 index)306EXTRACT_VEC,307308// Compute Luau 'not' operation on destructured TValue309// A: tag310// B: int (value)311NOT_ANY,312313// Perform a TValue comparison, supported conditions are LessEqual, Less and Equal314// A, B: Rn315// C: condition316CMP_ANY,317318// Perform a comparison of two integer numbers. Result is an integer register containing 0 or 1319// A, B: int320// C: condition321CMP_INT,322323// Perform a comparison of two tags. Result is an integer register containing 0 or 1324CMP_TAG,325// A, B: tag326// C: condition (eq/not_eq)327328// Perform tag and value comparison. Result is an integer register containing 0 or 1329CMP_SPLIT_TVALUE,330// A: tag331// B: tag (constant: boolean/number/string)332// C, D: value333// E: condition (eq/not_eq)334335// Unconditional jump336// A: block/vmexit/undef337JUMP,338339// Jump if TValue is truthy340// A: Rn341// B: block (if true)342// C: block (if false)343JUMP_IF_TRUTHY,344345// Jump if TValue is falsy346// A: Rn347// B: block (if true)348// C: block (if false)349JUMP_IF_FALSY,350351// Jump if tags are equal352// A, B: tag353// C: block (if true)354// D: block (if false)355JUMP_EQ_TAG,356357// Perform a conditional jump based on the result of integer comparison358// A, B: int359// C: condition360// D: block (if true)361// E: block (if false)362JUMP_CMP_INT,363364// Jump if pointers are equal365// A, B: pointer (*)366// C: block (if true)367// D: block (if false)368JUMP_EQ_POINTER,369370// Perform a conditional jump based on the result of double comparison371// A, B: double372// C: condition373// D: block (if true)374// E: block (if false)375JUMP_CMP_NUM,376377// Perform a conditional jump based on the result of float comparison378// A, B: float379// C: condition380// D: block (if true)381// E: block (if false)382JUMP_CMP_FLOAT,383384// Perform jump based on a numerical loop condition (step > 0 ? idx <= limit : limit <= idx)385// A: double (index)386// B: double (limit)387// C: double (step)388// D: block (if true)389// E: block (if false)390JUMP_FORN_LOOP_COND,391392// Perform a conditional jump based on cached table node slot matching the actual table node slot for a key393// A: pointer (LuaNode)394// B: Kn395// C: block (if matches)396// D: block (if it doesn't)397JUMP_SLOT_MATCH,398399// Get table length400// A: pointer (LuaTable)401TABLE_LEN,402403// Get string length404// A: pointer (string)405STRING_LEN,406407// Allocate new table408// A: unsigned int (array element count)409// B: unsigned int (node element count)410NEW_TABLE,411412// Duplicate a table413// A: pointer (LuaTable)414DUP_TABLE,415416// Insert an integer key into a table and return the pointer to inserted value (TValue)417// A: pointer (LuaTable)418// B: int (key)419TABLE_SETNUM,420421// Try to convert a double number into a table index (int) or jump if it's not an integer422// A: double423// B: block424TRY_NUM_TO_INDEX,425426// Try to get pointer to tag method TValue inside the table's metatable or jump if there is no such value or metatable427// A: table428// B: int (TMS enum)429// C: block430TRY_CALL_FASTGETTM,431432// Create new tagged userdata433// A: int (size)434// B: int (tag)435NEW_USERDATA,436437// Convert integer into a double number438// A: int439INT_TO_NUM,440441// Convert unsigned integer into a double number442// A: uint443UINT_TO_NUM,444445// Convert unsigned integer into a float number446// A: uint447UINT_TO_FLOAT,448449// Converts a double number to an integer. 'A' may be any representable integer in a double.450// A: double451NUM_TO_INT,452453// Converts a double number to an unsigned integer. For out-of-range values of 'A', the result is arch-specific.454// A: double455NUM_TO_UINT,456457// Converts a float number to a double458// A: float459FLOAT_TO_NUM,460461// Converts a double number to a float462// A: double463NUM_TO_FLOAT,464465// Converts a float number to a vector with the value in X/Y/Z (use NUM_TO_FLOAT to convert from double)466// A: float467FLOAT_TO_VEC,468469// Adds VECTOR type tag to a vector, preserving X/Y/Z components470// A: TValue471TAG_VECTOR,472473// Clear high register bits of an unsigned integer register. Used to sanitize value of 'producesDirtyHighRegisterBits' instructions.474// A: uint475TRUNCATE_UINT,476477// Adjust stack top (L->top) to point at 'B' TValues *after* the specified register478// This is used to return multiple values479// A: Rn480// B: int (offset)481ADJUST_STACK_TO_REG,482483// Restore stack top (L->top) to point to the function stack top (L->ci->top)484// This is used to recover after calling a variadic function485ADJUST_STACK_TO_TOP,486487// Execute fastcall builtin function with 1 argument in-place488// This is used for a few builtins that can have more than 1 result and cannot be represented as a regular instruction489// A: unsigned int (builtin id)490// B: Rn (result start)491// C: Rn (first argument)492// D: int (result count)493FASTCALL,494495// Call the fastcall builtin function496// A: unsigned int (builtin id)497// B: Rn (result start)498// C: Rn (argument start)499// D: Rn or Kn or undef (optional second argument)500// E: Rn or Kn or undef (optional third argument)501// F: int (argument count or -1 to use all arguments up to stack top)502// G: int (result count or -1 to preserve all results and adjust stack top)503INVOKE_FASTCALL,504505// Check that fastcall builtin function invocation was successful (negative result count jumps to fallback)506// A: int (result count)507// B: block (fallback)508CHECK_FASTCALL_RES,509510// Fallback functions511512// Perform an arithmetic operation on TValues of any type513// A: Rn (where to store the result)514// B: Rn (lhs)515// C: Rn or Kn (rhs)516// D: int (TMS enum with arithmetic type)517DO_ARITH,518519// Get length of a TValue of any type520// A: Rn (where to store the result)521// B: Rn522DO_LEN,523524// Lookup a value in TValue of any type using a key of any type525// A: Rn (where to store the result)526// B: Rn527// C: Rn or unsigned int (key)528GET_TABLE,529530// Store a value into TValue of any type using a key of any type531// A: Rn (value to store)532// B: Rn533// C: Rn or unsigned int (key)534SET_TABLE,535536// Store an import from constant or the import path537// A: Rn (where to store the result)538// B: Kn539// C: unsigned int (import path)540// D: unsigned int (pcpos)541GET_CACHED_IMPORT,542543// Concatenate multiple TValues into a string544// A: Rn (value start)545// B: unsigned int (number of registers to go over)546// Note: result is stored in the register specified in 'A'547// Note: all referenced registers might be modified in the operation548CONCAT,549550// Load function upvalue551// A: UPn552GET_UPVALUE,553554// Store TValue into a function upvalue555// A: UPn556// B: TValue557// C: tag/undef (tag of the value that was written)558SET_UPVALUE,559560// Guards and checks (these instructions are not block terminators even though they jump to fallback)561562// Guard against tag mismatch563// A, B: tag564// C: block/vmexit/undef565// In final x64 lowering, A can also be Rn566// When DebugLuauAbortingChecks flag is enabled, A can also be Rn567// When undef is specified instead of a block, execution is aborted on check failure568CHECK_TAG,569570// Guard against a falsy tag+value571// A: tag572// B: value573// C: block/vmexit/undef574CHECK_TRUTHY,575576// Guard against readonly table577// A: pointer (LuaTable)578// B: block/vmexit/undef579// When undef is specified instead of a block, execution is aborted on check failure580CHECK_READONLY,581582// Guard against table having a metatable583// A: pointer (LuaTable)584// B: block/vmexit/undef585// When undef is specified instead of a block, execution is aborted on check failure586CHECK_NO_METATABLE,587588// Guard against executing in unsafe environment, exits to VM on check failure589// A: block/vmexit/undef590// When undef is specified, execution is aborted on check failure591CHECK_SAFE_ENV,592593// Guard against index overflowing the table array size594// A: pointer (LuaTable)595// B: int (index)596// C: block/vmexit/undef597// When undef is specified instead of a block, execution is aborted on check failure598CHECK_ARRAY_SIZE,599600// Guard against cached table node slot not matching the actual table node slot for a key601// A: pointer (LuaNode)602// B: Kn603// C: block/undef604// When undef is specified instead of a block, execution is aborted on check failure605CHECK_SLOT_MATCH,606607// Guard against table node with a linked next node to ensure that our lookup hits the main position of the key608// A: pointer (LuaNode)609// B: block/vmexit/undef610// When undef is specified instead of a block, execution is aborted on check failure611CHECK_NODE_NO_NEXT,612613// Guard against table node with 'nil' value614// A: pointer (LuaNode)615// B: block/vmexit/undef616// When undef is specified instead of a block, execution is aborted on check failure617CHECK_NODE_VALUE,618619// Guard against access at specified offset with [min, max) range of bytes overflowing the buffer length620// When base offset source number is provided, instruction will additionally validate that the integer and double versions of base are exact621// A: pointer (buffer)622// B: int (base offset)623// C: int (access range min inclusive)624// D: int (access range max exclusive)625// E: double/undef (base offset source double)626// F: block/vmexit/undef627// When undef is specified instead of a block, execution is aborted on check failure628CHECK_BUFFER_LEN,629630// Guard against userdata tag mismatch631// A: pointer (userdata)632// B: int (tag)633// C: block/vmexit/undef634// When undef is specified instead of a block, execution is aborted on check failure635CHECK_USERDATA_TAG,636637// Guard against the result of integer comparison being false638// A, B: int639// C: condition640// D: block/vmexit/undef641// When undef is specified instead of a block, execution is aborted on check failure642CHECK_CMP_INT,643644// Special operations645646// Check interrupt handler647// A: unsigned int (pcpos)648INTERRUPT,649650// Check and run GC assist if necessary651CHECK_GC,652653// Handle GC write barrier (forward)654// A: pointer (GCObject)655// B: Rn (TValue that was written to the object)656// C: tag/undef (tag of the value that was written)657BARRIER_OBJ,658659// Handle GC write barrier (backwards) for a write into a table660// A: pointer (LuaTable)661BARRIER_TABLE_BACK,662663// Handle GC write barrier (forward) for a write into a table664// A: pointer (LuaTable)665// B: Rn (TValue that was written to the object)666// C: tag/undef (tag of the value that was written)667BARRIER_TABLE_FORWARD,668669// Update savedpc value670// A: unsigned int (pcpos)671SET_SAVEDPC,672673// Close open upvalues for registers at specified index or higher674// A: Rn (starting register index)675CLOSE_UPVALS,676677// While capture is a no-op right now, it might be useful to track register/upvalue lifetimes678// A: Rn or UPn679// B: unsigned int (1 for reference capture, 0 for value capture)680CAPTURE,681682// Operations that don't have an IR representation yet683684// Set a list of values to table in target register685// A: unsigned int (bytecode instruction index)686// B: Rn (target)687// C: Rn (source start)688// D: int (count or -1 to assign values up to stack top)689// E: unsigned int (table index to start from)690// F: undef/unsigned int (target table known size)691SETLIST,692693// Call specified function694// A: Rn (function, followed by arguments)695// B: int (argument count or -1 to use all arguments up to stack top)696// C: int (result count or -1 to preserve all results and adjust stack top)697// Note: return values are placed starting from Rn specified in 'A'698CALL,699700// Return specified values from the function701// A: Rn (value start)702// B: int (result count or -1 to return all values up to stack top)703RETURN,704705// Adjust loop variables for one iteration of a generic for loop, jump back to the loop header if loop needs to continue706// A: Rn (loop variable start, updates Rn+2 and 'B' number of registers starting from Rn+3)707// B: int (loop variable count, if more than 2, registers starting from Rn+5 are set to nil)708// C: block (repeat)709// D: block (exit)710FORGLOOP,711712// Handle LOP_FORGLOOP fallback when variable being iterated is not a table713// A: Rn (loop state start, updates Rn+2 and 'B' number of registers starting from Rn+3)714// B: int (loop variable count and a MSB set when it's an ipairs-like iteration loop)715// C: block (repeat)716// D: block (exit)717FORGLOOP_FALLBACK,718719// Fallback for generic for loop preparation when iterating over builtin pairs/ipairs720// It raises an error if 'B' register is not a function721// A: unsigned int (bytecode instruction index)722// B: Rn723// C: block (forgloop location)724FORGPREP_XNEXT_FALLBACK,725726// Increment coverage data (saturating 24 bit add)727// A: unsigned int (bytecode instruction index)728COVERAGE,729730// Operations that have a translation, but use a full instruction fallback731732// Load a value from global table at specified key733// A: unsigned int (bytecode instruction index)734// B: Rn (dest)735// C: Kn (key)736FALLBACK_GETGLOBAL,737738// Store a value into global table at specified key739// A: unsigned int (bytecode instruction index)740// B: Rn (value)741// C: Kn (key)742FALLBACK_SETGLOBAL,743744// Load a value from table at specified key745// A: unsigned int (bytecode instruction index)746// B: Rn (dest)747// C: Rn (table)748// D: Kn (key)749FALLBACK_GETTABLEKS,750751// Store a value into a table at specified key752// A: unsigned int (bytecode instruction index)753// B: Rn (value)754// C: Rn (table)755// D: Kn (key)756FALLBACK_SETTABLEKS,757758// Load function from source register using name into target register and copying source register into target register + 1759// A: unsigned int (bytecode instruction index)760// B: Rn (target)761// C: Rn (source)762// D: Kn (name)763FALLBACK_NAMECALL,764765// Operations that don't have assembly lowering at all766767// Prepare stack for variadic functions so that GETVARARGS works correctly768// A: unsigned int (bytecode instruction index)769// B: int (numparams)770FALLBACK_PREPVARARGS,771772// Copy variables into the target registers from vararg storage for current function773// A: unsigned int (bytecode instruction index)774// B: Rn (dest start)775// C: int (count)776FALLBACK_GETVARARGS,777778// Create closure from a child proto779// A: unsigned int (nups)780// B: pointer (table)781// C: unsigned int (protoid)782NEWCLOSURE,783784// Create closure from a pre-created function object (reusing it unless environments diverge)785// A: unsigned int (bytecode instruction index)786// B: Rn (dest)787// C: Kn (prototype)788FALLBACK_DUPCLOSURE,789790// Prepare loop variables for a generic for loop, jump to the loop back edge unconditionally791// A: unsigned int (bytecode instruction index)792// B: Rn (loop state start, updates Rn Rn+1 Rn+2)793// C: block794FALLBACK_FORGPREP,795796// Instruction that passes value through, it is produced by constant folding and users substitute it with the value797// A: operand of any type798SUBSTITUTE,799800// Pseudo instruction to mark VM registers as implicitly used at the location801// A: Rn (start)802// B: int (count, -1 to mark all registers after start)803MARK_USED,804805// Pseudo instruction to mark VM registers as dead at the location806// A: Rn (start)807// B: int (count, -1 to mark all registers after start)808MARK_DEAD,809810// Performs bitwise and/xor/or on two unsigned integers811// A, B: int812BITAND_UINT,813BITXOR_UINT,814BITOR_UINT,815816// Performs bitwise not on an unsigned integer817// A: int818BITNOT_UINT,819820// Performs bitwise shift/rotate on an unsigned integer821// A: int (source)822// B: int (shift amount)823BITLSHIFT_UINT,824BITRSHIFT_UINT,825BITARSHIFT_UINT,826BITLROTATE_UINT,827BITRROTATE_UINT,828829// Returns the number of consecutive zero bits in A starting from the left-most (most significant) bit.830// A: int831BITCOUNTLZ_UINT,832BITCOUNTRZ_UINT,833834// Swap byte order in A835// A: int836BYTESWAP_UINT,837838// Calls native libm function with 1 or 2 arguments839// A: builtin function ID840// B: double841// C: double/int (optional, 2nd argument)842INVOKE_LIBM,843844// Returns the string name of a type based on tag, alternative for type(x)845// A: tag846GET_TYPE,847848// Returns the string name of a type either from a __type metatable field or just based on the tag, alternative for typeof(x)849// A: Rn850GET_TYPEOF,851852// Find or create an upval at the given level853// A: Rn (level)854FINDUPVAL,855856// Read i8 (sign-extended to int) from buffer storage at specified offset857// A: pointer (buffer)858// B: int (offset)859BUFFER_READI8,860861// Read u8 (zero-extended to int) from buffer storage at specified offset862// A: pointer (buffer)863// B: int (offset)864BUFFER_READU8,865866// Write i8/u8 value (int argument is truncated) to buffer storage at specified offset867// A: pointer (buffer)868// B: int (offset)869// C: int (value)870BUFFER_WRITEI8,871872// Read i16 (sign-extended to int) from buffer storage at specified offset873// A: pointer (buffer)874// B: int (offset)875BUFFER_READI16,876877// Read u16 (zero-extended to int) from buffer storage at specified offset878// A: pointer (buffer)879// B: int (offset)880BUFFER_READU16,881882// Write i16/u16 value (int argument is truncated) to buffer storage at specified offset883// A: pointer (buffer)884// B: int (offset)885// C: int (value)886BUFFER_WRITEI16,887888// Read i32 value from buffer storage at specified offset889// A: pointer (buffer)890// B: int (offset)891BUFFER_READI32,892893// Write i32/u32 value to buffer storage at specified offset894// A: pointer (buffer)895// B: int (offset)896// C: int (value)897BUFFER_WRITEI32,898899// Read float value (use FLOAT_TO_NUM to convert to double) from buffer storage at specified offset900// A: pointer (buffer)901// B: int (offset)902BUFFER_READF32,903904// Write float value (use NUM_TO_FLOAT to convert from double) to buffer storage at specified offset905// A: pointer (buffer)906// B: int (offset)907// C: float (value)908BUFFER_WRITEF32,909910// Read double value from buffer storage at specified offset911// A: pointer (buffer)912// B: int (offset)913BUFFER_READF64,914915// Write double value to buffer storage at specified offset916// A: pointer (buffer)917// B: int (offset)918// C: double (value)919BUFFER_WRITEF64,920};921922enum class IrConstKind : uint8_t923{924Int,925Uint,926Double,927Tag,928Import,929};930931struct IrConst932{933IrConstKind kind;934935union936{937int valueInt;938unsigned valueUint;939double valueDouble;940uint8_t valueTag;941};942};943944enum class IrCondition : uint8_t945{946Equal,947NotEqual,948Less,949NotLess,950LessEqual,951NotLessEqual,952Greater,953NotGreater,954GreaterEqual,955NotGreaterEqual,956957UnsignedLess,958UnsignedLessEqual,959UnsignedGreater,960UnsignedGreaterEqual,961962Count963};964965enum class IrOpKind : uint32_t966{967None,968969Undef,970971// To reference a constant value972Constant,973974// To specify a condition code975Condition,976977// To reference a result of a previous instruction978Inst,979980// To reference a basic block in control flow981Block,982983// To reference a VM register984VmReg,985986// To reference a VM constant987VmConst,988989// To reference a VM upvalue990VmUpvalue,991992// To reference an exit to VM at specific PC pos993VmExit,994};995996// VmExit uses a special value to indicate that pcpos update should be skipped997// This is only used during type checking at function entry998inline constexpr uint32_t kVmExitEntryGuardPc = (1u << 28) - 1;9991000struct IrOp1001{1002IrOpKind kind : 4;1003uint32_t index : 28;10041005IrOp()1006: kind(IrOpKind::None)1007, index(0)1008{1009}10101011IrOp(IrOpKind kind, uint32_t index)1012: kind(kind)1013, index(index)1014{1015}10161017bool operator==(const IrOp& rhs) const1018{1019return kind == rhs.kind && index == rhs.index;1020}10211022bool operator!=(const IrOp& rhs) const1023{1024return !(*this == rhs);1025}1026};10271028static_assert(sizeof(IrOp) == 4);10291030enum class IrValueKind : uint8_t1031{1032Unknown, // Used by SUBSTITUTE, argument has to be checked to get type1033None,1034Tag,1035Int,1036Pointer,1037Float,1038Double,1039Tvalue,10401041Count1042};10431044using IrOps = SmallVector<IrOp, 6>;10451046struct IrInst1047{1048IrCmd cmd;10491050// Operands1051// All frequently used instructions use only A-F slots.1052IrOps ops;10531054uint32_t lastUse = 0;1055uint16_t useCount = 0;10561057// Location of the result (optional)1058X64::RegisterX64 regX64 = X64::noreg;1059A64::RegisterA64 regA64 = A64::noreg;1060bool reusedReg = false;1061bool spilled = false;1062bool needsReload = false;1063};10641065inline IrOp& getOp(IrInst& inst, uint32_t idx)1066{1067if (LUAU_UNLIKELY(idx >= inst.ops.size()))1068{1069inst.ops.resize(idx + 1);1070}1071return inst.ops[idx];1072}10731074inline IrOp& getOp(IrInst* inst, uint32_t idx)1075{1076return getOp(*inst, idx);1077}10781079inline bool hasOp(IrInst& inst, uint32_t idx)1080{1081return idx < inst.ops.size();1082}10831084// TODO: once we update kind checks to not use getOp, it will no longer cause a resize and second part can be removed1085#define HAS_OP_A(inst) (0 < (inst).ops.size() && (inst).ops[0].kind != IrOpKind::None)1086#define HAS_OP_B(inst) (1 < (inst).ops.size() && (inst).ops[1].kind != IrOpKind::None)1087#define HAS_OP_C(inst) (2 < (inst).ops.size() && (inst).ops[2].kind != IrOpKind::None)1088#define HAS_OP_D(inst) (3 < (inst).ops.size() && (inst).ops[3].kind != IrOpKind::None)1089#define HAS_OP_E(inst) (4 < (inst).ops.size() && (inst).ops[4].kind != IrOpKind::None)1090#define HAS_OP_F(inst) (5 < (inst).ops.size() && (inst).ops[5].kind != IrOpKind::None)1091#define HAS_OP_G(inst) (6 < (inst).ops.size() && (inst).ops[6].kind != IrOpKind::None)10921093#define OPT_OP_A(inst) (0 < (inst).ops.size() && (inst).ops[0].kind != IrOpKind::None ? (inst).ops[0] : IrOp{})1094#define OPT_OP_B(inst) (1 < (inst).ops.size() && (inst).ops[1].kind != IrOpKind::None ? (inst).ops[1] : IrOp{})1095#define OPT_OP_C(inst) (2 < (inst).ops.size() && (inst).ops[2].kind != IrOpKind::None ? (inst).ops[2] : IrOp{})1096#define OPT_OP_D(inst) (3 < (inst).ops.size() && (inst).ops[3].kind != IrOpKind::None ? (inst).ops[3] : IrOp{})1097#define OPT_OP_E(inst) (4 < (inst).ops.size() && (inst).ops[4].kind != IrOpKind::None ? (inst).ops[4] : IrOp{})1098#define OPT_OP_F(inst) (5 < (inst).ops.size() && (inst).ops[5].kind != IrOpKind::None ? (inst).ops[5] : IrOp{})1099#define OPT_OP_G(inst) (6 < (inst).ops.size() && (inst).ops[6].kind != IrOpKind::None ? (inst).ops[6] : IrOp{})11001101// When IrInst operands are used, current instruction index is often required to track lifetime1102inline constexpr uint32_t kInvalidInstIdx = ~0u;11031104struct IrInstHash1105{1106static const uint32_t m = 0x5bd1e995;1107static const int r = 24;11081109static uint32_t mix(uint32_t h, uint32_t k)1110{1111// MurmurHash2 step1112k *= m;1113k ^= k >> r;1114k *= m;11151116h *= m;1117h ^= k;11181119return h;1120}11211122static uint32_t mix(uint32_t h, IrOp op)1123{1124static_assert(sizeof(op) == sizeof(uint32_t));1125uint32_t k;1126memcpy(&k, &op, sizeof(op));11271128return mix(h, k);1129}11301131size_t operator()(const IrInst& key) const1132{1133// MurmurHash2 unrolled1134uint32_t h = 25;11351136h = mix(h, uint32_t(key.cmd));1137for (size_t i = 0; i < 7; i++)1138h = mix(h, i < uint32_t(key.ops.size()) ? key.ops[i] : IrOp{});11391140// MurmurHash2 tail1141h ^= h >> 13;1142h *= m;1143h ^= h >> 15;11441145return h;1146}1147};11481149struct IrInstEq1150{1151bool operator()(const IrInst& a, const IrInst& b) const1152{1153if (a.cmd != b.cmd)1154return false;1155if (a.ops.size() == b.ops.size())1156{1157for (size_t i = 0; i < a.ops.size(); i++)1158if (a.ops[i] != b.ops[i])1159return false;1160}1161else if (a.ops.size() < b.ops.size())1162{1163size_t i = 0;1164for (; i < a.ops.size(); i++)1165if (a.ops[i] != b.ops[i])1166return false;1167for (; i < b.ops.size(); i++)1168if (b.ops[i].kind != IrOpKind::None)1169return false;1170}1171else1172{1173size_t i = 0;1174for (; i < b.ops.size(); i++)1175if (a.ops[i] != b.ops[i])1176return false;1177for (; i < a.ops.size(); i++)1178if (a.ops[i].kind != IrOpKind::None)1179return false;1180}1181return true;1182}1183};11841185enum class IrBlockKind : uint8_t1186{1187Bytecode,1188Fallback,1189Internal,1190Linearized,1191Dead,1192};11931194inline constexpr uint32_t kBlockNoStartPc = ~0u;11951196inline constexpr uint8_t kBlockFlagSafeEnvCheck = 1 << 0;1197inline constexpr uint8_t kBlockFlagSafeEnvClear = 1 << 1;1198inline constexpr uint8_t kBlockFlagEntryArgCheck = 1 << 2;11991200struct IrBlock1201{1202IrBlockKind kind;1203uint8_t flags = 0;1204uint16_t useCount = 0;12051206// 'start' and 'finish' define an inclusive range of instructions which belong to this block inside the function1207// When block has been constructed, 'finish' always points to the first and only terminating instruction1208uint32_t start = ~0u;1209uint32_t finish = ~0u;12101211uint32_t sortkey = ~0u;1212uint32_t chainkey = 0;1213uint32_t expectedNextBlock = ~0u;12141215// Bytecode PC position at which the block was generated1216uint32_t startpc = kBlockNoStartPc;12171218Label label;1219};12201221struct BytecodeMapping1222{1223uint32_t irLocation;1224uint32_t asmLocation;1225};12261227struct BytecodeBlock1228{1229// 'start' and 'finish' define an inclusive range of instructions which belong to the block1230int startpc = -1;1231int finishpc = -1;1232};12331234struct BytecodeTypes1235{1236uint8_t result = LBC_TYPE_ANY;1237uint8_t a = LBC_TYPE_ANY;1238uint8_t b = LBC_TYPE_ANY;1239uint8_t c = LBC_TYPE_ANY;1240};12411242struct BytecodeRegTypeInfo1243{1244uint8_t type = LBC_TYPE_ANY;1245uint8_t reg = 0; // Register slot where variable is stored1246int startpc = 0; // First point where variable is alive (could be before variable has been assigned a value)1247int endpc = 0; // First point where variable is dead1248};12491250struct BytecodeTypeInfo1251{1252std::vector<uint8_t> argumentTypes;1253std::vector<BytecodeRegTypeInfo> regTypes;1254std::vector<uint8_t> upvalueTypes;12551256// Offsets into regTypes for each individual register1257// One extra element at the end contains the vector size for easier arr[Rn], arr[Rn + 1] range access1258std::vector<uint32_t> regTypeOffsets;1259};12601261struct ValueRestoreLocation1262{1263IrOp op; // Operand representing the location (Rn/Kn)1264IrValueKind kind; // The kind of value at the restore location1265IrCmd conversionCmd; // Type conversion instruction that was used to store the value at the restore location1266};12671268struct IrFunction1269{1270std::vector<IrBlock> blocks;1271std::vector<IrInst> instructions;1272std::vector<IrConst> constants;12731274std::vector<BytecodeBlock> bcBlocks;1275std::vector<BytecodeTypes> bcTypes;12761277std::vector<BytecodeMapping> bcMapping;1278uint32_t entryBlock = 0;1279uint32_t entryLocation = 0;1280uint32_t endLocation = 0;12811282std::vector<uint32_t> extraNativeData;12831284// For each instruction, an operand that can be used to recompute the value1285std::vector<ValueRestoreLocation> valueRestoreOps;1286std::vector<uint32_t> validRestoreOpBlocks;12871288BytecodeTypeInfo bcOriginalTypeInfo; // Bytecode type information as loaded1289BytecodeTypeInfo bcTypeInfo; // Bytecode type information with additional inferences12901291Proto* proto = nullptr;1292bool variadic = false;12931294CfgInfo cfg;12951296LoweringStats* stats = nullptr;12971298bool recordCounters = false; // Taken from CompilationOptions for easy access12991300// Stores register tags that are known after constant propagating through a block, indexed by that block's index1301std::vector<std::vector<uint8_t>> blockExitTags; // blockIdx → tag array13021303IrBlock& blockOp(IrOp op)1304{1305CODEGEN_ASSERT(op.kind == IrOpKind::Block);1306return blocks[op.index];1307}13081309IrInst& instOp(IrOp op)1310{1311CODEGEN_ASSERT(op.kind == IrOpKind::Inst);1312return instructions[op.index];1313}13141315IrInst* asInstOp(IrOp op)1316{1317if (op.kind == IrOpKind::Inst)1318return &instructions[op.index];13191320return nullptr;1321}13221323IrConst& constOp(IrOp op)1324{1325CODEGEN_ASSERT(op.kind == IrOpKind::Constant);1326return constants[op.index];1327}13281329uint8_t tagOp(IrOp op)1330{1331IrConst& value = constOp(op);13321333CODEGEN_ASSERT(value.kind == IrConstKind::Tag);1334return value.valueTag;1335}13361337std::optional<uint8_t> asTagOp(IrOp op)1338{1339if (op.kind != IrOpKind::Constant)1340return std::nullopt;13411342IrConst& value = constOp(op);13431344if (value.kind != IrConstKind::Tag)1345return std::nullopt;13461347return value.valueTag;1348}13491350int intOp(IrOp op)1351{1352IrConst& value = constOp(op);13531354CODEGEN_ASSERT(value.kind == IrConstKind::Int);1355return value.valueInt;1356}13571358std::optional<int> asIntOp(IrOp op)1359{1360if (op.kind != IrOpKind::Constant)1361return std::nullopt;13621363IrConst& value = constOp(op);13641365if (value.kind != IrConstKind::Int)1366return std::nullopt;13671368return value.valueInt;1369}13701371unsigned uintOp(IrOp op)1372{1373IrConst& value = constOp(op);13741375CODEGEN_ASSERT(value.kind == IrConstKind::Uint);1376return value.valueUint;1377}13781379unsigned importOp(IrOp op)1380{1381IrConst& value = constOp(op);13821383CODEGEN_ASSERT(value.kind == IrConstKind::Import);1384return value.valueUint;1385}13861387std::optional<unsigned> asUintOp(IrOp op)1388{1389if (op.kind != IrOpKind::Constant)1390return std::nullopt;13911392IrConst& value = constOp(op);13931394if (value.kind != IrConstKind::Uint)1395return std::nullopt;13961397return value.valueUint;1398}13991400double doubleOp(IrOp op)1401{1402IrConst& value = constOp(op);14031404CODEGEN_ASSERT(value.kind == IrConstKind::Double);1405return value.valueDouble;1406}14071408std::optional<double> asDoubleOp(IrOp op)1409{1410if (op.kind != IrOpKind::Constant)1411return std::nullopt;14121413IrConst& value = constOp(op);14141415if (value.kind != IrConstKind::Double)1416return std::nullopt;14171418return value.valueDouble;1419}14201421uint32_t getBlockIndex(const IrBlock& block) const1422{1423// Can only be called with blocks from our vector1424CODEGEN_ASSERT(&block >= blocks.data() && &block <= blocks.data() + blocks.size());1425return uint32_t(&block - blocks.data());1426}14271428uint32_t getInstIndex(const IrInst& inst) const1429{1430// Can only be called with instructions from our vector1431CODEGEN_ASSERT(&inst >= instructions.data() && &inst <= instructions.data() + instructions.size());1432return uint32_t(&inst - instructions.data());1433}14341435void recordRestoreLocation(uint32_t instIdx, ValueRestoreLocation location)1436{1437CODEGEN_ASSERT(location.op.kind == IrOpKind::None || location.op.kind == IrOpKind::VmReg || location.op.kind == IrOpKind::VmConst);14381439if (instIdx >= valueRestoreOps.size())1440valueRestoreOps.resize(instIdx + 1);14411442valueRestoreOps[instIdx] = location;1443}14441445ValueRestoreLocation findRestoreLocation(uint32_t instIdx, bool limitToCurrentBlock) const1446{1447if (instIdx >= valueRestoreOps.size())1448return {};14491450// When spilled, values can only reference restore operands in the current block chain1451if (limitToCurrentBlock)1452{1453for (uint32_t blockIdx : validRestoreOpBlocks)1454{1455const IrBlock& block = blocks[blockIdx];14561457if (instIdx >= block.start && instIdx <= block.finish)1458return valueRestoreOps[instIdx];1459}14601461return {};1462}14631464return valueRestoreOps[instIdx];1465}14661467ValueRestoreLocation findRestoreLocation(const IrInst& inst, bool limitToCurrentBlock) const1468{1469return findRestoreLocation(getInstIndex(inst), limitToCurrentBlock);1470}14711472bool hasRestoreLocation(const IrInst& inst, bool limitToCurrentBlock) const1473{1474return findRestoreLocation(getInstIndex(inst), limitToCurrentBlock).op.kind != IrOpKind::None;1475}14761477BytecodeTypes getBytecodeTypesAt(int pcpos) const1478{1479CODEGEN_ASSERT(pcpos >= 0);14801481if (size_t(pcpos) < bcTypes.size())1482return bcTypes[pcpos];14831484return BytecodeTypes();1485}1486};14871488inline IrCondition conditionOp(IrOp op)1489{1490CODEGEN_ASSERT(op.kind == IrOpKind::Condition);1491return IrCondition(op.index);1492}14931494inline int vmRegOp(IrOp op)1495{1496CODEGEN_ASSERT(op.kind == IrOpKind::VmReg);1497return op.index;1498}14991500inline int vmConstOp(IrOp op)1501{1502CODEGEN_ASSERT(op.kind == IrOpKind::VmConst);1503return op.index;1504}15051506inline int vmUpvalueOp(IrOp op)1507{1508CODEGEN_ASSERT(op.kind == IrOpKind::VmUpvalue);1509return op.index;1510}15111512inline uint32_t vmExitOp(IrOp op)1513{1514CODEGEN_ASSERT(op.kind == IrOpKind::VmExit);1515return op.index;1516}15171518} // namespace CodeGen1519} // namespace Luau152015211522