Skip to main content

luaur_code_gen/functions/
fold_constants.rs

1use crate::enums::ir_cmd::IrCmd;
2use crate::enums::ir_op_kind::IrOpKind;
3use crate::functions::byteswap::byteswap;
4use crate::functions::compare_ir_utils::compare_f64_f64_ir_condition;
5use crate::functions::compare_ir_utils_alt_b::compare_i32_i32_ir_condition;
6use crate::functions::compare_ir_utils_alt_c::compare_i64_i64_ir_condition;
7use crate::functions::condition_op::condition_op;
8use crate::functions::countlz_bit_utils::countlz_u32;
9use crate::functions::countlz_bit_utils_alt_b::countlz_u64;
10use crate::functions::countrz_bit_utils::countrz_u32;
11use crate::functions::countrz_bit_utils_alt_b::countrz_u64;
12use crate::functions::get_op_ir_data::get_op_mut;
13use crate::functions::kill_ir_utils::kill_ir_function_ir_inst;
14use crate::functions::lrotate::lrotate;
15use crate::functions::replace_ir_utils::replace_ir_function_ir_op_ir_op;
16use crate::functions::replace_ir_utils_alt_b::replace_ir_function_ir_block_u32_ir_inst;
17use crate::functions::rrotate::rrotate;
18use crate::functions::substitute::substitute;
19use crate::functions::substitute_with_truncated_uint::substitute_with_truncated_uint;
20use crate::macros::codegen_assert::CODEGEN_ASSERT;
21use crate::records::ir_block::IrBlock;
22use crate::records::ir_builder::IrBuilder;
23use crate::records::ir_function::IrFunction;
24use crate::records::ir_inst::IrInst;
25use crate::records::ir_op::IrOp;
26use crate::type_aliases::ir_ops::IrOps;
27use luaur_vm::enums::lua_type::lua_Type;
28use luaur_vm::functions::luai_numidiv::luai_numidiv;
29use luaur_vm::functions::luai_nummod::luai_nummod;
30
31// IrUtils.cpp: `constexpr double kDoubleMaxExactInteger = 9007199254740992.0;`
32const K_DOUBLE_MAX_EXACT_INTEGER: f64 = 9007199254740992.0;
33
34const LUA_TNIL: u8 = lua_Type::LUA_TNIL as u8;
35const LUA_TBOOLEAN: u8 = lua_Type::LUA_TBOOLEAN as u8;
36const LUA_TNUMBER: u8 = lua_Type::LUA_TNUMBER as u8;
37const LUA_TINTEGER: u8 = lua_Type::LUA_TINTEGER as u8;
38
39fn make_inst(cmd: IrCmd, ops: &[IrOp]) -> IrInst {
40    let mut v = IrOps::new();
41    for &o in ops {
42        v.push(o);
43    }
44    IrInst {
45        cmd,
46        ops: v,
47        ..Default::default()
48    }
49}
50
51pub fn fold_constants(
52    build: &mut IrBuilder,
53    function: &mut IrFunction,
54    block: &mut IrBlock,
55    index: u32,
56) {
57    let inst_ptr: *mut IrInst = &mut function.instructions[index as usize];
58    let cmd = unsafe { (*inst_ptr).cmd };
59
60    // OP_A(inst)..OP_F(inst): read the n-th operand (copy; IrOp is Copy)
61    let read = move |idx: usize| -> IrOp {
62        let s = unsafe { (*inst_ptr).ops.as_slice() };
63        if idx < s.len() {
64            s[idx]
65        } else {
66            IrOp::default()
67        }
68    };
69    let is_const = move |idx: usize| -> bool {
70        let s = unsafe { (*inst_ptr).ops.as_slice() };
71        let op = if idx < s.len() {
72            s[idx]
73        } else {
74            IrOp::default()
75        };
76        op.kind() == IrOpKind::Constant
77    };
78
79    match cmd {
80        IrCmd::ADD_INT => {
81            if is_const(0) && is_const(1) {
82                // Add as unsigned to force two's complement evaluation (avoid signed overflow UB)
83                let lhs = function.int_op(read(0));
84                let rhs = function.int_op(read(1));
85                let sum = lhs.wrapping_add(rhs);
86                let c = build.const_int(sum);
87                substitute(function, unsafe { &mut *inst_ptr }, c);
88            }
89        }
90        IrCmd::SUB_INT => {
91            if is_const(0) && is_const(1) {
92                let lhs = function.int_op(read(0));
93                let rhs = function.int_op(read(1));
94                let sum = lhs.wrapping_sub(rhs);
95                let c = build.const_int(sum);
96                substitute(function, unsafe { &mut *inst_ptr }, c);
97            }
98        }
99        IrCmd::SEXTI8_INT => {
100            if is_const(0) {
101                let value = function.int_op(read(0)) as i8 as i32;
102                let c = build.const_int(value);
103                substitute(function, unsafe { &mut *inst_ptr }, c);
104            }
105        }
106        IrCmd::SEXTI16_INT => {
107            if is_const(0) {
108                let value = function.int_op(read(0)) as i16 as i32;
109                let c = build.const_int(value);
110                substitute(function, unsafe { &mut *inst_ptr }, c);
111            }
112        }
113        IrCmd::ADD_NUM => {
114            if is_const(0) && is_const(1) {
115                let v = function.double_op(read(0)) + function.double_op(read(1));
116                let c = build.const_double(v);
117                substitute(function, unsafe { &mut *inst_ptr }, c);
118            }
119        }
120        IrCmd::SUB_NUM => {
121            if is_const(0) && is_const(1) {
122                let v = function.double_op(read(0)) - function.double_op(read(1));
123                let c = build.const_double(v);
124                substitute(function, unsafe { &mut *inst_ptr }, c);
125            }
126        }
127        IrCmd::MUL_NUM => {
128            if is_const(0) && is_const(1) {
129                let v = function.double_op(read(0)) * function.double_op(read(1));
130                let c = build.const_double(v);
131                substitute(function, unsafe { &mut *inst_ptr }, c);
132            }
133        }
134        IrCmd::DIV_NUM => {
135            if is_const(0) && is_const(1) {
136                let v = function.double_op(read(0)) / function.double_op(read(1));
137                let c = build.const_double(v);
138                substitute(function, unsafe { &mut *inst_ptr }, c);
139            }
140        }
141        IrCmd::IDIV_NUM => {
142            if is_const(0) && is_const(1) {
143                let v = luai_numidiv(function.double_op(read(0)), function.double_op(read(1)));
144                let c = build.const_double(v);
145                substitute(function, unsafe { &mut *inst_ptr }, c);
146            }
147        }
148        IrCmd::MOD_NUM => {
149            if is_const(0) && is_const(1) {
150                let v = luai_nummod(function.double_op(read(0)), function.double_op(read(1)));
151                let c = build.const_double(v);
152                substitute(function, unsafe { &mut *inst_ptr }, c);
153            }
154        }
155        IrCmd::MIN_NUM => {
156            if is_const(0) && is_const(1) {
157                let a1 = function.double_op(read(0));
158                let a2 = function.double_op(read(1));
159                let c = build.const_double(if a1 < a2 { a1 } else { a2 });
160                substitute(function, unsafe { &mut *inst_ptr }, c);
161            }
162        }
163        IrCmd::MAX_NUM => {
164            if is_const(0) && is_const(1) {
165                let a1 = function.double_op(read(0));
166                let a2 = function.double_op(read(1));
167                let c = build.const_double(if a1 > a2 { a1 } else { a2 });
168                substitute(function, unsafe { &mut *inst_ptr }, c);
169            }
170        }
171        IrCmd::UNM_NUM => {
172            if is_const(0) {
173                let c = build.const_double(-function.double_op(read(0)));
174                substitute(function, unsafe { &mut *inst_ptr }, c);
175            }
176        }
177        IrCmd::FLOOR_NUM => {
178            if is_const(0) {
179                let c = build.const_double(function.double_op(read(0)).floor());
180                substitute(function, unsafe { &mut *inst_ptr }, c);
181            }
182        }
183        IrCmd::CEIL_NUM => {
184            if is_const(0) {
185                let c = build.const_double(function.double_op(read(0)).ceil());
186                substitute(function, unsafe { &mut *inst_ptr }, c);
187            }
188        }
189        IrCmd::ROUND_NUM => {
190            if is_const(0) {
191                let c = build.const_double(function.double_op(read(0)).round());
192                substitute(function, unsafe { &mut *inst_ptr }, c);
193            }
194        }
195        IrCmd::SQRT_NUM => {
196            if is_const(0) {
197                let c = build.const_double(function.double_op(read(0)).sqrt());
198                substitute(function, unsafe { &mut *inst_ptr }, c);
199            }
200        }
201        IrCmd::ABS_NUM => {
202            if is_const(0) {
203                let c = build.const_double(function.double_op(read(0)).abs());
204                substitute(function, unsafe { &mut *inst_ptr }, c);
205            }
206        }
207        IrCmd::SIGN_NUM => {
208            if is_const(0) {
209                let v = function.double_op(read(0));
210                let r = if v > 0.0 {
211                    1.0
212                } else if v < 0.0 {
213                    -1.0
214                } else {
215                    0.0
216                };
217                let c = build.const_double(r);
218                substitute(function, unsafe { &mut *inst_ptr }, c);
219            }
220        }
221        IrCmd::ADD_FLOAT => {
222            if is_const(0) && is_const(1) {
223                let v = ((function.double_op(read(0)) as f32)
224                    + (function.double_op(read(1)) as f32)) as f64;
225                let c = build.const_double(v);
226                substitute(function, unsafe { &mut *inst_ptr }, c);
227            }
228        }
229        IrCmd::SUB_FLOAT => {
230            if is_const(0) && is_const(1) {
231                let v = ((function.double_op(read(0)) as f32)
232                    - (function.double_op(read(1)) as f32)) as f64;
233                let c = build.const_double(v);
234                substitute(function, unsafe { &mut *inst_ptr }, c);
235            }
236        }
237        IrCmd::MUL_FLOAT => {
238            if is_const(0) && is_const(1) {
239                let v = ((function.double_op(read(0)) as f32)
240                    * (function.double_op(read(1)) as f32)) as f64;
241                let c = build.const_double(v);
242                substitute(function, unsafe { &mut *inst_ptr }, c);
243            }
244        }
245        IrCmd::DIV_FLOAT => {
246            if is_const(0) && is_const(1) {
247                let v = ((function.double_op(read(0)) as f32)
248                    / (function.double_op(read(1)) as f32)) as f64;
249                let c = build.const_double(v);
250                substitute(function, unsafe { &mut *inst_ptr }, c);
251            }
252        }
253        IrCmd::MIN_FLOAT => {
254            if is_const(0) && is_const(1) {
255                let a1 = function.double_op(read(0)) as f32;
256                let a2 = function.double_op(read(1)) as f32;
257                let c = build.const_double((if a1 < a2 { a1 } else { a2 }) as f64);
258                substitute(function, unsafe { &mut *inst_ptr }, c);
259            }
260        }
261        IrCmd::MAX_FLOAT => {
262            if is_const(0) && is_const(1) {
263                let a1 = function.double_op(read(0)) as f32;
264                let a2 = function.double_op(read(1)) as f32;
265                let c = build.const_double((if a1 > a2 { a1 } else { a2 }) as f64);
266                substitute(function, unsafe { &mut *inst_ptr }, c);
267            }
268        }
269        IrCmd::UNM_FLOAT => {
270            if is_const(0) {
271                let c = build.const_double((-(function.double_op(read(0)) as f32)) as f64);
272                substitute(function, unsafe { &mut *inst_ptr }, c);
273            }
274        }
275        IrCmd::FLOOR_FLOAT => {
276            if is_const(0) {
277                let c = build.const_double((function.double_op(read(0)) as f32).floor() as f64);
278                substitute(function, unsafe { &mut *inst_ptr }, c);
279            }
280        }
281        IrCmd::CEIL_FLOAT => {
282            if is_const(0) {
283                let c = build.const_double((function.double_op(read(0)) as f32).ceil() as f64);
284                substitute(function, unsafe { &mut *inst_ptr }, c);
285            }
286        }
287        IrCmd::SQRT_FLOAT => {
288            if is_const(0) {
289                let c = build.const_double((function.double_op(read(0)) as f32).sqrt() as f64);
290                substitute(function, unsafe { &mut *inst_ptr }, c);
291            }
292        }
293        IrCmd::ABS_FLOAT => {
294            if is_const(0) {
295                let c = build.const_double((function.double_op(read(0)) as f32).abs() as f64);
296                substitute(function, unsafe { &mut *inst_ptr }, c);
297            }
298        }
299        IrCmd::SIGN_FLOAT => {
300            if is_const(0) {
301                let v = function.double_op(read(0)) as f32;
302                let r: f32 = if v > 0.0 {
303                    1.0
304                } else if v < 0.0 {
305                    -1.0
306                } else {
307                    0.0
308                };
309                let c = build.const_double(r as f64);
310                substitute(function, unsafe { &mut *inst_ptr }, c);
311            }
312        }
313        IrCmd::SELECT_NUM => {
314            if is_const(2) && is_const(3) {
315                let c = function.double_op(read(2));
316                let d = function.double_op(read(3));
317                let repl = if c == d { read(1) } else { read(0) };
318                substitute(function, unsafe { &mut *inst_ptr }, repl);
319            } else if read(0) == read(1) {
320                // If the values are the same, no need to worry about the condition check
321                let repl = read(0);
322                substitute(function, unsafe { &mut *inst_ptr }, repl);
323            }
324        }
325        IrCmd::SELECT_VEC => {
326            if read(0) == read(1) {
327                let repl = read(0);
328                substitute(function, unsafe { &mut *inst_ptr }, repl);
329            }
330        }
331        IrCmd::SELECT_IF_TRUTHY => {
332            if read(1) == read(2) {
333                let repl = read(1);
334                substitute(function, unsafe { &mut *inst_ptr }, repl);
335            }
336        }
337        IrCmd::NOT_ANY => {
338            if is_const(0) {
339                let a = function.tag_op(read(0));
340
341                if a == LUA_TNIL {
342                    let c = build.const_int(1);
343                    substitute(function, unsafe { &mut *inst_ptr }, c);
344                } else if a != LUA_TBOOLEAN {
345                    let c = build.const_int(0);
346                    substitute(function, unsafe { &mut *inst_ptr }, c);
347                } else if is_const(1) {
348                    let c = build.const_int(if function.int_op(read(1)) == 1 { 0 } else { 1 });
349                    substitute(function, unsafe { &mut *inst_ptr }, c);
350                }
351            }
352        }
353        IrCmd::CMP_INT => {
354            if is_const(0) && is_const(1) {
355                let res = compare_i32_i32_ir_condition(
356                    function.int_op(read(0)),
357                    function.int_op(read(1)),
358                    condition_op(read(2)),
359                );
360                let c = build.const_int(if res { 1 } else { 0 });
361                substitute(function, unsafe { &mut *inst_ptr }, c);
362            }
363        }
364        IrCmd::CMP_INT64 => {
365            if is_const(0) && is_const(1) {
366                let lhs = function.int64_op(read(0));
367                let rhs = function.int64_op(read(1));
368                let res = compare_i64_i64_ir_condition(lhs, rhs, condition_op(read(2)));
369                let c = build.const_int(if res { 1 } else { 0 });
370                substitute(function, unsafe { &mut *inst_ptr }, c);
371            }
372        }
373        IrCmd::CMP_TAG => {
374            if is_const(0) && is_const(1) {
375                let cond = condition_op(read(2));
376                CODEGEN_ASSERT!(
377                    cond == crate::enums::ir_condition::IrCondition::Equal
378                        || cond == crate::enums::ir_condition::IrCondition::NotEqual
379                );
380
381                let same = function.tag_op(read(0)) == function.tag_op(read(1));
382                let val = if cond == crate::enums::ir_condition::IrCondition::Equal {
383                    if same {
384                        1
385                    } else {
386                        0
387                    }
388                } else if !same {
389                    1
390                } else {
391                    0
392                };
393                let c = build.const_int(val);
394                substitute(function, unsafe { &mut *inst_ptr }, c);
395            }
396        }
397        IrCmd::CMP_SPLIT_TVALUE => {
398            CODEGEN_ASSERT!(is_const(1));
399
400            let cond = condition_op(read(4));
401            CODEGEN_ASSERT!(
402                cond == crate::enums::ir_condition::IrCondition::Equal
403                    || cond == crate::enums::ir_condition::IrCondition::NotEqual
404            );
405
406            if cond == crate::enums::ir_condition::IrCondition::Equal {
407                if is_const(0) && function.tag_op(read(0)) != function.tag_op(read(1)) {
408                    let c = build.const_int(0);
409                    substitute(function, unsafe { &mut *inst_ptr }, c);
410                } else if is_const(2) && is_const(3) {
411                    // If the tag is a constant, this means previous condition has failed because tags are the same
412                    let known_same_tag = is_const(0);
413                    let tag_b = function.tag_op(read(1));
414                    let same_value;
415                    if tag_b == LUA_TBOOLEAN {
416                        same_value = compare_i32_i32_ir_condition(
417                            function.int_op(read(2)),
418                            function.int_op(read(3)),
419                            crate::enums::ir_condition::IrCondition::Equal,
420                        );
421                    } else if tag_b == LUA_TNUMBER {
422                        same_value = compare_f64_f64_ir_condition(
423                            function.double_op(read(2)),
424                            function.double_op(read(3)),
425                            crate::enums::ir_condition::IrCondition::Equal,
426                        );
427                    } else if tag_b == LUA_TINTEGER {
428                        let lhs = function.int64_op(read(2));
429                        let rhs = function.int64_op(read(3));
430                        same_value = compare_i64_i64_ir_condition(
431                            lhs,
432                            rhs,
433                            crate::enums::ir_condition::IrCondition::Equal,
434                        );
435                    } else {
436                        CODEGEN_ASSERT!(false, "unsupported type");
437                        same_value = false;
438                    }
439
440                    if known_same_tag && same_value {
441                        let c = build.const_int(1);
442                        substitute(function, unsafe { &mut *inst_ptr }, c);
443                    } else if same_value {
444                        let r = make_inst(IrCmd::CMP_TAG, &[read(0), read(1), read(4)]);
445                        replace_ir_function_ir_block_u32_ir_inst(function, block, index, r);
446                    } else {
447                        let c = build.const_int(0);
448                        substitute(function, unsafe { &mut *inst_ptr }, c);
449                    }
450                }
451            } else {
452                if is_const(0) && function.tag_op(read(0)) != function.tag_op(read(1)) {
453                    let c = build.const_int(1);
454                    substitute(function, unsafe { &mut *inst_ptr }, c);
455                } else if is_const(2) && is_const(3) {
456                    let known_same_tag = is_const(0);
457                    let tag_b = function.tag_op(read(1));
458                    let different_value;
459                    if tag_b == LUA_TBOOLEAN {
460                        different_value = compare_i32_i32_ir_condition(
461                            function.int_op(read(2)),
462                            function.int_op(read(3)),
463                            crate::enums::ir_condition::IrCondition::NotEqual,
464                        );
465                    } else if tag_b == LUA_TNUMBER {
466                        different_value = compare_f64_f64_ir_condition(
467                            function.double_op(read(2)),
468                            function.double_op(read(3)),
469                            crate::enums::ir_condition::IrCondition::NotEqual,
470                        );
471                    } else if tag_b == LUA_TINTEGER {
472                        let lhs = function.int64_op(read(2));
473                        let rhs = function.int64_op(read(3));
474                        different_value = compare_i64_i64_ir_condition(
475                            lhs,
476                            rhs,
477                            crate::enums::ir_condition::IrCondition::NotEqual,
478                        );
479                    } else {
480                        CODEGEN_ASSERT!(false, "unsupported type");
481                        different_value = false;
482                    }
483
484                    if different_value {
485                        let c = build.const_int(1);
486                        substitute(function, unsafe { &mut *inst_ptr }, c);
487                    } else if known_same_tag {
488                        let c = build.const_int(0);
489                        substitute(function, unsafe { &mut *inst_ptr }, c);
490                    } else {
491                        let r = make_inst(IrCmd::CMP_TAG, &[read(0), read(1), read(4)]);
492                        replace_ir_function_ir_block_u32_ir_inst(function, block, index, r);
493                    }
494                }
495            }
496        }
497        IrCmd::JUMP_EQ_TAG => {
498            if is_const(0) && is_const(1) {
499                if function.tag_op(read(0)) == function.tag_op(read(1)) {
500                    let r = make_inst(IrCmd::JUMP, &[read(2)]);
501                    replace_ir_function_ir_block_u32_ir_inst(function, block, index, r);
502                } else {
503                    let r = make_inst(IrCmd::JUMP, &[read(3)]);
504                    replace_ir_function_ir_block_u32_ir_inst(function, block, index, r);
505                }
506            }
507        }
508        IrCmd::JUMP_CMP_INT => {
509            if is_const(0) && is_const(1) {
510                let res = compare_i32_i32_ir_condition(
511                    function.int_op(read(0)),
512                    function.int_op(read(1)),
513                    condition_op(read(2)),
514                );
515                let r = if res {
516                    make_inst(IrCmd::JUMP, &[read(3)])
517                } else {
518                    make_inst(IrCmd::JUMP, &[read(4)])
519                };
520                replace_ir_function_ir_block_u32_ir_inst(function, block, index, r);
521            }
522        }
523        IrCmd::JUMP_CMP_NUM => {
524            if is_const(0) && is_const(1) {
525                let res = compare_f64_f64_ir_condition(
526                    function.double_op(read(0)),
527                    function.double_op(read(1)),
528                    condition_op(read(2)),
529                );
530                let r = if res {
531                    make_inst(IrCmd::JUMP, &[read(3)])
532                } else {
533                    make_inst(IrCmd::JUMP, &[read(4)])
534                };
535                replace_ir_function_ir_block_u32_ir_inst(function, block, index, r);
536            }
537        }
538        IrCmd::JUMP_CMP_FLOAT => {
539            if is_const(0) && is_const(1) {
540                let a = function.double_op(read(0)) as f32 as f64;
541                let b = function.double_op(read(1)) as f32 as f64;
542                let res = compare_f64_f64_ir_condition(a, b, condition_op(read(2)));
543                let r = if res {
544                    make_inst(IrCmd::JUMP, &[read(3)])
545                } else {
546                    make_inst(IrCmd::JUMP, &[read(4)])
547                };
548                replace_ir_function_ir_block_u32_ir_inst(function, block, index, r);
549            }
550        }
551        IrCmd::TRY_NUM_TO_INDEX => {
552            if is_const(0) {
553                let value = function.double_op(read(0));
554
555                // To avoid undefined behavior of casting a value not representable in the target type, we check the range
556                if value >= i32::MIN as f64 && value <= i32::MAX as f64 {
557                    let arr_index = value as i32;
558
559                    if arr_index as f64 == value {
560                        let c = build.const_int(arr_index);
561                        substitute(function, unsafe { &mut *inst_ptr }, c);
562                    } else {
563                        let r = make_inst(IrCmd::JUMP, &[read(1)]);
564                        replace_ir_function_ir_block_u32_ir_inst(function, block, index, r);
565                    }
566                } else {
567                    let r = make_inst(IrCmd::JUMP, &[read(1)]);
568                    replace_ir_function_ir_block_u32_ir_inst(function, block, index, r);
569                }
570            }
571        }
572        IrCmd::INT_TO_NUM => {
573            if is_const(0) {
574                let c = build.const_double(function.int_op(read(0)) as f64);
575                substitute(function, unsafe { &mut *inst_ptr }, c);
576            }
577        }
578        IrCmd::INT64_TO_NUM => {
579            if is_const(0) {
580                let v = function.int64_op(read(0)) as f64;
581                let c = build.const_double(v);
582                substitute(function, unsafe { &mut *inst_ptr }, c);
583            }
584        }
585        IrCmd::UINT_TO_NUM => {
586            if is_const(0) {
587                let c = build.const_double((function.int_op(read(0)) as u32) as f64);
588                substitute(function, unsafe { &mut *inst_ptr }, c);
589            }
590        }
591        IrCmd::UINT_TO_FLOAT => {
592            if is_const(0) {
593                let c = build.const_double(((function.int_op(read(0)) as u32) as f32) as f64);
594                substitute(function, unsafe { &mut *inst_ptr }, c);
595            }
596        }
597        IrCmd::NUM_TO_INT => {
598            if is_const(0) {
599                let value = function.double_op(read(0));
600
601                // matches luai_num2int range check
602                if value >= i32::MIN as f64 && value <= i32::MAX as f64 {
603                    let c = build.const_int(value as i32);
604                    substitute(function, unsafe { &mut *inst_ptr }, c);
605                }
606            }
607        }
608        IrCmd::NUM_TO_UINT => {
609            if is_const(0) {
610                let value = function.double_op(read(0));
611
612                // matches luai_num2unsigned range check
613                if value >= -K_DOUBLE_MAX_EXACT_INTEGER && value <= K_DOUBLE_MAX_EXACT_INTEGER {
614                    let c = build.const_int((value as i64 as u32) as i32);
615                    substitute(function, unsafe { &mut *inst_ptr }, c);
616                }
617            }
618        }
619        IrCmd::NUM_TO_INT64 => {
620            if is_const(0) {
621                let value = function.double_op(read(0));
622
623                if value >= i64::MIN as f64 && value < i64::MAX as f64 {
624                    let c = build.const_int_64(value as i64);
625                    substitute(function, unsafe { &mut *inst_ptr }, c);
626                }
627            }
628        }
629        IrCmd::FLOAT_TO_NUM => {
630            // float -> double for a constant is a no-op
631            if is_const(0) {
632                let c = build.const_double(function.double_op(read(0)));
633                substitute(function, unsafe { &mut *inst_ptr }, c);
634            }
635        }
636        IrCmd::NUM_TO_FLOAT => {
637            // double -> float for a constant just needs to lower precision
638            if is_const(0) {
639                let c = build.const_double((function.double_op(read(0)) as f32) as f64);
640                substitute(function, unsafe { &mut *inst_ptr }, c);
641            }
642        }
643        IrCmd::TRUNCATE_UINT => {
644            // Truncating a constant integer is a no-op as constant integers only store 32 bits
645            if is_const(0) {
646                let repl = read(0);
647                substitute(function, unsafe { &mut *inst_ptr }, repl);
648            }
649        }
650        IrCmd::CHECK_TAG => {
651            if is_const(0) && is_const(1) {
652                if function.tag_op(read(0)) == function.tag_op(read(1)) {
653                    kill_ir_function_ir_inst(function, unsafe { &mut *inst_ptr });
654                } else {
655                    let r = make_inst(IrCmd::JUMP, &[read(2)]); // Shows a conflict in assumptions on this path
656                    replace_ir_function_ir_block_u32_ir_inst(function, block, index, r);
657                }
658            }
659        }
660        IrCmd::CHECK_TRUTHY => {
661            if is_const(0) {
662                if function.tag_op(read(0)) == LUA_TNIL {
663                    let r = make_inst(IrCmd::JUMP, &[read(2)]); // Shows a conflict in assumptions on this path
664                    replace_ir_function_ir_block_u32_ir_inst(function, block, index, r);
665                } else if function.tag_op(read(0)) == LUA_TBOOLEAN {
666                    if is_const(1) {
667                        if function.int_op(read(1)) == 0 {
668                            let r = make_inst(IrCmd::JUMP, &[read(2)]); // Shows a conflict in assumptions on this path
669                            replace_ir_function_ir_block_u32_ir_inst(function, block, index, r);
670                        } else {
671                            kill_ir_function_ir_inst(function, unsafe { &mut *inst_ptr });
672                        }
673                    }
674                } else {
675                    kill_ir_function_ir_inst(function, unsafe { &mut *inst_ptr });
676                }
677            }
678        }
679        IrCmd::CHECK_CMP_NUM => {
680            if is_const(0) && is_const(1) {
681                if compare_f64_f64_ir_condition(
682                    function.double_op(read(0)),
683                    function.double_op(read(1)),
684                    condition_op(read(2)),
685                ) {
686                    kill_ir_function_ir_inst(function, unsafe { &mut *inst_ptr });
687                } else {
688                    let r = make_inst(IrCmd::JUMP, &[read(3)]);
689                    replace_ir_function_ir_block_u32_ir_inst(function, block, index, r);
690                }
691            }
692        }
693        IrCmd::CHECK_CMP_INT => {
694            if is_const(0) && is_const(1) {
695                if compare_i32_i32_ir_condition(
696                    function.int_op(read(0)),
697                    function.int_op(read(1)),
698                    condition_op(read(2)),
699                ) {
700                    kill_ir_function_ir_inst(function, unsafe { &mut *inst_ptr });
701                } else {
702                    let r = make_inst(IrCmd::JUMP, &[read(3)]); // Shows a conflict in assumptions on this path
703                    replace_ir_function_ir_block_u32_ir_inst(function, block, index, r);
704                }
705            }
706        }
707        IrCmd::ADD_INT64 => {
708            if is_const(0) && is_const(1) {
709                let lhs = function.int64_op(read(0));
710                let rhs = function.int64_op(read(1));
711                let c = build.const_int_64(lhs.wrapping_add(rhs));
712                substitute(function, unsafe { &mut *inst_ptr }, c);
713            }
714        }
715        IrCmd::SUB_INT64 => {
716            if is_const(0) && is_const(1) {
717                let lhs = function.int64_op(read(0));
718                let rhs = function.int64_op(read(1));
719                let c = build.const_int_64(lhs.wrapping_sub(rhs));
720                substitute(function, unsafe { &mut *inst_ptr }, c);
721            }
722        }
723        IrCmd::MUL_INT64 => {
724            if is_const(0) && is_const(1) {
725                let lhs = function.int64_op(read(0));
726                let rhs = function.int64_op(read(1));
727                let c = build.const_int_64(lhs.wrapping_mul(rhs));
728                substitute(function, unsafe { &mut *inst_ptr }, c);
729            }
730        }
731        IrCmd::DIV_INT64 => {
732            if is_const(0) && is_const(1) {
733                let lhs = function.int64_op(read(0));
734                let rhs = function.int64_op(read(1));
735                if rhs != 0 && !(lhs == i64::MIN && rhs == -1) {
736                    let c = build.const_int_64(lhs / rhs);
737                    substitute(function, unsafe { &mut *inst_ptr }, c);
738                }
739            }
740        }
741        IrCmd::IDIV_INT64 => {
742            if is_const(0) && is_const(1) {
743                let lhs = function.int64_op(read(0));
744                let rhs = function.int64_op(read(1));
745                if rhs != 0 && !(lhs == i64::MIN && rhs == -1) {
746                    let mut q = lhs / rhs;
747                    // Floored division: adjust if signs differ and there's a remainder
748                    if (lhs ^ rhs) < 0 && q.wrapping_mul(rhs) != lhs {
749                        q -= 1;
750                    }
751                    let c = build.const_int_64(q);
752                    substitute(function, unsafe { &mut *inst_ptr }, c);
753                }
754            }
755        }
756        IrCmd::UDIV_INT64 => {
757            if is_const(0) && is_const(1) {
758                let lhs = function.int64_op(read(0)) as u64;
759                let rhs = function.int64_op(read(1)) as u64;
760                if rhs != 0 {
761                    let c = build.const_int_64((lhs / rhs) as i64);
762                    substitute(function, unsafe { &mut *inst_ptr }, c);
763                }
764            }
765        }
766        IrCmd::REM_INT64 => {
767            if is_const(0) && is_const(1) {
768                let lhs = function.int64_op(read(0));
769                let rhs = function.int64_op(read(1));
770                if rhs != 0 && !(lhs == i64::MIN && rhs == -1) {
771                    let c = build.const_int_64(lhs % rhs);
772                    substitute(function, unsafe { &mut *inst_ptr }, c);
773                }
774            }
775        }
776        IrCmd::UREM_INT64 => {
777            if is_const(0) && is_const(1) {
778                let lhs = function.int64_op(read(0)) as u64;
779                let rhs = function.int64_op(read(1)) as u64;
780                if rhs != 0 {
781                    let c = build.const_int_64((lhs % rhs) as i64);
782                    substitute(function, unsafe { &mut *inst_ptr }, c);
783                }
784            }
785        }
786        IrCmd::MOD_INT64 => {
787            if is_const(0) && is_const(1) {
788                let lhs = function.int64_op(read(0));
789                let rhs = function.int64_op(read(1));
790                if rhs != 0 && !(lhs == i64::MIN && rhs == -1) {
791                    let mut rem = lhs % rhs;
792                    // Floored modulus: adjust if remainder != 0 and signs differ
793                    if rem != 0 && (rem ^ rhs) < 0 {
794                        rem += rhs;
795                    }
796                    let c = build.const_int_64(rem);
797                    substitute(function, unsafe { &mut *inst_ptr }, c);
798                }
799            }
800        }
801        IrCmd::CHECK_DIV_INT64 => {
802            if is_const(0) && is_const(1) {
803                let lhs = function.int64_op(read(0));
804                let rhs = function.int64_op(read(1));
805                if rhs != 0 && !(lhs == i64::MIN && rhs == -1) {
806                    kill_ir_function_ir_inst(function, unsafe { &mut *inst_ptr });
807                // guard is satisfied, eliminate it
808                } else {
809                    let r = make_inst(IrCmd::JUMP, &[read(2)]);
810                    replace_ir_function_ir_block_u32_ir_inst(function, block, index, r);
811                }
812            }
813        }
814        IrCmd::CHECK_CMP_INT64 => {
815            if is_const(0) && is_const(1) {
816                let lhs = function.int64_op(read(0));
817                let rhs = function.int64_op(read(1));
818                if compare_i64_i64_ir_condition(lhs, rhs, condition_op(read(2))) {
819                    kill_ir_function_ir_inst(function, unsafe { &mut *inst_ptr });
820                } else {
821                    let r = make_inst(IrCmd::JUMP, &[read(3)]);
822                    replace_ir_function_ir_block_u32_ir_inst(function, block, index, r);
823                }
824            }
825        }
826        IrCmd::BITAND_INT64 => {
827            if is_const(0) && is_const(1) {
828                let op1 = function.int64_op(read(0));
829                let op2 = function.int64_op(read(1));
830                let c = build.const_int_64(op1 & op2);
831                substitute(function, unsafe { &mut *inst_ptr }, c);
832            } else if is_const(0) && function.int64_op(read(0)) == 0 {
833                let c = build.const_int_64(0);
834                substitute(function, unsafe { &mut *inst_ptr }, c);
835            } else if is_const(0) && function.int64_op(read(0)) == -1 {
836                let repl = read(1);
837                substitute(function, unsafe { &mut *inst_ptr }, repl);
838            } else if is_const(1) && function.int64_op(read(1)) == 0 {
839                let c = build.const_int_64(0);
840                substitute(function, unsafe { &mut *inst_ptr }, c);
841            } else if is_const(1) && function.int64_op(read(1)) == -1 {
842                let repl = read(0);
843                substitute(function, unsafe { &mut *inst_ptr }, repl);
844            }
845        }
846        IrCmd::BITXOR_INT64 => {
847            if is_const(0) && is_const(1) {
848                let op1 = function.int64_op(read(0));
849                let op2 = function.int64_op(read(1));
850                let c = build.const_int_64(op1 ^ op2);
851                substitute(function, unsafe { &mut *inst_ptr }, c);
852            } else if is_const(0) && function.int64_op(read(0)) == 0 {
853                let repl = read(1);
854                substitute(function, unsafe { &mut *inst_ptr }, repl);
855            } else if is_const(1) && function.int64_op(read(1)) == 0 {
856                let repl = read(0);
857                substitute(function, unsafe { &mut *inst_ptr }, repl);
858            }
859        }
860        IrCmd::BITOR_INT64 => {
861            if is_const(0) && is_const(1) {
862                let op1 = function.int64_op(read(0));
863                let op2 = function.int64_op(read(1));
864                let c = build.const_int_64(op1 | op2);
865                substitute(function, unsafe { &mut *inst_ptr }, c);
866            } else if is_const(0) && function.int64_op(read(0)) == 0 {
867                let repl = read(1);
868                substitute(function, unsafe { &mut *inst_ptr }, repl);
869            } else if is_const(0) && function.int64_op(read(0)) == -1 {
870                let c = build.const_int_64(-1);
871                substitute(function, unsafe { &mut *inst_ptr }, c);
872            } else if is_const(1) && function.int64_op(read(1)) == 0 {
873                let repl = read(0);
874                substitute(function, unsafe { &mut *inst_ptr }, repl);
875            } else if is_const(1) && function.int64_op(read(1)) == -1 {
876                let c = build.const_int_64(-1);
877                substitute(function, unsafe { &mut *inst_ptr }, c);
878            }
879        }
880        IrCmd::BITNOT_INT64 => {
881            if is_const(0) {
882                let op1 = function.int64_op(read(0));
883                let c = build.const_int_64(!op1);
884                substitute(function, unsafe { &mut *inst_ptr }, c);
885            }
886        }
887        IrCmd::BITLSHIFT_INT64 => {
888            if is_const(0) && is_const(1) {
889                let n = function.int64_op(read(0)) as u64;
890                let i = function.int64_op(read(1));
891                let result = if (-63..=63).contains(&i) {
892                    (if i < 0 {
893                        n >> ((-i) as u32)
894                    } else {
895                        n << (i as u32)
896                    }) as i64
897                } else {
898                    0
899                };
900                let c = build.const_int_64(result);
901                substitute(function, unsafe { &mut *inst_ptr }, c);
902            }
903        }
904        IrCmd::BITRSHIFT_INT64 => {
905            if is_const(0) && is_const(1) {
906                let n = function.int64_op(read(0)) as u64;
907                let i = function.int64_op(read(1));
908                let result = if (-63..=63).contains(&i) {
909                    (if i < 0 {
910                        n << ((-i) as u32)
911                    } else {
912                        n >> (i as u32)
913                    }) as i64
914                } else {
915                    0
916                };
917                let c = build.const_int_64(result);
918                substitute(function, unsafe { &mut *inst_ptr }, c);
919            }
920        }
921        IrCmd::BITARSHIFT_INT64 => {
922            if is_const(0) && is_const(1) {
923                let n = function.int64_op(read(0));
924                let i = function.int64_op(read(1));
925                let result = if (-63..=63).contains(&i) {
926                    if i < 0 {
927                        ((n as u64) << ((-i) as u32)) as i64
928                    } else {
929                        n >> (i as u32) // arithmetic shift for signed i64
930                    }
931                } else if i < -63 {
932                    0
933                } else if n < 0 {
934                    -1
935                } else {
936                    0
937                };
938                let c = build.const_int_64(result);
939                substitute(function, unsafe { &mut *inst_ptr }, c);
940            }
941        }
942        IrCmd::BITLROTATE_INT64 => {
943            if is_const(0) && is_const(1) {
944                let n = function.int64_op(read(0)) as u64;
945                let s = ((function.int64_op(read(1)) as u64) % 64) as u32;
946                let r = if s != 0 {
947                    (n << s) | (n >> (64 - s))
948                } else {
949                    n
950                };
951                let c = build.const_int_64(r as i64);
952                substitute(function, unsafe { &mut *inst_ptr }, c);
953            }
954        }
955        IrCmd::BITRROTATE_INT64 => {
956            if is_const(0) && is_const(1) {
957                let n = function.int64_op(read(0)) as u64;
958                let s = ((function.int64_op(read(1)) as u64) % 64) as u32;
959                let r = if s != 0 {
960                    (n >> s) | (n << (64 - s))
961                } else {
962                    n
963                };
964                let c = build.const_int_64(r as i64);
965                substitute(function, unsafe { &mut *inst_ptr }, c);
966            }
967        }
968        IrCmd::BITCOUNTLZ_INT64 => {
969            if is_const(0) {
970                let n = function.int64_op(read(0)) as u64;
971                let c = build.const_int_64(countlz_u64(n) as i64);
972                substitute(function, unsafe { &mut *inst_ptr }, c);
973            }
974        }
975        IrCmd::BITCOUNTRZ_INT64 => {
976            if is_const(0) {
977                let n = function.int64_op(read(0)) as u64;
978                let c = build.const_int_64(countrz_u64(n) as i64);
979                substitute(function, unsafe { &mut *inst_ptr }, c);
980            }
981        }
982        IrCmd::BYTESWAP_INT64 => {
983            if is_const(0) {
984                let a = function.int64_op(read(0)) as u64;
985                let result = byteswap(a);
986                let c = build.const_int_64(result as i64);
987                substitute(function, unsafe { &mut *inst_ptr }, c);
988            }
989        }
990        IrCmd::BITAND_UINT => {
991            if is_const(0) && is_const(1) {
992                let op1 = function.int_op(read(0)) as u32;
993                let op2 = function.int_op(read(1)) as u32;
994                let c = build.const_int((op1 & op2) as i32);
995                substitute(function, unsafe { &mut *inst_ptr }, c);
996            } else if is_const(0) && function.int_op(read(0)) == 0 {
997                let c = build.const_int(0);
998                substitute(function, unsafe { &mut *inst_ptr }, c);
999            } else if is_const(0) && function.int_op(read(0)) == -1 {
1000                let op = read(1);
1001                substitute_with_truncated_uint(function, block, unsafe { &mut *inst_ptr }, op);
1002            } else if is_const(1) && function.int_op(read(1)) == 0 {
1003                let c = build.const_int(0);
1004                substitute(function, unsafe { &mut *inst_ptr }, c);
1005            } else if is_const(1) && function.int_op(read(1)) == -1 {
1006                let op = read(0);
1007                substitute_with_truncated_uint(function, block, unsafe { &mut *inst_ptr }, op);
1008            }
1009        }
1010        IrCmd::BITXOR_UINT => {
1011            if is_const(0) && is_const(1) {
1012                let op1 = function.int_op(read(0)) as u32;
1013                let op2 = function.int_op(read(1)) as u32;
1014                let c = build.const_int((op1 ^ op2) as i32);
1015                substitute(function, unsafe { &mut *inst_ptr }, c);
1016            } else if is_const(0) && function.int_op(read(0)) == 0 {
1017                let op = read(1);
1018                substitute_with_truncated_uint(function, block, unsafe { &mut *inst_ptr }, op);
1019            } else if is_const(0) && function.int_op(read(0)) == -1 {
1020                let r = make_inst(IrCmd::BITNOT_UINT, &[read(1)]);
1021                replace_ir_function_ir_block_u32_ir_inst(function, block, index, r);
1022            } else if is_const(1) && function.int_op(read(1)) == 0 {
1023                let op = read(0);
1024                substitute_with_truncated_uint(function, block, unsafe { &mut *inst_ptr }, op);
1025            } else if is_const(1) && function.int_op(read(1)) == -1 {
1026                let r = make_inst(IrCmd::BITNOT_UINT, &[read(0)]);
1027                replace_ir_function_ir_block_u32_ir_inst(function, block, index, r);
1028            }
1029        }
1030        IrCmd::BITOR_UINT => {
1031            if is_const(0) && is_const(1) {
1032                let op1 = function.int_op(read(0)) as u32;
1033                let op2 = function.int_op(read(1)) as u32;
1034                let c = build.const_int((op1 | op2) as i32);
1035                substitute(function, unsafe { &mut *inst_ptr }, c);
1036            } else if is_const(0) && function.int_op(read(0)) == 0 {
1037                let op = read(1);
1038                substitute_with_truncated_uint(function, block, unsafe { &mut *inst_ptr }, op);
1039            } else if is_const(0) && function.int_op(read(0)) == -1 {
1040                let c = build.const_int(-1);
1041                substitute(function, unsafe { &mut *inst_ptr }, c);
1042            } else if is_const(1) && function.int_op(read(1)) == 0 {
1043                let op = read(0);
1044                substitute_with_truncated_uint(function, block, unsafe { &mut *inst_ptr }, op);
1045            } else if is_const(1) && function.int_op(read(1)) == -1 {
1046                let c = build.const_int(-1);
1047                substitute(function, unsafe { &mut *inst_ptr }, c);
1048            }
1049        }
1050        IrCmd::BITNOT_UINT => {
1051            if is_const(0) {
1052                let c = build.const_int(!(function.int_op(read(0)) as u32) as i32);
1053                substitute(function, unsafe { &mut *inst_ptr }, c);
1054            }
1055        }
1056        IrCmd::BITLSHIFT_UINT => {
1057            if is_const(0) && is_const(1) {
1058                let op1 = function.int_op(read(0)) as u32;
1059                let op2 = function.int_op(read(1));
1060                let c = build.const_int((op1 << ((op2 & 31) as u32)) as i32);
1061                substitute(function, unsafe { &mut *inst_ptr }, c);
1062            } else if is_const(1) && function.int_op(read(1)) == 0 {
1063                let op = read(0);
1064                substitute_with_truncated_uint(function, block, unsafe { &mut *inst_ptr }, op);
1065            }
1066        }
1067        IrCmd::BITRSHIFT_UINT => {
1068            if is_const(0) && is_const(1) {
1069                let op1 = function.int_op(read(0)) as u32;
1070                let op2 = function.int_op(read(1));
1071                let c = build.const_int((op1 >> ((op2 & 31) as u32)) as i32);
1072                substitute(function, unsafe { &mut *inst_ptr }, c);
1073            } else if is_const(1) && function.int_op(read(1)) == 0 {
1074                let op = read(0);
1075                substitute_with_truncated_uint(function, block, unsafe { &mut *inst_ptr }, op);
1076            }
1077        }
1078        IrCmd::BITARSHIFT_UINT => {
1079            if is_const(0) && is_const(1) {
1080                let op1 = function.int_op(read(0));
1081                let op2 = function.int_op(read(1));
1082                // signed arithmetic right shift
1083                let c = build.const_int(op1 >> ((op2 & 31) as u32));
1084                substitute(function, unsafe { &mut *inst_ptr }, c);
1085            } else if is_const(1) && function.int_op(read(1)) == 0 {
1086                let op = read(0);
1087                substitute_with_truncated_uint(function, block, unsafe { &mut *inst_ptr }, op);
1088            }
1089        }
1090        IrCmd::BITLROTATE_UINT => {
1091            if is_const(0) && is_const(1) {
1092                let c = build.const_int(lrotate(
1093                    function.int_op(read(0)) as u32,
1094                    function.int_op(read(1)),
1095                ));
1096                substitute(function, unsafe { &mut *inst_ptr }, c);
1097            } else if is_const(1) && function.int_op(read(1)) == 0 {
1098                let op = read(0);
1099                substitute_with_truncated_uint(function, block, unsafe { &mut *inst_ptr }, op);
1100            }
1101        }
1102        IrCmd::BITRROTATE_UINT => {
1103            if is_const(0) && is_const(1) {
1104                let c = build.const_int(rrotate(
1105                    function.int_op(read(0)) as u32,
1106                    function.int_op(read(1)),
1107                ));
1108                substitute(function, unsafe { &mut *inst_ptr }, c);
1109            } else if is_const(1) && function.int_op(read(1)) == 0 {
1110                let op = read(0);
1111                substitute_with_truncated_uint(function, block, unsafe { &mut *inst_ptr }, op);
1112            }
1113        }
1114        IrCmd::BITCOUNTLZ_UINT => {
1115            if is_const(0) {
1116                let c = build.const_int(countlz_u32(function.int_op(read(0)) as u32));
1117                substitute(function, unsafe { &mut *inst_ptr }, c);
1118            }
1119        }
1120        IrCmd::BITCOUNTRZ_UINT => {
1121            if is_const(0) {
1122                let c = build.const_int(countrz_u32(function.int_op(read(0)) as u32));
1123                substitute(function, unsafe { &mut *inst_ptr }, c);
1124            }
1125        }
1126        IrCmd::CHECK_BUFFER_LEN => {
1127            if is_const(1) && is_const(4) {
1128                // If base offset and base offset source double value are both constants, we can get rid of that check or fallback
1129                if (function.int_op(read(1)) as f64) == function.double_op(read(4)) {
1130                    let u = build.undef();
1131                    // This disables equality check at runtime
1132                    replace_ir_function_ir_op_ir_op(
1133                        function,
1134                        get_op_mut(unsafe { &mut *inst_ptr }, 4),
1135                        u,
1136                    );
1137                } else {
1138                    let r = make_inst(IrCmd::JUMP, &[read(5)]); // Shows a conflict in assumptions on this path
1139                    replace_ir_function_ir_block_u32_ir_inst(function, block, index, r);
1140                }
1141            } else if read(1).kind() == IrOpKind::Inst && is_const(4) {
1142                // If only the base offset source double value is a constant, it means we couldn't constant-fold NUM_TO_INT
1143                let e_op = read(4);
1144                let inner = function.inst_op(read(1));
1145                let ok = inner.cmd == IrCmd::NUM_TO_INT
1146                    && inner.ops.as_slice().get(0).copied().unwrap_or_default() == e_op;
1147                CODEGEN_ASSERT!(ok);
1148
1149                let r = make_inst(IrCmd::JUMP, &[read(5)]); // Shows a conflict in assumptions on this path
1150                replace_ir_function_ir_block_u32_ir_inst(function, block, index, r);
1151            }
1152        }
1153        _ => {}
1154    }
1155}